[PROPOSAL] VACUUM Progress Checker.

Started by Rahila Syedover 10 years ago222 messages
#1Rahila Syed
rahilasyed90@gmail.com
2 attachment(s)

Hello Hackers,

Following is a proposal for feature to calculate VACUUM progress.

Use Case : Measuring progress of long running VACUUMs to help DBAs make
informed decision
whether to continue running VACUUM or abort it.

Design:

A shared preload library to store progress information from different
backends running VACUUM, calculate remaining time for each and display
progress in the
in the form a view.

VACUUM needs to be instrumented with a hook to collect progress
information (pages vacuumed/scanned) periodically.

The patch attached implements a new hook to store vacuumed_pages and
scanned_pages count at the end of each page scanned by VACUUM.

This information is stored in a shared memory structure.

In addition to measuring progress this function using hook also calculates
remaining time for VACUUM.

The frequency of collecting progress information can be reduced by
appending delays in between hook function calls.

Also, a GUC parameter

log_vacuum_min_duration can be used.

This will cause VACUUM progress to be calculated only if VACUUM runs more
than specified milliseconds.

A value of zero calculates VACUUM progress for each page processed. -1
disables logging.

Progress calculation :

percent_complete = scanned_pages * 100 / total_pages_to_be_scanned;

remaining_time = elapsed_time * (total_pages_to_be_scanned - scanned_pages)
/ scanned_pages;

Shared memory struct:

typedef struct PgStat_VacuumStats

{

Oid databaseoid;

Oid tableoid;

Int32 vacuumed_pages;

Int32 total_pages;

Int32 scanned_pages;

double elapsed_time;

double remaining_time;

} PgStat_VacuumStats[max_connections];

Reporting :

A view named 'pg_maintenance_progress' can be created using the values in
the struct above.

pg_stat_maintenance can be called from any other backend and will display
progress of

each running VACUUM.

Other uses of hook in VACUUM:

Cost of VACUUM in terms of pages hit , missed and dirtied same as
autovacuum can be collected using this hook.

Autovacuum does it at the end of VACUUM for each table. It can be done
while VACUUM on a table is in progress.
This can be helpful to track manual VACUUMs also not just autovacuum.

Read/Write(I/O) rates can be computed on the lines of autovacuum.
Read rate patterns can be used to help tuning future vacuum on the
table(like shared buffers tuning)
Other resource usages can also be collected using progress checker hook.

Attached patch is POC patch of progress calculation for a single backend.

Also attached is a brief snapshot of the output log.

Attachments:

vacuum_progress_estimate.patchapplication/octet-stream; name=vacuum_progress_estimate.patchDownload
diff --git a/contrib/Makefile b/contrib/Makefile
index bd251f6..f3d3fe6 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -47,7 +47,8 @@ SUBDIRS = \
 		tsm_system_time \
 		tsearch2	\
 		unaccent	\
-		vacuumlo
+		vacuumlo	\
+		vacuum_progress
 
 ifeq ($(with_openssl),yes)
 SUBDIRS += sslinfo
diff --git a/contrib/vacuum_progress/Makefile b/contrib/vacuum_progress/Makefile
new file mode 100644
index 0000000..5fa5fc4
--- /dev/null
+++ b/contrib/vacuum_progress/Makefile
@@ -0,0 +1,15 @@
+# contrib/passwordcheck/Makefile
+
+MODULE_big = vacuum_progress
+OBJS = vacuum_progress.o $(WIN32RES)
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/vacuum_progress
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/vacuum_progress/vacuum_progress.c b/contrib/vacuum_progress/vacuum_progress.c
new file mode 100644
index 0000000..0c862cb
--- /dev/null
+++ b/contrib/vacuum_progress/vacuum_progress.c
@@ -0,0 +1,126 @@
+/*-------------------------------------------------------------------------
+ *
+ * vacuum_progress.c
+ *
+ * IDENTIFICATION
+ *	  contrib/vacuum_progress/vacuum_progress.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "commands/vacuum.h"
+#include "portability/instr_time.h"
+#include "fmgr.h"
+#include "funcapi.h"
+#include "storage/block.h"
+#include "access/htup_details.h"
+#include "pgstat.h"
+
+PG_MODULE_MAGIC;
+
+extern void _PG_init(void);
+#define SKIP_PAGES_THRESHOLD	((BlockNumber) 32)
+
+typedef struct PgStat_Vacuum
+{
+	instr_time start_time;
+	double elapsed_time;
+	double remaining_time;
+	uint32 total_pages;
+	uint32 scanned_pages;
+	uint32 vacuumed_pages;
+} PgStat_Vacuum;
+
+PgStat_Vacuum backend_stat;
+
+/*
+ * Calculating progress at the end of every page scanned by lazy_scan_heap.
+ * This can be improved to calculate progress at specified intervals only.
+ *
+ * Vacuum Progress is measured in terms of pages scanned
+ * versus total pages to be scanned.
+ */
+
+static void
+calculate_progress(BlockNumber vacuumed_pages, BlockNumber pages_to_be_skipped,
+					BlockNumber scanned_pages)
+{
+	uint32 percent_complete;
+	instr_time elapsed_time;
+	INSTR_TIME_SET_CURRENT(elapsed_time);
+	INSTR_TIME_SUBTRACT(elapsed_time,backend_stat.start_time);
+	backend_stat.elapsed_time = INSTR_TIME_GET_DOUBLE(elapsed_time);
+
+	backend_stat.scanned_pages = scanned_pages;
+	backend_stat.vacuumed_pages = vacuumed_pages;
+
+	if(pages_to_be_skipped > SKIP_PAGES_THRESHOLD)
+	{
+		backend_stat.total_pages = backend_stat.total_pages - pages_to_be_skipped;
+	}
+
+	percent_complete = scanned_pages * 100 / backend_stat.total_pages;
+
+	backend_stat.remaining_time = backend_stat.elapsed_time *
+								(backend_stat.total_pages - scanned_pages) / scanned_pages;
+	elog(LOG,"%d pages vaccumed %d scanned_pages %d total pages\n",
+								vacuumed_pages, scanned_pages, backend_stat.total_pages);
+	elog(LOG,"%f s elapsed time %f s remaining time", backend_stat.elapsed_time,
+								backend_stat.remaining_time);
+}
+
+/*
+ * Storing VACUUM start time and total pages to be scanned
+ * in a global struct shared across backends.
+ */
+static void
+initial_vacuum_stats(BlockNumber total_pages)
+{
+	backend_stat.total_pages= total_pages;
+	INSTR_TIME_SET_CURRENT(backend_stat.start_time);
+}
+
+/*
+ * SQL callable function for creating  a view displaying VACUUM progress
+ */
+PG_FUNCTION_INFO_V1(pg_get_vacuum_progress);
+Datum
+pg_get_vacuum_progress(PG_FUNCTION_ARGS)
+{
+	TupleDesc	tupdesc;
+	Datum				value[5];
+	bool null[5];
+
+	tupdesc = CreateTemplateTupleDesc(5, false);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "VacuumedPages",
+						INT4OID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "ScannedPages",
+						INT4OID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 3, "TotalPages",
+						INT4OID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 4, "ElapsedTime",
+						FLOAT8OID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 5, "RemainingTime",
+						FLOAT8OID, -1, 0);
+
+	BlessTupleDesc(tupdesc);
+
+	value[0] = UInt32GetDatum(backend_stat.vacuumed_pages);
+	value[1] = UInt32GetDatum(backend_stat.scanned_pages);
+	value[2] = UInt32GetDatum(backend_stat.total_pages);
+	value[3] = Float8GetDatum(backend_stat.elapsed_time);
+	value[4] = Float8GetDatum(backend_stat.remaining_time);
+	PG_RETURN_DATUM(HeapTupleGetDatum(
+								   heap_form_tuple(tupdesc, value, null)));
+}
+
+/*
+ * Module initialization function
+ */
+void
+_PG_init(void)
+{
+	vacuum_stats = calculate_progress;
+	vacuum_stat_initial = initial_vacuum_stats;
+}
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index a01cfb4..6e6bab0 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -157,7 +157,8 @@ static bool lazy_tid_reaped(ItemPointer itemptr, void *state);
 static int	vac_cmp_itemptr(const void *left, const void *right);
 static bool heap_page_is_all_visible(Relation rel, Buffer buf,
 						 TransactionId *visibility_cutoff_xid);
-
+vacuum_stats_type vacuum_stats = NULL;
+vacuum_stats_initial_type vacuum_stat_initial = NULL;
 
 /*
  *	lazy_vacuum_rel() -- perform LAZY VACUUM for one heap relation
@@ -470,8 +471,13 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 
 	indstats = (IndexBulkDeleteResult **)
 		palloc0(nindexes * sizeof(IndexBulkDeleteResult *));
-
 	nblocks = RelationGetNumberOfBlocks(onerel);
+
+	if(vacuum_stat_initial)
+	{
+		(*vacuum_stat_initial)(nblocks);
+	}
+
 	vacrelstats->rel_pages = nblocks;
 	vacrelstats->scanned_pages = 0;
 	vacrelstats->nonempty_pages = 0;
@@ -520,7 +526,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		vacuum_delay_point();
 	}
 	if (next_not_all_visible_block >= SKIP_PAGES_THRESHOLD)
+	{
 		skipping_all_visible_blocks = true;
+	}
 	else
 		skipping_all_visible_blocks = false;
 
@@ -559,7 +567,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			 * following blocks.
 			 */
 			if (next_not_all_visible_block - blkno > SKIP_PAGES_THRESHOLD)
+			{
 				skipping_all_visible_blocks = true;
+			}
 			else
 				skipping_all_visible_blocks = false;
 			all_visible_according_to_vm = false;
@@ -1062,6 +1072,10 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		 */
 		if (vacrelstats->num_dead_tuples == prev_dead_count)
 			RecordPageWithFreeSpace(onerel, blkno, freespace);
+		if(vacuum_stats)
+		{
+			(*vacuum_stats)(vacuumed_pages, (next_not_all_visible_block - blkno), vacrelstats->scanned_pages);
+		}
 	}
 
 	pfree(frozen);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index e3a31af..a700b94 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -192,6 +192,15 @@ extern void vacuum_delay_point(void);
 extern void lazy_vacuum_rel(Relation onerel, int options,
 				VacuumParams *params, BufferAccessStrategy bstrategy);
 
+typedef void (*vacuum_stats_type) (BlockNumber vacuumed_pages, BlockNumber pages_to_be_skipped,
+								BlockNumber scanned_pages);
+
+extern PGDLLIMPORT vacuum_stats_type vacuum_stats;
+
+typedef void (*vacuum_stats_initial_type) (BlockNumber nblocks);
+
+extern PGDLLIMPORT vacuum_stats_initial_type vacuum_stat_initial;
+
 /* in commands/analyze.c */
 extern void analyze_rel(Oid relid, RangeVar *relation, int options,
 			VacuumParams *params, List *va_cols, bool in_outer_xact,
VAcuum_progress_report.PNGimage/png; name=VAcuum_progress_report.PNGDownload
�PNG


IHDR8zB��sRGB���gAMA���a	pHYs���o�d��IDATx^����T5��{��FZE�EmAAE@TZ�ZFE]"�6"HC����@���I�y@D@A&���O~7{�S��IU�:���f�Z*;����]��|I�� " " " " " eB��)�z�" " " " " "`$p�	D@D@D@D@D@���N�4�[��O>���O�������-L���L�b.����+s�d��1��k��-���@���B�3��J�|yWL	E@D@��@$ph���������/�k������]w�e~���
���1�4k��������7�>�l�U����y���L�z�����j���~��o��?$�+��b.��b��?���{��f��EQ�+W�47�t�9������}��
�~��GM�F�l�
��C��#G��k~���H8���\����F���?��E��>R�g��U�~ia��%�_��W)g�:������������������k,G��i�C��*������Uh�B�\��O!�RZ��#,p��s�������K���Hw���_6'�p�y�����u��;��cy��*�����os����w�}�|���VD<�������G���S���?�1co������O���{�=3b��M������
?��g�6g�q�y���mzD�_|Q���gO��k�(���+Z�%p�������J�K����w�I�&�+������>����O�<�����R%(A��fF/.L�:��r�)�8'����2O>�����;��M�41���?�lqu@��_��m���#T�	��r6�t��!k��=j���o������?����������13��ys+�Bm������;w6[�n��<M����k�U�V6-3l}��1��c�R>�A�v������+����W���w�n����{ �C]������3l���s�F�d��-^������a��f����yj��}4C�+���|�����������7�P�L��+_���������|������2d�y���s�[&����~!��{>�����1k����K/��|���Q�
M�w�^s��7F��W��������>��	�'�|����?���_���<���_~��y��-��C���3���8|�o�>��|�q%�]���>}�5����������3�p���q�5v���l#��a�r�Re��g���k���;wZ�:FTC.v��4��m30�[���<M������z���_�v�l. p���-[�P��������^�c��M��7�4u��
6�\CF���!��=������J���B�0J��?��|��������x�I��������,D�d>����?!y p�y}��������������������/i�
�����N�������j�����o��,�.f�x��������5" " ��@��Y�b�5H6l����{��@�h��	\�|2EO1�+�������0���L� ��/�w�}g�����q����
FDfH8�'O6�w��>i����_oe��������"��G}%g��V�Z�����������G�!>
��~��~��~�V~_�(+}�J0���@s�Q�L�S�N������M�K�I����=z��*p2�_�|�'$=� p2E=�����������|�k�
38.���Y��Oz.����z�ry~B�_�����T�*p���ok���=�<�q�y�|N��.�����l3Di7w68�����rf`��>�>����X#���p�Yqc��>_}�UV-'p]�4���	���"1���������@���OZ�}�s}��+XU���n��\~^z�%�+��}������+[��U������mU
8�����.�k��=}���Ik��=�~ie���Q�f��i�6mz�|���'�2�2�����]#" "P�	���sW����NU	�\\h�5�/�3����%w�2��ZD�\�\��<2��Q��1Lr�Z�c
��3��M�������� I��r�z�~��Y7-\{0��<��(9��g����kaf�I*��|�@#���)��l�����M�K���?.e�v�?�a�E���q��������������]z��x�����/M���!���i�@!��s�Me�|�������'���:��C H��J�1���>�,;vlT�$�SU�`,g�d �����3�_p�&n@�5>���b��
�>0n\`�3�������v=\��w�t9.8� �`�����'��!x��5;�k.m�8�.,[����Z�����������#��*p����Y��3p}�i$_�uy���C��&P����>�hc�����&p���F�kr��.]�������C�W�|J����w.j���~�U>.j�����t����@i� p��)s�b7���%�Dc������\x�����B��1J����6(��0~�x�M4;4�m�t_zvh����3��h���0`�����~����p�� �
\ef��e]�2nc�tg��C�|��u31�+ych�& i��ly��5ef
k���m��v��-Z���w$��3;������/�g
��,\���������-����+��|���@E�Q~y��g��a����W8�(����������������}������gsQI��|�����/=�I��~��y>��q���5�C���^q�v&�X����������>*��!��o�
*���0�������[#/X��H/3 �"F]��X�D����t�g��}���"Ov�:������v��Fg���F�1�H�$��=��s��u������l�w_�s���60�����K�a��OX�C�;����:��m{X�����n�G$#Y��)p��_~�u]��&�W~_�p�cq:�F�s5�������oM����J��K�������}������CNH�������|�}'��B���|����Sh�={�D���i��n���5����k=t����@i�NiS�+w��a����@�`��n���~AUB�I@�F6{�UZ���D%G��7�x��:�$3�le� " " �H@�[��I�6��\m�������k���#�M�UP��G@����j," " " " eK@�l�V��G@����j," " " " eK@�l��4+�����7���������������T&�H������q���!�<��s��{q�b�3>2�:�������<8������)���������).�7��7�~����~���m�6J�a�|�95!��L��y�5�4j���[\r�%�����O��.X�|�����L�:u�Y7w�y��:x���+�t��a�|__|�P>���K�y�}h�ND@D@D@D@�D�����
�w���f��if��	6��-[�_|a?7�p����k��o��9��C�a�-D�u������={F���q�6L�n�:��U�V����IJ����8!�+fZ�z���m�n�s�N����$���u+�:����fI�	_|�P>���3'N�\����������@N��a$cz��U���z��3�����M��}�����>������e��/f�T�N�����IJ��l��!�q��gZ��n���S����7
40��O���5k��W��=���4���w7�:u�Z,�����������k������>|�
2f�N9���Os���([_��������E@D@D@D@J��8G��nM�����,M4k����3�,X�����-"p��k��cF�m?�
����[����?�-pZ�li�gez>.0���I|H����'U����q�W�I�&s�}��g�6mjg��ec�N\���]��j����:�?�@��6u�T��t�E�kN��E����v��f�������@�
6�E���\�����\�pS�=m��M����n��c��f��%'pf��m]�\����4������#�-*��I����	37������gN>�d;�����['�%t��������'�
��������9��
a\<8X�0*��
X�������	��54����I���o\�Z�ha����IJ8�����-p��
����q���u��U����.���*���?�K6����5k�D� ��n���vC����{�:��'��M��%K����"P�t�b
tv`��l"'3��F^K�.5|�AV��[�����G�g�a.\��R���
��6e����T������u�&��������m����L��G'�����@��Y�n�5��M�	�#j�V�������pScb_z�C6��`�kp�j��
����m�C���)�"��#G�[o�������1"*V��]����;'���//^l����	i�q��A�h�uQ�/�t�I�.j����w��]a�+�\�ND@D@D@D�<D����8������������p���}���Y,��K�N�m��]1����&}�E�{��S>��k<V�\ig�6lh�-[a��V�����W/��f-�:���+����CA��?�qH�d`��A�I�&���>�[���@���xwvkc�r��I 8?���`�Q}v���Ed�l.f�����E1�I��&]>�l2���
1���$}���,�R8�IN�A��|��/b��%�]�\�=z��U|�u�Va�#->����$p��_�~���n�,3W.���um�����D@D@D@D@j&�D���C�����C��U���������@�$ �S3��,k�������e%U)�T8� " " " " " eC@�l�R��Q(8e����������������@� ���l����dD��	L�2E���p�}J�q�P4�~���'	����I
������h�s�]w����3f�f���'�?�|����VY�7l�`:v�hN8�S�~}{��o��f����q���-����VYa���Z��
������b���G5�5��T7h��:�9r$B}���G���7�|��6���!P,����^��\��?w]R�����j�R��k�B�WZ�}������Ro[�OD�"�`���C�s�9�����K����s���_������3���3����y��G����_l']�~�����m��y�{D~�?={�4]�v������
�����P�T1��a����7�c�����V�u���O�
}���4R��H�e@O�4�\y��V,�����j&�/����X�S����k�B��N���Oj� ��LH�z��xqa�����y��g�u�y��'��v��a�Tf��u���_�re����?o�4i��6G���<���.FE��}M�V��}�.]��]���01b���������\���W/3d�;��,�����@����js��'Z�\��/�k�������I{����o���&���o��=�W��]60i�;|�����^���C��l/^lg7�7on6lh&N�h���}�h���|�N�����w����a���:D�8|�o��y���l����z���\@�����x��6��|�����������Y*�2����k��56������<h����W~��?_<���1c����93z?��O_���{�`q|�������6o�l�y����8��g�?�sI��_H�����}��_��_������kK_����}"[�J��!�������������=�����:*^��@�����A��'�D������w��03��_~���O��-�W>)�O�nj��u��s��5��v�A��^p���e�=H2>��F#���m30�[���=�����z�`�1�|�r{
b�n����of�L.���@\����u��wW�!3���w�}�i����]�������y�I'��B9i���a6��������!}Z�C���\<6n�h�?|��(	Q��&�5j���������/�~�}�v�w��kg�����(����k�J�����qV���;w�4�-�<CC���
��f�_�}���8������0m�i�&��!�re
�:�O?�4Q�$����I��k_���_��b�Ou�|���w�"������i�.���������������~��r$$pV�XaPY����5�@�G��}������~���]�~2EO1�����5�X��hj��mm���]��f���9�'/�A�Ei��q�g~��'{O��>�(�g���A�'S����{�\~��Y����t��������V>~�0�_}������1C��*��Z�~f�:u����u�&����!������u���'�>A�C�5`���o�ag!��kg4����2�����!*��q�?�0������3#��e��G�Q�������H�.^~.J�!�>���?}|x�y������)p��u��NZ�I�������Of����{��WE�T������x�/���������w.�_���<
qq����>�F��@�
�����?��=fw�"`�12���a=r������pc��������c��W����*��n��t7������nJ4�B��=zt�-�����C.?�������4�0�����=�\�^z�%{�4��,I?P!|�#����}�70��+��b�>��
�%3-c�e����0s�,
�,������f]h��K%��6	��������m�=n������ �N��"��i�'}���8i�����ef���T��av������m�I'����OkG_��������WZ|U�Ou~�|���w�"��[�������~���~�|��<
8I���]����w���r#$p|.j�]�;U)p�/39�}����7�l����i��4*?pn��g�*S���N��Q�������c�V>�T��a-!��+�O�����/��Q<����M�fChH�C�gS�~��Y7@\�g�y��>T�����(��~�@u�%�{��f�������Qn�����k|�/�����������$	��3���8��q�uf�%pp=dP��=|ps������f���O�ly��_��^��WZ|U�Ou~�|�������w@�NH����
�����K���N�N��@����c-i��|����N16����s�����*D1�w��	mH~�0�\X�lY�:u���f�&p|.j��}sB���u��\]���d�������������p�������OZ�C���!��Nb�k���$�?�|e���l�8�E����������/^\<�w���������{�������!�7�|s�MPxW9�>����C���$Q��8�s�����3��7� \d��x��'��a3�?������WZ|U�O���W�?�����/���|}��'��dsQ��~�����~?B�_��?�~�F��@���V��)�QIfX�+nW\pA�]����0F<Ef��lc

���}�
�#�|���=nS�^p��@�����n���(V���f����6��v[���	�H�f����?����@ #������Y�\�����I��}���C,�w��}$e��u Z�ha��:n�q!����>�������d��+��Y�l_p������F0�K���rNTF&���Q{�������v�W�ut��u���W0�}�g/���
kV��(�<O�l�A��au!��������'�7\������K�NH�b}�7"�����8�l3]�|���>��|���?�}}�/_����>����vN���k
�_I�)�������KJ��|��g����WW��@�� p2���nc��m����o��x`a;F.#� Qg�U,���	�{�����z��m��x�!�0�0N�m��a�h�y��w�;Z���L�mVYC���.`8���3\>��c��Mtf���E
C�+X3�M8i|]�9���E��s(_�}|����P�����)�D7������7���;V.po_�{���-���f������gO��q�6m�}�P~�_�/��-�3��s�l�9���W~����g�3��9������ ���a��OU	_��l����C�?���k_���_��)�������3�s�_i��������x~I�}�����{w�S����x(��)�
���yW(�M�Z�|�����[qS��@���Wf
�cu��9��"P$p��_p���1��oq���l2�No������1M�<�~����J��/��PQD ��N �B_p������oq\x�����5�QW#P��I�_�R�J�_�-�r�@2	��K��i�\6c��Uq(18%� *����������@�$p�g��" " " " " %F@��D����N���RD@D@D@D@D��H��X��8" " " " " ������R�����������	�kGD@D@D@D@D 8��SJ�# �Sb
�������������O@'vJ)" " " " "Pb$pJ�AT��	H���N)E@D@D@D@D@J��N�5��#" " " " "�?	���)����������@���)�QqD@D@D@D@D@�' ��?;�(18%� *����������@�$p�g��" " " " " %F@��D����N���2_}��i���9��������GJ"" " " " �"��k�.s�w����/��N0�_~��;w�My�-��?��OY?�^{m�����?S�~}�����}�����������������OGy�7��7�~����~���m�6J��7����������#�5�4j����/������{���/_n���*S�Ns����;���F<x����7�������w�����es�����n���������~
M��D@D@D@D@D �@$p���js��W��k��i���	&�L�l�b��������5�\������>��cs��g�s�9�������!�(={���u�������9���M����<[�je�����N�0k@�q�R� �y^�z�j+6�m���s�N����$�������O4C��s�]w�e���/>�h���kg&N�z����X��H:���U���{��eg<�������}����|0�5I�8��0�_�*!�'�]v��..p��;��82d��E\����V?���c��Tf����M�
�����lg��e���gx.���������S�b��/>���3}���q���[A���)��b���c:e��w�}|uS��������@�����#��i������	�f���9s���/������c��1c�����'W�3�|+~����N��-
����A������!�/	Q0y���
�4��<���
3i��c.����L��M���l���V_���X�Z?]'" " " "���\��N�j]�.��"�q��o�$PX�R�vm�w�^s����?��l����<�fp2G���7��+nj��m���������`8w�����1����������=>���w��}vvd��EE8i�C�q6��@c���W_���7o�9�����/>~o	����u" " " "P�*���g�3s�L�*�q9x��`���<�+\`��s�g�&p2���_��&u��qMk����>s')}\� $��o���)V7@�Fh���f����V�Z�r�����N�P.� ���Ye�8������
	��C���D@D@D@D@��@�6�K�,�F6�w�@����5�����9���P��Z�t�����
�fp�=j�8��p��h���UEW`��)S��
����4��p��[7�E
���]��m���f�P>8��t��������@>��u��Q�l�O�HQ���_����.�������@fs]��vP6l��
�mR>wM1��9��z���w��f��Q�B�v���t��9��>�}��x�b�>l�MH����
��C;_��}���NJtQ�����{��
3_���u" " " " �A 8�����A�l��7nl�?��3�h��g�ba70_z'p�m���B�����4��.j�70f��8�\��r�J;���aC�l��[_��>�����u�z�M4k�\@�!.V�Xa�O�*������C�&�
2M�41�}���*�M�������[[�+���������L��y�������#/B 3ds1����-��N�6���e��n���t�'�3eA����AL"p2J
��0e}�E .a������������[�
3i�!�C�$��������}w;f���r���k���]�� " " " "P3	$���L�uu&�l���D@D@D@D@j&	����eYk6���%���,+�J�����������Q(8e��������������������������@���)��TED@D@D@D@D@$p�*�[O��6z-�0e���l'=f��Y<
"P���B��N�<K�|�������8p��,����/����>w�u�a[�x�1c�i���=p����7�>�l��2 v�z���L�z��0�������R��%!�_y�s������=�\�h��(������i��f�����W�����	'�|�����}��+Z��#�,pV�Ze���,Yb����r��}��W _z_����5�4m��>;g�u�y�������9U��p�r�;<�:����/����:���SH��VD@D����9��c��w���V�:4���/�lN8���s��u���w�y�<��#UV�������O7�������
���~:������:u��CG1��z�-����F����{f��6>I�����\�>��������>��E�P��p=Y����d���x_��`�^x��X��L�6��t�I��'���B�����������kT����}�kz|�����T��!$p�	af�����S���'�3*�������������I���3�WDXh��o��m��<�Y��6lXbq|���KK��'�m}����o��7o��
��@#�hg�l������[+$O8����i���M�b-.����J[>�A�v������+��W~_�v��mn��{��`��������7�3�F�g<���]����x�b��Q#��a��f����yj��}4C�+�����Sh����������^s���g-���GM�����y�*�?��S�^�z�!C��|�>G�eN�4��1��B���|��/�O�c���[�^z����o#~����w���������rz~|���E@D@�� ��y�fk�}��'Q�>��s�������+������l����Uf8t���?�*.0��w����������#�v��f����8��b���<&�4�����F..;����M��?� p��{+��]0n�8kd�]��������1�}�Qk��f��mf���u����	�_|�`d���_�v�l. pp���e�uD�G�}�������6m2���[7�@se������P�?����P���i��
I�K���_�~�7^>�	}�����)��3�0O=��9x����n�����i���8$�Q�_1�@�b�	1��w�����?�����,9/T������u" " ��@������a���f����r\L�� ��)z�!p\YV�^��`�S&~�}�������y]x���8��eF#"3$x����y��7�����c�,����mr���&-��~�3c��H>�����GE��}�U�V����Q���G�!>
��~s�q���~��~�V~_�(+}�J0���@s�~���v����S'������,�����K����"px&�ON�e�����7������]A8���8_�!p��?[�d>��4����f+k>����4r��������/�����T?�*p���o	0��l����!n��#���������_����m�(���;n?�|�O�����s�����_}�UV-'p]�4���	���"1�������a��DI�����W>���r��RUi��{���qH�����^2����'�����������Q���K*����3�����OVe�w�C�������~���'/�����&PF�k���vS���Oz���]F�@�\����x��G H��\���?�. v�J���B���|���=g��(�[���"�g��Xh�� ����������u(>j�h�GHp\6��x�$�����pK�M��3�<3J��y��g��cg�������4���:�R0�X��+�����?�<}�}�����?����M$�<�nk�:We�we��y���)������|���	�\�9w(�����Ne<?���x��G H������M��fa8�W.$	���dc9�&I��\D����.0q���a�m�������`������2����p���{P2\p�An�����Op[C��kv+m�8�.,[����Z�����������#��*p��� M
Ikp�F�1�|�i����o��?I�PG\�n��6k�g�Ic�/��%��*��+C�����O��<���8�|�;���?�*�R~~B�1]#" "P}T8����]��f��m�1�����gG!��y����l�m��b����ga�h��M�&:�������KugD�C�t���<�������g�43@��&��G��W��������u91�+.@�	`=Hh��<����$�$Q?�����E�/����h�fv�q�s��~��>S��.c���v]��W_mn���({_�}�������B�Q~y��g�)v�:|�p�����������N�������}����}��7[�4\��;&>��5�M�7��R���/I������!��k�|���&i3@��^q�v&�X�������/�����T/N�A{��m��6�,�f&c�������2���TF��)�g`��D����t�g���K�,y����'�h�O���f�!?�j������f��L3Kn�W�������s���60�����K�a��OX�C�;����:��m{X��K~������d�!��2"�����D���+�u,^G� ���|�y����&�~���1�}.����k__��=����������M��)_����,Of(���8i�����#���B����'�f�M�6v�xO��T��\���E@D@J�@$pJ��*]�`
3[
" �O��2�u�V�U	E@D@j$	����Wi	��k�H�O?��ns���l��L,[Y+�����@)��)�V��e������*Wl0�9`���Z8r��jSvTD@D�����ym����������@���)��U�D@D@D@D@D�����ym����������@���)��-��q�I����y'l�� " " " " �I 8�v�2w�q�=��sH8�d����^������N8+�~��6��p!$�u�]�������s/�Nq���n��q����?�����m��Qz��;��	)_eB��k��Q�������?�x�u_w�����UW]e���c�����;m���m]9��C�;���C�����@�\vdR��$	N�g'+��k��i���	&�{m��%:}��n0�\sM����7G���Gv�aQv�q!$=�g��Q���s��������[Gy�j��~8I���a����'�|�	=3���W[��m��(j����;D�I|�g�V :t���k9���0����|��kg&N�z����X��H:���U���{��eg<�������}����|0�5I�8��0�_�*!�'�B�wq����	���!C�-�'~���q��;��2����n4h`�O�e;k�,S�^={��i|�w���x���/>������^{�]�E����V�1�w�)��>}��C�E������h_�/" " " "P:��9r��uk?~��di�Y�ff��9f�����l��3v�X3f�3z�h��U���?�����9o���eKC=+;`��qa��w�xH��@BL�<9k�|��N�PW\q��4i�1��w�}�i��v��Y6����/�eX��	����c	D.jS�N�.N]t������\
kYj��m���k8`���a�1Y�	��u>n]�3�q%�M
��M�6#p�����;�3f����={�u�s���gt��������,Z���'�h7�&ph���OF�7o�9�����/>~o	����u" " " "P�*���g�3s�L�*�q9x��`���<�+\`��s�g�&p2���_��&u��qMk����>s')}\� $��o���)V7@�Fh���f����V�Z�r�����N�P.� ���Ye�8������
	��C���D@D@D@D@��@�6�K�,�F6�w�@����5�����9���P��y-]��|��Y�o
38G�5g�q�Y�pa��ZH���+����)S��R�C_f8�������K^�.j�����3�(	�PR�ND@D@D@D �g��u�(f6�'X���a[�����~p��M�m�}���d �����q;�
6�����&:�|��b.b9r����[M�����#�b���������sb�}p�ra����}�z��8����v�P5��I'�������{���f�B���D@D@D@D@��@$p�m�=���*��7n����	g����7����n`��N�d�����;i�i��]��n`�0�#p���c���vv�a��f��e��l5}������p�b�h�R���C\�X��p�:TDi�!�C��M
d�4ib>��3�U8�����w�g�6�*W��"�����
�gG.^�@f��b����[���;l����&��;��O�g
���+5���D�d��a��"6�@\��������][��[�nf:��C��>>I�-����g��v�b1s��/�]��M�NKAD@D@D@D�fHtQ��8T��L��::[�+���������L85�����l>��Kn]QYVR�H% ��"" " " " "P6$p��)U	���! �S6M������������H��Tlm���|8HVAJ���)S�]y	7�����E��G�*K(q��8p��,����/����>w�u�a[�x�1c�i���=p����7�>�l�U��
�c����N0����^���o���o�nn��f{��r���m���|�p�U�VY�P]}��S��?����Q�Fv��
��C��#G�D�o���H�9����oV��P�=�e@sV�;����\P���K�}r+E�_��\I��_!5)��[h�B?B��kD@J�@��y����9��c��w���V�`�����/[q��s��u���w�y�<��#UV��/���������?�����g�����Ks����/����Y��p�$�BM
��@���
��*f�9u����{��=�����. p���c�u���Hu�"(�=i�$s��WZ�8��5d��'�RT��I���$��R�B����/��#���FD�t	fB���g��S�N�������v�����2O>���f;v�0|*3l������\�2�����7M�4����g�	&D����0*���kZ�jeG�L�v����`���B�s���r�C�^���!C���`�&����������'�hys������Zs����%���^j���[���o�9f�^i3v��������vV�z�/0�:d�Y�x���h���i����8q�-c���m�!��rM�T�������w��a�L��d��7�����k�o��N=�T�?.����kn��F�u>\v�e����Z�?��,m�����i���]v��A��]���0����y.��������!������������P�o��O�y�f�@�{��w�������_H�����}��_��_�������������������C��i�!�}�����{>���}uT��� ������O>���������1aD������-[Z7���|R.L�>���U+r����
�w�~����i��f�l�b������k��>F�p}0`�i��u��8����z��������k#u���F03�`r��?�
��z��wW�!#`���w�}�i����]����9�@�
���)'b��@��������i�I�Yp���q�������$D���{��Q�kC����/�h���kp����k���A�?�Q�w�}�
������q��H�mw��i-Zdy����D�$�;�����/�'p|����g���o����z��\������O?M8�������������/v�T����|���M�_!����o����;-���~?|��������E�		�+V�T����fM0P��%~��}6����6�k���L�S�����_�j
:�=0���m[[&7[t��Qk�w�q��3����A��������O��H}�Q��9��N��qq�=��u��B�w#�.=3T��*D����=�W_}5*��y��#�'j1��	 t��)J���e�4���<�����W&O�l��_�7�x��B�;��h�?��e�'��CT����?�.A|:<���H0k�o����G�����y���������_H�O���O�u�u�����Be
fz�$�S���k_�d6tf���_i�U�>�����_<����B�I�_����;�����E���U��i]#�F�R�sA����l��;U0�����9rd�QN�6m���O�L��0BC/8F1]��q���������f(H��q�������
���1F�e�h93*���D.����k|
1�w�( p����c���K/�do����%�*�y$�?4������}��W.������1�B������fi�ea&75�%0�B[�]*���IH�W�^mg%po��qc�Wv�y�����q|@ ���O{>B�}'���8��l�����8��������>I�������>���������}�����?�x��s�_��	I�_����+�������P����O����
yG�(7A��������q�S���2���w��yq�a�w-f��My�������sF��a���oZ@���uZ@�$�Z�������_������J8��"��ue�	�����1��#r0����i��`
i|��l���%n��>"�<�L�}������Q�3�����K8��s����e����M+?��h_<���58���+��I8�Og@'�q|����*K��z�� �{�������q��*��q���O������{��WE�T����|����li�%pB��)T�$����_*��\���V��@����c-i�D�q�q!I�c��l
q�=������F!b0���n4�B�����a���l��
.ju��1,H�i������g P0'D�a�K��E�MF���Z�I9���r�B�z�.*>>i�M�/�;�5;��:����X�����yN�8����tQ������M���sAb7��=�{�{��QZ_������g�]|�U��O�����|�5I��������>s�����;�l���_�=2�?�H���_i�U�>i�'_������|����li�~����?\^I�}�}�!/����!��5"P�*��������d��E����vu�T���]�p
c��Qd�����@1���0�o`��02�x���\r�uK���%[D��j����`gQ�ng��rK�?+nI�N3����o��n��B�I8n�.�?��1*���#8��@F�%\X��,���,�c����$��>���!��E�>�����2�h���;D�[�E\�����O��kF��%Kl��5k��N`@`����[��}���\��J���$;�1jO`�!�5���;����Av�:���+��������C����7Q�C�l�A��au!��������'�7\����}*pB��cx��<��T���d���V����i�C��������o��S���Gi�TpM��+�������~�����!������C9������r!PA�����m�1X8������5�����
�\#�XD��ST��'0�����x�������Ha��M��&��"#	�4�6�Q����w�y����	��f�5l���SZ�>����>�m��Dg��QJ\�0�q������)�*p�����ZH����9����>�i��Qt(�I������]��o��}v��+�
�����������S��G\����'����h����	��������<���=C&���)�}��=�x�0�K0���A.��?M�_6�p�T�������i�/_���_��b�O9<���U��������'����~����/���������*^��@$p��B��/8F��C@|��V6f9�u�V��T�"Pf���Y��X���t��	��F��\�mk�e�[\���-.�����3=�m�kLGSEE �����������K�1T$ ���\�mj�e�[\��^x�u��u�m�D@��~����T�R��j��\"�L@G�CD@D��`�_�'��Xj,DU\D@J��N�5��#" " " " "�?	���)����������@���)�QqD@D@D@D@D@�' ��?;�(18%� *����������@�$p�g��" " " " " %F@��D����N���RD@D@D@D@D��H��X��8" " " " " ������R�����������	�kGD@D@D@D@D 8��SJ�# �Sb
�������������O@'vJ)" " " " "Pb$pJ�AT��	H���N)E@D@D@D@D@J��N�5��#" " " " "�?	���)����������@���)�QqD@D@D@D@D@�' ��?;����W_}e�7on�;�8s����������������@2�H������q��/��9�����_n���kS�r�-�O�S����^��������_������F�����������~������=���Q���w������������m������7��o��6�~��H�F�2�5:&�K.��<���^�������������S��z����;��Q�u}��7��>��C�|}�!�C��~�����n3?�������B��:"	�����\y���w���f��if��	6�-[��/���~n��s�5�D���yst��?���y����s�1���~�}HzJ��=�|��~��7+p�>�l��u�(�V�Z���')�8�P7B\���/�f��^����m��E9����~�hq!�/�_~��9�����C��\{�]w��>���Z�v����'�^��D@D@D@D@D 'V�0��1�j�*o�^�z��la����o��f������zMRz��l��J*��e�]f�����N� ��bo8�{����v��a?�~��w��A3}��(�Y�f�z���i|�w�n:u���X>������L_�,`�E����V�1�w�)��>}��C�E������h_�/" " " "P:��9r��uk?~��di�Y�ff��9f����/�t�3v�X3f�3z�h��U���?�����9o���eKC=+;`��qa��w�xH��@BL�<��'�(�+���L�4������>��iS;��,�t����2,V���O�����������"��S�Z��.��`\���9	����]�����8p����66l8&����~�.�� �J���i�6m:F�$�w38�;v43f�(9�3{�l�r�u�������o��Y�hQQN��n�M� ���y��W�l���gN>�d;�����['�%t��������'�
��������9��
a\<8X�0*��
X�������	��54����I���o\�Z�ha����IJ8�����-p��
����q���u��U����.���*���?�K6����5k�D� ��n���vC����{�:��'��M��%K����"P�t�b
tv`��l"'3����.]j>���������G��3�8�,\�0�E-�~U���m��)v�f��!�/3u��MtQ�%/i5f�|�q���?��N()]'" " " "��D��n�:k3��,RG�����_m?�K���6�����l2�Y�\�����
fw�s�D���]S�E�#G�4��z�����1bDT��]�v5�;wN��n_.,^���[{���'~�8�����F_:���]����{������Wh�t��������@y��-��q#[E������>���1?����X�
���	�l�<3�b`'m3M�����
��|N1�x�\���.5l��,[�,������?�x�{�^l�Z*t��+V���C�
�(->��������A�L�&M�g�}f�
g��x�}������V�
" " " " 5�@$p~�a�����������\�����u�b��v�M�|r�d��b`'�I�L�CYp�&p����RC�"LY_�f�KX���:=z��k��t����LGZ|���'I��%t�~�l����Y,f�\������ic�i)���������L�.j5�j]�	0[�@g�r��I@�f�{Y���'8��+*�J�R" " " " �$p�AD@D@D@D@D@���N�4�*"" " " " " ��> " " " " "P6$p��)U	��J!���l����^K=L�2�(�I�3�p���@�������S<�R/_����E@D@*�@$ph8K&)���/�k8���]w�e��7f��a�5kf�<������>[9�����z�!S�^={����o~������wIH�W^y�\|��v+�s�=�,Z�(���3o��&s�i�Y#�����;-���>j5jd�n���:t�9r�H���Q�L��Mm�Yg�e{��
�W���G�Y��Z���/-,Y����_�*"��Y����B����}�������*�����Uh�B�]��O!�RZ��#,p��s�a+��K�Z����/�lN8���s��u���w�y�<��#UV�������O7������KD��O?|_z�L�:u���ko������O���{�=3b��M����n���#`�����O_u��I_x��b�
3m�4s�I'�'�|2�~Uq�NUP�~_����������x_�������?�@����K������� ��L3#��N�jO����_�L	�
!��;��M�41���?�lqu@��_��m�����YNhzW�a���:$��{�5�_~yh��l[��}���@��7�B-4��8�!��sg�u��
���k��fZ�je�2���Os���(��W^i��5��.]��]�vE������{�ns�
7��s�e�]T�o��&r�snx����;wntM���������6lh&N�h�����G�p���
������?������C�������������W/3d��������I�&��i�����#�q����K����K���~U���{��57�xc��0xUN�OH����C H�l���o�|�IT��?��~�~�z���/���[��-[Z7����eaV�fq�n��}�[���*V�vm3}�tk��_���������������%����n6n�h�����'��_�~�k�������g���k���;wZ�:FTC.B��4��m30�[���<M�����#�=f���kg�p��{��-[�� (^?_�}������M���o�i���l��2��@g[C�����
(��g
��l(�W��v��__��������/���}�Iu��#�8��>��S������~0��/��_H���W��5*�+�W:!�����c��o��o��>����%G��
�R~B�^�����T/A�(���
��1z�wh�q1�k��O��)��qeY�z�����d_������l^^x�5�1t����������'O6�w��~�"�����c_��g��Y�|��}��GQrf_j��U�o..j����i��q��c��A���7������wi������q�t��*
4'���T:u�d��+���|�������0i�����O�g!������������������|�k�
38.�������G�2������,����|(^D@D���T���~K��
f{0�1h��?���
�l�]��5=��f��p��q��g3���>�0�������K*����E�1k������/�W_}��E�	\�>M�0s�(0�H�4����. p��E���OZ�}�s}��+X�Q���n��\~^z�%�+��}�������������4N���/��]�^��C���?y��w�4�2z���X3g����d�|���'�2�2�����/�����T?A��������w�SU'�lM�K���9s�D���������\�� rp����H6#+����C�
6+	�����O�$pp9c=nu�i���a��gF�8�<�L��\f�I*��|�@#���)��l������M�K���x9���\���9������y������~i��y>�M�
q.���n*3����Ne<?��C�" " ��@���U
c;i�|�Y>v���@����M0��m2�t��E���/��7 �F��!��M7Pp��!��K�m��}WHw<x����{P���,X�z=��5o<�f���q���L�����e�*��e�8^~_�X/����9b�x��	y�&�`$��I��@��?��|����o����z��g2�������]^I��~>��|�2�#P�,�I�\����(�|\�J���������T/�:a��?n���K����g�;;
������]�0��g�h���X�C��o��f;��m������C.U��Mv8����7fy���o�m���Q�(-=�S�Q�+��Y���Z\��|���5���\��g�|��E�������14X���Af~lYM�X���"����-Z��������6x`f���'0���L��:���u=W_}����[��}����G�v�
F���
����������C�����������]�|��/_�O���_��
����G�����O��<�|��x��M�{�W\a�a���][N�OH����C����o���v����*����X����5�������2���(b��x(�&��"dmeL:�3�����R�';}�x��v�D|�kf��s[���g!=8��vj���{�����w\ ���;�����f�_Z�'�x���x�y�eu�����m�S��|������d�!��2"�����9s�h_�}���������F�s5�����]�6���lm������+��}}��'p|�}��������x�}�'$�4���~!��{>��e�YKsQ+4��={�m���ic��G��J�����^D@D��	D������;����_AD��	�YF�n�J��*����@�$ �S#���*-�Szm���#�����7�x��*�v�L,[Y+�����@)��)�V��e������*Wl0�9`���Z:r��jSvTD@D�����ym����������@���)��U�D@D@D@D@D�����ym����������@���)��-��qvO����y2l�� " " " " �I 8�v�2w�q�=C�sH8�d����^�������N8+�~��6�nq!$�u�]�������s/�Nq���n��q����?�����m��Qz��;��	)_eB��k��Q�Q�F����=����$�������������S��us��w�����rH���w�����������4��� " " " " �I 8��NV�k��5��M3&L����e����/���n0�\sM����7G���Gv�aQv�q!$=�g��Q��~������[���l����..p��;���u#�NH�*zf^�W��bc��mQ������$���u+�:����fI�	_|�P>���3'N�\����������@N��a$cz��U������G�0|�p��o_3p�@���f�&)=�t�6��Y%��S��..p��;��82d��E\����V?���c��Tf����M�
�����lg��e���gO�w!�o���M�N���'`|�!�}<8i=s0��v��a+���;��SL�>}��C��l}���b���n�(V�9r��5�?�[�4��Y33g��`�{fB�P��;v�3f�=z���*p���oE��?����i������0���0`��;W<$�E !
&O�\T���?��W\a&M�t����w�i����}b��u:q���w�}B���D@D@D@D@�X�����S���E]d0�9�:��Z���k��{����6l�pLi's���Kq3�����{��M��8I���s����3JN���=����@��3:i|���ggG-ZTT���?�g84fn�'���7��|��v���NhK�:(OvQ��g��9s�u��<xp�@aT��.�N����3I8�kh��_o�:������h��~�9���>.p����[�� Y#�q�F�u�VS�V-C�]H�[U'�(�l�
hk����A�������i����u" " " " �O q��%K�X#�;D�t���������DNf(�E���.]j>���������G��3�8�,\�0�E-�~U���m��)v�f��!�/3u��MtQ�%/i5f�|�q���?��N()]'" " " "��D��n�:k3��,RG�����_m?�K���6�����l2�Y�\�����
fw�s�D���]S�E�#G�4��z�����1bDT��]�v5�;wN��n_.,^���[O���'~�8�����F_:���]����{������Wh�t��������@y��-��q#[E������>���1?����X�
���	�l�<3�b`'m3M�����
��|N1�x�\���.5l��,[�,������?�x�{�^l�Z*t��+V���C�
�(->��������A�L�&M�g�}f�
g��x�}������V�
" " " " 5�@$p~�a�����������\�����u�b��v�M�|r�d��b`'�I�L�CYp�&p����RC�"LY_�f�KX���:=z��k��t����LGZ|���'I��%t�~�l����Y,f�\������ic�i)���������L�.j5�j]�	0[�@g�r��I@�f�{Y���'8��+*�J�R" " " " �$p�AD@D@D@D@D@���N�4�*"" " " " " ��> " " " " "P6$p��)U	��jA�������*�@)�2e��+/�R��p��h��(Te)%N 84�%�~���5�������2l�3f�0��5�N�����g����o���t����p�	�~������~�-�������7�l���PR����m[���n�j�*+�k�"p�Y�G}�4j��nS��A3t�Ps�������	4'��|����*��@�h��rgxq�����u��������m=��s��E�jl;�}JhR�q�.���^�������~���x��",pz�!s�9���]�t�0q.����V\<��sf��u��w�1�<�H���nD������l���3�D�����������_~i��Yc8pC�&�B �hV��@���:�|�=f��ml��\8}��������h������4i����+�X�_�C���S���r�J��[o�O?��H�/�l��>�^������o�
�w���B���RG]#"P:�3!��������S��S���W;Sr�Yg�'�|�[�;v>��n�j���v����7M�4����g�	&D����0*���kZ�jeG�L�v����`���B�s���r�C�^���!C���`�&����������'�hys������Zs����%���^j���[���o�9f�^i3v��������vV�z�/0�:d�Y�x���h���i����8q�-c���m�!��rM�T�������w��a�L��d��7�����k�o��N=�T�?.����kn��F�u>\v�e����Z�?��m�����i���]v��A��]���0����y.��������!������������P�o��O�y�f;��{�#p2���m��5�����.FH�����}��_�_������kt_����;Of�J�X!�������������=����:*^��@�����A��'�D������w��0"�����B��-�W>)�O�nj�����^��a����Z�~�������N3[�l�Ib@v��5J��F�L��
0��n��B�D�SO=e�������5���u�Z#���C0���qDu����+���0_����>��iS���]kY����[ }�r�����l ������C������,�xl�������Q��M�=j�(�������_4s��5�b�w��kg�����(����k�J�����qV���;w���M�
�76�
0H�v|���?_�O�����O��=m��i����g�2�s�;3/�38�k����d���
�D�-.����m>�k_���_��b�Ou�|�����B;Q��7���������|��:T����O�"P����+�*�\\p�&����o�>���O�T>���������t��3���%er�EG��F�q�g?��0#x�
4(��z��O?�d�����GE���#�0(]@�d���c�Y�l!$7���3C�y�BNZ��q�`~��W����7��P1���q����B�N��4i|]�I38!|�#����}}e����O����5`�y��7�,���c������}YF�!|��q?DA��>������3�C�������=zD������x 1�x��(�����NZ����Y�]���.T��a���K8I������}��/������#����k_�d�'�}}�����h�������/��K{��������������Wf�"�B\��{������r#P����02�l?���TE�xcdC�z���F91���icG?12q��

���t���������F��� ��jd����nJ4�B��=zt�-�����C.?�������4�0�1���q�n��.S/����U_W���>��T�����������D��KfZ���/of��a���<������mw�d�&!�_�z���`����y_����cW'������>����	�������2��k]*K�0;B[~���6�$�����`O.�~�A)_����>���������}�����?�x�7ry�e�OI�_����+�������P����O�����=���r$$p|.j�]�;U)p�/39�V������k1��l�������(�3|��\|���8>�N��u��I�?������?�|��4��Z.B_W6�����	_z�x<"#>)L�6�
�����~���\���#���3����
_��E8��H��v�Z�p��E�l��=�i�����g�0�����o�58I����$>�/|\`�Ye	\�d}�y��7ng�B�����9s�D��u
�Yy_[��_�����WZ|U�Ou~�|����_�~�����~����/�(T�$����_*���v�u"P.��6kI� "Xx���I��dk�{���\u�U6
�Auv��q��F����e��UpQc�����4��sQ���3(�����������&#��E-���i|]9��J=��������B�����r��I����\��l�8�E-.�3]�B���?x�`��{�\���5pO����{o��W~.��@��Ys��w�������z:_|Mu�,��@��'�A�����p��2��\PA�1�M���2�?�H���_i�U�>i�'_������|�!�+�%��dsQ��~�������~?B�^��?�~�F��@���V|[�v���<�(�F���������e��1��(2�Fg�x�kph�7��WF<���K.�����j���-�q�	5�y�a�3�(b��[n�%���$F��Ez����m��V���	�H�f����?����@ #�.,�f���`��}�c}�g�}f����"w���}|][�ha�����".�Z�C�'q�5	�}��%���5��'0 0���#�%F�s9'*�#��\��=��y�5���;����{v�:���+��������C����7Q�C�l�A��au!��������'�7\�1�}*pB��cx��<��T���d��������?�i��xw3���3B���������������B���?��i�TpM>�+����~?\�I�C�����W�rj_]/�B���q��u�c�p�m��k$��1�F��:s��b	fO`���'����w���[��h5���������q�m�h�([Fs�;��c���e�o��
�}v�)-���ad���m�3��(%.R��^�����G8i|]�q-�oQ�������h�l>��P��=;�1��F������f��q��e����������3���z�����gO��1k�������!�<_�=[�g>���"�	0B�d�k_�}��/��
fti:<�E���������8��x�>�=�s��-���C�?���k_���_��)���=��
����{�}�����x�I�_�����������WW��@��N�T�X�����Bq�o:����,g�n�����U�����2k������.�  ������m��L|�+p��e��Xvzc�'��w��h���A������<��P�	5��"�$pA���M��L|�+p�`�sPp1�u�m�D@��~����T�R��j��\"�L@G�CD@D��`�_�'��Xj,DU\D@J��N�5��#" " " " "�?	���)����������@���)�QqD@D@D@D@D@�' ��?;�(18%� *����������@�$p�g��" " " " " %F@��D����N���RD@D@D@D@D��H��X��8" " " " " ������R�����������	�kGD@D@D@D@D 8��SJ�# �Sb
�������������O@'vJ)" " " " "Pb$pJ�AT��	H���N)E@D@D@D@D@J��N�5��#" " " " "�?	���)����������@���)�QqD@D@D@D@D@�' ��?;����W_}e�7on�;�8s����������������@2�H������q��/��9�����_n���kS�r�-�O�S����^��������_������F�����������~������=���Q���w������������m������7��o��6�~��H�F�2�5:&�K.��<���^�������������S��z����;��Q�u}��7��>��C�|}�!�C��~�����n3?�������B��:"	�����\y���w���f��if��	6�-[��/���~n��s�5�D���yst��?���y����s�1���~�}HzJ��=�|��~��7+p�>�l��u�(�V�Z���')�8�P7B\���/�f��^����m��E9����~�hq!�/�_~��9�����C��\{�]w��>���Z�v����'�^��D@D@D@D@D 'V�0��1�j�*o�^�z��la����o��f������zMRz��l��J*��e�]f�����N� ��bo8�{����v��a?�~��w��A3}��(�Y�f�z���i|�w�n:u���X>������L_�,`�E����V�1�w�)��>}��C�E������h_�/" " " "P:��9r��uk?~��di�Y�ff��9f����/�t�3v�X3f�3z�h��U���?�����9o���eKC=+;`��qa��w�xH��@BL�<��'�(�+���L�4������>��iS;��,�t����2,V���O�����������"��S�Z��.��`\���9	����]�����8p����66l8&����~�.�� �J���i�6m:F�$�w38�;v43f�(9�3{�l�r�u�������o��Y�hQQN��n�M� ���y��W�l���gN>�d;�����['�%t��������'�
��������9��
a\<8X�0*��
X�������	��54����I���o\�Z�ha����IJ8�����-p��
����q���u��U����.���*���?�K6����5k�D� ��n���vC����{�:��'��M��%K����"P�t�b
tv`��l"'3����.]j>���������G��3�8�,\�0�E-�~U���m��)v�f��!�/3u��MtQ�%/i5f�|�q���?��N()]'" " " "��D��n�:k3��,RG�����_m?�K���6�����l2�Y�\�����
fw�s�D���]S�E�#G�4��z�����1bDT��]�v5�;wN��n_.,^���[{���'~�8�����F_:���]����{������Wh�t��������@y��-��q#[E������>���1?����X�
���	�l�<3�b`'m3M�����
��|N1�x�\���.5l��,[�,������?�x�{�^l�Z*t��+V���C�
�(->��������A�L�&M�g�}f�
g��x�}������V�
" " " " 5�@$p~�a�����������\�����u�b��v�M�|r�d��b`'�I�L�CYp�&p����RC�"LY_�f�KX���:=z��k��t����LGZ|���'I��%t�~�l����Y,f�\������ic�i)���������L�.j5�j]�	0[�@g�r��I@�f�{Y���'8��+*�J�R" " " " �$p�AD@D@D@D@D@���N�4�*"" " " " " ��> " " " " "P6$p��)U	��J!���l����^K=L�2�(�I�3�p���@�������S<�R/_����E@D@*�@$ph8K&)���/�k8���]w�e��7f��a�5kf�<������>[9�����z�!S�^={����o~������wIH�W^y�\|��v+�s�=�,Z�(���3o��&s�i�Y#���Q�;��	�/���Q����o�g�ph*�m��-���G5�5��n���:t�9r�Hp���B�H9�U�V����%K����_U���=B�oZ�|����a�����9��L���������?j�(��iS���:�,��c�U8X����A7,����j���SH��VD@D����9��c��w���V�`d����/[�����3���3����y��G�����������n�}�]�������~:������:u��CG3o������O���{�=3b��)p18�����t��5J��Ks����/����Y��p &���@����7	�g���r8k)	�?�5|��W2_�B�@��_��|�������3�D����,���f��f��iv��'��UK�%D D��PqU�jL H�0�������S��S�1��gT5����c��S��I�&����g�-�Q����m��A#���L��Y��G��Q�y��EQg�}��0aB��9DLR6l����Ch��l[��}������7�B-4��8�!��sg�u��
���k��fZ�je�2���Os���(��W^i��5�S��]��x_�}���{�5��?�@�_v�eA����o"���,\|�s����5�.j�/��o0o����8q�}���o�B���+������Z<�n�������|���~��������?!�����2��>�1�:i�$�4��B���|��/�O�c���[�^z����o�j�~�����o������	i]#" "P}	���7[��O>�j�������uF=����eK��V����C�,����,�����{+_z\�j��m�O�n�c�	30?���1y��
\�:.���~��ACypW����1�����f����X>|��n��q��Y#{���f�����������$
�s0�[���<M������z���_�v�l. p`�e��.���p���+��=6m�d�|�MS�n�`��1d:�\fxV0@��<S�fC	�������������x��`f���Z�jY����V~��x�����|��G�0���SO�������{�/��}��+_����d��w��w�}L��M����g�w���P�S��OH����E H���X�O�7�������!�d��bW���W[����!O��A�_������u��Z�C����"p���z3x��
I;�w�q��3$�x�<y���,�\�1c��H>�����GE��}�@����E��W_5�7��C|4(�?������O������GY���W���W����Rf��:u2p����f��[hz_��x�_��W+h�+�i<?�cfs}�������8����B�s
'S�����8i��������&M�0��3����Oz.�����L�<?����D@D@��J8����5gf���b�����<�r�'[z�|M�r�"��a�=�=��M�6v]q�/!b�76;���Q����c���g��}__}�UV-�72�3'�3��H3nj��'^DI�����W>���r�5MUi��{��I[p�y�����������O�=I����+�&�]#G����,HH�c���%�\R�����B������.~O_�!p����|���_�@=ztT��3g�M2C>���we�ry~B����^���E����C�b�������$�����3gN��[���"�'p�nP�2a�1����G_�~��}��#rp�%0r�������b����l.y��I�z�C�V��F-���g�%G����$��$��W>g���������Z6_�}m���������w��0z��a���E�k|��7�l"�WG����.o��x��[��/M���!���i�@!��s��)^�|�������'�����E H��
�1���>�,;vlT�$�SU�`,g�d �����3�_p�&n@�5>���C��a�3�|H�������0���b����q�O�E�{��AIq�a��^O?A�!x��5;��z�m�8�.,[����Z�����������#��*p��Oc��'i�W��F��_�G�����/>���s�������#\�n�����|�f���.I����'pB�W�|J�����`�qQ+��'��5" " ��@��[T�v�n���K�����-	���\`W$\O0��g�h���X�C��o��f���m������C.U��Mv0����;fy���o�m��H�{Z������J�!����!�i\�0(8���Y�fY�P��=X���m���Y��&lI�=Y��(<����-Z�����6x`f���u"0���R��a]�����������r�-Q�������G;s����\����W�B�3�|���%����W�������_���7�x��?�/q��#����g�
���wT|�����|��O8���������G��{)[�3c�;�wm9=?!��kD@D@��
'� J���Ae�#f3�����k��[�ef����E<c��w���2&��t_z�Y�d��O<����ow��C6~��Z{��mG���~6��g��M���1�X�D��#�n~Hws���60�����K�a��OX�C�;����:��������_�~�HF��)S� ������3�����W>�����A�Q�\
4�0��qG���D����?��g������6��_�>�������mO�cm"=��y�����_���'>M���_H������E�x��\�
M�g��h�u�
�e<�'�P��O����" " �M 8�]L���	����
" �O�Y�n���~AUB�I@�F6{�UZ���D%G�5��2k��1�qle� " " �H@�[��I�6��\m�����b�k �|+�����@���)��Q�D@D@D@D@D@r& ��32%(U8��2*����������@�$prF���������sP�nYAD@D@D@D@*�@$pv��e���{���p���s��8p1�9�g�pF���m�CC�s�H�{����nLg����|7n�8�UZ�~��^��m�(=��������2�g�5j�({�gf�`��<�:���`����tx�����;���F<x���C:]�0T���/>�(��P�������u��������@�&	N�g'+��k��i���	&X:[�l�N����5�\����Ayd��e�B�#Pz����N+�=l�l��u�g�V��wq����	f
�!.pB�W�n�z�j+6�m��f����;D�I|�g�V :t���k9���0����|��kg&N�z����X��H:���U���9���la����o��f������zMRz��l��J*�	���]\�$�wq6d�{�����3�~\�c���������
����G�r�{�z��	�.���������S�b��/>���'�g���]�>l���r�����9t�P��/�]X����M�" " " " �C�
�#G�X�����{K�&�5kf���c,X`�L�
8c��5c��1�G���\�����h�����8-[�4���=`p���$�$D�����*p�������+��I�������3M�6��O���N'.X}�.�b�Oh�t���������K rQ�:u�uq�����5�V�"PX�R�vm�w�^s��{ ��
��"M�d���u)nW2��pO��i�1')����p�����1cF�	���g[�;�{|F'���}�����E��*p���v�l���M�d�y����O>��`������	m	]'" " " �I��.j{��13g���B��(�������8��x&i's
����mR'p���-Z��3gp���B�}��y�bu�!k�6n�h�n�jj��e(�i|�J�����M���m��5Q6�c���}��� ->���ND@D@D@D��	$n�d�kdcx��.]�X���0�����������K�|�U����0�s��Qs�g��F�����*���M�2�n��,U<��e��n���.j��%���l�/>.0������	%��D@D@D@D@�!�(p��[g�bf|�E���������w)���������M2+������a��m�C���)�"��#G�[o�������1"*V��]����;'���//^l����	i�q��A�h�uQ�/�t�I�.j����w��]a�+�\�ND@D@D@D�<D����8������������p���}���Y,��K�N�m��]1����&}�E�{��S>��k<V�\ig�6lh�-[a��V�����W/��f-�:���+����CA��?�qH�d`��A�I�&���>�[���@���xwvkc�r��I 8?���`�Q}v���Ed�l.f�����E1�I��&]>�l2���
1���$}���,�R8�IN�A��|��/b��%�]�\�=z��U|�u�Va�#->����$p��_�~���n�,3W.���um�����D@D@D@D@j&�D���C�����C��U���������@�$ �S3��,k�������e%U)�T8� " " " " " eC@�l�R��Q(8e����������������@� ���l����dD��	L�2E���p�}J�q�P4�~���'	����I
������h�s�]w����3f�f���'�?�|����VY�7l�`:v�hN8�S�~}{��o���������o�g�p()��l�����W
7Z�j��5�8�����>j5jd��n���:t�9r�H����o��jo��fum
��C�X4ge�3�8�������s���K_��\��X�S����+w>�+����M_��Gh=u��@i8=��9��s[�.]��
�8^~�e+.�{�9�n�:��;��Gy��jy��[��~�z�����=��3���t�b.��r���_�5k���P�I���?�U�?P��?����?�p���g�[9<�N�>}����s���?��_$�2�'M�d���J+��'��!b����{��i�v�Z���~��j�R�yR�q��������o�������(
A���z��Y�����S����x�Yg�'�|�[�;v>��n�j�W�\e�����&M�D�?�����	������

}��5�Z��#��]�vE�a0b�����sgC���W�^f��!vv�Y0f��Aq��_}����O����_��^{�����
����K/5�~��M��7�3{���l\��w��a;�G������,^���n4o��4l��L�8���}�����/���@��	���oh?q�
6�t��!J����|���k��f�&}��SO���j�����o��\���e�]��������������-�^(�f����e�����+?	���/��b��1��g���B��������8�������t�7o6����{����3�_<����v�{��y��
i�����o���B����>���s���?��xgI�_��*���������|�����WG��@�8��0�?����������F���_h���uc�����!�����M�Z�"7:��0@0�0�qW���_p1x��v�if��-� I��(."}�2��6`���u�
�#pUO=��-#���/�� F���k�`�f���/�,�������p�BF�|�����L��MmX�v�es$n���I�SN���_�������>�����q�F[����GI8�:�7���Q����_|���;���I�i�����.p��l����*q�W�q��Y�J�����,Z���
�76�
0H�v|���?_�O�����3"���M���<��)p���?���D������D?�]��	
�����������/v�T����|�������>���M��p�����/����9�������E�		�+V�T����fM0P��%~��}6����6�k���L�S�����_�j
:�=0���m[[&7[��9F�q�g?��0#x�
4(��z��O?�d�����GE���#�0(]@�d���c�Y�l!$7���3C�y�BNZ����`~��W��3:�#�'j1��	 t��)J���e�4���<�����W&O�l��_�7�x��B�;��h�?��e�'��CT����?�.A|:<���H0k�o����G�����y���������_H�O���O�u�u�����Be
fz�$���������z3x�����������Yf���_i�U�>�����_<����:V����������+��G!.j���sz�u��	�J8�����=z��TE�xcdC�z���F91���icG?12q��

���t���������F��� ��jd����nJ4�B��=zt�-�����C.?�������4�0�1���q�n��.S/����U_W���>��T���������W^1�D��KfZ���/of��a���<������mw�d�&!�_�z���������y_����cW'������>����	�������2��kb*K�0;B[~���6�$���x�����>������}}�����h�������/�������.Hz��~?\�i�_��E�
����.��o�;Z��@�8>5�������8�������������%��%fS����|lD@����kX~����g���E�8I��!'-�4.��W����~��k�i|]�|���'p|�}�������0m�4+BC�=�j�r� ���3�<�f*p|�gf�L?f �:��=�\�p�B��E�(�{n���5>��la|
����
kp����I|_���:��8��!JX��7G�=��+n����p���m���O��������J������������s�_i}�X'�Q�BN��������
}�u��� ���
�Z�&��`�=�7.$	�bl2��1���s�UW�(DA|�����]���8;�-[V�E�N�:f����"M��\�|��
���3,s����������sQK3 )G_WN�B�R�x�E��'����s��@`'�f��\gp��k�2w�b6�	����.j!������{��� ��k��������(���\�s�����.�	
�*��'�}���t���$�ZY��b�~r�9s������l�����
�_\������3�?�H���_i�U�>i�'_������|�������~����?\�I�}�}��r�~�|����?�~�F��@���V�v�nT���
���\Pa�/v-�5�OF��6:���X�C����u�^`�0��w.\r�%�-
W^�l��}���;#��"v;���[��1XqKbt�Y���~��v�m�L��q�tq�a��Q������2�-��of�r9�W>�'a0~��g����-r���$����E���n�q!����>��kF��%Kl��5k��N`@`����[��}���\��J��L�s��v�]�	��}���qd��O?����?�xy�pq��a�J|���#9��)3�.�����|�O<��]���31�BNH�b}�7"�����8�l3]�|���g����6�5��?�H���_�x_�-�}���sm��I����|}*�������MJ��|����S����x(N�Ap��mc�a��Y�
p�����,l�h`��"�����%p�=A�qo\002�������V��@|�_���C�a$a�f�&#
������;�w�4���������m�]�pJ��g�|���-t����1J���6�W�{��;�BN_W~\�[��:������7�0���:	�����At�����o����c��������_�r�`{j6���={�D����<���!�<_�=[�g>��g��!r2���������fti:<�E���������8�����gW�|BH����|��{����>����v�U�����~�������7)���~?|e/����U�"P."�S.*V=x�1��P������0���[��4�r�2#����4����s��E�H�6Be��o[c.��
Fmq�d4������o�]c:�**y(����/�%�D�_B����@ 	�@P���oSc/��
6����'�kl�� "F������0��z���T[F��d8�" " 5�k��>�l�Rc!��" "Pb$pJ�AT��	H���N)E@D@D@D@D@J��N�5��#" " " " "�?	���)����������@���)�QqD@D@D@D@D@�' ��?;�(18%� *����������@�$p�g��" " " " " %F@��D����N���RD@D@D@D@D��H��X��8" " " " " ������R�����������	�kGD@D@D@D@D 8��SJ�# �Sb
�������������O@'vJ)" " " " "Pb$pJ�AT��	H���N)E@D@D@D@D@J��N�5��#" " " " "�?	���)e���+��yss�q���O?=��DD@D@D@D@�	Dg��]��;�0��_�	'�`.��r3w�\���[n1����~����(�����~��6�_�5�>$�u�]�5��������?6�������o�7n��.-�?�`�m��m���o���}�����+VG5j�i���1�_r�%�����u,_��\u�U�N�:��SO5w�y��:x�����o���������������o7��v�����O?��L����������@�H�\}����+�4�k��5��M3&L��l���|���s�
7�k��&������}�����3�4��s�y�����C�#Pz�������o�Y�s��g���[Gy�j��~8I���a����'�|A4��h���Vll��-�a����;D�I|����/��'�h�j�������&�	_|����k��L�81�r]'" " " " 9���t��U�Vy�����xd��7}��54>�`�k��#pH�-``#��UBP!N.��2�]\�$�wq6d�{�����3�~\�c���������
����G���5���W� �\H���{w��S����	_|f�2g�.j������SN9�����:t(���.,F����x��!`��#G�[�����%K��53s��1,0^xa���c��1c����G�O�g���V4����y��-[�Y����0�s�C_�`���E8i�Cy\q�f��I�\~�}���M���'f�X���x�a��'�~�ND@D@D@D��%��M�:��8]t�E���7��I����v��f������������a�1y���d���u)nW2��pO��i�1')����p�����1cF�	���g[�;�{|F'���}�����E��*p���v�l�������e3o�<s��'�,_|��8�-��D@D@D@D�<	T�Em��=f����U�r�����Qy�W��:�>�$M�d��Y�~�M�����E�}�NR���AH�o�>o�S�n�0d�������[M�Z��v!�oU	�4��\�	6@��Y�&�q�w��o�����[����������?��m��,Yb�l����Kk��fs9���58��t�R��d8�58��=z��q�f����.j!�����.oS�L�<0Ki|���[�n��.yI��1�����$��|$pBI�:�|$
�u��Y����`�:��m����k��]
75�!��'>d���
�����6l�0���&:�|��b.b9r����[M�����#�b���������sb�}p�ra����}����8����v�P5��I'�������{���f�B���D@D@D@D@��@$p�m�=���*��7n����	g����7����n`��N�d�����;i�i��]��n`�0�#p���c���vv�a��f��e��l5}������p�b�h�R���C\�X��p�:TDi�!�C��M
d�4ib>��3�U8�����w�g�6�*W��"�����
�gG.^�@f��b����[���;l����&��;��O�g
���+5���D�d��a��"6�@\��������][��[�nf:��C��>>I�-����g��v�b1s��/�]��M�NKAD@D@D@D�fHtQ��8T��L��::[�+���������L85�����l>��Kn]QYVR�H% ��"" " " " "P6$p��)U	���! �S6M������������H��T
��f�m>�Z�a��)E�Nz��1��xD�
�_��/F��y�z��]�/" "P9"�3p�@�Y2I��_~1\�-|���.����0c���Y3{�����o�}���)e@.����C�z���3`���~��o����KB����+���/�[�{��f��EQ��y�M7��N;����o�p�
6��;�N8���_��/�/����	'$�|����U����r8�V���KK�,1�����w�{����B����p��7�3�8�s��m����}��r��
�_��/�����R/�(=��p�9���]�t�0C��j���/[�����3���3����y��G�����������n�}�]���
40O?�t��}�3u�����"F�z�-����F����{f��6>��Aa��_��|�����3�<��O�>��S�9p�@p���B	��������+�/}��]�t1�_~����/��5k�r��������~���	��?��*����@98�403�xqa������_���D�u�Y��'��2��c��S��I�&����g�-�����K��m���y�G����u��a����?o�gf���k0�
Z�c�c��o��v������W(�/c�G;#d;w�l�W<�	��^{��j���e�
�hW^y�-� N1�w��e�+��|�w����s�e�]�������o��=�=�3�s�����tQ[�x�i���e��aC3q�D�<�o��>?_�}���B������}��f��	Q1^x�s�gD���o_������/=��z�2C����<G�FM�4��~!��{>����1k����K/��|���Q�
M�w�^s��7F��W��������>���������'�D5�����w�H8���Y_h���uc��p��![fU\`��������/��#GL��������q��Y��=��?�xL��������U�V�c�������F�2�����n��q��Y#{���f������������Z�4�
0��n��B�4����/�,\���k������}[�l�����]�F�����G{q�M�6\����l��B��@g[C���[�
(��>A;2J��?��|��������s�[�4���Z�~���!p���������?!y peO=����?�`�/_�m����=�|IkT�W>����������V��i���7�Yr^��)��'��u����@�"$pV�Xa��H\p�h�q1�k��O��)��qeY�z�����d_������l^^x�5�1t������
�_��Wk�#��M =yf�f%	�W_}����v�e���vf�����E��X0+�O���}�Q���Z�o..j��q��Q~��A�E���w�q���~�����W>�J_������4���>|���"t�����W~_���o��}�������G��B��a���BZ���W�B�����)����'����=��p��	fp\`�,[Y�I���}����ry~|}G�" " ��@�
����[�l0����^�7����P�W�dK������X�6C�$P0�����e���6=�����>�![6�$��?f-�{V�����W_}��E����H8��0
�,#�����F�]@���OZ�}�s}��+X+U���n��\~^z�%�+��}����i�GR<���m��������1K������q�B��+#��]����C���?y��w�4�2z���X3g�4M�6=o>���we�ry~|���E@D@�� ��sQs�j������*�S��/�3����������j�&P��a6�������QQ��e*p�M�fK.��#5�y�g�es���O8���P�$������?��3������f�������4���:�R0�X��+���|������}��%�}���~T6��}��������o_�����|��K�s��s;_��	�?��p7M(���s�m$���,p*��	�?�ND@D��8,���:i��t��:�B����M0��m2�t��E���/��7 ���C�@���{�UW]uLO	M����4��y��������T\p�An�����O0d�������*F�3���-����V~_�X/����9b�x��	y����'i�W��F��_�G����Z<"����2���B���}�a�&���8���	�����8�E-.P�qQ+��'����E@D@J�@���H|b�v���\�M4�=nI��(����z���(=�Fc�T���z��f���m������C.U���:T8�#^��P���~��������7������:�s!-=3H��{��D#�*v���!1s�ch�&�� �����X��,������I��E�/0j���������]/S���g���v]��W_mn���(_�}�������B�Q~y��g�vc�+f�2g�(l>�t������]�|��/_�%�\b��p���E4������������|��O�����O��<�|������oz�K���k���k���	i]#" "P}T8����nT�Yea639�����F^<����V\�@%�;}c���dmeL:�3�������N_'�x�]?� ��l��V��~ a�o��Y�*�KK���������f�r�`��CX�?�8�5����-���'��"�2�w�yY�up=��a�.���_���q�z�L������[�6������{���_o�
"���j����'���m�����A�3p��W~�+��}����xD-������s����������/�O���_H������E�x��\�
M�g��h�u�R�e<�'�P��O����" " �M 8�]L���	���)��'�,t�n�J��*����@�$ �S#���*-�Szm���#��)\j���cfb��ZAD@D@J��N)�J
,�N
ltU��`�����R��W�R% �S�-�r�����������L@'gdJ " " " " "P�$pJ�eT.��	H���L	
!�����7���������������T&�H������q����!�<��s��{q�b�3>2�:�������<�� ��������c���v�`���).�7��7�~����~���m�6J��|�95!��L��y�5�z�8�����N��.X�|������9#�us��w�����rH���w�����������4��� " " " " �I 8��NV�k��5��M3&L����e����/���n�����o��9*�<��[����!�(={���u�s�6L�n�:��U�V����IJ����8!��L��yq�;bc��mQ���;�w������@:t�P�7�r�%�'`|�!�C��k��L�81�r]'" " " " 9���t��U�Vy�����xd��7}��54>�`�k��#pH�-``#��UBP!N8����')�8��!C��[�N��i���;v�Oe����4h��L�>=�v��Y�^�z��{��v���t��)k�|�������3g�.j������SN9�����:t(���.,F����x��!`��#G�[�����%K��53s��1,�g&d���c��1c����G�O�g���V4����y��-[�Y����0�s�C_�`���E8i�Cy\q�f��I�\~�}���M���'f�X���x�a��'�~�ND@D@D@D��%��M�:��8]t�E��S�s(�e�]����w�9p��=n��
�d�&p2G���7��+nj��m���������`8w�����1����������=>���w��}vvd��EE8i�C�q6��@c�&~2��y���'�lg�|��{K�������$Pa�={���3gZW!����F�Y_��t��X<�4����f���6�8��kZ�-���38I��!��}��N����5B7n4[�n5�j�2���4�U%p���r�&p�p��f��(�1�m���nH�zo]'" " " "P���^�d�5�1�CJ�.]���l|��A�d�B\��k�����>�*p|kp��9z��9��3����]�B�W]�]��L�b7x`�*��2�Q�n�D5\��vQc���I�C�H����u" " " " �H8����F1�	>�"uD
�J������njlC�KO|�&��u
��Am��av78�MtH��5�\�>r�Hs������{�#FD�
���kW��s��>@��������m���4!->.p�7���|�.j���N:)�E-�����+�|��K�����������H�0��{1�U4�o���]������o0�9����|������3�!v�6�����q/���a�G�s����+��R��
��e�"l!|�j����������6���rA��X�b��>:t� ���C��>I�4�4i��|��gv�p6������nmlU� " " " "P3	D����0���\���������Y�(f;iw���'�M �!v�A���8�Wj1���<(5�/���El��������G���O�n�*�t����}|�[B�����=����b��_���M�6v����������@�$���V3q�����ut�*W��$pjf��e��|���������*%" " " "�J@GDD@D@D@D@D�lH��MS�"" " " " " 8�" " " " " eC@�l�R��Q����m��p����2�)S�h��n �O	7N����"@U�"P�"�3p�@�Y2I��_~1\�-|���.����0c���Y3{�����o�}��*���
L���	'�`���o�������o����|���%���m��UY�J�F�V�����`!�����GM�F��6�
40C�5G��P�~���@sB��7���M�r{����,w��{���z�Ul�b�O�?I���;��R�B����/��#���FD�t��z��s�9��x�.]jF�/����=��Y�n�y��w�#�<Re5�����hY�~�����m��y����]�t1�_~����/��5kNb(��P������b���P���o�����������O�>��Z�9p���T�/�b��&M2W^y��q���z�I�@ ��8:��������o�������(A���z��Y�����S��������)9�����O>����;��[�n�?�+W���}���M�&M���}��f��	��_x�k�����}��V�Z�z�]�vE�a0b�����sgC���W�^f��!vv�Y0f��Aq��_}����O����_��^{�����
����K/5�~��M��7�3{���l\��w��a;�G������,^���n4o��4l��L�8���}����||�&�*�OH�}|C���n��a�C�Q2��||^{�5�7�{��z��P{��57�x���:.������l-���e����?oi�b@�4����.;x����._�I���|�<c����?s�����?}�c���v���e@(�7�v����y�a ����;F�d����
}���Z���7��E�|��n��������?��x��_i�;���������|������WG��@�8��0�?����������F����_h���uc�����!�����M�Z�"7:��0@0�0�qW���_p1x��v�if��-� I��]�F�q��c�	�����[W����z���l9�]�|��1R�n]k�7�`&|�c �`�h��w�}w��2�+�}��g�6mj����k-�0 q��PN��r"v�
$���~�||H�V���!���7��><J��A���I�G�e�64������f���W$�N�v�lt���e{��w�P�����7��T�v���f��E�ghh���AT��A���c�+�����}��?}|z��i���M���<��)p���?���D����
}�����������/���S��?_�����6������i�.���������������~��r$$pV�XaPY����5�@�G��}������~���]�~2EO1�����5�X��hj��mm��l���G���q��3,��^p�
�.�^���O?�{b }��GQ<��,J8����=��c��.[���p���Pe����V>~�0�_}��������3T��"p����g&���S�(M_�i�N�H�hz__�<y���;p|
l�x�
;1v�X;�1~�x_�Q|�xf�QA@�s�?�0����!�gF�Y�|h�=������<��]��\���B�}'��������}�*S�0���%	���W��������l����������}�����?�x�/���������w.�_���<
qQ����>�F��@�
�����?��=fw�"`�12���a=r��
��tm�������Ga��^p�b��Q����W_eu#��P���f52�{�M7%|!�c�=:���rfT�!��\�G{S��b���Q@�8c7F����^z��*��+K�T�H�hz__�
���+�\���%3-c������3��0��Lnj�K`�����T��k����^���J��F���������1���6�8> �V~��=!�>���?}|_f�\`MLe	fGh�����f�$p��_��?_��������WZ|U�Ou~�|����t.��l��������������_�Q��I��'�R����u��� ��sQs�j������J��}����������0j��3K������Q�����9�������7- �L���:- p�F�CNZ�i\����i���@�	�r��������O������9�Ia��iV0��4>�{6���7@\�g�y��>T�����(��~�@u�%�{��f�������Qn�����k|�/�����������$	��3���8��q�uf�%pp=df��=|ps������f��W�?����i�}���}�����h�������/���B��8!��2*p�~�}�/��oh;�:(AW���M,�����$�S�M�5�=��c���*��� ��:�Q��YZ���a���e�*����S�,X� �_�	���/��@��u�e.�8W5~4�w.ji$�H���I^U�������?4}.��$�����NR�c�W��Z��9��\��?�E-�_���6��w���\�D�}���Fi}��B��/�5w�MPxW9�>����C���$Q��8�s�����3��7� \d��x����_�=2�?�H���_i�U�>i�'_������|����H�������p�'�����~���:�H�t��#�
���6���F%�y`Q0�`�]]p�v�b�2\��d�m��m<P�584�X��+�#�|��%�\b��p��E��������0��F���-������[���"�������n��g��[���3f�J�����g �m	x3��v0�����>	����>�}�E�n������>���-Z�������".�Z�C�'q�5	�}��%���5��'0 0��z�-k����{.�D��ad����'����N���kl��� �p�~��p_�Y�����!�kV��(��!r6��\���R~_���?��v�.r���>8!���1<��p�|*K�d��tQ��?�����������|���[h������Q�&\�O�
y����i�.���!�����+9������r!PA�����m�1X8������5�����
�\#�XD��ST��'0�����x�������Ha��M��&��"#	�4�6�Q����w�y����	��f�5l���SZ�>����>�m��Dg��QJ\�0�q������)�*p�����ZH����9����>�i��Qt(�I������]��o��}v��+�
�����������S��G\����'����h����	��������<���=C&���)�}��=�x�0�K0���A.��?M�_6�p�T����?���i�����}}�/_|����?�|�*pB�������~?�y'�~������W�rj_]/�B 8�R�b��#�
�! ��\+�����u+Nc*W(3z���As���?G`�\J��N`#T�.��5�2�-��a��MFc��������5����"��B�Oz���^BI��%�*���	U�.�65�2�-��a��/���p���6�
" a
}?���\�W��K�eT.H& ���!" "Pc	��/���f,5�*." %F@��D����N���RD@D@D@D@D��H��X��8" " " " " ������R�����������	�kGD@D@D@D@D 8��SJ�# �Sb
�������������O@'vJ)" " " " "Pb$pJ�AT��	H���N)E@D@D@D@D@J��N�5��#" " " " "�?	���)����������@���)�QqD@D@D@D@D@�' ��?;�(18%� *����������@�$p�g��" " " " " %F@��D����N���RD@D@D@D@D��H��X��8" " " " " ������R�A����2��77�w�9�����AID@D@D@D@D �@$pv��e���������p�	���/7s���)o����?�)���k��r����g���o����_��C�_w�uY�������c��~��(O���q������������m�F��������~\�bu�Q�F�F���%�\b�q/_w�����UW]e���cN=�Ts��w���������Q^~��������ln��vs�m�������O��t������������W_m���J���v�Z3m�43a����-[�_|a?7�p����k��o��9���l�<�Ls�9����?�>$=�g��Q��~����8g�}�i��u�g�V��wq����	f
�!.pB�D3��V�^m���m��v��i�C������/����x��f����o�����lR�������z�v����C/�u" " " " "�+pI��^�j�7q�^���G�0|�p��o_3p�@���f�&)=�t�6��Y%����.���NRz'pgC������=���u;v��������i����>}z���Y�L�z���4���w7�:u�Z,��������`�/s0��v��a+���;��SL�>}��C��l}���b���n�(V�9r��5�?�[�4��Y33g��`�s��V��;v�3f�=z���*p���oE��?����i������0���0`��;W<$�E !
&O�\T���?��W\a&M�t����w�i����}b��u:q���w�}B���D@D@D@D@�X�����S���E]d0��x���
kYj��m���k8`���?�
6�G�N��[��fp%�M
��M�6#p�����;�3f����={�u�s���gt��������,Z���'�h7�&ph������Q6���3'�|�������-���ND@D@D@��@�]����cf��i]�0.,P�g}���q�c�L�N�������N��7�i-Z���g��$���D����8��C�m���l�����U�Pn��V��I��%��a���5k�l�|�}�v�!AZ|��u��������@�H�&z��%����(]�t�:;��a6��
Y�C^K�.5|�AV��[�����G�g�a.\��R���
��6e����T������u�&��������m����L��G'�����@��Y�n�5��M�	�#j�V�������pScb_z�C6��`�kp�j��
����m�C���)�"��#G�[o�������1"*V��]����;'���//^l����	i�q��A�h�uQ�/�t�I�.j����w��]a�+�\�ND@D@D@D�<D����8������������p���}���Y,��K�N�m��]1����&}�E�{��S>��k<V�\ig�6lh�-[a��V�����W/��f-�:���+����CA��?�qH�d`��A�I�&���>�[���@���xwvkc�r��I 8?���`�Q}v���Ed�l.f�����E1�I��&]>�l2���
1���$}���,�R8�IN�A��|��/b��%�]�\�=z��U|�u�Va�#->����$p��_�~���n�,3W.���um�����D@D@D@D@j&�D���C�����C��U���������@�$ �S3��,k�������e%U)�T8� " " " " " eC@�l�R��Q(8e����������������@�`�i����A���L�R�����c8�GA�A���Uh�b�)�g�����W�" " �C 84�%�~���5�������2l�3f�0��5�N�����g���R���Y=���W��=����7���[@���$$�+��b.��b�����k-Z�?j�(��iSw�Yg��{���OC��Y8��������
�B�H9�U�V����%K����_E���eH�M�����xw���_�w8�/������o����������m��*��G����Uh�B�_��O!�RZ��#,p0��9��V�K�.�f���Q�^~�es�	'���{��[�����;��G������6��~�y��w
^6h��<�������G���S�:�r�J��[o�O?�4�Q��/�+V�i��Y#��'���}��'M�d���JkHg8���
�B	�"@
�2�%e�K_H<"�w���={��]�?]�t1�_~����/��5k���)T!���F%(eA��fF/.L�:��:��B<�q�>��;v�0|*;4i����������|���m�������k
2|�s������{���U������l[��}��#���7�B-4��#F�����������uk��i���^3�Z��i�a����9p�@�QG��q�A�k��(�W~_�v��mG��?�@�_v�eA����o"���,D|�s����5�.j�/6�5��6lh&N�h�����G3���
������/4>~��G������y��?g�}��0aBt=�	g�q�Kp�	��W�^f��!�����I8��/��}�GZ����9f����u����o��6�V�����kn�����a��������5" " ��@���������O��}����������Y�ft�Z�li��*3:t���Y����}��yo�K��S�vm3}�tkc�1���?&���_�h���?�8�3f��������*9n�8kd�]��������1�}�Qk�������[WH�&p^|�E����3|����y���9�����-[�� (>��+��|����i�y��7M��u�
4W���lk0p���Y����L!v�
%����N!������}������BHz�c������3|�'$�������`�u�����i������F�~����A'X�}���T+����~�}f7n�hg�x��������5" " ��@����
�{��
Q���;4�����5A�'S�C����^��
cy���/����w6�/������`Dd����������=z���	_����X0+�O@�Q��>�(J��K�Z�*���E��W_5�7��C|4(�?�����3?����.����QV�"��.`�U���D�����L�S�N����,����K_h|�����z3x��c�%i�/b���>��D�[h(���{ p2E=q��C����:d>��4����pe+k>����2h���\���>��D@D@��J8����5gf��D�;�q�y�|N��.�����l3D�]r�%��|������������8�����Z8�����K��W_eu�r��O8��0
�,u�M�]@�0���$����+���\��Z��4���p��8$����K/_�}���_���������<J�=2$=���ic�
����,_h(���{ p\����k?NZ�����p�K(�G���5s�L��If�'�{~�.�l S.�Oh�u" " ��@�����9w5~�]@�T��	qqIk_zgl��3'������j�n�F�/\v0�X��]�`������C��P0|0���`3����4�<�I8���
�"��p���?��3��#p�y��������T~_���FzXS
k�|���������/>~��`�;_z������~����?���
��H|�����s��s+_��	�?��p7M(���s�����O�R8�����]#" "P�	��s���>�,;vlT�$�SU�`,g�d �����3�_p�&n@�5>,�%�����m��f
����?��c�!���l�2��������"��{������,X�z=�C����\S�hs����t���]�2o/��|��@����P<W���|��M3@}���!����.�|��/_<�g3x����+-="���ut��!.������]�$��k?_��<i��8��K�8�����E�����gL�����Tn����QoF.�&���%��(����?owbD�Qz�����@1��P�����m���,m�������C.U���:T8��E����>��g�|���L�58��g�~,bf�*\�04X�z�����v�b
k��~�m+�2C�-l��(u<0���{�#D`�����u>.��z���js�-�D����+��0Pa�A����Y@t������+�s�f��\����k����o���������3�/=��'\���l�XJ��{����/�I��~��y>��q�8��{)[s3c�;�wm9=?!��kD@D@��
'� >���Ae�^f3�����k��[�eA*��Q�x(��q���2&��t_zf�����N<�D�~�mwM\6vq���Q1�;�����f�_Z�E<��V�0r�y�eu��u���G|���_�~�HF��)S� ��Z���m�}����:�#la�?W
>�<a�Q��6���l}c�g������WJh��������S���{�Y�l��QL�!��Md���{��������NZ������H�_!���{����Yg-[�#xr
����Z]/" "P�"�S��T���kpX�� "P��,�[�n�_P�PD@D�F�����^z���)�6Q�D�`V��7��k����X��V�R$ �S��R�$�S]U�6�`�s�pq���o����?��/FPP��$1�	������.Gd`FADQ`P`I0� ��DIt�{����w��������>}>�V/]��S�w�g�[oaZ:j��jSwUTD@D�����y}����������@���)��U�D@D@D@D@D�����y}����������@���)��-��q6P�-�y(�[V��$	���w������!�9$�G2w�\[.f;�#����h������a\I��"��8p��uLg����|7~�x�UZ�~��^��}�(=�j����[��W���y�=�Z�8���'���N��.X�|������9!�us��w��C���rH���w�����e�����4xdR��$	N�����k��i��������e����_���7�lOw���ysTy��.D���BHzJ�^��|]����&��i���uk�]\�$�w�U�F����U&���8���}��(j��]�;D�I|��u+�6����fI�	_|�P>:t0'N�\����������@N���M:��U�Vy�����xd#F�0���3�
2=�P�k��#pH�-0�F|����B�p
=��NRz'pgC��E�N����q���;��2����n6lh�O�e;k�,S�~}{��i|{���xJ�O���C��x�p�
����M��9b���~�����9|�p��/�]X����M�" " " " �C�
��G�Z��	&xk�&�7on���c,X`�L�
8���3c��5c����\�����h�����8�Z�2���z>.8�`�I|H����'g��O�����I���]�vf��I�]~����f����'V�����x�a��'�}�ND@D@D@D���L��N�jM�.�����S�s(�e�]����o�9x��=n��
�e�&p2���})n��)fj��m���8������0q������3JN���=����@��+:i|���oWG-ZTT���?tg84Vn�'���7��v�iv�/['�'t��������'�
^����kf��iM��\2$X��V��.�O����3I8�{h��_o�:������l��~�����>.p;v�[�k �#�q�F�m�6S�V-C�]H�[U'�(�l��5k�D� ��n���!AZ|h��ND@D@D@D��	$��^�d��d3�(]�v�t<��a5��
1Q#��K���>�(�����a���c����2.������*�^��L�b<�Ji|Y��W�^��&yI^�Xm���f�P>8��t��������@>��u������`����|���`.��n�}��q2���\��8j�����������k���}��Q���o7}��1#G�����[�n�K�.�c������������4!->.p�����P5�R��uM��������Sa�+�^�ND@D@D@D�<D����8�W���I�&v��O0qf2?���3g��
���	�ln�Y]�`'��&}�D��0c�)�S�=+W���K�52��-��������'�l��a���h�R���C\�X��PN�N�*������C�������M���?���
��@���xW>��pU� " " " "P3	D��G�x��G.&������l��Y]�N�6�������n�;��O�g
���+5���D�d��a��"�E .a������������{�
+i�!�Co�$��K���������X�\���w��m����R��M�j&��:`����r��I@�f�{Y�������e#�(�T8 " " " " " eC@�l�R
���(8e��j�������������@� �kk�z�� Y(eS�L���� �O	wN����"@U�"P�"�3h� �Y2I��_~1\�-|����[�x�1c�i���=p��/4�=�\�5��
����3��r�i���=����~����WYEO`A�V�����`!������L�������
�a����G�F�����H�9����oW��P�=�5���,w��{��z�U��b�O��I���;����B����/��#���FD�t��~��w�yW�K�.��I�����/���Y�n�y������>Ze-���K������7�|������>���������
��8�U�E�U��s����
e��=�����. p���k�u���h�*�H�5��4i������X�����OI����w|��F�>M_��GHu��@�8����_���N�jO����_�J�9��c�z�)o�v��i�Tf��m���_�re���/�h�6mj������~�����[�7�]�v5�w����`����B�K�.��x����:t�]]b�es~P\���\s�9��S-o�u���
7�`x�����������[m�o�����x���ec�V�#G��U=���`2��a�������F�-L�F����m;v�h����k�~�������7d���>|����S����
>>o������?����\@�����r�-6��x�p�W�/Vk��/�T�e�~Kk��5kl�D�:t��s����$L�x���c�F���93z>��O����g�}����B(�7�~����y�A�|���	������2�!���?��M{~�6_|���:�nl���/>���6�C��i�!�}������?���}mT��� ����	���~���/����b�i������|Z�je��*3��1�ra����V�Z���Rpg�q���e�=H�	d�n�����0��-�������M�6m*d������O?m���v�����H�z��$��YC0����	���"�l�{���/�
��~���i���k���,0i$0��[��+������V	>��(���?$}�8��c�����#F��� pu�o����G�

>>/����;w���c�;:t�c���;�����[�����?~�x+R��]�v�E�Y���I�&Q�V�_�}���8������W/;�7m�d0=�^�L��L����g�%
�l��2������������/v�T���7~|���Mz��<���i�.���������������}��r$$pV�XaP��v��d���.�������<���]���d��b������	�x���}{['V�|�!�n���������N2?���-�	���������N��qq�?�����+�V%$���e��K/WV!'�~��1a~�������7��P�f��D-�~V�;w����u�&����!����������'�1��C��������������+&L�e����gFy�
+i�����7���&�!�gE�U�|��={F������|01�x��(m����N�����^�Y����\�L��J���$p��_e<�|��������_��+-�*������������o!������������W��"�BL��{������r#P��������v����;U���f���Q�FUx������o1]`R�����_g5#��P���V52���_��8���	��1c�ly[��J<���K��o�_AC�+
7����1�d��W^�E��uuI��
�CI�M�#��!|_{�5s���V/�i�0:J_�!|X9`��UV�0S�_�.�E�� �OB��z�j�*�yu�O�C����%+�����8�B ����v���N����q|Y�r�=1�%pX�/���{�}��I�>�|���_���_i�U�?��S)�����wc:��[�gB�������J����/�(T�$���w)���Y��@98>5g������8��������`��o�������.�����L���:- p��Z�����&p!?���V?�T��a/!����O�����/��Q<��$>)L�6�
�����q�S����[3@L�g�}��>T�����(��q����K8�������	u����L�?��&��xV�{p����T���$p�n�������3�,���!+������#���7[�6�|�G__�����\��+-�*��:�����_��S,�2��[�'���W~�<}��x(7AS&kINl�����$�S'�:�o�����������8&v.,[����Z�:u��R�G��������M���nb��`��D�M��;��	$�H���I^����x�D��'����s��@`'���Z�+8I��=_�^�X�s���}���Q2M�B���?d���G�\�X��'R�}������}&P��[o����U��O�����|�=I��������>s�����3�l�7�r}�e����i�C����J����I{>��w��?�����3���I���f���py%�����~�<��s���O��@9� p�jwc����$+l
�����.����/��a�O�"�6:���b���c����La0���'�����u.8&���f_
��n���(V��x;�*����k����
��	�I�V�x+����=8����K����U���������?�	���n����&w���}|]�Z�li��:n�q!����>��IL��,Yb0��5k�N0�`B��;��I0�K�}����4>���so�	x�\�	��|�]�vv^��<��
p_�������!
����(���L�q�A�Xau!��������'�7L�X������N��b�7"���>�%p2�f����_������O������/�7~��r��\�9���|��o��~�~?\�I��������������V��@�� p����c&l�un�`'����v&���F��:�ST��'0�f�	��x���:�"�I��ln��D1��m�\p�9Z������U�P���&Ni��&.��3�-�Mtf�xK��mL����c=�BN_W�8blQ����~����[t&P�������r�v�������;�c��������7�|�r��=5�>�g������m�Z}�P~�_�/�������b5F��Lq���������]��9�<]�!o��@4�t�MV� ��SU�7�������O{~�����������s�g�'d|��}��7��#�w���o|�~?|�/����U�"P."�S.
*V;x���]�8�7�ke�a��{����L�*eF@�_�uh��Q��L��@	��	���~�[c.��
��b���X<���w�]c�*y(����/�%�D�_B����@ 	�@P�>������oq.��bk���n�D@��|����T�R��j��^"�L@G�CD@D��`�_�'g,5�." %F@��:D����N���RD@D@D@D@D��H��X��:" " " " " ������R�����������	��UGD@D@D@D@D 8��SJ�# �Sb�������������O@'vJ)" " " " "Pb$pJ�CT��	H���N)E@D@D@D@D@J��N�u��#" " " " "�?	���)����������@���)�QuD@D@D@D@D@�' ��?;�(18%�!�����������@�$p�g��" " " " " %F@��:D����N���RD@D@D@D@D��H��X��:" " " " " ������R�A����6-Z�0'�t�9��3��AID@D@D@D@D �@$pv��m���.��?���r�)��+�4s���)o��6��?�!���n�r����g4h`����_��C��x��Y�?p������m��y��(O������������������G����[����[��W��4z�h��q��������O<���.X�|�����M�:u����gs��w��C�������Q^����w�����es��w�;����������~
M��D@D@D@D@D �@$p���s�UW&�k��5��M3�?���d��-��/����o��\{����7o���'����>��w�y��?��I�@���W��+���~���s�5m����l����..p��;���m#�NH��h�y��������}{���]��w�����W_�SO=�6������s�M�0����M�����8qb���ND@D@D@D@r"`o��L�Z����w��v�#[1b�����4h�y����^���C�l�	6��U%���+����NRz'pgC��E�N����q���;��2����n6lh�O�e;k�,S�~}��s!�o�=L����V�'`|�!�}<X��\���9r�
2V�N?�t��o_s���([_�����k��E@D@D@D@J��8G��fM&L��,M4o����3�,X��\|���.p��g��k��c?�
����[����?�-pZ�jehge&�|\8p���+��"��'O.��I���]�vf��I�]~����f����'V�����x�a��'�}�ND@D@D@D���L��N�jM�.���������I����v��f��}����������a�qy���d��w�R�
�d��a��i���NRz��������33f�(9�3{�lkr�m��������]Y�hQQN��a�M� �X�y����l���gN;�4������-���ND@D@D@��@/j{��53g���BL.�,Px+��
��������	��=4����I���oL�Z�li��\�IJ8��;�-p�5����q���m��U����.���*���?�K6�����5k�l�|�c��� ->�l]'" " " "P��D/Y��N��x���]��	:����������!��K���>�(�����a���c����2.������*�^��L�b<�Ji|Y��W�^��&yI^�Xm���f�P>8��t��������@>��u������`�:����|���`.��n�}��q2���\��8j�����������k���}��Q���o7}��1#G�����[�n�K�.�c������������7!->.p�����P5�R��uM��������Sa�+�^�ND@D@D@D�<D����8�W���I�&v��O0qf2?���3g��
���	�ln�Y]�`'��&}�D��0c�)�S�=+W���K�52��-��������'�l��a���h�R���C\�X��PN�N�*������C�������M���?���
��@���xW>��pU� " " " "P3	D��G�x��G.&������l��Y]�N�6�������n�;��O�g
���+5���D�d��a��"�E .a������������{�
+i�!�Co�$��K���������X�\���w��m����R��M�j����sIDAT&��:`����r��I@�f�{Y�������e#�(�T8 " " " " " eC@�l�R
���(8e��j�������������@���4n��p�k��)S������c
g�(�@1:�
M_�6��,�����_D@D�rDg��A��d��/��b��3Z��s�=���0c���ys{���^h�{����e@.x�z���M����0<������R��KB����k��K/����?�|�h��(���G�f����s�9�<���>�p����/��3��"`��QZwN��_�r����nd�/d����Y�j�m_ZX�d���?�Yd��g2~�*�K_h��w�	`7��~��
UJ����_��O@����B���R�
i��������`��x8����x�.]j��a��������SN1/���Y�n�y������>Ze-����e�<�L�����l���y��g����G���S�:��x��w�g�}����/���Y�b��6m��[��y�����>���9���8�$�z��e�u��>��E�P���S���__�|��G������?x�`T-������O�'�@��9��T
D@D@��@��a%����S�N���39'�U���>	���;
��M�65���?�l1u@��_������f����3W^y�q�#b2N�E��3
40����+}�6����_?����P�-�P
�1�~F�v���l���B�4���o���[����1��Op���*[?�A�v������;��W_����cn��f[>e ������������>�����s�F�d��-^��4n��2o����8q���:v��������o�����/�3|���j��/_��}���KO|������C�C=d�#�2'M�d���_H������E��9V����u�����[�F�*4��}��-���?��*��'��u����@�!$p6o�l'o�~�i��/���~�~�z�V�����+o�[�je��*3>|�������������E��=z���]�L�>�N��������y�����
��0D�0��
��B�g������${���f��]���7������t�l���8��i��B�4����/��i+|:t�y�����[�l�����
������_��i�&�iT�z��'h��!o���!`����
P�?�b��P���i����>��/4��8L�1_���t�����j����k��������2O?��9t����t������������G�q���/����{�q��7=��=�q�F�J��8�~������^�fW��
6D���=�1Ac���!�d��bW���W[�����<u��|����;���_l'�Lty��$"[�L&�n��&D��t�Mf��!Y�I�-!+����`���?�8J��K�Z�*���D���_7M�4��C|<8�?����N2?����.����QW�"��.0���	��#F��+U���;8����3��-4�/_<�����z���7��"N�0�V�w��}������\����|��?NZ�g+?��p��	Vp\�\6[]�I����xi��1���	�]'" "P}T��9p��m9+�� 
��;�'X��3Q�W�dK�����a��m����.����\��>��
�$�1_�$��?V-�yV|�������:����.}��a�����"��35�u��[v%���V_��X`���BU����'�C&?��������?U5�����#)>[����|��/����B������.^���8i�O^�����&P��Uk�����If�'���&�8�)��'d����E H��L���?�. v�J��Lh|]�K�&�s����r�2��Z��G�I�K�(��}����>&>L�X��BHp�4�<�I8���
�=��0�ab��gG�#p�}��������T_����.��&h�e����G��[hz_���l�3�����������;�]z��|���|��&p������&P�s���	�x��I_��2������D@D@�� ��Zl���`���pLO\H8U�d��r6'I�gn��L�E�����a�-3L����;aI
i�=	�p��������=z�T����^�8�l
���A� l\�ms��a���e�*��e���~��@����Q<W���|Z�&��Iz���W��N��_�G�����/>�
�<�H�	������0�w��tI��>�r�:�#P���'�3Q������1Q+��'����E@D@J�@��W�Lw���7o.q����%L8�(���/�h�
1a�-=n��9(���=?n�q���&:�|_z<�aRE�y���S�
:�z���4
�2�0���$��Y�����k�w$���i�3r���iC�M�xe#o&�	`?Hh`Bz����=H���������eK;.������+;x���_l4�8��Y�p���s�5���n�-��W_�z��iW�a�A����^��xu���
��M@}\�����'��u�������Y��� N@����.~N����������/=�I�����?��q8(�
�c^��];��g,��r�B�_�����TN�A����A��*�Y�a�:`�;��6��&�
� &=�S�o�U(2w!{;�c�A�I<_zV��O_��z��?��]��]|��D�k��\���cW�����i�!��
�8�5����#�|�I+rX
������|��'��ik�]��o�i�G$#���)p���f���&�W_�0��y�F�s�����'&w�n����A����������7~}��/}!�L����z{_2vXYu\���/��������/=�i'��B��w����Sh��{�Fn���mk]�#xr
�z���]/" "P�"�S��T���{px�� "P�p���{����j(" "P#	H���n/�FK��^��F"�|��g��7n�q��J,��D@D@D�	H��b���:I���NW��
p&����5���]�y$pj^���" " " " "P�$p��k�0�y$pj^���" " " " "P�$p��kK�a�
��E{���D@D@D@D@D�2	Dg��������gUp	����;���������<���24h`���
B��x��Y�8p��uLY���������Wi���{m�����6�w�CR���������M����+��C�x����$�������������S��us��w��C���rH���w�����������4xdR��$	N�����k��i��������e����_���7�l�������7o���!�x���(^w\I�@���W��+���`s�d�6m�<[�nm�����N��j@�q�R�������������}{�k�.����$�����H�
f��Z�$��/>�(�:��'�^��D@D@D@D@D 'V��&����U���9���la���_�~f��A����zMRz��&��/V�T�N�����IJ��l�����������>���s��Tf����M��
�����lg��e���oO�w!�o�=L����V�'`|�!�}<n����V��&jG�������O?�����>|8���.,F����x��!`���G�Y��	�5K��77s��1,�g&d��q����c��1c��O�g���V4����y�V�Z�Y��	=h0���$�$D�����*p����h����4i�q�����Y�fv��U6����/�eX��	m��8�"��S�Z�K.��0����\
{Yj��m���g<h���a�qY�	��}>n_�[A��35��6m�t��IJ�Vp�8_w�uf��%'pf��mM�\����4������#�-*��I�:��	+7������gN;�4������-���ND@D@D@��@/j{��53g���BL.�,Px+��
��������	��=4����I���oL�Z�li��\�IJ8��;�-p�5����q���m��U����.���*���?�K6�����5k�l�|�c��� ->�l]'" " " "P��D/Y��N��x���]��	:��������������K�G}�U������s��1s�Yg��F^�B�WC/oS�L�X���4��p��W/�D
��$/j�����3�(	�PR�ND@D@D@D �g��uvR�j�O�IQ�[�o���~0��L
7�����8�l`�{p�����[op�MtH��5���>j�(s����>}���#GF�
���[7��K��1@�`������m��z��8���C_��c�n���&j��x�{�����Z/]'" " " "P"��j�i���h���I���'�83��}����X��K�N67��.�L���L�>n�FY�������)���+W���F��e��E�B��j���O���0��M4{�\@�!.V�Xa(�S�NQZ|���!������M��M���n]��d ^_�+om�*W��"���#�X���#^�@f�fb6`���.�q'��t���d�d7d��t�'�3uA����AL"p2J
��0e�"��v�suz��i�V����{��������O���%t��������U,V�\������mk�i)���������L�&j5�Z]�	�Z�@�U���������@�$ �S3��,[��	�_r�����j��������@*	
��! �S6]������������H�h�����������
	���J5DD@D@D@D@D@Gc�Z��5n��p����2�)S��]y	w����;�U��G�*K(q��4h��,����/����>��s��-o<��1�4o��8y�����{����a�s�u��SN9�4h��x��o�E������'��U�VY�P]c��S��?��c�q���Mu��
��a����G#�w�yg$��P{����kW����@sV�;����2CR<�~�aS�~}{F�<P��X�:�X�S��?��[H�}�������6���!,p�q<����x�.]j�8^}�U+.^x��n�:��{��G}��Zz�����I��_o>��[�g�}6*�_e=��q�n�.�����0������={�=���s]@�����\�><�HU~�k=i�$s�UWY��M�$���_�2g�y�y���
#��y��"����-V��z��?��[H�}�������6���!$pX	������S��S���W�&��s�1O=���e;w�4|*3l������\�2���_4M�6�������IE�~�L������k��f���QR�9�r@�u�����{�6C���K���l�����k�1��z����.������[Y�%���_n�n�j�������+m�.���9r����.������l/^lW7Z�ha5jd&N�h���cG;~>�\����'��>�!c$~����M�N���8|�o��y��7��d����q�o�>s�-��x����W\<�X�e����R����-�]k���i8]v��!;�]���0m����/���?s����2>}�c��=�����8�|�=�n����5|�AV�������G�,x	B�?�|��������b�Ou��\2~2�I��-m�<�~?B�������w�C����x(7A�#�O?�4j�_|a�c��7����W_y��j����Ufp�c"�����M�Z���/>�.<��8��e�{�$�n��EIY�g��[�������6m�T�������~�0��
�����5��z���I0�
�`r��?�mE40����{+�_�0_����~��Y3;��]kY`�H`Y�n];V�'�O=;�||}?P>>�OkH��q�	���m�G�%A� �0����G�6\||^~�e3w�\�c�;v:t�`����w������������[�J�����,Z���
M�41�
���"�����x����O�^�z�1�i�&��!�re
gZ��>��8���������N��U���P����������/���S���B�O|�$=�BQ��7�������7�|���W����O�"P����+��!�X\p�"LP����Xb�P�vm��=�8������:�=�6�}���N���C:������Ki�I'�d~��'[&��?�8���9�	��L����qs��Wf�JH��
�����^:��BNZ���c�����G��7o�]���*��Z&��:w��I��2MZ�	�CI�M�'�'O�c�q�����[o�eW!��gW4&L���2�����&������������	V-�
L@{��%��_�j|�����������8i����{�g��.�r�2+��_6������W�61^� T���SZ�����?�yg�����_�S���B������[�s!�����py����9�������!��kD��T��q&(?���]������L�x3�D����Q�*����������.0�q���������V(H��q�����/I�����`��1Q��-gE%r����~�7m���!������a��`2��+�������$�@��!��������������9��s+����L�	�/�>��J�*+y���/�Mf�&�8q}�~&��J`�F������#�x	����c�� ��H�?���!�>��6>}|_V�\`�Be	VG������f�)p|�q��+/jr8������}�����������?�����1���-�3!�����py��~��y*p�~�����?���kD�		���3W����NU
�e�����3��b��[k��#$>�����;-��#S���N����!'-��	\��op�����&p��EH����8>�>��K�c�G�0�O
��M��!4��a��������G���g�m�8���2�pf�"�6��?�|�p�Bk�E����/���5�	�/��������?��$	���@'�q|����*K�`z�� �{�`�����1���W���J��4���I���WZ|U�Ou��|�����=���)��	_��P�����+�T���<�u��� ���
��$'�6�cz�B��)���l�����\}���}���L�;�-[V�D�N�:f���c"M��L�|��&T�	Q7��e�j���&o���Z��z��u�$/��hG<`����������B ��X�a-�������L�Z��9��L�>����	�&j!���2d����G.H�s
�)����������>(_����Z�	
�*��'�}���t���$�ZY��b�~r�9s�������/~�N����d ��3�?�H���_i�U�?i�'_�N�����l7����-M��O6�����+)�����#�!V��?�}�F��@��W��[�vo%Yy`S0�^�]]t�E�|���0�x����c���}`
�o<��_��sy�1a�
4v�x;�����dLX1K��4�H�������;*d�&p�&]�X1���������}2�/��oV���[���������?���!6��M��	$e�����l���;�u��C���?$}#L���/Y��`�3k�,;�`���M�L�1_��{.�D����$��x�J����N���k��������������~6�rar�D�=+q'
��C<ruP/VX]��o����|�I��
9Vb.���`�C=}|��������Oe	�L��������~/�����M������������/v������H����|��}�?i�.���w����~��PN��k��E�\T8������16�:7����x`c;�\�\#��Q��U,���	���{�$3|�����c���4��h&QLly�{�g��&p(?�f������)-�����}&:�����o)1�b���U�>}��8B��I����G�-�O�C������6>x���}�=��X!�{?��_��w���y.��7��|�M��{��8����{�Fn���mk=�!xB��C|���{\�g���{��,!r2���������Vt�^��� t��}��t�M7Ya��O)	w�'}�\�������}�/_|����?w�*p|�7�}�O��I��I�_��_���}mU���H��K���p�yW(�M�Z�|X����{q:S��@���Wf�cs��9��"P$p;��p������������i�6Oo����z������@
}>���z	%Q��Pg�*"H@'T���bj�e�[\�����/���pb��u�0�>�t��q.������3��$������������K�������@���)�QuD@D@D@D@D@�' ��?;�(18%�!�����������@�$p�g��" " " " " %F@��:D����N���RD@D@D@D@D��H��X��:" " " " " ������R�����������	��UGD@D@D@D@D 8��SJ�# �Sb�������������O@'vJ)" " " " "Pb$pJ�CT��	H���N)E@D@D@D@D@J��N�u��#" " " " "�?	���)����������@���)�QuD@D@D@D@D@�' ��?;�(18%�!�����������@�$p�g��y����M�-�I'�d�<��<rPH&	���w�������O2��r����+���sm��n����������������
�<~�������7�xc��8`�����g�y&����n�������?�����}��Q�o���~�u����k �=�4n����/��2��Ox���/_n���jS�N��?���}��6���C��o��v����_�|}�!�C��y����;�0?�������B��:"	�k���\u�U�����k��i����?o3��e����/����o6�^{m����7G}��'����6��w������C�#Pz�����������9��sM�6m�<[�nm�����N��j@�q�R� �y^�z�j+6�o���k�.����$����W��SO5��
�s�=��c���/>�h�;t�`&N�z����X���t&��V��&����]��F�a���g
dz����$�G��.[`���bU	A�8���+�wq����	����Cmq�/3�}\�s�N���������
����G���5���_� �\H���G��s����	_|V�2W�&jG�������O?�����>|8���.,F����x��!`���G�Y��	�5K��77s��1,0_|q��q����c��1c��O�g���V4����y�V�Z�Y��	=h0���$�$D�����*p����h����4i�q�����Y�fv��U6����/�eX��	m��8�"��S�Z�K.��0�~���r(�e�]����o�9x�����h6l�p\i+8�o������)fj��m���8������0q������3JN���=����@��+:i|���oWG-ZTT���?tg84Vn^��(�y����N;��`���eK�������$P�����{���3����!C��������8��x&i's����mR'p����-[��3Wp���B�c��y�b
�!{�6n�h�m�fj��e��i|�J�����M��p��f��(�1�����:$H�-[����������?�D7�K�,��l&�!�k��v��6>�� r2C!{p�k������>�*p|{pX�9v��9������#/j!�������)S�X�R�C_V8����h��I^�5V�|�q���?��N()]'" " " "��D��n�:;)f5�'�������7�|c?�Ka��b_z�C�d60�=8�������78�&:�~��bnb5j�����M�>}���#�j���������K��0�ra����p�MH���xq���/�D��T�n�D�x|��={�TX�
���(��a��4b�U4�o������L������a��Y,x��w'��gVB&�In�I7Q�,��Xa�G�s����+��R�F���e�"l!|q5}��'[�u�z�&��T. �+V�0���S�
�(->�����d`����i�����?���q2��/����6\�+���������L��y��G�������	/B 3d310`@V���v�M�|rq2�a�2�N:������ �JM� &8����E���g�KX���:={��{��t����JGZ|���'I�����v�an�*+W.���um�����D@D@D@D@j&�D���C���X�C���\AD@D@D@D�f�����^�����/�}Ee�H5JD@D@D@D �������������@���)��TCD@D@D@D@D@$p4D@D@D@D@D@���N�t�"" " " " " ��1P)p=��m>�Z�a��)Eq'=v�X�Y<
"P���B��M�<K�~�n�����4h��,����/����>��s��-o<��1�4o��8y�����{�rj���~�aS�~}{�<`~�����������������K�+���?�,Z�(�����Y�f6��s�1�?�x��O{�1��qc��aC3l�0s���(��|_|pC�x!c����U�l����%K�?���"R��u������xWk�zrgPq>�����;����o��v�s>�:�
M_H�K��)�]J+" "Pz������3��]�t�0L�]x��W�)��b^x��n�:��{��G}��Z�����y������7x��x��g����G���S�:�r�J��;���>�,��I�K/�dV�Xa�M�f���k�z��(�t���7����=�����.����7��J��'�B��/}�����&M2W]u�+q���8}��������Op��3���s�J " " Y	VBXA��0u�T{�����jWJX��O��h���������iS���#�SDXh��o��}No����>s��W&?|�p��S�(�W�/��NV����gW�XEj���j��>9r��g�l�.]��m�*$O8o���i���M�
����I/���i��]������}���o��=���o��S���+�j���~{��������s�k2M�/^lW�`��Q#3q�D{?u��1Z����W�B��/}������v��>8N�d�/��A�p��|�'$���{��C���z��G�e"�i�����#m|Q>c�Uk�el]~��f���Q�
M�o�>s�-�D�/����	�]#" "P}	&&L�>����e_|���n�����*��W���j����Uf8|��-�UX������{������v��f���vr��A��W/���?&�����[���1���q����1������{���`���v��v�Z�k�.k^�����u&�������M�6m*$O8/���a��c�����C���3�8�l����"���|�������M�6L����<Asuy�m\V��W��2���;��|�O��B��/}������1�X��\��l_����a���+�0i#���?!� pXy}�����C��
������������/i�
����+^:!��������oz�s���b��*pJ��	�{]#" "P�	����l��!jo���	�����4A�'S�C����^��
&�L��?���K��w���.��b;9f�����l�2������f������N�y1�w{�|���}�#�VE�	<����G�Y}�U�V���������I�&Q~����G�g�����~�����W?��X���&U9As�Q�J�s����������K_h<�g��g���)>������[o�eW��gW!'L��C�2�]&�LQO���8i�������&M���������Oz.�yi��1���	D�PD@D���T�s���pV6X�a"��N�C|��<�|N��.���&��V�X1����*����c���g��{�]U!�������c���g�'���_�uV-'p]�4���	o�YE�M3fj�����;DI|����W?7X�r�=QU9Asea�������+��b|���O������xL�������6%M���?q��BC!�����q}/�����'/����K(c����5s�L��$3����?q�Q����:�t����@�!$p|&j�\�z;U%p|&4����w��9s�DY�}�U-���#�$(���<��}����x��0�a����B�������O������P�a��i���>;*�����F�g��9~���������������l��������K_h<�������ff�-c��B.�c��K�w��2�o|������_����C�Wh�@!���l0���,p*���e�Z��A H�`J�d<��6�l�t��$�SUN�,gs2�T~�&���]t��O �Ve��`rt�w�	�/0���]�+��+/3~��!�G�A�h�,X�z=��5o<�g's�o�3H�-[V�D-�����_A�s�F�\��i���'�
>4_�C:)t|�;�}�������������� �z���0��B��O����'pB��)p����~a��D�����{L�����T^��nX������%n������	?�\x���W!&l���mt6����C���M4���D'��K��6�lh;o4����u���Z�4�8�nE�(&�A����Y���Z\`�����������g7�3�`O�sr�KK<J��$�$����V�e��-[�q�9�s����X�"���F�L��>���}=�\s�������}�����(D�G��*p�X�����#G*�s�6�Mps���W��#������w6�����W5�� R1/d�'�s�|�'�HZ������C��)p(��v���}�<cy�������FD@D��� p�z��T���1��&����x`c+oUY�@1���tT,��"douL:�3�|_zV��O_��z��?��]��������	6��<1��o��^��W�/�7��!��
�8�5����@<���V���.� ���'�|b�q���o����C�3e
D4����f�����W?��n��&+la�?�	|Xybr��&�q�m (}\��������#������&p|�������5c���\P�o����&p��/��}�G���n�ki&j����wo�f�m���e<�'�P��O����" " �M 8�]M���	��7�
" �Og��w/����" " 5��N����k�N���j$�f���f5O�����ZAD@D@J��N)�J
��N
�t5������a��i��Q��M�UQ��G@����Z," " " " eK@�l�V
��G@����Z," " " " eK@�l��4��@-Z�����nYAD@D@D@D@*�@$pv��m���.{���p���smY�������N8�A�6��p!$��7����X�����).�7��?�~����~���o�>J�a�|�u����U&�x^�G�6�7>.{}��'������/_n���jS�N{���w�m�:d��!�.p*����?�
r@(���S�C��:��'��������k��i����?o�o���|����s��7�k��6������28�;����!�(�z���u�q�l�l��M�g����wq����	V
h!.pB��YH��������}{���]��w�����V 6l���k9���0����������8qb���ND@D@D@D@r"`o��L�Z������Y��F�a���g
dz����$�G��.[`���bU	A�8�z�����N� ��j���x�i����;w�Oe����4l��L�>=�v��Y�~����{������t��9k�|�������3W�&jG�������O?�����>|8���.,F����x��!`���G�Y��	�5K��77s��1,�g&d��q����c��1c��O�g���V4����y�V�Z�Y��	=h0���$�$D�����*p����h����4i�q�����Y�fv��U6����/�eX��	m��8�"��S�Z�K.��0����\
{Yj��m���g<h���a�qY�	��7�n_�[A��35��6m�t��IJ�Vp�8_w�uf��%'pf��mM�\����4������#�-*��I�:��	+7������gN;�4������-���ND@D@D@��@/j{��53g���BL.�,Px+��
��������	��=4����I���oL�Z�li��\�IJ8��;�-p�5����q���m��U����.���*���?�K6�����5k�l�|�c��� ->�l]'" " " "P��D/Y��N��x���]��	:��������������K�G}�U������s��1s�Yg��F^�B�WC/oS�L�X���4��p��W/�D
��$/j�����3�(	�PR�ND@D@D@D �g��uvR�j�O�IQ�[�o���~0��L
7�����8�l`�{p�����[op�MtH��5���>j�(s����>}���#GF�
���[7��K��1@�`������m��z��8���C_��c�n���&j��x�{�����Z/]'" " " "P"��j�i���h���I���'�83��}����X��K�N67��.�L���L�>n�FY�������)���+W���F��e��E�B��j���O���0��M4{�\@�!.V�Xa(�S�NQZ|���!������M��M���n]��d ^_�+om�*W��"���#�X���#^�@f�fb6`���.�q'��t���d�d7d��t�'�3uA����AL"p2J
��0e�"��v�suz��i�V����{��������O���%t��������U,V�\������mk�i)���������L�&j5�Z]�	�Z�@�U���������@�$ �S3��,[��	�_r�����j��������@*	
��! �S6]������������H�h�����������
	���J5DD@D@D@D@D@Gc�Z��5n��p����2�)S��]y	w����;�U��G�*K(q��4h��,����/����>��s��-o<��1�4o��8y�����{����a�s�u��SN9�4h��x��o�E������'��U�VY�P]c��S��?��c�q���Mu��
��a����G#�w�yg$��P{����kW����@sV�;����2CR<����/1g�q��;v���}X��)u�����-���>M_��GHu��@�8?��9����x�.]j�8^}�U+.^x��n�:��{��G}��Zz�����I��_o>��[�g�}6*�_e=��q�n�.�����0������={�=���s]@�����\�><�HU~�k=i�$s�UWY��M�$���f�����d	c��?EN��m����|�`���B����F]#"P:�+!��������S��S���W�Rr�9����z����;w>��m�f�yK���/�h�6mj������~�����[�7�]�v5�w�����	z]�t���C������C���`,��������k����jys���o�����%���_n�n�j�������+m�.���9r����.������l/^lW7Z�ha5jd&N�h���cG;~>�\����'��>�!c$~����M�N���8|�o��y��7��d�������j��}��[n��\���+��"x|�Z���Y��/��[Z���Yc�8p ����Cv��<|�'a����s_�;6*�����!d|����={���Bq���{<����
/j+��/�������O�_��=���/���S��?7�|����|K�!�������������������6*^��@���������~���/����b�~4���+/�V�ZY3���~L�\�>}��U��5�������[�l�I2����[��%|&}�e��}�8p�i��M��8�����
<��._��^��W����7�`&|�3@\�VD��{���B���������7��53���k�Z�4�@��[���I�SO���_�������>d�`�q�F��#FDI8�:�7i����
����_~���;��1v:t�`����w������B%>�����V����v�2�-�<CC�&M��&$�;�����/�'p|����W�^vo���`z��\�������>;N����|���}�}��{~����?�����_�?I����@��7�������7�|���W����O�"P����+�*�X\p�"LP��%~���6��g�1�k���L�S�������?;�c�oS��oo��j�/>�cy�
<8��v�t�I���~�e2A�����x��#��P����=.���7W^ye������p�^z����*D����=&����zT�y���*��"p��e��J�s��Q�4�.���>�������q2y�d;&w��0����[vb��qvEc��	�,��>��(QA`%������F� >�<���H�j�o`��g�(�_��W���&f�?����x��I�>>��<�0�u���)pX�u��	_��W������������=����������x�/���\Hz��~?\���~e�/�(�D���H��(7�*p�	��?�hW{����Z��o��h0�5jT����x_y���&5�}_�uV0��
�8nU#�<6'M�B�g0f��([������@�R?����W�����&{�1:�L���+��4��.I?P!|�#����}c30��k��f�=��
�%3-FgB��7�+�����Jfj�K`�����T���IH�W�^mW%0o����|H�v�`E���cV�_��������x��I�>>�/+[.�g���#����o��8��x��+p|���_���_i�U�?��S)�����w�'��[�gB�������J����/�(T�$���w)���Y��@98>5g������8��J�w�}g������n�GH|Z�wZ�jU���A�8I"&D����6����
����~��{�i|]�|���'p|�}����&�Ia��iV0��4>�{�j�����b��p<���m����~VF��c^$��\����o.\hM���v�eZ���7����Z���������$���t�$>�/|\`�Ye	LYd�y��7����x�+p�#������'y��_i�U�?�����_|�{��|���X'd|Q�BN�����Ry������"P��6L��� "�x���I�N�u����7s��W'��/>3!?pL�\X�lY�:u�����4��3Q���� P1'D��2����?���w&jiH�������0�������OZ�C���!��Nb���\Wp��{�27�����3Q����&d��������!�G�� ��50O�����/J��?�L�|���zk'(<������!��{�hke	V�9��}���c��g&���x'"p��?�H���_i�U�?i�'_�N�����l7����-M��O6�����+)�����#�!V��?�}�F��@��W��[�vo%Yy`S0�^�]]t�E�|���0�x����W�:��
���3(�x���x_���c��h�������n��1a�,����"������;���m��q�t1�a����������M�����Y���n�������0~���v���mr�M )�����e��v�a���|�?��!��a���}��%S�Y�f���&������c����\��J���I<��V���!�5���;����kg������3��0���M��_81`�������!�	9�:�+�.���7�|�O>������+1�]vY��	_����F�s�����N&�l{p��d�g��}���'���`H������=�|���[h������9���{�����O����?��2�|��6�S����x(�;�0��sc������
���$9���$�7� 6Qgz�*�����	���{�$3|�����c���4��h&QLly�{�g��&p(?�f�=�}v��SZ��	���7�Lnu�Y?&P�H1����O�>�S!T���u���#�����||���M�Eg�>	�Oy��=�������;�c��������7�|�r��=5�>�g������m�Z}�P~�_�/�������b5F��Lq���������]��9�<]�!o��@4�t�MV� ��SJq����\���.����_���=�|����r��\�*p|�7����'����~����/����
�����*^��@$p��A�j8��+����s�l>�rv���8��\E����+���9����r(8��P���bk�e�[\��[[�4y��(+=q��5f���"��B�O����^BI��%�����	U�.��{��W��`���/�&����F]AD �@��'�a�K�*�����%�$p4:D@D@j,���}rq�Rc!��" "Pb$pJ�CT��	H���N)E@D@D@D@D@J��N�u��#" " " " "�?	���)����������@���)�QuD@D@D@D@D@�' ��?;�(18%�!�����������@�$p�g��" " " " " %F@��:D����N���RD@D@D@D@D��H��X��:" " " " " ������R�����������	��UGD@D@D@D@D 8��SJ�# �Sb�������������O@'vJ)" " " " "Pb$pJ�CT��	H���N)E@D@D@D@D@J��N�u��#" " " " "�?	���)e���k��Es�I'�3�<3��DD@D@D@D@�	Dg�������2����)��b���J3w�\����n3���~n���(�����A�6�_�5�>$��7��5�����6��g����o�?~��.-�?�`�m��}���o���m��5�}�H�G�6�7>.��.��<��^�������������S����6w�}��:t��m��o�������~__|�P6w�y����;��?�h~����d�ND@D@D@D@�D��k�1W]u�a��v�Z3m�4�����L�l�b���K��������^����QA�|��9����y��g>��������^�zE���~��7+p�=�\��M�(���[���')�8��6B\���/�f��^�������G9����~�hq!�/�_}��9��S��a���\{�=���>�����:��'�^��D@D@D@D@D 'V��&����U���{��mW<��#F�~���A���z(�5I�8���`#�XUBP!N���
�]\�$�wq6t�P[D\���Lk�����~*3�����a��f���Q��f�2���7<������t��9k�|��������U�����#G� c����O7}��5��������?��)^D@D@D@D�tX�s��Qk�4a�o��@�����9s����_\�g��qf���f��1�����?�
?��s��U�V�vVv`B�������!�/	Q0y���
�4��<��kg&M�t�����i���]}b��}:q���w�B���D@D@D@D@N,��Dm�������K.1L��z���
{Yj��m���g<h���?�
6�G�
N�~�/�� `J���i�6m:N�$�w+8L����:3c���8�g��&w.����N�������E�U���������_=�f��y���N�+X��x�8�=��D@D@D@D�<	T���w�^3s�Lk*��r��!������p�}:�|,�I����C�~�z��	��4�e��������q������c��X�a���7�m���Z�j��B��8i�C�d88@��Y�&�q�w;v��	��C��u" " " " �O �M��%K�$��w�@����������9���P��Z�t������
�Vp�;f�:�,�p����ZH��b(��m��)���T�����z��%��a���E��6_|\`&��#�JJ����������C Q��[��N�YM�	6�#jp+��7���R��������'�
�u���6|�p�
����������G�en��v��O3r���Z!|�u�f�t��8���\X�x��\{���'^@���5Qc,��[7�D-/{��=V�B���D@D@D@D@��@$pXm�<��q���4ib�?�g&���o�8s��|����������	v��i��M�(30V��8����r�J����Q#�l��[_\M�|���{�^��f/�:���+�t���� J��z;$9<x�i���������p�����w���
W�
" " " " 5�@$py��`���x�b����L���E1���a�.�\� @���L���$}���.�R8�IN�A��|��/�Y��.p�N��=��*>��w�����?��I8������{��������xw]��m�>-��I �D�f�P��3V���*W��$pjf��e�q>��Kn_QY6R�H% ��"" " " " "P6$p��+�	���! �S6]������������H�hT
\O�v���z�2eJQ�I�;�p���@������hS<�R�_����E@D@*�@$p
d8K&)���/�k8���=��cp�3f�0��7�N^x��������Z�����~���_������~�- ���$$�k��f.��R������7�-��=z�i����;��s���?^��Sw!g��3x8��_�;v�[o���a����S�}����U����r8�V���KK�,1���?�w�2B�oZ�|��/t|?��c�q����j���6l�9z�h�s>�:�
M_H�K��)�]J+" "Pz������3��]�t�0L2\x��W�)��b^x��n�:��{��G}��Z�����y������7x�$��g�	.��1S�N{����+�;��c>���(D�K/�dV�Xa�M�fE�SO=u\��&M2W]u��(��/}��]��W^i���+�f�����)� �s�z�7~}5��/4����}7�|�$y����@\'V�>BN�i�j*" "P��+!�� ^\�:u�=u��_�+%�Zd��g6~����Oe��M����Q��: �B�/}���sz3�}�YA�7o�+@|��q'�����=�\����G�!����V����gW�x��E+�B}<r�H���.]��m��UH�&p�x�
��uk����}���F�}��k����wG������g���E���`���+������F�w���+�s�����4Q[�x�]}�y�F��������c�h��W_E}������}��=��n:u��kV��	��w��f������������ ��_H������E��9V����u�����[�F�*4��}��-���?��*��'��u����@�!$p��3����O��}��������/�������/�j����Uf8|��-�UX������{�����v��f���vr��A��W/���?&�����[�(������O�
NfF��1d�~��!C}1W���0~�x;�^�v���k�5���jh�D�	:i0�8p�i��M��i���_6L�0�c��C�68g�q���e�5D�������E�6m2o����W�^���1�
t�=LpY��^a����B��J��?��|��������g+?���}�q�F;G��kZ�?!!px����O�������{�/��}���_��/W�t�Yq�����|��y�����=���P�S��OH����E H�`v��|��
Q�x{�wL��,���a>�����e���Vx01f"O��A�_������u����1]Vt�Dd����M^��7�={����N�������t�I��

�"4�b��H>�G{>���(9�/�j���7��_�4i�$��1x��������O?������ue,b^������9Q����*B���
|����o�����/>��|��������^D���������������������'��p��	Vp\`�7[]�I��e���F.���\����x��G�R��,V6X�a"��N�C|���<�|N��.���&��V�X1����*��a*��������H8����	E��mmZL�0ob��Z8���d����_��D�	\�>M��r�[`V�`�������-�����I���~n,�r�{:�r�������$`���+�_�}�SU��>H�?����/d|c&����&����cT��wy#p\�����'����w�����1c�j��9�:=���w�O�d2�r�����E@D@�� ��3Qs�j������*��3��u�/��l��3'������j�f�� L�x���>x��
c��BRz&���|������|�����&_�g
&j�h�!$�	\�I�$	L���YfZ��0�?�����8�>�l���9~���������������l�����7~M���_�����L*����wep�<�������4�C�����4�B��������/e�S�O.cH�����TAS&IN��fc��q��V'	��r2�d9�����37Ig�����L|����V���
&Gw�q������a����}��e�	VZzD��W]�*P�	^��8d���G��QJ�� �`����'1o<�g's�o�3H`7Q�,8^_��/����9b�x��	�����-iN�|&h���t�o��<����}��=���c6��i�r�.�$���?��	�?N��q&j�,������r����C�����	��������%n������	?�\x���W!&l���mt6����C&L�`�D��,�MtR���xh�����FNqoYl��4
�1�/���L�|��������nw1�
����y��yv�?����&m\�b�$�$����V�e��-[�q�(sX���o�	�~��>S���g���v_�5�\cn���({_�}�c�TD�G��*p�]x�:r�H�s��84�W�#�7~]�|��/_|!��B&������5k���������/�I��>�r�H�C������k��<k���	�]#" "P}T8q��o�7�l�f%�����$/����\��"�����(�%p�A�����I}&��K�*y����SO��'��k����O��NHzD�M7�d7�������y�o��CX�qPkf����0x��'�����.� ���s�=<���7���#���g�8�h\kc���&�W_�0���F�s�����'&w�n������7�%__�}���>��K_H|��GT^00��d����-Z���!�7~B�H8i�����#m|Qw��4�B����7r��^@\�#xr
�z���]/" "P�"�S��T���{p��� "P�p���{����j(" "P#	H���n/�FK��^��F"�`V��[o�U3<���+k(E8��+5�N85����jC���+���F��6uWEE@D@j	����j����������-	���Z5LD@D@D@D@j	����j����������-	�����lg�h���'��e�L����{������2�C�y$s���eq�b�3>2�:���
�<8�����7�xc�28`�Sg����|7~�x�UZ�~��^��}�(=������[��W���y�=���8���'���N��.X�|������99�us��w��C���rH���w�����e�����4xdR��$	N�����k��i��������e����_���7�l�������7o���!�x���(^w\I�@���W��+���`s�d�6m�<[�nm�����N��j@�q�R�������#6�o�Eqz;�!Z\H�K<�[9�t��a�o��0K�O���C��������8qb���ND@D@D@D@r"`o��L�Z������Y��F�a���g
dz����$�G��.[`���bU	A�8�z�����N� ��j���x�i����;w�Oe����4l��L�>=�v��Y�~����{������t��9k�|�������3W�&jG�������O?�����>|8���.,F����x��!`���G�Y��	�5K��77s��1,�g&d��q����c��1c��O�g���V4����y�V�Z�Y��	=h0���$�$D�����*p����h����4i�q�����Y�fv��U6����/�eX��	m��8�"��S�Z�K.��0����\
{Yj��m���g<h���a�qY�	��7�n_�[A��35��6m�t��IJ�Vp�8_w�uf��%'pf��mM�\����4������#�-*��I�:��	+7������gN;�4������-���ND@D@D@��@/j{��53g���BL.�,Px+��
��������	��=4����I���oL�Z�li��\�IJ8��;�-p�5����q���m��U����.���*���?�K6�����5k�l�|�c��� ->�l]'" " " "P��D/Y��N��x���]��	:��������������K�G}�U������s��1s�Yg��F^�B�WC/oS�L�X���4��p��W/�D
��$/j�����3�(	�PR�ND@D@D@D �g��uvR�j�O�IQ�[�o���~0��L
7�����8�l`�{p�����[op�MtH��5���>j�(s����>}���#GF�
���[7��K��1@�`������m��z��8���C_��c�n���&j��x�{�����Z/]'" " " "P"��j�i���h���I���'�83��}����X��K�N67��.�L���L�>n�FY�������)���+W���F��e��E�B��j���O���0��M4{�\@�!.V�Xa(�S�NQZ|���!������M��M���n]��d ^_�+om�*W��"���#�X���#^�@f�fb6`���.�q'��t���d�d7d��t�'�3uA����AL"p2J
��0e�"��v�suz��i�V����{��������O���%t��������U,V�\������mk�i)���������L�&j5�Z]�	�Z�@�U���������@�$ �S3��,[��	�_r�����j��������@*	
��! �S6]������������H�h�����������
	���J5DD@D@D@D@D@Gc�Z��5n��p����2�)S��]y	w����;�U��G�*K(q��4h��,����/����>��s��-o<��1�4o��8y�����{����a�s�u��SN9�4h��x��o�E������'��U�VY�P]c��S��?��c�q���Mu��
��a����G#�w�yg$��P{����kW����@sV�;����2CR����M�f���<��s���?^�`�������R�����=�B�]�������~��Q�����`�������;���w���V�0�s��W_����^0���3����y��G����^z�=�t�����O>��{��g��}�UV�XP�?'����B���~C�?�����g�[9<�N��}����s����T��@�&��&M2W]u���NR<����^2+V�0��M3u��5O=�T�Z_���J������-���>M_��GHu��@�8����_���N�jO����_�J	oC~0w��i�Tf��m���_�re���/�h�6mj������~�����[�7�]�v5�w����`����B�K�.��x����:t�]]b�es~p\���\s�9��S-o�u���
7�`x�����������[m�o�����x���ec�V�#G��U=���`2��a�������F�-L�F����m;v��E��%���4>!���
#�k�n:u�}����|���o�a�&c�����P���3��r���:^>\q�����Z�?��*}�����f������e�������?	���/��b���Q�3g���!��7>���c_�8���������6o�lxQ��d8��x^��w����+����i��������������:�7>|�i������M��I�_����P��k��E��	|L�?�����_|�����H��W_}����U+k�V��������O7�j��ft������;��3��-[�A�L �u�%e	�Io��o�nh��iS!ko��~�i��7���/�� F���g'���*��_�LW���d��{��P~!o�|������	c`����&�&��1f�PO��z"vX
$���~�||H�����!���7���1"J��A�a�I�1��������/��s��;v����C;]�|��n����*�	�������"����k�Y�h���4ib.0!aE�1���7�|�>���>>�z��cx��M�C���8����}��q���O�����������}�}��{~����?�����_�I�7���=�~?\�i�o����~��_����>��@98�5 `����[a���.�������<���]���d��b������	�x���}{['V�|�!�n���������N2?���-�	���������N��qq��'�Q
����t�a��YV!'�~��1a~�������7��P�f��D-�~V�;w����u�&����I��
M�'�'O�c�q�����[o�eW!��gW4&L���2�����V�(����ot	��M�C������&�={������5>�`bv��sQ����	����������]x�P���^�/�����A�L�y�^.�����}��YFf���_i�U�?�����_<���|CI�?���w.�_���<
1Q����?�F��@�
g����?��&z��TE`���q&L�G�U�-�/�WGp��t�I�k��_�����$��V52���_��8���	��1c�ly[��J<���K��o�_AC�+
7����1�d��W^�E��uuI��
�CI�M�#��!|_{�5s���V/�i�0:J_�!|X9`��UV�0S�_�.�E�� �OB��z�j�*�yu�O�C���cR����c�� ��H�?���!�>��6>}|_V�\`�Be	VG������f�)p|����%�e�]f����t[t���}���_��+-�*���|*���7>|�n��|�6�������W���o|�G�'����K��s��u��� ��3Qs�j������J�C���|��w{^�pxk������~�.����B���A�8Io�CNZ�i����N���*M���������'p|�}����(��a����`
i|�8���3@L�g�}��>T�����(��q����K8�������	u����M�?��&��xV�{p����T���$p�n�������3�,���!+������#�������Jf�I/Y|}���i�}��r}��������|����/>���=���)��	_��P�����+�T����[�"Pn��6L��� "�x���I�N�u����7s��W'��/>3!?pL�\X�lY�:u�����4��3Q���� P1'D��2�����?���w&jiH�������0�������OZ�C���!��Nb���\Wp��{�2�j����3Q����&d��������!�G�� ��50O�l6����?��L�|���zk'(<������!��{�hke	V�9��}���c��g&��x��D�w��8&s����O�����WZ|U�O�����}����/>���=���I���f���py%�����~������!��5"P�*�Z�����{+����1������.����e�����������x�{p��7��S��x��w.��}���	;o��a���m��%c��Yo�YEz��w�d"������+f��������o�@F�%\���*W|u��~_��������?�c�M�n��oI�>��~-[���s�����i�I���$&�K�,1����5��'�@0���w�$�%���rNT�L������a���a���];��/\g�yf�	���l�������{V�N|��x&�8��^���R����?���v�&r��`�*pB��c��������,���6���5��x����{��W�}����4�!��{~��}����)����q����|�����M����_��_���}mU���
�@���1fb��Y�x��v�llg���k��3=EK��z��l��0��_��Cy�!��$19��&�I[��^p�����	���Yen�]`����o����>��B�Dg�����H1����O�>�S!T���u���#�����||���M�Eg�>	�Oy��=�������;�c��������7�|�r��=5�>�g������m�Z}�P~�_�/�������b5F��Lq���������]��9�<�E�������n���R8���l��_�����������/���S����\����;����������������������V��@��N�4�X����w���t����U�����3����e��96G��#0].%@@'�*�Xl��L|�+pxk��&oc���JO��w�hj��A������<��P�	u��"�$pA��,��^&��88�����	'�I�QW#P��I�_�R�J�_�=�z�@2	��K��i�\���X�j����	��UGD@D@D@D@D 8��SJ�# �Sb�������������O@'vJ)" " " " "Pb$pJ�CT��	H���N)E@D@D@D@D@J��N�u��#" " " " "�?	���)����������@���)�QuD@D@D@D@D@�' ��?;�(18%�!�����������@�$p�g��" " " " " %F@��:D����N���RD@D@D@D@D��H��X��:" " " " " ������R�����������	��UGD@D@D@D@D 8��SJ�# �Sb�������������O@'vJ�������h���t�I��3��#%�d����{���������'s�)��+�����;������������n�!�����i������_��I��7f����������{��g�<�����o�KK��?�k��o����o�w[�n
n_�����M��������.3O<�����`�������6u��1�����w�m�:d����oGy���������?���w�i������?��~�)4��� �����k�UW]e���]��L�6�<���6�-[��/���~n��fs���F���ysT�'�|b�>�ls�y��?�0�>$=�W�^Q����~��
�s�=��i�&��u������IJ����8!����E�W��bc���Q�v���!Z\H�K�W_}eN=�T3l�0�7��s�=6�O���C��6�C�f������:���8�Ig2�j�*o���{��la���_�~f��A����zMRz��&��/V�T��+���~8I���A�
:�8�2���u;w��������i����>}z���Y�L���
��4�=z�0�;w�Z-��������`�/s0n�v��+�X�;���M��}�����l}���b���m�(V�=z��5M�0�[�4��ys3g��`�s��W��7n�;v�3f���*p���oE��?����i���������qa���s�xH��@BL�<��'�(�v���I�&w����o�5kfW�Xec�N\���]�������:�K 2Q�:u�5q���K����z+'��^���k�}�����?���f��
����������Kq+��a��y��M��8I��
�������1����������=����w���vud��EE8i�C�q6��@c����_���7o�9����
�/>^�NhO�:(O��������9��
1�2dH�@��<�+\`��3�g�&p2���_��&u��1Mk����>s')}\� $:v����)�0@�Gh���f��m�V�Z�z�����N�P.�hk����A���;�C�����u��������@�Ht�d�;�f�"P�v�j'�x`��j"'3����.]j>��������c�����:�,\�0�����
xy�2e�u��*U<��e��^�z�&j��%yQc���I�C�H����u" " " " �H8�����bV|�M���J��7���fj�!��'>��@fs���<�
>�z�sn�C���)�&�Q�F��o�������92�V�n���.]�$���//^l����������:�BM�Ku��M4Q������gO����z�:�� 	V[0O� F\E��&M���>����h��&����70_z'p��yfu!d���f��q5����|N1�x�\���.5j��,[�,��W�'�|��^��n��K��q�b�
C9�:u� ���C���INl�6mj>��s�*'����]�xk�U���������@�$	�Gy�:��>���"2C6�duQ�;iw���''&�!���>I�)p���b��yPj(_�)��p��������gO���O���+�t�����}�.����o��v�b�r��/�]��m[�OKAD@D@D@D�fH4Q��8���L��::��D@D@D@D@j&	����e�j�Op���WT��T�D@D@D@D@R	H�h������������
	���J5DD@D@D@D@D@Gc@D@D@D@D@D�lH��MW�!" " " " " 8�B������A���L�Rw�c��5��� � P��*4}1�����W��+��!	�A���I
������h�s�=������3L������^x�y���*����9���6����g�<�����~H��.	I��k��K/���">�����E���G�m�5kf��9�����W8���;���o��v�>-������)+~�jpc�t!c����U�l����%K�?���"N�6d��U����x���_�b�8��p��Y��YP��*���������_��/���|��.�(=��p�y�\�.]��
�a��E-z��W�)��b^x��n�:��{��G}��Z�����y������7x��aC��3���K���S��=t���;��c>���(&e/���Y�b��6m��[��y���*��}���K�����A���x:��������[p���B	��������������>���9��?ig��I������������O�'�@��9��T
D@D@��@��a%����S�N���3�&�U���>	���;
��M�65���?�l1u@��_������f����3W^ye3|�������Ox��1��A3o�������~����7V~Z�ha�Zh�������.]��m��UH�&p�x�
��uk��6�^\�1��~\�8������{w�����������|���|�@�_q�A����o�[}c_��;wntM������M���-�F���'���c���
�������o��}���]���$��y�f�B��8�����|���_z�{��m�jz�!{a�� #��_H������E��9V����u�����[�F�*4��}��-���?��*��'��u����@�!$p�x0����O��}���������������-o���5c��p��a[>�*.���w�����K��QS�vm3}�t;9F\�����?&����
+,&��1���s|�����`���	
�������k��]�vY�:�����{�N�I�}�v3p�@��M�
����/�l�7�I��u������6m����"��+T����GQ��M���������:�������	.+z�+L@��S�VC	�����o��������x�I���1.Y�	�����F����'�s�Yg���~�:t��0-_���!���?\����0�x�����b��{�=�Y�����=�q�F�J��8�~������^�fWL86l�����|��II\L`� ��)z�!p\]V�^�n1��N� ��/�w�}g���������.+:L"��dB�&�\�������z����7���L�0!J����s�M7�!C���U!�VE�	<����G�Y}�U�V������&M�D�!>��qs�I'��~��~�V_��+c�J��U����#F��*B���������g��[hz_���x�I����={�K}'���kc!�����������������y�k�
+8.`.�����G��������,���7v/" "P�T��9p��%���=�&4��	?�L��8�����{z�,g[!b����.�`.���L�0�I
I���0�����2�Z8���d��?_�uV-'p]�4���	o�YE�M3fj�����. J��'������������	�+3N&�L~^y������j��i�GR|����T������^�&pB��Lf�����]<_�!p����|��+/M��3&����3�����Ozw��MFq S.����R����@�#$p|&j�\�z;U%p|&4�n��w��9s�DY�}�U-���#�$(�8"@p$��x�M���l��Ca��D�
�	n�f�G>I�3�CaV���=L��>���x���>���l��I���~n�Fz�S
4������#��-4�/_|��l�ED;���`�I��7f�.�rU��wyq�<����e���4�C�����4�B����^�����Ne�?��C�" " ��@��a-6�IN��fc8�W.$	��r2�d9�����37Ig�����L|��������
&Gw�q�����#�<����-�}
�����+')�=z%�mx�[�`A�����������=�m�8L ]X�lY������������#6��*p��O���=8Io������I�����w��������	^ pv���"���p&���_>f���W�����O���'R�8�?�0����Z)�?���x��E�����S�;b���7���fb��&�xr��_�^��������cu`?n�����&:�|_z<�aFC�y���S�
:�z���4
�1���h1���N�dc��iwN�/�1���7�Ig��
=61�u��L4��~����:��s�$�$����V�e��-[�q���s����8G����3�|.\h��\s�5���n�����W?�w0AE�Qy��{�x�:r�H�3��84�W_?���K�������g��}�����gs���D-����!�7~B�H8���	����D
����]�vv&�X���t������>*���$��s���U6f���du��v�ll�M.R@Lz�����X�D����t�gR����R�'��N=�T����&.;7�gs����z����R���/�1����]%�'�CX�qPkf����0x��'���
\pAVs�O>���=<����|�M�="q�~�L�����6�/�M�����a^�s�
"���:A�+OL�2�D3����o�K������7~}����xVc����2vu�8�����x��	�#M���_H������E����L�
M�w����z��m��xO��T��\���E@D@J�@$pJ���]�`+[
" �Og��w/����" " 5��N����k�N���j$��j��gO.�Y����������"	�R��X'	���jr�!�������Q�FU����" " 5��N��s�XD@D@D@D@���N�v�&" " " " 5��N��s�XD@D@D@D@���N�vmi6���Z�ha��������������T&�H������u�]���!�<��s���8p1��g�pVF�
l�]�BH�o�1k���)��S\�o�?~��*-�?�`�m��}���&��s8B�W��3�=z�i���qEpp�O<}���]�|�rs��W�:u���n���nu��!�V�t��P������|80�B9���R�V������������@$p89OVL|��]k�M�f��y�~��-��q>7�|����k��o��9*�C���Q�������W�(_W�1����6m�Dy�n��~8I���a����'�~>����^�������G�����~�hq!�/��n��a�������,	>�����:��'�^��D@D@D@D@D 'V��&����U���9���la���_�~f��A����zMRz��&��/V�T�N�����IJ��l�����������>���s��Tf����M��
�����lg��e���oO�w!�o�=L����V�'`|�!�}<8i=s0n�v��+�X�;���M��}�����l}���b���m�(V�=z��5M�0�[�4��ys3g��`�{fB�P��7n�;v�3f���*p���oE��?����i���������qa���s�xH��@BL�<��'�(�v���I�&w����o�5kfW�Xec�N\���]�������:�K 2Q�:u�5q���K�kN��E����v��f��}�����@�
6�E���|�����L�0S�<m��M�	���n���u�]gf��Qrg�������_�I���~�:�h���
�4���8��A��r?}��y���N�+X��x�8�=��D@D@D@D�<	T���w�^3s�Lk*��r��!������p�}:�|,�I����C�~�z��	��4�e��������q������c��X�a���7�m���Z�j��B��8i�C�d88@��Y�&�q�w;v��	��C��u" " " " �O �M��%K�$��w�@����������9���P��y-]��|��GY�o+8��3g�u�Y�pa�E-�}U1��6e����U�xH��
G�z�M�0�K���j�/>.0������	%��D@D@D@D@�!�(p��[g'��&���5�����o�s)��pC�KO|�������yP>|����D���]S�M��F�2��~�����9rdT����u3]�tI�f_.,^�����	i�q�/ �?t����1�����h�����g��
+_���u" " " " �A 8��`��A�����M�4��|��3����7L�9�o`��N�ds���B�;��4��&j��+L��b��X�r�]]j���Y�lY�-�/��O>�d��S/�D����b���r:u�TA�������<�4m��|����U8N����������r��I 8�<��u0�[}<r1�Ed�l&f���w���I�O.N LvC&�I}�>S�P\�	�$'���P�S��,q	k8W�g��vo����WX�H��z�$	\B�����=��X�b��_���m��v����������@�$�h�V3q�����ut\�+���������L85�����8���%���,�F������������(8e��j�����������������������@���)��TCD@D@D@D@D@$p4����q����fD��	L�2E��K���?%�9E��~?�UY��	&	�A���I
������h�s�=������3L������^x�y�����y^������q�v��c��
e�Y���6����g�<�����~����WYCN`A�V����k`�"p����{�4n����n���6l�9z�h���;���jo��vu�
��C�Xh��rg|q�WfH����������R�x"�O���B���Q�}���@M#,p��w�����K��a������jN9���/�u�����{�<���U���>0#G����f8������g�i��}���L2�y���~��*k�	,���Xu[t�?P�l?�r����2f��mt�p]8}��������O4R�_$��@O�4�\u�U��M�$���g�0�l�����m���b'r���-4}��������4A��V>/.L�:��:�������s�9�<��S^~;w�4|��f8M�65���?�b��kE��x_}�T�����n�����]����wG�`��B�K�.f��m�����:t�y����*������*�5�\cN=�T�\��/�n���Z�/i/��r�u�V���o�=n��i+z�x�����#v��v1n��>|�f�x�b����E��Q#3q�D[��;��E������4>!������������N�:E_#p�.�����o����c�q�o�>s�-��x����W\<�X�e����R������f��������������?	���/���g�3g�4<B��o|�����|��_^���p�]�y�fs���^�d8��x����WvH�������_��_�������[�����;~B��i�!�}����������6*^�� �����O?�4j�_|a�[�~�}���_}�����Z��fl�
�i���;�D����w������������<n��-��I&���u��b����L��o74m����5�7�O?��a�������k#�����`�f����/&�+���d��{��P~!o�|�����M�f��Y�v�e����@��[��%������V	>��(���?$}�8�d�����#F��� pu�w����G�

>>/����;w�5�d�t����A(�������������[�J�����,Z���
M�41�
���b�����x����O�^�z�1�i�&��!�re
gZ��>��8���w�����}�����_���_������o|��?��o���+;���_��������k��E�:8+V��?�6l�����1�G��.���v����)zN��qu]�z�a���`"F�����C:������Ki�I'�d~��'���	������V�Z�|8����=�����+��Z����N��K/�t\Y�����������_��?o�<�B��UVp��e��J�s��Q�4�.���>i?���}�d���vL0���=`�y����*��q�����	|YF�!|��Q�����<D��O7i?+�Z������3J�����<��������Ei�/$�'p�����:�B�{]��Be
Vz�l�O�����_}������3����J��������o|��?i�_���;��~e�/�(�D������V�TG�*p����?�hW{����R�!�
N\�`��I8��C�����.0�q��������9��� p��Ff�8PH�����`��1Q��-gE%r����~���)��egJ��q�=�L�^y��$���3�*�O�lhz�8����k��s�=��x�L����H��
����4���������7��A��������U	���{|2R�/0u:v����i�'���#$�'p����������y�,���}������3�/��)�����������}�����������?����W��I{��~?\�i�_��E�
����.��y>������D����C�b�T�������=�������e
�i��$bBNZ�i������~��{�i|]�|��?��M�|�}����&�Ia��iV0��4>�L�������G���g�m�8������8f��6��?�|�p�Bk�E��7�W��M�}�������?���'I�0>}������3�,���!+������#���>}�5ML���G���-MZ����_��+-�*��:�������������~�~�B�y*p�~�}����7�g����@ H�`J�d,��{:�X�i�I�T��0Js2���L~�����l��
&ju��1,H�&M��L�|��&T�	U7���9���?���w&jiH�I���A^����x�D��'����s���,,�5�r]�I����t�j�8�D��?���i��/��!C��=z���:��<������(���\�3����z�����,�;H����~:_|Om�,��j1g?���9sl��D����|��������?�}�����������}'����_|e�����l&j��W~Rz���o��~�|����i����H����kU�M-����,��;}��\t�E�x����vOo4yK���l������u?������?�.�3�&C��D��}��v�@��og��v[��	+fI����������;*d�&p�&]�XQ���������}2��y��o��8f����f�L?��s�/l�w��CWp���:�l���K�uXEs!�Z�C�'q�4�I��%K�<�f��c�	&L�q��$�%���r�T����$��x+O������;����kg��q�p��'������}U81`�������1��G�G.���7�|�O>������o�/���`�C=}|��������Pe	�L�������������������=�|����r��\?������I��p�Nz~��/����(����U�"P]T8������M1?�l�un~`'����u&���F!2=AK�09�V�
������t�gR��Cy�!�$19��&�I[��^p�����	���Yu���z1qJ��7p��f��m�n�3���D��6�W}������'����'�|b�����||���M�Eg��>	�Oy�����u?��_������������7�|�r�%��q8{�����m��z�C���s��r�����|>��!�Q!r2���������������/zxy��C����h������A���T�o|������O{~�����������s��������{���~��g���o|�~?|�@9�������B 8����ZOp�yW(�M�Z�|p���{��t�r�2#����:4����s��E�
H�T��~�UR��&�-����-n�y��7Vz�n��f �!"P�>�t��S�0K��VQ"H@'���Bp��kz��W��`���/������5��9�_�	�|����$/T��d��R5��N
j����@2���}rq�"�" " UC@�j8��*  �S�U����������@�����*ED@D@D@D@D�
H�Td!" " " " "P5$p���J�8UYE����������T
	����RD@D@D@D@D@���N@V" " " " " UC@�j8��*  �S�U����������@�����*ED@D@D@D@D�
H�Td!" " " " "P5$p���J�8UYE����������T
	����RD@D@D@D@D@���N@V" " " " " UC@�j8��*  �S�U����������@�����*ED@D@D@D@D�
H�Td��'���_�-Z��N:��y��B#" " " " �J 8�w�6w�u�����dN9�s��W��s���n��6��?�!���n�*�����4h�������F���������������=��3Q���w�����������������������[���R��2=z�i���q�_v�e��'���u,_��\}���N�:��������m��C�l[�~��(������;���C������;�w�a~��G��O?�&�u" " " " "D 8�\s�������k��i������f�e����_���7�l�������7o�
���O��g�m�;�<���F���G����+�����o�Y�s����6m�Dy�n��~8I���a����'�~A4��h���Vll��=�a��]�;D�I|���������j�
f���{���&�	_|���w���L�81�r]'" " " " 9��7�L�W�Z�M��wo���-�1�����4�<��CY�IJ��!]������
qr�W���')�8���C��"�'^fZ��n����S����7
64��O���5k��_��A�����G��s��Y��0���>��e��M��9b���~�����9|�p��/�]X����M�" " " " �C�
��G�Z��	&xk�&�7on���c,X`.���J8���3c��5c����\�����h�����8�Z�2���z>.8�`�I|H����'U������];3i���.����M�f����l���V_���X��>]'" " " "pb	D&jS�N�&N�\r�ar��[o�$P��R�vm�o�>s��A��?��l����<�Vp2���})nS2��0O��i�q')�[�a�|�u��3f����={�5�s���Wt��������,Z���'��0�&ph������Q6���3��v�]�������	�	]'" " " �I����{���3gZS!&�C�	(��g���q�c�L�N�������N��7�i-[���g��$���D���8�C�m���l�����U�Po��V��I��%����m��5Q6�c���c�uH�Z��(�n��,Yb'�L�CJ��]�l|X�A�d�B������K�G}�U������s��1s�Yg��F^�B�WC/oS�L�X���4��p��W/�D
��$/j�����3�(	�PR�ND@D@D@D �g��uvR�j�O�IQ�[�o���~0��L
7�����8�l`�{p�����[op�MtH��5���>j�(s����>}���#GF�
���[7��K��1@�`������m�����8���C_��c�n���&j��x�{�����Z/]'" " " "P"��j�i���h���I���'�83��}����X��K�N67��.�L���L�>n�FY�������)���+W���F��e��E�B��j���O���0��M4{�\@�!.V�Xa(�S�NQZ|���!������M��M���n]��d ^_�+om�*W��"���#�X���#^�@f�fb6`���.�q'��t���d�d7d��t�'�3uA����AL"p2J
��0e�"��v�suz��i�V����{��������O���%t��������U,V�\������mk�i)���������L�&j5�Z]�	�Z�@�U���������@�$ �S3��,[��	�_r�����j��������@*	
��! �S6]������������H�h�����������
	���J5DD@D@D@D@D@Gc�R�z��|8�����)S��Nz�����xD�
_��/F��y�z���~�/" "P9"�3h� �Y2I��_~1\�-|����[�x�1c�i���=p��/4�=�\��2 <g=����~����x����o)��%!�_{�5s���ZW����Y�hQ�����M�f�l�9��c��
�raZ�;��3N(����Q�;v�0��z�=��CU9�h�������#�,pV�Ze���,Yb���V�
e����J���z|�?A:�
M_H�K��)�]J+" "Pz������3��]�t�0��
�Z�����SN9����f��u����3�>�h���_���9��3����o8��a����g�	.��1S�N{����+�;��c>���(}�����+��i��y����x_zN��}����s���(}��]��W^i���+�f�����YJA����o��j�K_����)��8'�������@98���2�xqa������_�����j��'���s��S��i�����G�-�����K��}�����w�}V���K��>|xbu�=�\����G�����:+�yv��_�~v��U�-ZX����#G�~F�v���l���B�4���o���[����!����������)�n���Q������g���E���`���+���������JN|�s����5�&j�/6�7��5jd&N�h���;F�|���*��������}���_z�{��m�jz�!{��:i�$�4��B��w��/�g��j�����/��l��5jV�����gn�������U9�?!��kD@D@�� ��y�f;y���O��}���������fu�Z�je��*3>|�������������E��=z���]�L�>�N�4h`z��e~��������o�u�f�C�#p��`��$����=��@&��2�s5�
�������k��]�vY�:�����{�N�I�i���M�6m*$O8/���a���+|:t�y���9��3��-[�� ���_�}���(c��M��z��O�\C�@g�C��=�&���)���P���i����>��/�x_�B�OH^*<����>���������������/i�
����+^:�����{�kV��y>q�n�����#�BN��?!}�kD@D@�� ���ba��
Q�x{�wL��,���a>�����e���Vx01f"O��A�_������u����1]Vd�Dd����M^C�����������*�����,&L��?v��-���N�VP��VE�	4����G�Y}�U�V������&M�D�!>��qC;��'�]Z�}����E�+]`�W�4'�G�aW��;w6p����g��[hz_�':��>��	I�5�LQ����C�����3�wM�@a�Vx��5��\�G�4r�.�r������>*U�8p����
V{��;�'X��3Q�W�dK�����a��m����.����\.�+&����'m����~A�7���Z8���d����_��D�	\�>M��r�[`V�x�������a��DI|����W?7X�r��TU9Asea�������+��b|���OU�� ��8Q�>>�2�]��w�2}���I������4�2f���Z3g��NO2C>���7��L��?!�G�����T/A�g������w�SU�gB��_z7��3gN����_�"�82'A�����Q���hb���?�.���O,�������(�}(L|����A!�M��L��'I�`r�~(L�0�������g���y��g�������T_����.��&h�e����G��[hz_�':��>���w��o|������_����C�Wh�@!�����B�������'t��:��C H�����$'�|�1�*�NU9`����@R����3�_t�E&>�p{|�|K�L��;���NX2�/}���<�H4AA�0������j��m82����#h��6<�-X� �z�	B�����x��)p�@��l��
&j������%t�=Gl�U�8!���&��	���!���.�|��/��(~M.���K8�������6��cN��3Q��`��D����\���(mfOq7����z��7�L��[���B.������o�xK���l����:�_7�|�A�����}����Im��f�N�*���L�0s�+Zi�Y���N��c>����9B�M�`j�D��{�M��C�M�������I������������w�}������eK;.e��+;x�� �_l��8��Y�p���s�5���n�-��W_�z��iW�a�A����^`_��9r�9GT6��@����W_?���K�������}||�����$���?��	�?\�N�
ec^�kzV�y���-��'��u����@�!PA��&��:7��Yec6+9LV`'y���V���!��'�	�k�%p�A�����I}&��K�*y����SO��'��k����O���3�����m�Yi`�)�`����n��n�g5'q�8��s���70�����K�a���OZ�C/�����:�������K~��7���d�!��2"����L7������y�6�0���
>�<1��t���6��	.�����_���	_�R�����_z��I������i����Vp
M�w����:{q���5����k;t����@i�NiWS�+w��a�JAD��	�,�{���_Q�PD@D�F�����^z���)�>Q�D�`�n�q��wVbqe� " " �H@�{��I�v��\m�`�s�0q��t��Q��������@�# �S��\-��%`N��������j����������@Y��)��TcD@D@D@D@D�f�����_���l�-Z��dp�� " " " " �I 8�2{�l�%���9p�W�^�|�����g�pVF�
�y.���BH�o�1k���)��S\�o�?~��*-�?�`�m��}���&�n������L���F�m�����OD_'�u,_��\}����������m��C�l[9��l�;���C�����@�\�����������T&�H�,]��pH���^k��s���a��$x��-��/����o��^���y���>���.D���BHz
������=&�0��M�(���[���')�8��]���8!��L��y�^��r��}{�k�.����$�����H�3��Z��,�����C�f������:���8��8E>�JM<���{��la���_�~f��A����zMRz��n���
q�)�|8I���A�
:�8�����;w�Oe������6}��(�Y�f������]H���G��s��������C��x���9��&jG�����x�����}����G�������_�/" " " "P:��9��S��?�l�^��i����3g�Y�`�=3![(D��7��;��3�~r8��������+pZ�jehge&�|\8p���+��"��'O.��I���]�vf��I�]~����f����'V�XE�V_���X��>]'" " " "pb	D&jL����q�F3u�Tk�������$P��R�vm�o�>s��A{ ��
�k]���|�����L�0S�<m��M�	���n���u�]gf��Qr��O���@��+:i|���o�j��EE8i�C�q6��@c�&~2��y��i��fW�|���%pB{B��������@y��E
��>}��W_}���"��9D��V��.�O�������g���6�8��iZ��-���+8�{p\���AHt��1o�S�a�0d�s��m�V�Z�z�����N�P.�gk����A���;�C�����u��������@�Ht}�-��7�g�qF��������c����DNf(�D��p���Ge8�=8��;v��u�Yf�����VC/oS�L1��M��T����~�W�^��&yI^�Xm���f�P>8��t��������@>.�	\p�W��IQ3w�\��7���R����8D@�8�l`�{p�5�9<L�B���)�&�Q�F��o�����92�V�n���.]�$���//^l����������:�BM�Ku��M4Q������gO����z�:�� 	�Eab�FvV^�?�1��	&�LF�g�0q�,������&����	vZ���ea�
�;'�~��b��X�r�]]j���Y�lYT����>�����:L�p��*t��+V����SA��?�vHr20x�`��iS����[W�8������[��D@D@D@D@j&�H��_��?"����z�\r�%A{p83'��b�I��&�\� @���L���$}���.��R8��!p2J
��(e�� .a�������n����{�
+i�!�Co�$�������[op��!�Y�r���k������ " " " "P3	$���=��L4juu#�jfw�*W��$pjf��e�q>��Kn_QY6R�H% ��"" " " " "P6$p��+�+pD@D@D@D@D@D�H��C/�
" " " " " �������������@���)��TCD@D@D@D@D@$p4D@D@D@D@D@��������t�<IEND�B`�
#2Pavel Stehule
pavel.stehule@gmail.com
In reply to: Rahila Syed (#1)
Re: [PROPOSAL] VACUUM Progress Checker.

Hi

2015-06-30 9:37 GMT+02:00 Rahila Syed <rahilasyed90@gmail.com>:

Hello Hackers,

Following is a proposal for feature to calculate VACUUM progress.

interesting idea - I like to see it integrated to core.

Use Case : Measuring progress of long running VACUUMs to help DBAs make
informed decision
whether to continue running VACUUM or abort it.

Design:

A shared preload library to store progress information from different
backends running VACUUM, calculate remaining time for each and display
progress in the
in the form a view.

probably similar idea can be used for REINDEX, CREATE INDEX, COPY TO
statements

I though about the possibilities of progress visualization - and one
possibility is one or two special column in pg_stat_activity table - this
info can be interesting for VACUUM started by autovacuum too.

Regards

Pavel

Show quoted text

VACUUM needs to be instrumented with a hook to collect progress
information (pages vacuumed/scanned) periodically.

The patch attached implements a new hook to store vacuumed_pages and
scanned_pages count at the end of each page scanned by VACUUM.

This information is stored in a shared memory structure.

In addition to measuring progress this function using hook also calculates
remaining time for VACUUM.

The frequency of collecting progress information can be reduced by
appending delays in between hook function calls.

Also, a GUC parameter

log_vacuum_min_duration can be used.

This will cause VACUUM progress to be calculated only if VACUUM runs more
than specified milliseconds.

A value of zero calculates VACUUM progress for each page processed. -1
disables logging.

Progress calculation :

percent_complete = scanned_pages * 100 / total_pages_to_be_scanned;

remaining_time = elapsed_time * (total_pages_to_be_scanned -
scanned_pages) / scanned_pages;

Shared memory struct:

typedef struct PgStat_VacuumStats

{

Oid databaseoid;

Oid tableoid;

Int32 vacuumed_pages;

Int32 total_pages;

Int32 scanned_pages;

double elapsed_time;

double remaining_time;

} PgStat_VacuumStats[max_connections];

Reporting :

A view named 'pg_maintenance_progress' can be created using the values in
the struct above.

pg_stat_maintenance can be called from any other backend and will display
progress of

each running VACUUM.

Other uses of hook in VACUUM:

Cost of VACUUM in terms of pages hit , missed and dirtied same as
autovacuum can be collected using this hook.

Autovacuum does it at the end of VACUUM for each table. It can be done
while VACUUM on a table is in progress.
This can be helpful to track manual VACUUMs also not just autovacuum.

Read/Write(I/O) rates can be computed on the lines of autovacuum.
Read rate patterns can be used to help tuning future vacuum on the
table(like shared buffers tuning)
Other resource usages can also be collected using progress checker hook.

Attached patch is POC patch of progress calculation for a single backend.

Also attached is a brief snapshot of the output log.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#3Thomas Munro
thomas.munro@enterprisedb.com
In reply to: Rahila Syed (#1)
Re: [PROPOSAL] VACUUM Progress Checker.

On Tue, Jun 30, 2015 at 7:37 PM, Rahila Syed <rahilasyed90@gmail.com> wrote:

Hello Hackers,

Following is a proposal for feature to calculate VACUUM progress.

Use Case : Measuring progress of long running VACUUMs to help DBAs make
informed decision
whether to continue running VACUUM or abort it.

+1

I was thinking recently that it would be very cool to see some
estimation of the progress of VACUUM and CLUSTER in a view similar to
pg_stat_activity, or the ps title.

--
Thomas Munro
http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#4dinesh kumar
dineshkumar02@gmail.com
In reply to: Rahila Syed (#1)
Re: [PROPOSAL] VACUUM Progress Checker.

On Tue, Jun 30, 2015 at 1:07 PM, Rahila Syed <rahilasyed90@gmail.com> wrote:

Hello Hackers,

Following is a proposal for feature to calculate VACUUM progress.

Use Case : Measuring progress of long running VACUUMs to help DBAs make
informed decision
whether to continue running VACUUM or abort it.

+1

I am excited to know how the progress works in when any of the statement
got blocked during locks. Rather displaying the stats in the LOG, shall we
have this in a pg_stat_vacuum_activity[ New catalog for all auto-vacuum
stats].

Best Regards,
Dinesh
manojadinesh.blogspot.com

Design:

Show quoted text

A shared preload library to store progress information from different
backends running VACUUM, calculate remaining time for each and display
progress in the
in the form a view.

VACUUM needs to be instrumented with a hook to collect progress
information (pages vacuumed/scanned) periodically.

The patch attached implements a new hook to store vacuumed_pages and
scanned_pages count at the end of each page scanned by VACUUM.

This information is stored in a shared memory structure.

In addition to measuring progress this function using hook also calculates
remaining time for VACUUM.

The frequency of collecting progress information can be reduced by
appending delays in between hook function calls.

Also, a GUC parameter

log_vacuum_min_duration can be used.

This will cause VACUUM progress to be calculated only if VACUUM runs more
than specified milliseconds.

A value of zero calculates VACUUM progress for each page processed. -1
disables logging.

Progress calculation :

percent_complete = scanned_pages * 100 / total_pages_to_be_scanned;

remaining_time = elapsed_time * (total_pages_to_be_scanned -
scanned_pages) / scanned_pages;

Shared memory struct:

typedef struct PgStat_VacuumStats

{

Oid databaseoid;

Oid tableoid;

Int32 vacuumed_pages;

Int32 total_pages;

Int32 scanned_pages;

double elapsed_time;

double remaining_time;

} PgStat_VacuumStats[max_connections];

Reporting :

A view named 'pg_maintenance_progress' can be created using the values in
the struct above.

pg_stat_maintenance can be called from any other backend and will display
progress of

each running VACUUM.

Other uses of hook in VACUUM:

Cost of VACUUM in terms of pages hit , missed and dirtied same as
autovacuum can be collected using this hook.

Autovacuum does it at the end of VACUUM for each table. It can be done
while VACUUM on a table is in progress.
This can be helpful to track manual VACUUMs also not just autovacuum.

Read/Write(I/O) rates can be computed on the lines of autovacuum.
Read rate patterns can be used to help tuning future vacuum on the
table(like shared buffers tuning)
Other resource usages can also be collected using progress checker hook.

Attached patch is POC patch of progress calculation for a single backend.

Also attached is a brief snapshot of the output log.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#5Simon Riggs
simon@2ndQuadrant.com
In reply to: Pavel Stehule (#2)
Re: [PROPOSAL] VACUUM Progress Checker.

On 30 June 2015 at 08:52, Pavel Stehule <pavel.stehule@gmail.com> wrote:

I though about the possibilities of progress visualization - and one
possibility is one or two special column in pg_stat_activity table - this
info can be interesting for VACUUM started by autovacuum too.

Yes, I suggest just a single column on pg_stat_activity called pct_complete

trace_completion_interval = 5s (default)

Every interval, we report the current % complete for any operation that
supports it. We just show NULL if the current operation has not reported
anything or never will.

We do this for VACUUM first, then we can begin adding other operations as
we work out how (for that operation).

--
Simon Riggs http://www.2ndQuadrant.com/
<http://www.2ndquadrant.com/&gt;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#6Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Rahila Syed (#1)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2015-06-30 PM 04:37, Rahila Syed wrote:

Design:

A shared preload library to store progress information from different
backends running VACUUM, calculate remaining time for each and display
progress in the
in the form a view.

[...]

Reporting :

A view named 'pg_maintenance_progress' can be created using the values in
the struct above.

pg_stat_maintenance can be called from any other backend and will display
progress of

+1

Just to clarify, the attached patch does not implement the view or the shared
memory initialization part yet, right? I understand your intention to get
comments on proposed hooks and shared memory structure(s) at this point. By
the way, how does a regular send stats to background stats collector approach
compares to the proposed hooks+shmem approach?

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#7Thom Brown
thom@linux.com
In reply to: Rahila Syed (#1)
Re: [PROPOSAL] VACUUM Progress Checker.

On 30 June 2015 at 08:37, Rahila Syed <rahilasyed90@gmail.com> wrote:

Hello Hackers,

Following is a proposal for feature to calculate VACUUM progress.

Use Case : Measuring progress of long running VACUUMs to help DBAs make
informed decision
whether to continue running VACUUM or abort it.

Design:

A shared preload library to store progress information from different
backends running VACUUM, calculate remaining time for each and display
progress in the
in the form a view.

VACUUM needs to be instrumented with a hook to collect progress
information (pages vacuumed/scanned) periodically.

The patch attached implements a new hook to store vacuumed_pages and
scanned_pages count at the end of each page scanned by VACUUM.

This information is stored in a shared memory structure.

In addition to measuring progress this function using hook also calculates
remaining time for VACUUM.

The frequency of collecting progress information can be reduced by
appending delays in between hook function calls.

Also, a GUC parameter

log_vacuum_min_duration can be used.

This will cause VACUUM progress to be calculated only if VACUUM runs more
than specified milliseconds.

A value of zero calculates VACUUM progress for each page processed. -1
disables logging.

Progress calculation :

percent_complete = scanned_pages * 100 / total_pages_to_be_scanned;

remaining_time = elapsed_time * (total_pages_to_be_scanned -
scanned_pages) / scanned_pages;

Shared memory struct:

typedef struct PgStat_VacuumStats

{

Oid databaseoid;

Oid tableoid;

Int32 vacuumed_pages;

Int32 total_pages;

Int32 scanned_pages;

double elapsed_time;

double remaining_time;

} PgStat_VacuumStats[max_connections];

Reporting :

A view named 'pg_maintenance_progress' can be created using the values in
the struct above.

pg_stat_maintenance can be called from any other backend and will display
progress of

each running VACUUM.

Other uses of hook in VACUUM:

Cost of VACUUM in terms of pages hit , missed and dirtied same as
autovacuum can be collected using this hook.

Autovacuum does it at the end of VACUUM for each table. It can be done
while VACUUM on a table is in progress.
This can be helpful to track manual VACUUMs also not just autovacuum.

Read/Write(I/O) rates can be computed on the lines of autovacuum.
Read rate patterns can be used to help tuning future vacuum on the
table(like shared buffers tuning)
Other resource usages can also be collected using progress checker hook.

Attached patch is POC patch of progress calculation for a single backend.

Also attached is a brief snapshot of the output log.

@@ -559,7 +567,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
* following blocks.
*/
if (next_not_all_visible_block - blkno > SKIP_PAGES_THRESHOLD)
+ {
skipping_all_visible_blocks = true;
+ }

There's no need to add those curly braces, or to subsequent if blocks.

Also, is this patch taking the visibility map into account for its
calculations?

--
Thom

#8Syed, Rahila
Rahila.Syed@nttdata.com
In reply to: Thom Brown (#7)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

There's no need to add those curly braces, or to subsequent if blocks

Yes, those are added by mistake.

Also, is this patch taking the visibility map into account for its calculations?

Yes, it subtracts skippable/all-visible pages from total pages to be scanned.
For each page processed by lazy_scan_heap, if number of all visible pages ahead exceeds the threshold, it is subtracted from
the ‘total pages to be scanned’ count.

The all visible pages are accounted for incrementally during the execution of VACUUM and not before starting the process.

Thank you,
Rahila Syed

From: pgsql-hackers-owner@postgresql.org [mailto:pgsql-hackers-owner@postgresql.org] On Behalf Of Thom Brown
Sent: Tuesday, June 30, 2015 2:20 PM
To: Rahila Syed
Cc: PostgreSQL-development
Subject: Re: [HACKERS] [PROPOSAL] VACUUM Progress Checker.

On 30 June 2015 at 08:37, Rahila Syed <rahilasyed90@gmail.com<mailto:rahilasyed90@gmail.com>> wrote:
Hello Hackers,

Following is a proposal for feature to calculate VACUUM progress.

Use Case : Measuring progress of long running VACUUMs to help DBAs make informed decision
whether to continue running VACUUM or abort it.

Design:

A shared preload library to store progress information from different backends running VACUUM, calculate remaining time for each and display progress in the
in the form a view.

VACUUM needs to be instrumented with a hook to collect progress information (pages vacuumed/scanned) periodically.
The patch attached implements a new hook to store vacuumed_pages and scanned_pages count at the end of each page scanned by VACUUM.
This information is stored in a shared memory structure.
In addition to measuring progress this function using hook also calculates remaining time for VACUUM.

The frequency of collecting progress information can be reduced by appending delays in between hook function calls.
Also, a GUC parameter
log_vacuum_min_duration can be used.
This will cause VACUUM progress to be calculated only if VACUUM runs more than specified milliseconds.
A value of zero calculates VACUUM progress for each page processed. -1 disables logging.

Progress calculation :

percent_complete = scanned_pages * 100 / total_pages_to_be_scanned;

remaining_time = elapsed_time * (total_pages_to_be_scanned - scanned_pages) / scanned_pages;

Shared memory struct:

typedef struct PgStat_VacuumStats

{

Oid databaseoid;

Oid tableoid;

Int32 vacuumed_pages;

Int32 total_pages;

Int32 scanned_pages;

double elapsed_time;

double remaining_time;

} PgStat_VacuumStats[max_connections];

Reporting :
A view named 'pg_maintenance_progress' can be created using the values in the struct above.
pg_stat_maintenance can be called from any other backend and will display progress of
each running VACUUM.

Other uses of hook in VACUUM:

Cost of VACUUM in terms of pages hit , missed and dirtied same as autovacuum can be collected using this hook.
Autovacuum does it at the end of VACUUM for each table. It can be done while VACUUM on a table is in progress.
This can be helpful to track manual VACUUMs also not just autovacuum.

Read/Write(I/O) rates can be computed on the lines of autovacuum.
Read rate patterns can be used to help tuning future vacuum on the table(like shared buffers tuning)
Other resource usages can also be collected using progress checker hook.

Attached patch is POC patch of progress calculation for a single backend.
Also attached is a brief snapshot of the output log.

@@ -559,7 +567,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
* following blocks.
*/
if (next_not_all_visible_block - blkno > SKIP_PAGES_THRESHOLD)
+ {
skipping_all_visible_blocks = true;
+ }
There's no need to add those curly braces, or to subsequent if blocks.
Also, is this patch taking the visibility map into account for its calculations?

--
Thom

______________________________________________________________________
Disclaimer: This email and any attachments are sent in strictest confidence
for the sole use of the addressee and may contain legally privileged,
confidential, and proprietary data. If you are not the intended recipient,
please advise the sender by replying promptly to this email and then delete
and destroy this email and any attachments without any further use, copying
or forwarding.

#9Rahila Syed
rahilasyed90@gmail.com
In reply to: Simon Riggs (#5)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

Thank you for suggestions.

Yes, I suggest just a single column on pg_stat_activity called pct_complete

Reporting remaining time also can be crucial to make decisions regarding
continuing or aborting VACUUM.
The same has been suggested in the thread below,

/messages/by-id/13072.1284826206@sss.pgh.pa.us

trace_completion_interval = 5s (default)

Every interval, we report the current % complete for any operation that

supports it. We just show NULL if the current operation has not reported
anything or never will.

We do this for VACUUM first, then we can begin adding other operations as

we work out how (for that operation).

Thank you for explaining. This design seems good to me except, adding more
than one columns(percent_complete, remaining_time) if required to
pg_stat_activity can be less user intuitive than having a separate view for
VACUUM.

-Rahila Syed

On Tue, Jun 30, 2015 at 2:02 PM, Simon Riggs <simon@2ndquadrant.com> wrote:

Show quoted text

On 30 June 2015 at 08:52, Pavel Stehule <pavel.stehule@gmail.com> wrote:

I though about the possibilities of progress visualization - and one
possibility is one or two special column in pg_stat_activity table - this
info can be interesting for VACUUM started by autovacuum too.

Yes, I suggest just a single column on pg_stat_activity called pct_complete

trace_completion_interval = 5s (default)

Every interval, we report the current % complete for any operation that
supports it. We just show NULL if the current operation has not reported
anything or never will.

We do this for VACUUM first, then we can begin adding other operations as
we work out how (for that operation).

--
Simon Riggs http://www.2ndQuadrant.com/
<http://www.2ndquadrant.com/&gt;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#10Rahila Syed
rahilasyed90@gmail.com
In reply to: Pavel Stehule (#2)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

I though about the possibilities of progress visualization - and one

possibility is one or two special column in pg_stat_activity table - this
info can be interesting for VACUUM >started by autovacuum too.

Thank you for suggestion. The design with hooks and a separate view was
mainly to keep most of the code outside core as the feature proposed is
specific to VACUUM command. Also, having a separate view can give more
flexibility in terms of displaying various progress parameters.

FWIW ,there was resistance to include columns in pg_stat_activity earlier
in the following thread,
/messages/by-id/AANLkTi=TcuMA38oGUKX9p5WVPpY+M3L0XUp7=PLT+LCT@mail.gmail.com

Thank you,
Rahila Syed

On Tue, Jun 30, 2015 at 1:22 PM, Pavel Stehule <pavel.stehule@gmail.com>
wrote:

Show quoted text

Hi

2015-06-30 9:37 GMT+02:00 Rahila Syed <rahilasyed90@gmail.com>:

Hello Hackers,

Following is a proposal for feature to calculate VACUUM progress.

interesting idea - I like to see it integrated to core.

Use Case : Measuring progress of long running VACUUMs to help DBAs make
informed decision
whether to continue running VACUUM or abort it.

Design:

A shared preload library to store progress information from different
backends running VACUUM, calculate remaining time for each and display
progress in the
in the form a view.

probably similar idea can be used for REINDEX, CREATE INDEX, COPY TO
statements

I though about the possibilities of progress visualization - and one
possibility is one or two special column in pg_stat_activity table - this
info can be interesting for VACUUM started by autovacuum too.

Regards

Pavel

VACUUM needs to be instrumented with a hook to collect progress
information (pages vacuumed/scanned) periodically.

The patch attached implements a new hook to store vacuumed_pages and
scanned_pages count at the end of each page scanned by VACUUM.

This information is stored in a shared memory structure.

In addition to measuring progress this function using hook also
calculates remaining time for VACUUM.

The frequency of collecting progress information can be reduced by
appending delays in between hook function calls.

Also, a GUC parameter

log_vacuum_min_duration can be used.

This will cause VACUUM progress to be calculated only if VACUUM runs more
than specified milliseconds.

A value of zero calculates VACUUM progress for each page processed. -1
disables logging.

Progress calculation :

percent_complete = scanned_pages * 100 / total_pages_to_be_scanned;

remaining_time = elapsed_time * (total_pages_to_be_scanned -
scanned_pages) / scanned_pages;

Shared memory struct:

typedef struct PgStat_VacuumStats

{

Oid databaseoid;

Oid tableoid;

Int32 vacuumed_pages;

Int32 total_pages;

Int32 scanned_pages;

double elapsed_time;

double remaining_time;

} PgStat_VacuumStats[max_connections];

Reporting :

A view named 'pg_maintenance_progress' can be created using the values
in the struct above.

pg_stat_maintenance can be called from any other backend and will display
progress of

each running VACUUM.

Other uses of hook in VACUUM:

Cost of VACUUM in terms of pages hit , missed and dirtied same as
autovacuum can be collected using this hook.

Autovacuum does it at the end of VACUUM for each table. It can be done
while VACUUM on a table is in progress.
This can be helpful to track manual VACUUMs also not just autovacuum.

Read/Write(I/O) rates can be computed on the lines of autovacuum.
Read rate patterns can be used to help tuning future vacuum on the
table(like shared buffers tuning)
Other resource usages can also be collected using progress checker hook.

Attached patch is POC patch of progress calculation for a single backend.

Also attached is a brief snapshot of the output log.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#11Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Rahila Syed (#10)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2015-07-02 AM 11:41, Rahila Syed wrote:

Hello,

I though about the possibilities of progress visualization - and one

possibility is one or two special column in pg_stat_activity table - this
info can be interesting for VACUUM >started by autovacuum too.

Thank you for suggestion. The design with hooks and a separate view was
mainly to keep most of the code outside core as the feature proposed is
specific to VACUUM command. Also, having a separate view can give more
flexibility in terms of displaying various progress parameters.

Unless I am missing something, I guess you can still keep the actual code that
updates counters outside the core if you adopt an approach that Simon
suggests. Whatever the view (existing/new), any related counters would have a
valid (non-NULL) value when read off the view iff hooks are set perhaps
because you have an extension that sets them. I guess that means any operation
that "supports" progress tracking would have an extension with suitable hooks
implemented.

Of course unless I misinterpreted Simon's words.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#12Sameer Thakur
samthakur74@gmail.com
In reply to: Rahila Syed (#10)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

Thank you for suggestion. The design with hooks and a separate view was

mainly to keep most of the >code outside core as the feature proposed is
specific to VACUUM command. Also, having a separate view >can give more
flexibility in terms of displaying various progress parameters.

FWIW ,there was resistance to include columns in pg_stat_activity earlier

in the following thread,

/messages/by-id/AANLkTi=TcuMA38oGUKX9p5WVPpY+M3L0XUp7=PLT+LCT@...

Perhaps as suggested in the link, the progress could be made available via a
function call which does progress calculation "on demand". Then we do not
need a separate view, or clutter pg_stat_activity, and also has benefit of
calculating progress just when it's needed.

--
View this message in context: http://postgresql.nabble.com/PROPOSAL-VACUUM-Progress-Checker-tp5855849p5856192.html
Sent from the PostgreSQL - hackers mailing list archive at Nabble.com.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#13Simon Riggs
simon@2ndQuadrant.com
In reply to: Rahila Syed (#9)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2 July 2015 at 03:00, Rahila Syed <rahilasyed90@gmail.com> wrote:

Yes, I suggest just a single column on pg_stat_activity called

pct_complete

Reporting remaining time also can be crucial to make decisions regarding
continuing or aborting VACUUM.
The same has been suggested in the thread below,

/messages/by-id/13072.1284826206@sss.pgh.pa.us

trace_completion_interval = 5s (default)

Every interval, we report the current % complete for any operation that

supports it. We just show NULL if the current operation has not reported
anything or never will.

We do this for VACUUM first, then we can begin adding other operations

as we work out how (for that operation).

Thank you for explaining. This design seems good to me except, adding more
than one columns(percent_complete, remaining_time)

It is attractive to have a "remaining_time" column, or a
"predicted_completion_timestamp", but those columns are prediction
calculations rather than actual progress reports. I'm interested in seeing
a report that relates to actual progress made.

Predicted total work required is also interesting, but is much less
trustworthy figure.

I think we'll need to get wider input about the user interface for this
feature.

if required to pg_stat_activity can be less user intuitive than having a
separate view for VACUUM.

I think it is a mistake to do something just for VACUUM.

Monitoring software will look at pg_stat_activity. I don't think we should
invent a separate view for progress statistics because it will cause users
to look in two places rather than just one. Reporting progress is fairly
cheap instrumentation, calculating a prediction of completion time might be
expensive.

Having said that, monitoring systems currently use a polling mechanism to
retrieve status data. They look at information published by the backend. We
don't currently have a mechanism to defer publication of expensive
monitoring information until requested by the monitoring system. If you
have a design for how that might work then say so, otherwise we need to
assume a simple workflow: the backend publishes whatever it chooses,
whenever it chooses and then that is made available via the monitoring
system via views.

Your current design completely misses the time taken to scan indexes, which
is significant.

There might be a justification to put this out of core, but measuring
progress of VACUUM wouldn't be it, IMHO.

--
Simon Riggs http://www.2ndQuadrant.com/
<http://www.2ndquadrant.com/&gt;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#14Guillaume Lelarge
guillaume@lelarge.info
In reply to: Simon Riggs (#13)
Re: [PROPOSAL] VACUUM Progress Checker.

Le 2 juil. 2015 7:28 AM, "Simon Riggs" <simon@2ndquadrant.com> a écrit :

On 2 July 2015 at 03:00, Rahila Syed <rahilasyed90@gmail.com> wrote:

Yes, I suggest just a single column on pg_stat_activity called

pct_complete

Reporting remaining time also can be crucial to make decisions regarding

continuing or aborting VACUUM.

The same has been suggested in the thread below,

/messages/by-id/13072.1284826206@sss.pgh.pa.us

trace_completion_interval = 5s (default)

Every interval, we report the current % complete for any operation that

supports it. We just show NULL if the current operation has not reported
anything or never will.

We do this for VACUUM first, then we can begin adding other operations

as we work out how (for that operation).

Thank you for explaining. This design seems good to me except, adding

more than one columns(percent_complete, remaining_time)

It is attractive to have a "remaining_time" column, or a

"predicted_completion_timestamp", but those columns are prediction
calculations rather than actual progress reports. I'm interested in seeing
a report that relates to actual progress made.

Agreed.

Predicted total work required is also interesting, but is much less

trustworthy figure.

And it is something a client app or an extension can compute. No need to
put this in core as long as we have the actual progress.

I think we'll need to get wider input about the user interface for this

feature.

if required to pg_stat_activity can be less user intuitive than having a

separate view for VACUUM.

I think it is a mistake to do something just for VACUUM.

Monitoring software will look at pg_stat_activity. I don't think we

should invent a separate view for progress statistics because it will cause
users to look in two places rather than just one. Reporting progress is
fairly cheap instrumentation, calculating a prediction of completion time
might be expensive.

+1

Having said that, monitoring systems currently use a polling mechanism to

retrieve status data. They look at information published by the backend. We
don't currently have a mechanism to defer publication of expensive
monitoring information until requested by the monitoring system. If you
have a design for how that might work then say so, otherwise we need to
assume a simple workflow: the backend publishes whatever it chooses,
whenever it chooses and then that is made available via the monitoring
system via views.

Your current design completely misses the time taken to scan indexes,

which is significant.

There might be a justification to put this out of core, but measuring

progress of VACUUM wouldn't be it, IMHO.

Show quoted text

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#15Syed, Rahila
Rahila.Syed@nttdata.com
In reply to: Amit Langote (#11)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

Unless I am missing something, I guess you can still keep the actual code that updates counters outside the core if you adopt an approach that Simon suggests.

Yes. The code to extract progress information from VACUUM and storing in shared memory can be outside core even with pg_stat_activity as a user interface.

Whatever the view (existing/new), any related counters would have a valid (non-NULL) value when read off the view iff hooks are set perhaps because you have an extension that sets them.
I guess that means any operation that "supports" progress tracking would have an extension with suitable hooks implemented.

Do you mean to say , any operation/application that want progress tracking feature will dynamically load the progress checker module which will set the hooks for progress reporting?
If yes , unless I am missing something such dynamic loading cannot happen if we use pg_stat_activity as it gets values from shared memory. Module has to be a shared_preload_library
to allocate a shared memory. So this will mean the module can be loaded only at server restart. Am I missing something?

Thank you,
Rahila Syed

______________________________________________________________________
Disclaimer: This email and any attachments are sent in strictest confidence
for the sole use of the addressee and may contain legally privileged,
confidential, and proprietary data. If you are not the intended recipient,
please advise the sender by replying promptly to this email and then delete
and destroy this email and any attachments without any further use, copying
or forwarding.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#16Tom Lane
tgl@sss.pgh.pa.us
In reply to: Syed, Rahila (#15)
Re: [PROPOSAL] VACUUM Progress Checker.

"Syed, Rahila" <Rahila.Syed@nttdata.com> writes:

Hello,

Unless I am missing something, I guess you can still keep the actual code that updates counters outside the core if you adopt an approach that Simon suggests.

Yes. The code to extract progress information from VACUUM and storing in shared memory can be outside core even with pg_stat_activity as a user interface.

Whatever the view (existing/new), any related counters would have a valid (non-NULL) value when read off the view iff hooks are set perhaps because you have an extension that sets them.
I guess that means any operation that "supports" progress tracking would have an extension with suitable hooks implemented.

Do you mean to say , any operation/application that want progress tracking feature will dynamically load the progress checker module which will set the hooks for progress reporting?
If yes , unless I am missing something such dynamic loading cannot happen if we use pg_stat_activity as it gets values from shared memory. Module has to be a shared_preload_library
to allocate a shared memory. So this will mean the module can be loaded

only at server restart. Am I missing something?

TBH, I think that designing this as a hook-based solution is adding a
whole lot of complexity for no value. The hard parts of the problem are
collecting the raw data and making the results visible to users, and
both of those require involvement of the core code. Where is the benefit
from pushing some trivial intermediate arithmetic into an external module?
If there's any at all, it's certainly not enough to justify problems such
as you mention here.

So I'd just create a "pgstat_report_percent_done()" type of interface in
pgstat.c and then teach VACUUM to call it directly.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#17Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Syed, Rahila (#15)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2015-07-02 PM 11:00, Syed, Rahila wrote:

Hello,

Unless I am missing something, I guess you can still keep the actual code that updates counters outside the core if you adopt an approach that Simon suggests.

Yes. The code to extract progress information from VACUUM and storing in shared memory can be outside core even with pg_stat_activity as a user interface.

Whatever the view (existing/new), any related counters would have a valid (non-NULL) value when read off the view iff hooks are set perhaps because you have an extension that sets them.
I guess that means any operation that "supports" progress tracking would have an extension with suitable hooks implemented.

Do you mean to say , any operation/application that want progress tracking feature will dynamically load the progress checker module which will set the hooks for progress reporting?
If yes , unless I am missing something such dynamic loading cannot happen if we use pg_stat_activity as it gets values from shared memory. Module has to be a shared_preload_library
to allocate a shared memory. So this will mean the module can be loaded only at server restart. Am I missing something?

Assuming that set of hooks per command and shared memory structure(s) is a way
to go, I meant to say that hook implementations per command would be in their
separate modules, of course loaded at the server start for shared memory). Of
those, your proposed patch has vacuum_progress, for example. And in context of
my comment above, that means the view would say NULL for commands for which
the module has not been set up in advance. IOW, between showing NULL in the
view and dynamically loading hook functions, we choose the former because I
don't know what the latter means in postgres.

Having said that, Tom's suggestion to export pgstat.c function(s) for
command(s) may be a more appealing way to go.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#18Syed, Rahila
Rahila.Syed@nttdata.com
In reply to: Tom Lane (#16)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

TBH, I think that designing this as a hook-based solution is adding a whole lot of complexity for no value. The hard parts of the problem are collecting the raw data and making the results visible to users, and both of those require involvement of the core code. Where is the benefit from pushing some trivial >intermediate arithmetic into an external module?
If there's any at all, it's certainly not enough to justify problems such as you mention here.

So I'd just create a "pgstat_report_percent_done()" type of interface in pgstat.c and then teach VACUUM to call it directly.

Thank you for suggestion. I agree that adding code in core will reduce code complexity with no additional overhead.
Going by the consensus, I will update the patch with code to collect and store progress information from vacuum in pgstat.c and
UI using pg_stat_activity view.

Thank you,
Rahila Syed

______________________________________________________________________
Disclaimer: This email and any attachments are sent in strictest confidence
for the sole use of the addressee and may contain legally privileged,
confidential, and proprietary data. If you are not the intended recipient,
please advise the sender by replying promptly to this email and then delete
and destroy this email and any attachments without any further use, copying
or forwarding.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#19Rahila Syed
rahilasyed90@gmail.com
In reply to: Syed, Rahila (#18)
1 attachment(s)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

Please find attached updated patch with an interface to calculate command
progress in pgstat.c. This interface currently implements VACUUM progress
tracking .
A column named percent_complete has been added in pg_stat_activity to
report progress.

VACUUM calls the progress calculation interface periodically at an interval
specified by pgstat_track_progress GUC in ms.
Progress calculation can be disabled by setting pgstat_track_progress as
-1.

Remaining_time for VACUUM is not included in current patch to avoid
cluttering pg_stat_activity with too many columns.
But the estimate as seen from previous implementation seems reasonable
enough to be included in progress information , may be as an exclusive view
for vacuum progress information.

GUC parameter 'pgstat_track_progress' is currently PGC_SUSET in line with
'track_activities' GUC parameter. Although IMO, pgstat_track_progress can
be made PGC_USERSET in order to provide more flexibility to any user to
enable/disable progress calculation provided progress is tracked only if
track_activities GUC parameter is enabled.

In this patch, index scans are not taken into account for progress
calculation as of now .

Thank you,
Rahila Syed.

Attachments:

Vacuum_progress_checker_v1.patchapplication/octet-stream; name=Vacuum_progress_checker_v1.patchDownload
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e82a53a..cf3fb1b 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -624,7 +624,8 @@ CREATE VIEW pg_stat_activity AS
             S.state,
             S.backend_xid,
             s.backend_xmin,
-            S.query
+            S.query,
+			S.percent_complete
     FROM pg_database D, pg_stat_get_activity(NULL) AS S, pg_authid U
     WHERE S.datid = D.oid AND
             S.usesysid = U.oid;
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index a01cfb4..04c8547 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -439,7 +439,8 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			   Relation *Irel, int nindexes, bool scan_all)
 {
 	BlockNumber nblocks,
-				blkno;
+				blkno,
+				total_pages_scanned;
 	HeapTupleData tuple;
 	char	   *relname;
 	BlockNumber empty_pages,
@@ -456,6 +457,8 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	bool		skipping_all_visible_blocks;
 	xl_heap_freeze_tuple *frozen;
 	StringInfoData buf;
+	TimestampTz previous_time;
+	TimestampTz current_time;
 
 	pg_rusage_init(&ru0);
 
@@ -471,12 +474,13 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	indstats = (IndexBulkDeleteResult **)
 		palloc0(nindexes * sizeof(IndexBulkDeleteResult *));
 
-	nblocks = RelationGetNumberOfBlocks(onerel);
+	total_pages_scanned = nblocks = RelationGetNumberOfBlocks(onerel);
 	vacrelstats->rel_pages = nblocks;
 	vacrelstats->scanned_pages = 0;
 	vacrelstats->nonempty_pages = 0;
 	vacrelstats->latestRemovedXid = InvalidTransactionId;
 
+	previous_time = GetCurrentTimestamp();
 	lazy_space_alloc(vacrelstats, nblocks);
 	frozen = palloc(sizeof(xl_heap_freeze_tuple) * MaxHeapTuplesPerPage);
 
@@ -520,7 +524,10 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		vacuum_delay_point();
 	}
 	if (next_not_all_visible_block >= SKIP_PAGES_THRESHOLD)
+	{
 		skipping_all_visible_blocks = true;
+		total_pages_scanned = total_pages_scanned - next_not_all_visible_block;
+	}
 	else
 		skipping_all_visible_blocks = false;
 
@@ -559,7 +566,10 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			 * following blocks.
 			 */
 			if (next_not_all_visible_block - blkno > SKIP_PAGES_THRESHOLD)
+			{
 				skipping_all_visible_blocks = true;
+				total_pages_scanned = total_pages_scanned - (next_not_all_visible_block - blkno);
+			}
 			else
 				skipping_all_visible_blocks = false;
 			all_visible_according_to_vm = false;
@@ -1062,6 +1072,17 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		 */
 		if (vacrelstats->num_dead_tuples == prev_dead_count)
 			RecordPageWithFreeSpace(onerel, blkno, freespace);
+
+		/*
+		 * Reporting vacuum progress to statistics collector
+		 */
+		if (pgstat_track_progress == 0 || (pgstat_track_progress > 0 &&
+				TimestampDifferenceExceeds(previous_time, current_time = GetCurrentTimestamp(),
+											pgstat_track_progress)))
+		{
+			previous_time = current_time;
+			pgstat_report_progress(total_pages_scanned, vacrelstats->scanned_pages);
+		}
 	}
 
 	pfree(frozen);
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index e9fbc38..e7ed315 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -67,6 +67,7 @@
 #include "utils/tqual.h"
 
 
+
 /* ----------
  * Timer definitions.
  * ----------
@@ -108,6 +109,7 @@ bool		pgstat_track_activities = false;
 bool		pgstat_track_counts = false;
 int			pgstat_track_functions = TRACK_FUNC_OFF;
 int			pgstat_track_activity_query_size = 1024;
+int 		pgstat_track_progress;
 
 /* ----------
  * Built from GUC parameter
@@ -2720,7 +2722,7 @@ pgstat_bestart(void)
 	beentry->st_clienthostname[NAMEDATALEN - 1] = '\0';
 	beentry->st_appname[NAMEDATALEN - 1] = '\0';
 	beentry->st_activity[pgstat_track_activity_query_size - 1] = '\0';
-
+	beentry->percent_complete = 0;
 	pgstat_increment_changecount_after(beentry);
 
 	/* Update app name to current GUC setting */
@@ -2805,6 +2807,7 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
 			/* st_xact_start_timestamp and st_waiting are also disabled */
 			beentry->st_xact_start_timestamp = 0;
 			beentry->st_waiting = false;
+			beentry->percent_complete = 0;
 			pgstat_increment_changecount_after(beentry);
 		}
 		return;
@@ -2837,7 +2840,33 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
 		beentry->st_activity_start_timestamp = start_timestamp;
 	}
 
+	beentry->percent_complete = 0;
+	pgstat_increment_changecount_after(beentry);
+}
+
+/* ---------------------------------------------
+ *  Called after every fixed interval from vacuum's scan of the relation
+ *  in order to calculate and store vacuum progress.
+ * ---------------------------------------------
+ */
+
+void
+pgstat_report_progress(BlockNumber total_pages, BlockNumber scanned_pages)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	if (!beentry)
+		return;
+
+	if (!pgstat_track_activities)
+		return;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->percent_complete = scanned_pages * 100 / total_pages;
 	pgstat_increment_changecount_after(beentry);
+
+	elog(LOG,"%d scanned_pages %d total pages\n", scanned_pages, total_pages);
+	elog(LOG,"%d percent_complete", beentry->percent_complete);
 }
 
 /* ----------
@@ -2994,6 +3023,8 @@ pgstat_read_current_status(void)
 				strcpy(localactivity, (char *) beentry->st_activity);
 				localentry->backendStatus.st_activity = localactivity;
 				localentry->backendStatus.st_ssl = beentry->st_ssl;
+				localentry->backendStatus.percent_complete = beentry->percent_complete;
+
 #ifdef USE_SSL
 				if (beentry->st_ssl)
 				{
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index f7c9bf6..7a05fd1 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -530,7 +530,7 @@ pg_stat_get_backend_idset(PG_FUNCTION_ARGS)
 Datum
 pg_stat_get_activity(PG_FUNCTION_ARGS)
 {
-#define PG_STAT_GET_ACTIVITY_COLS	22
+#define PG_STAT_GET_ACTIVITY_COLS	23
 	int			num_backends = pgstat_fetch_stat_numbackends();
 	int			curr_backend;
 	int			pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
@@ -777,6 +777,11 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 			nulls[13] = true;
 		}
 
+		if (beentry->percent_complete != 0)
+			values[22] = Int32GetDatum(beentry->percent_complete);
+		else
+			nulls[22] = true;
+
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
 
 		/* If only a single backend was requested, and we found it, break. */
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 1bed525..fc86289 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -2309,7 +2309,15 @@ static struct config_int ConfigureNamesInt[] =
 		-1, -1, INT_MAX,
 		NULL, NULL, NULL
 	},
-
+	{
+		{"track_activity_progress", PGC_SUSET, STATS_COLLECTOR,
+			gettext_noop("Collects progress information of executing commands."),
+			GUC_UNIT_MS
+		},
+		&pgstat_track_progress,
+		-1, -1, INT_MAX,
+		NULL, NULL, NULL
+	},
 	{
 		{"log_autovacuum_min_duration", PGC_SIGHUP, LOGGING_WHAT,
 			gettext_noop("Sets the minimum execution time above which "
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 6fd1278..9c75bec 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2777,7 +2777,7 @@ DATA(insert OID = 3057 ( pg_stat_get_autoanalyze_count PGNSP PGUID 12 1 0 0 0 f
 DESCR("statistics: number of auto analyzes for a table");
 DATA(insert OID = 1936 (  pg_stat_get_backend_idset		PGNSP PGUID 12 1 100 0 0 f f f f t t s 0 0 23 "" _null_ _null_ _null_ _null_ _null_ pg_stat_get_backend_idset _null_ _null_ _null_ ));
 DESCR("statistics: currently active backend IDs");
-DATA(insert OID = 2022 (  pg_stat_get_activity			PGNSP PGUID 12 1 100 0 0 f f f f f t s 1 0 2249 "23" "{23,26,23,26,25,25,25,16,1184,1184,1184,1184,869,25,23,28,28,16,25,25,23,16,25}" "{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{pid,datid,pid,usesysid,application_name,state,query,waiting,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,ssl,sslversion,sslcipher,sslbits,sslcompression,sslclientdn}" _null_ _null_ pg_stat_get_activity _null_ _null_ _null_ ));
+DATA(insert OID = 2022 (  pg_stat_get_activity			PGNSP PGUID 12 1 100 0 0 f f f f f t s 1 0 2249 "23" "{23,26,23,26,25,25,25,16,1184,1184,1184,1184,869,25,23,28,28,16,25,25,23,16,25,23}" "{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{pid,datid,pid,usesysid,application_name,state,query,waiting,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,ssl,sslversion,sslcipher,sslbits,sslcompression,sslclientdn,percent_complete}" _null_ _null_ pg_stat_get_activity _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active backends");
 DATA(insert OID = 3099 (  pg_stat_get_wal_senders	PGNSP PGUID 12 1 10 0 0 f f f f f t s 0 0 2249 "" "{23,25,3220,3220,3220,3220,23,25}" "{o,o,o,o,o,o,o,o}" "{pid,state,sent_location,write_location,flush_location,replay_location,sync_priority,sync_state}" _null_ _null_ pg_stat_get_wal_senders _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active replication");
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index e3a31af..e5f1ac9 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -155,7 +155,6 @@ extern int	vacuum_freeze_table_age;
 extern int	vacuum_multixact_freeze_min_age;
 extern int	vacuum_multixact_freeze_table_age;
 
-
 /* in commands/vacuum.c */
 extern void ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(int options, RangeVar *relation, Oid relid,
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 9ecc163..704584b 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -20,6 +20,7 @@
 #include "utils/hsearch.h"
 #include "utils/relcache.h"
 
+#include "storage/block.h"
 
 /* ----------
  * Paths for the statistics files (relative to installation's $PGDATA).
@@ -776,6 +777,9 @@ typedef struct PgBackendStatus
 
 	/* current command string; MUST be null-terminated */
 	char	   *st_activity;
+
+	int		percent_complete;
+
 } PgBackendStatus;
 
 /*
@@ -871,6 +875,7 @@ extern PGDLLIMPORT int pgstat_track_activity_query_size;
 extern char *pgstat_stat_directory;
 extern char *pgstat_stat_tmpname;
 extern char *pgstat_stat_filename;
+extern int  pgstat_track_progress;
 
 /*
  * BgWriter statistics counters are updated directly by bgwriter and bufmgr
@@ -928,6 +933,7 @@ extern void pgstat_initialize(void);
 extern void pgstat_bestart(void);
 
 extern void pgstat_report_activity(BackendState state, const char *cmd_str);
+extern void pgstat_report_progress(BlockNumber total_pages, BlockNumber scanned_pages);
 extern void pgstat_report_tempfile(size_t filesize);
 extern void pgstat_report_appname(const char *appname);
 extern void pgstat_report_xact_timestamp(TimestampTz tstamp);
#20Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Rahila Syed (#19)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2015-07-16 AM 05:18, Rahila Syed wrote:

GUC parameter 'pgstat_track_progress' is currently PGC_SUSET in line with
'track_activities' GUC parameter.

Naming the GUC pgstat* seems a little inconsistent. It could be called,
say, track_maintenance_progress_interval/track_vacuum_progress_interval.
That way, it will look similar to existing track_* parameters:

#track_activities = on
#track_counts = on
#track_io_timing = off
#track_functions = none # none, pl, all
#track_activity_query_size = 1024 # (change requires restart)

Also, adding the new GUC to src/backend/utils/misc/postgresql.conf.sample
might be helpful.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#21Fujii Masao
masao.fujii@gmail.com
In reply to: Rahila Syed (#19)
Re: [PROPOSAL] VACUUM Progress Checker.

On Thu, Jul 16, 2015 at 5:18 AM, Rahila Syed <rahilasyed90@gmail.com> wrote:

Hello,

Please find attached updated patch with an interface to calculate command
progress in pgstat.c.

Thanks for updating the patch!

I got the following compiler warning.

guc.c:2316: warning: initialization makes pointer from integer without a cast

make check-world caused lots of failures in my environment.

The following query caused a segmentation fault.

SELECT name FROM (SELECT pg_catalog.lower(name) AS name FROM
pg_catalog.pg_settings UNION ALL SELECT 'session authorization'
UNION ALL SELECT 'all') ss WHERE substring(name,1,3)='tra';

Regards,

--
Fujii Masao

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#22Sameer Thakur-2
Sameer.Thakur@nttdata.com
In reply to: Simon Riggs (#13)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

Your current design completely misses the time taken to scan indexes, which

is significant.
I tried to address this issue in the attached patch.
The patch calculates index scan progress by measuring against scanned pages
in LVRelStats. It checks for a change current page being scanned and
increments the progress counter. When counter reaches scanned pages number
in LVRelStats, progress is 100% complete. For now the progress is emitted as
a warning (so no config changes needed to see progress)
Thoughts?
regards
Sameer IndexScanProgress.patch
<http://postgresql.nabble.com/file/n5858109/IndexScanProgress.patch&gt;

--
View this message in context: http://postgresql.nabble.com/PROPOSAL-VACUUM-Progress-Checker-tp5855849p5858109.html
Sent from the PostgreSQL - hackers mailing list archive at Nabble.com.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#23Michael Paquier
michael.paquier@gmail.com
In reply to: Sameer Thakur-2 (#22)
Re: [PROPOSAL] VACUUM Progress Checker.

On Thu, Jul 16, 2015 at 1:30 PM, Sameer Thakur-2 wrote:

Thoughts?
regards
Sameer IndexScanProgress.patch
<http://postgresql.nabble.com/file/n5858109/IndexScanProgress.patch&gt;

I am not really willing to show up as the picky guy here, but could it
be possible to receive those patches as attached to emails instead of
having them referenced by URL? I imagine that you are directly using
the nabble interface.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#24Thakur, Sameer
Sameer.Thakur@nttdata.com
In reply to: Michael Paquier (#23)
1 attachment(s)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

I am not really willing to show up as the picky guy here, but could it be possible to receive those patches as attached to emails instead of having them referenced by URL? I >imagine that you are directly using the nabble interface.

Just configured a new mail client for nabble, did not know how to use it within an existing conversation.
Now I can send the patch attached!
Thanks
Sameer

______________________________________________________________________
Disclaimer: This email and any attachments are sent in strictest confidence
for the sole use of the addressee and may contain legally privileged,
confidential, and proprietary data. If you are not the intended recipient,
please advise the sender by replying promptly to this email and then delete
and destroy this email and any attachments without any further use, copying
or forwarding.

Attachments:

IndexScanProgress.patchapplication/octet-stream; name=IndexScanProgress.patchDownload
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index a01cfb4..a3e5b61 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -105,6 +105,8 @@ typedef struct LVRelStats
 	BlockNumber old_rel_pages;	/* previous value of pg_class.relpages */
 	BlockNumber rel_pages;		/* total number of pages */
 	BlockNumber scanned_pages;	/* number of pages we examined */
+	BlockIdData last_scanned_page; /* Used for measuring index scan progress */
+	BlockNumber current_index_scanned_page_count;
 	BlockNumber pinskipped_pages;		/* # of pages we skipped due to a pin */
 	double		scanned_tuples; /* counts only tuples on scanned pages */
 	double		old_rel_tuples; /* previous value of pg_class.reltuples */
@@ -1364,6 +1366,7 @@ lazy_vacuum_index(Relation indrel,
 	ivinfo.strategy = vac_strategy;
 
 	/* Do bulk deletion */
+	vacrelstats->current_index_scanned_page_count=0;
 	*stats = index_bulk_delete(&ivinfo, *stats,
 							   lazy_tid_reaped, (void *) vacrelstats);
 
@@ -1736,7 +1739,20 @@ lazy_tid_reaped(ItemPointer itemptr, void *state)
 {
 	LVRelStats *vacrelstats = (LVRelStats *) state;
 	ItemPointer res;
-
+	BlockNumber current_count;
+	float percentage_complete;
+	if (((itemptr->ip_blkid.bi_hi != vacrelstats->last_scanned_page.bi_hi) || (itemptr->ip_blkid.bi_lo != vacrelstats->last_scanned_page.bi_lo)) && (vacrelstats->current_index_scanned_page_count < vacrelstats->scanned_pages))
+	{
+		vacrelstats->last_scanned_page=itemptr->ip_blkid;
+		vacrelstats->current_index_scanned_page_count = vacrelstats->current_index_scanned_page_count + 1;
+		current_count = vacrelstats->current_index_scanned_page_count;
+		percentage_complete= ((float)current_count/(float)vacrelstats->scanned_pages)*100;
+		elog(WARNING,"Percentage completion %f", percentage_complete);
+	}
+	else
+	{
+		vacrelstats->last_scanned_page=itemptr->ip_blkid;
+	}
 	res = (ItemPointer) bsearch((void *) itemptr,
 								(void *) vacrelstats->dead_tuples,
 								vacrelstats->num_dead_tuples,
#25dinesh kumar
dineshkumar02@gmail.com
In reply to: Fujii Masao (#21)
Re: [PROPOSAL] VACUUM Progress Checker.

Hi

On Wed, Jul 15, 2015 at 9:27 PM, Fujii Masao <masao.fujii@gmail.com> wrote:

On Thu, Jul 16, 2015 at 5:18 AM, Rahila Syed <rahilasyed90@gmail.com>
wrote:

Hello,

Please find attached updated patch with an interface to calculate command
progress in pgstat.c.

Thanks for updating the patch!

I got the following compiler warning.

guc.c:2316: warning: initialization makes pointer from integer without a
cast

make check-world caused lots of failures in my environment.

Yeah, i got couple of warnings with plain make.

The following query caused a segmentation fault.

It was the typo I believe. I see the problem is with GUC definition in
guc.c. There should be "NULL" between gettext_noop and GUC_UNIT_MS.

Regards,
Dinesh
manojadinesh.blogspot.com

SELECT name FROM (SELECT pg_catalog.lower(name) AS name FROM

Show quoted text

pg_catalog.pg_settings UNION ALL SELECT 'session authorization'
UNION ALL SELECT 'all') ss WHERE substring(name,1,3)='tra';

Regards,

--
Fujii Masao

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#26Syed, Rahila
Rahila.Syed@nttdata.com
In reply to: Amit Langote (#20)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

Naming the GUC pgstat* seems a little inconsistent.

Sorry, there is a typo in the mail. The GUC name is 'track_activity_progress'.

Also, adding the new GUC to src/backend/utils/misc/postgresql.conf.sample
might be helpful

Yes. I will update.

Thank you,
Rahila Syed

______________________________________________________________________
Disclaimer: This email and any attachments are sent in strictest confidence
for the sole use of the addressee and may contain legally privileged,
confidential, and proprietary data. If you are not the intended recipient,
please advise the sender by replying promptly to this email and then delete
and destroy this email and any attachments without any further use, copying
or forwarding.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#27Jim Nasby
Jim.Nasby@BlueTreble.com
In reply to: Thakur, Sameer (#24)
Re: [PROPOSAL] VACUUM Progress Checker.

On 7/15/15 11:38 PM, Thakur, Sameer wrote:

Hello,

I am not really willing to show up as the picky guy here, but could it be possible to receive those patches as attached to emails instead of having them referenced by URL? I >imagine that you are directly using the nabble interface.

Just configured a new mail client for nabble, did not know how to use it within an existing conversation.

Does this actually handle multiple indexes? It doesn't appear so, which
I'd think is a significant problem... :/

I'm also not seeing how this will deal with exhausting
maintenance_work_mem. ISTM that when that happens you'd definitely want
a better idea of what's going on...
--
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Data in Trouble? Get it in Treble! http://BlueTreble.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#28Thakur, Sameer
Sameer.Thakur@nttdata.com
In reply to: Jim Nasby (#27)
1 attachment(s)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

Does this actually handle multiple indexes? It doesn't appear so, which I'd think is a significant problem... :/

Please find v2 attached which does this.

I'm also not seeing how this will deal with exhausting maintenance_work_mem. ISTM that when that happens you'd definitely want a better idea of what's going on...

Will work on this aspect in v3.
Thank you,
Sameer

______________________________________________________________________
Disclaimer: This email and any attachments are sent in strictest confidence
for the sole use of the addressee and may contain legally privileged,
confidential, and proprietary data. If you are not the intended recipient,
please advise the sender by replying promptly to this email and then delete
and destroy this email and any attachments without any further use, copying
or forwarding.

Attachments:

IndexScanProgress_v2.patchapplication/octet-stream; name=IndexScanProgress_v2.patchDownload
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index a01cfb4..98655f5 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -105,6 +105,8 @@ typedef struct LVRelStats
 	BlockNumber old_rel_pages;	/* previous value of pg_class.relpages */
 	BlockNumber rel_pages;		/* total number of pages */
 	BlockNumber scanned_pages;	/* number of pages we examined */
+	BlockIdData last_scanned_page; /* Used for measuring index scan progress */
+	BlockNumber current_index_scanned_page_count;
 	BlockNumber pinskipped_pages;		/* # of pages we skipped due to a pin */
 	double		scanned_tuples; /* counts only tuples on scanned pages */
 	double		old_rel_tuples; /* previous value of pg_class.reltuples */
@@ -119,6 +121,8 @@ typedef struct LVRelStats
 	int			max_dead_tuples;	/* # slots allocated in array */
 	ItemPointer dead_tuples;	/* array of ItemPointerData */
 	int			num_index_scans;
+	int			total_num_indexes;
+	int			current_scanned_index;
 	TransactionId latestRemovedXid;
 	bool		lock_waiter_detected;
 } LVRelStats;
@@ -1092,12 +1096,16 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	{
 		/* Log cleanup info before we touch indexes */
 		vacuum_log_cleanup_info(onerel, vacrelstats);
+		vacrelstats->total_num_indexes=nindexes;
 
 		/* Remove index entries */
 		for (i = 0; i < nindexes; i++)
+		{
+			vacrelstats->current_scanned_index = i;
 			lazy_vacuum_index(Irel[i],
 							  &indstats[i],
 							  vacrelstats);
+		}
 		/* Remove tuples from heap */
 		lazy_vacuum_heap(onerel, vacrelstats);
 		vacrelstats->num_index_scans++;
@@ -1364,6 +1372,7 @@ lazy_vacuum_index(Relation indrel,
 	ivinfo.strategy = vac_strategy;
 
 	/* Do bulk deletion */
+	vacrelstats->current_index_scanned_page_count = 0;
 	*stats = index_bulk_delete(&ivinfo, *stats,
 							   lazy_tid_reaped, (void *) vacrelstats);
 
@@ -1736,7 +1745,23 @@ lazy_tid_reaped(ItemPointer itemptr, void *state)
 {
 	LVRelStats *vacrelstats = (LVRelStats *) state;
 	ItemPointer res;
-
+	BlockNumber current_count;
+	float current_index_progress = 0;
+	float all_index_progress = 0;
+	if (((itemptr->ip_blkid.bi_hi != vacrelstats->last_scanned_page.bi_hi) || (itemptr->ip_blkid.bi_lo != vacrelstats->last_scanned_page.bi_lo)) && (vacrelstats->current_index_scanned_page_count < vacrelstats->scanned_pages))
+	{
+		vacrelstats->last_scanned_page = itemptr->ip_blkid;
+		vacrelstats->current_index_scanned_page_count = vacrelstats->current_index_scanned_page_count + 1;
+		current_count = vacrelstats->current_index_scanned_page_count;
+		current_index_progress = ((float)current_count/(float)vacrelstats->scanned_pages);
+		elog(WARNING,"Current index percentage completion %f", current_index_progress * 100);
+		all_index_progress = ((vacrelstats->current_scanned_index + current_index_progress)/vacrelstats->total_num_indexes) * 100;
+		elog(WARNING,"Overall index percentage completion %f", all_index_progress);
+	}
+	else
+	{
+		vacrelstats->last_scanned_page=itemptr->ip_blkid;
+	}
 	res = (ItemPointer) bsearch((void *) itemptr,
 								(void *) vacrelstats->dead_tuples,
 								vacrelstats->num_dead_tuples,
#29Jim Nasby
Jim.Nasby@BlueTreble.com
In reply to: Thakur, Sameer (#28)
Re: [PROPOSAL] VACUUM Progress Checker.

On 7/20/15 4:32 AM, Thakur, Sameer wrote:

Hello,

Does this actually handle multiple indexes? It doesn't appear so, which I'd think is a significant problem... :/

Please find v2 attached which does this.

I think it'd be better to combine both numbers into one report:

elog(WARNING,"Current/Overall index percentage completion %f/%f",
current_index_progress * 100, all_index_progress);

It'd also be good to standardize on where the * 100 is happening.

Also, AFAIK:

(itemptr->ip_blkid.bi_hi != vacrelstats->last_scanned_page.bi_hi) ||
(itemptr->ip_blkid.bi_lo != vacrelstats->last_scanned_page.bi_lo)

can be replaced by

(itemptr->ipblkid != vacrelstats->last_scanned_page)

and

vacrelstats->current_index_scanned_page_count =
vacrelstats->current_index_scanned_page_count + 1;

can simply be

vacrelstats->current_index_scanned_page_count++;
--
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Data in Trouble? Get it in Treble! http://BlueTreble.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#30Robert Haas
robertmhaas@gmail.com
In reply to: Simon Riggs (#5)
Re: [PROPOSAL] VACUUM Progress Checker.

On Tue, Jun 30, 2015 at 4:32 AM, Simon Riggs <simon@2ndquadrant.com> wrote:

Yes, I suggest just a single column on pg_stat_activity called pct_complete

trace_completion_interval = 5s (default)

Every interval, we report the current % complete for any operation that
supports it. We just show NULL if the current operation has not reported
anything or never will.

I am deeply skeptical about the usefulness of a progress-reporting
system that can only report one number. I think that, in many cases,
it won't be possible to compute an accurate completion percentage, and
even if it is, people may want more data than that for various reasons
anyway.

For example, in the case of VACUUM, suppose there is a table with
1,000,000 heap pages and 200,000 index pages (so it's probably
over-indexed, but whatever). After reading 500,000 heap pages, we
have found 0 dead tuples. What percentage of the work have we
finished?

It's hard to say. If we don't find any dead tuples, we've read half
the pages we will eventually read and are therefore half done. But if
we find even 1 dead tuple, then we've got to scan all 200,000 index
pages, so we've read only 41.7% of the pages we'll eventually touch.
If we find so many dead tuples that we have to scan the indexes
multiple times for lack of maintenance_work_mem, we'll eventually read
1,000,000 + 200,000k pages, where k is the number of index scans; if
say k = 5 then we are only 25% done. All of these scenarios are
plausible because, in all likelihood, the dirty pages in the table are
concentrated near the end.

Now we could come up with ways of making good guesses about what is
likely to happen. We could look at the data from pg_stat_all_tables,
historical results of vacuuming this table, the state of the
visibility map, and so on. And that all might help. But it's going
to be fairly hard to produce a percentage of completion that is
monotonically increasing and always accurately reflects the time
remaining. Even if we can do it, it doesn't seem like a stretch to
suppose that sometimes people will want to look at the detail data.
Instead of getting told "we're X% done" (according to some arcane
formula), it's quite reasonable to think that people will want to get
a bunch of values, e.g.:

1. For what percentage of heap pages have we completed phase one (HOT
prune + mark all visible if appropriate + freeze + remember dead
tuples)?
2. For what percentage of heap pages have we completed phase two (mark
line pointers unused)?
3. What percentage of maintenance_work_mem are we currently using to
remember tuples?

For a query, the information we want back is likely to be even more
complicated; e.g. EXPLAIN output with row counts and perhaps timings
to date for each plan node. We can insist that all status reporting
get boiled down to one number, but I suspect we would be better off
asking ourselves how we could let commands return a richer set of
data.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#31Simon Riggs
simon@2ndQuadrant.com
In reply to: Robert Haas (#30)
Re: [PROPOSAL] VACUUM Progress Checker.

On 21 July 2015 at 21:24, Robert Haas <robertmhaas@gmail.com> wrote:

On Tue, Jun 30, 2015 at 4:32 AM, Simon Riggs <simon@2ndquadrant.com>
wrote:

Yes, I suggest just a single column on pg_stat_activity called

pct_complete

trace_completion_interval = 5s (default)

Every interval, we report the current % complete for any operation that
supports it. We just show NULL if the current operation has not reported
anything or never will.

I am deeply skeptical about the usefulness of a progress-reporting
system that can only report one number. I think that, in many cases,
it won't be possible to compute an accurate completion percentage, and
even if it is, people may want more data than that for various reasons
anyway.

The goal here was to have a common metric for all tasks. Multiple numbers
are OK for me, but not extended detail (yet!)

As I said later:

Simon Riggs <simon@2ndquadrant.com> wrote:

I'm interested in seeing a report that relates to actual progress made.
Predicted total work required is also interesting, but is much less

trustworthy figure.

I'm aware of the difficulties for VACUUM in particular and agree with your
scenario/details.

That does not deter me from wishing to see high level information, even it
varies or is inaccurate. The "arrival time" on my Sat Nav is still useful,
even if it changes because of traffic jams that develop while my journey is
in progress. If the value bothers me, I look at the detail. So both summary
and detail information are useful, but summary is more important even
though it is less accurate.

Instead of getting told "we're X% done" (according to some arcane

formula), it's quite reasonable to think that people will want to get
a bunch of values, e.g.:

1. For what percentage of heap pages have we completed phase one (HOT
prune + mark all visible if appropriate + freeze + remember dead
tuples)?
2. For what percentage of heap pages have we completed phase two (mark
line pointers unused)?
3. What percentage of maintenance_work_mem are we currently using to
remember tuples?

For a query, the information we want back is likely to be even more
complicated; e.g. EXPLAIN output with row counts and perhaps timings
to date for each plan node. We can insist that all status reporting
get boiled down to one number, but I suspect we would be better off
asking ourselves how we could let commands return a richer set of
data.

I agree that it is desirable to have a more detailed breakdown of what is
happening. As soon as we do that we hit the need for very action-specific
information reporting, which renders the topic much harder and much more
specific.

For me, the user workflow looks like these....

Worried: "Task X is taking ages? When is it expected to finish?"
Ops: 13:50
<sometime later, about 14:00>
Worried: "Task X is still running? But I thought its ETA was 13:50?"
Ops: Now says 14:30
Worried: "Is it stuck, or is it making progress?"
Ops: Looks like its making progress
Worried: "Can we have a look at it and find out what its doing?"

Worried: "When will Task Y finish?"
Ops: Monday at 11am
Worried: "Bad news! We should cancel it on Sunday evening."

The point is that nobody looks at the detailed info until we have looked at
the summary. So the summary of progress/completion time is important, even
if it is possibly wrong. The detail is also useful. I think we should have
both, but I'd like to see the summary info first, because it is the most
useful, best leading indicator of problems.

In terms of VACUUM specifically: VACUUM should be able to assess beforehand
whether it will scan the indexes, or it can just assume that it will need
to scan the indexes. Perhaps VACUUM can pre-scan the VM to decide how big a
task it has before it starts.

--
Simon Riggs http://www.2ndQuadrant.com/
<http://www.2ndquadrant.com/&gt;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#32Thakur, Sameer
Sameer.Thakur@nttdata.com
In reply to: Jim Nasby (#29)
1 attachment(s)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

I think it'd be better to combine both numbers into one report:
It'd also be good to standardize on where the * 100 is happening.

Done

can be replaced by
(itemptr->ipblkid != vacrelstats->last_scanned_page)

Get compiler error : invalid operands to binary != (have ‘BlockIdData’ and ‘BlockIdData’)

vacrelstats->current_index_scanned_page_count++;

Done
Please find v3 attached.

I am struggling to create maintenance work memory exhaustion. Did the following
maintenance_work_mem=1MB.
Inserted 10 million records in tbl1 with 3 indexes. Deleted 5 million and vacuumed. So far no error. I could keep bumping up the records to say 100 million and try to get this error.
This seems a tedious manner to simulate maintenance work memory exhaustion. Is there a better way?
To insert I am using COPY (from a csv which has 10 million records) and building indexes after insert is complete.
Thank you
Sameer

______________________________________________________________________
Disclaimer: This email and any attachments are sent in strictest confidence
for the sole use of the addressee and may contain legally privileged,
confidential, and proprietary data. If you are not the intended recipient,
please advise the sender by replying promptly to this email and then delete
and destroy this email and any attachments without any further use, copying
or forwarding.

Attachments:

IndexScanProgress_v3.patchapplication/octet-stream; name=IndexScanProgress_v3.patchDownload
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index a01cfb4..ac44df2 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -105,6 +105,8 @@ typedef struct LVRelStats
 	BlockNumber old_rel_pages;	/* previous value of pg_class.relpages */
 	BlockNumber rel_pages;		/* total number of pages */
 	BlockNumber scanned_pages;	/* number of pages we examined */
+	BlockIdData last_scanned_page; /* Used for measuring index scan progress */
+	BlockNumber current_index_scanned_page_count;
 	BlockNumber pinskipped_pages;		/* # of pages we skipped due to a pin */
 	double		scanned_tuples; /* counts only tuples on scanned pages */
 	double		old_rel_tuples; /* previous value of pg_class.reltuples */
@@ -119,6 +121,8 @@ typedef struct LVRelStats
 	int			max_dead_tuples;	/* # slots allocated in array */
 	ItemPointer dead_tuples;	/* array of ItemPointerData */
 	int			num_index_scans;
+	int			total_num_indexes;
+	int			current_scanned_index;
 	TransactionId latestRemovedXid;
 	bool		lock_waiter_detected;
 } LVRelStats;
@@ -595,12 +599,16 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 
 			/* Log cleanup info before we touch indexes */
 			vacuum_log_cleanup_info(onerel, vacrelstats);
+			vacrelstats->total_num_indexes=nindexes;
 
 			/* Remove index entries */
 			for (i = 0; i < nindexes; i++)
+			{
+				vacrelstats->current_scanned_index = i;
 				lazy_vacuum_index(Irel[i],
 								  &indstats[i],
 								  vacrelstats);
+			}
 			/* Remove tuples from heap */
 			lazy_vacuum_heap(onerel, vacrelstats);
 
@@ -1092,12 +1100,16 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	{
 		/* Log cleanup info before we touch indexes */
 		vacuum_log_cleanup_info(onerel, vacrelstats);
+		vacrelstats->total_num_indexes=nindexes;
 
 		/* Remove index entries */
 		for (i = 0; i < nindexes; i++)
+		{
+			vacrelstats->current_scanned_index = i;
 			lazy_vacuum_index(Irel[i],
 							  &indstats[i],
 							  vacrelstats);
+		}
 		/* Remove tuples from heap */
 		lazy_vacuum_heap(onerel, vacrelstats);
 		vacrelstats->num_index_scans++;
@@ -1364,6 +1376,7 @@ lazy_vacuum_index(Relation indrel,
 	ivinfo.strategy = vac_strategy;
 
 	/* Do bulk deletion */
+	vacrelstats->current_index_scanned_page_count = 0;
 	*stats = index_bulk_delete(&ivinfo, *stats,
 							   lazy_tid_reaped, (void *) vacrelstats);
 
@@ -1736,7 +1749,22 @@ lazy_tid_reaped(ItemPointer itemptr, void *state)
 {
 	LVRelStats *vacrelstats = (LVRelStats *) state;
 	ItemPointer res;
-
+	BlockNumber current_count;
+	float current_index_progress = 0;
+	float all_index_progress = 0;
+	if (((itemptr->ip_blkid.bi_hi != vacrelstats->last_scanned_page.bi_hi) || (itemptr->ip_blkid.bi_lo != vacrelstats->last_scanned_page.bi_lo)) && (vacrelstats->current_index_scanned_page_count < vacrelstats->scanned_pages))
+	{
+		vacrelstats->last_scanned_page = itemptr->ip_blkid;
+		vacrelstats->current_index_scanned_page_count = vacrelstats->current_index_scanned_page_count ++;
+		current_count = vacrelstats->current_index_scanned_page_count;
+		current_index_progress = ((float)current_count/(float)vacrelstats->scanned_pages) * 100;
+		all_index_progress = ((vacrelstats->current_scanned_index + current_index_progress)/vacrelstats->total_num_indexes) * 100;
+		elog(WARNING, "Current index percentage completion = %f, Overall index percentage completion = %f",current_index_progress, all_index_progress);
+	}
+	else
+	{
+		vacrelstats->last_scanned_page=itemptr->ip_blkid;
+	}
 	res = (ItemPointer) bsearch((void *) itemptr,
 								(void *) vacrelstats->dead_tuples,
 								vacrelstats->num_dead_tuples,
#33Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Thakur, Sameer (#32)
Re: [PROPOSAL] VACUUM Progress Checker.

Thakur, Sameer wrote:

Hello,

I think it'd be better to combine both numbers into one report:
It'd also be good to standardize on where the * 100 is happening.

Done

can be replaced by
(itemptr->ipblkid != vacrelstats->last_scanned_page)

Get compiler error : invalid operands to binary != (have ‘BlockIdData’ and ‘BlockIdData’)

vacrelstats->current_index_scanned_page_count++;

Done
Please find v3 attached.

I am struggling to create maintenance work memory exhaustion. Did the following
maintenance_work_mem=1MB.
Inserted 10 million records in tbl1 with 3 indexes. Deleted 5 million and vacuumed. So far no error. I could keep bumping up the records to say 100 million and try to get this error.
This seems a tedious manner to simulate maintenance work memory exhaustion. Is there a better way?
To insert I am using COPY (from a csv which has 10 million records) and building indexes after insert is complete.

I don't think there's any maintenance work exhaustion that results in an
error. The system is designed to use all the memory it is allowed to,
and to have other strategies when it's not sufficient to do the whole
sort.

Not sure what Jim meant. Maybe he meant to be aware of when spilling to
disk happens? Obviously, things become slower, so maybe you need to
consider it for progress reporting purposes.

--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#34Amit Langote
amitlangote09@gmail.com
In reply to: Alvaro Herrera (#33)
Re: [PROPOSAL] VACUUM Progress Checker.

On Wed, Jul 22, 2015 at 8:19 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Not sure what Jim meant. Maybe he meant to be aware of when spilling to
disk happens? Obviously, things become slower, so maybe you need to
consider it for progress reporting purposes.

Perhaps the m_w_m determines how many dead tuples lazy_scan_heap() can
keep track of before doing a lazy_vacuum_indexes() +
lazy_vacuum_heap() round. Smaller the m_w_m, more the number of index
scans, slower the progress?

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#35Robert Haas
robertmhaas@gmail.com
In reply to: Simon Riggs (#31)
Re: [PROPOSAL] VACUUM Progress Checker.

On Wed, Jul 22, 2015 at 3:02 AM, Simon Riggs <simon@2ndquadrant.com> wrote:

For me, the user workflow looks like these....

Worried: "Task X is taking ages? When is it expected to finish?"
Ops: 13:50
<sometime later, about 14:00>
Worried: "Task X is still running? But I thought its ETA was 13:50?"
Ops: Now says 14:30
Worried: "Is it stuck, or is it making progress?"
Ops: Looks like its making progress
Worried: "Can we have a look at it and find out what its doing?"

How does Ops know that it is making progress? Just because the
completion percentage is changing?

In terms of VACUUM specifically: VACUUM should be able to assess beforehand
whether it will scan the indexes, or it can just assume that it will need to
scan the indexes. Perhaps VACUUM can pre-scan the VM to decide how big a
task it has before it starts.

Well, we can assume that it will scan the indexes exactly once, but
the actual number may be more or less; and the cost of rescanning the
heap in phase 2 is also hard to estimate.

Maybe I'm worrying over nothing, but I have a feeling that if we try
to do what you're proposing here, we're gonna end up with this:

https://xkcd.com/612/

Most of the progress estimators I have seen over the ~30 years that
I've been playing with computers have been unreliable, and many of
those have been unreliable to the point of being annoying. I think
that's likely to happen with what you are proposing too, though of
course like all predictions of the future it could turn out to be
wrong.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#36Simon Riggs
simon@2ndQuadrant.com
In reply to: Robert Haas (#35)
Re: [PROPOSAL] VACUUM Progress Checker.

On 22 July 2015 at 13:00, Robert Haas <robertmhaas@gmail.com> wrote:

On Wed, Jul 22, 2015 at 3:02 AM, Simon Riggs <simon@2ndquadrant.com>
wrote:

For me, the user workflow looks like these....

Worried: "Task X is taking ages? When is it expected to finish?"
Ops: 13:50
<sometime later, about 14:00>
Worried: "Task X is still running? But I thought its ETA was 13:50?"
Ops: Now says 14:30
Worried: "Is it stuck, or is it making progress?"
Ops: Looks like its making progress
Worried: "Can we have a look at it and find out what its doing?"

How does Ops know that it is making progress? Just because the
completion percentage is changing?

You could, but that is not the way I suggested.

We need
* Some measure of actual progress (the definition of which may vary from
action to action, e.g. blocks scanned)
* Some estimate of the total work required
* An estimate of the estimated time of completion - I liked your view that
this prediction may be costly to request

In terms of VACUUM specifically: VACUUM should be able to assess

beforehand

whether it will scan the indexes, or it can just assume that it will

need to

scan the indexes. Perhaps VACUUM can pre-scan the VM to decide how big a
task it has before it starts.

Well, we can assume that it will scan the indexes exactly once, but
the actual number may be more or less; and the cost of rescanning the
heap in phase 2 is also hard to estimate.

Maybe I'm worrying over nothing, but I have a feeling that if we try
to do what you're proposing here, we're gonna end up with this:

https://xkcd.com/612/

Most of the progress estimators I have seen over the ~30 years that
I've been playing with computers have been unreliable, and many of
those have been unreliable to the point of being annoying. I think
that's likely to happen with what you are proposing too, though of
course like all predictions of the future it could turn out to be
wrong.

Almost like an Optimizer then. Important, often annoyingly wrong, needs
more work.

I'm not proposing this feature, I'm merely asking for it to be defined in a
way that makes it work for more than just VACUUM. Once we have a way of
reporting useful information, other processes can be made to follow that
mechanism, like REINDEX, ALTER TABLE etc.. I believe those things are
important, even if we never get such information for user queries. But I
hope we do.

I won't get in the way of your search for detailed information in more
complex forms. Both things are needed.

--
Simon Riggs http://www.2ndQuadrant.com/
<http://www.2ndquadrant.com/&gt;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#37Robert Haas
robertmhaas@gmail.com
In reply to: Simon Riggs (#36)
Re: [PROPOSAL] VACUUM Progress Checker.

On Wed, Jul 22, 2015 at 8:24 AM, Simon Riggs <simon@2ndquadrant.com> wrote:

* An estimate of the estimated time of completion - I liked your view that
this prediction may be costly to request

I'm saying it may be massively unreliable, not that it may be costly.
(Someone else may have said that it would be costly, but I don't think
it was me.)

Most of the progress estimators I have seen over the ~30 years that
I've been playing with computers have been unreliable, and many of
those have been unreliable to the point of being annoying. I think
that's likely to happen with what you are proposing too, though of
course like all predictions of the future it could turn out to be
wrong.

Almost like an Optimizer then. Important, often annoyingly wrong, needs more
work.

Yes, but with an important difference. If the optimizer mis-estimates
the row count by 3x or 10x or 1000x, but the plan is OK anyway, it's
often the case that no one cares. Except when the plan is bad, people
don't really care about the method used to derive it. The same is not
true here: people will rely on the progress estimates directly, and
they will really care if they are not right.

I'm not proposing this feature, I'm merely asking for it to be defined in a
way that makes it work for more than just VACUUM. Once we have a way of
reporting useful information, other processes can be made to follow that
mechanism, like REINDEX, ALTER TABLE etc.. I believe those things are
important, even if we never get such information for user queries. But I
hope we do.

I won't get in the way of your search for detailed information in more
complex forms. Both things are needed.

OK.

One idea I have is to create a system where we expose a command tag
(i.e. VACUUM) plus a series of generic fields whose specific meanings
are dependent on the command tag. Say, 6 bigint counters, 6 float8
counters, and 3 strings up to 80 characters each. So we have a
fixed-size chunk of shared memory per backend, and each backend that
wants to expose progress information can fill in those fields however
it likes, and we expose the results.

This would be sorta like the way pg_statistic works: the same columns
can be used for different purposes depending on what estimator will be
used to access them.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#38Jim Nasby
Jim.Nasby@BlueTreble.com
In reply to: Amit Langote (#34)
Re: [PROPOSAL] VACUUM Progress Checker.

On 7/22/15 6:58 AM, Amit Langote wrote:

On Wed, Jul 22, 2015 at 8:19 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Not sure what Jim meant. Maybe he meant to be aware of when spilling to
disk happens? Obviously, things become slower, so maybe you need to
consider it for progress reporting purposes.

Perhaps the m_w_m determines how many dead tuples lazy_scan_heap() can
keep track of before doing a lazy_vacuum_indexes() +
lazy_vacuum_heap() round. Smaller the m_w_m, more the number of index
scans, slower the progress?

Yes. Any percent completion calculation will have to account for the
case of needing multiple passes through all the indexes.

Each dead tuple requires 6 bytes (IIRC) of maintenance work mem. So if
you're deleting 5M rows with m_w_m=1MB you should be getting many passes
through the indexes. Studying the output of VACUUM VERBOSE will confirm
that (or just throw a temporary WARNING in the path where we start the
scan).
--
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Data in Trouble? Get it in Treble! http://BlueTreble.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#39Jim Nasby
Jim.Nasby@BlueTreble.com
In reply to: Robert Haas (#37)
Re: [PROPOSAL] VACUUM Progress Checker.

On 7/22/15 9:15 AM, Robert Haas wrote:

I'm not proposing this feature, I'm merely asking for it to be defined in a

way that makes it work for more than just VACUUM. Once we have a way of
reporting useful information, other processes can be made to follow that
mechanism, like REINDEX, ALTER TABLE etc.. I believe those things are
important, even if we never get such information for user queries. But I
hope we do.

I won't get in the way of your search for detailed information in more
complex forms. Both things are needed.

OK.

One idea I have is to create a system where we expose a command tag
(i.e. VACUUM) plus a series of generic fields whose specific meanings
are dependent on the command tag. Say, 6 bigint counters, 6 float8
counters, and 3 strings up to 80 characters each. So we have a
fixed-size chunk of shared memory per backend, and each backend that
wants to expose progress information can fill in those fields however
it likes, and we expose the results.

This would be sorta like the way pg_statistic works: the same columns
can be used for different purposes depending on what estimator will be
used to access them.

If we want to expose that level of detail, I think either JSON or arrays
would make more sense, so we're not stuck with a limited amount of info.
Perhaps DDL would be OK with the numbers you suggested, but
https://www.pgcon.org/2013/schedule/events/576.en.html would not, and I
think wanting query progress is much more common.
--
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Data in Trouble? Get it in Treble! http://BlueTreble.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#40Thakur, Sameer
Sameer.Thakur@nttdata.com
In reply to: Jim Nasby (#38)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

Yes. Any percent completion calculation will have to account for the case of needing multiple passes through all the indexes.

Each dead tuple requires 6 bytes (IIRC) of maintenance work mem. So if you're deleting 5M rows with m_w_m=1MB you should be getting many passes through the indexes. >Studying the output of VACUUM VERBOSE will confirm that (or just throw a temporary WARNING in the path where we start the scan).

Yes I see the problem now. I get the message "WARNING: Overall index percentage completion 100.000000" logged > 25 times while vacuuming after 5 million records deleted.
Figuring out number of multiple index passes beforehand, accurately, is the problem to solve. Clearly need to study this some more.
Thank you,
Sameer Thakur | Senior Software Specialist | NTTDATA Global Delivery Services Private Ltd | w. +91.20.6641.7146 | VoIP: 8834.8146 | m. +91 989.016.6656 | sameer.thakur@nttdata.com | Follow us on Twitter@NTTDATAAmericas

______________________________________________________________________
Disclaimer: This email and any attachments are sent in strictest confidence
for the sole use of the addressee and may contain legally privileged,
confidential, and proprietary data. If you are not the intended recipient,
please advise the sender by replying promptly to this email and then delete
and destroy this email and any attachments without any further use, copying
or forwarding.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#41Thakur, Sameer
Sameer.Thakur@nttdata.com
In reply to: Simon Riggs (#5)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

logged > 25 times

Sorry, it is much lower at 7 times. Does not change overall point though
regards
Sameer Thakur | Senior Software Specialist | NTTDATA Global Delivery Services Private Ltd | w. +91.20.6641.7146 | VoIP: 8834.8146 | m. +91 989.016.6656 | sameer.thakur@nttdata.com | Follow us on Twitter@NTTDATAAmericas

______________________________________________________________________
Disclaimer: This email and any attachments are sent in strictest confidence
for the sole use of the addressee and may contain legally privileged,
confidential, and proprietary data. If you are not the intended recipient,
please advise the sender by replying promptly to this email and then delete
and destroy this email and any attachments without any further use, copying
or forwarding.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#42Robert Haas
robertmhaas@gmail.com
In reply to: Jim Nasby (#39)
Re: [PROPOSAL] VACUUM Progress Checker.

On Wed, Jul 22, 2015 at 11:28 AM, Jim Nasby <Jim.Nasby@bluetreble.com> wrote:

If we want to expose that level of detail, I think either JSON or arrays
would make more sense, so we're not stuck with a limited amount of info.
Perhaps DDL would be OK with the numbers you suggested, but
https://www.pgcon.org/2013/schedule/events/576.en.html would not, and I
think wanting query progress is much more common.

You need to restrict the amount of info, because you've got to
preallocate enough shared memory to store all the data that somebody
might report.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#43Jim Nasby
Jim.Nasby@BlueTreble.com
In reply to: Robert Haas (#42)
Re: [PROPOSAL] VACUUM Progress Checker.

On 7/23/15 2:43 PM, Robert Haas wrote:

On Wed, Jul 22, 2015 at 11:28 AM, Jim Nasby <Jim.Nasby@bluetreble.com> wrote:

If we want to expose that level of detail, I think either JSON or arrays
would make more sense, so we're not stuck with a limited amount of info.
Perhaps DDL would be OK with the numbers you suggested, but
https://www.pgcon.org/2013/schedule/events/576.en.html would not, and I
think wanting query progress is much more common.

You need to restrict the amount of info, because you've got to
preallocate enough shared memory to store all the data that somebody
might report.

I was thinking your DSM stuff would come into play here. We wouldn't
want to be reallocating during execution, but I'd expect we would know
during setup how much memory we actually needed.
--
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Data in Trouble? Get it in Treble! http://BlueTreble.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#44Robert Haas
robertmhaas@gmail.com
In reply to: Jim Nasby (#43)
Re: [PROPOSAL] VACUUM Progress Checker.

On Fri, Jul 24, 2015 at 2:00 PM, Jim Nasby <Jim.Nasby@bluetreble.com> wrote:

You need to restrict the amount of info, because you've got to
preallocate enough shared memory to store all the data that somebody
might report.

I was thinking your DSM stuff would come into play here. We wouldn't want to
be reallocating during execution, but I'd expect we would know during setup
how much memory we actually needed.

You could make that work, but it would be a pretty significant amount
of new mechanism. Also, if it's to be practical to report progress
frequently, it's got to be cheap, and that precludes reporting vast
volumes of data.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#45Jim Nasby
Jim.Nasby@BlueTreble.com
In reply to: Thakur, Sameer (#41)
Re: [PROPOSAL] VACUUM Progress Checker.

On 7/23/15 5:18 AM, Thakur, Sameer wrote:

Hello,

logged > 25 times

Sorry, it is much lower at 7 times. Does not change overall point though

I think it's related to the problem of figuring out how many dead tuples
you expect to find in the overall heap, which you need to do to have any
hope of this being a comprehensive estimate.

My inclination at this point is to provide a simple means of providing
the raw numbers and let users test it in the wild. A really crude method
of doing that might be to trap SIGINFO (if we're not using it already)
and elog current status.
--
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Data in Trouble? Get it in Treble! http://BlueTreble.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#46Josh Berkus
josh@agliodbs.com
In reply to: Simon Riggs (#5)
Re: [PROPOSAL] VACUUM Progress Checker.

On 07/24/2015 11:06 AM, Jim Nasby wrote:

On 7/23/15 5:18 AM, Thakur, Sameer wrote:

Hello,

logged > 25 times

Sorry, it is much lower at 7 times. Does not change overall point though

I think it's related to the problem of figuring out how many dead tuples
you expect to find in the overall heap, which you need to do to have any
hope of this being a comprehensive estimate.

What about just reporting scanned pages/total pages ? That would be
easy and cheap to track. It would result in some herky-jerky
"progress", but would still be an improvement over the feedback we don't
have now.

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#47Mike Blackwell
mike.blackwell@rrd.com
In reply to: Josh Berkus (#46)
Re: [PROPOSAL] VACUUM Progress Checker.

​Something like that would be helpful. I just had to stop one after an
hour and have no idea how much longer it would have taken.​

__________________________________________________________________________________
*Mike Blackwell | Technical Analyst, Distribution Services/Rollout
Management | RR Donnelley*
1750 Wallace Ave | St Charles, IL 60174-3401
Office: 630.313.7818
Mike.Blackwell@rrd.com
http://www.rrdonnelley.com

<http://www.rrdonnelley.com/&gt;
* <Mike.Blackwell@rrd.com>*

On Fri, Jul 24, 2015 at 5:41 PM, Josh Berkus <josh@agliodbs.com> wrote:

Show quoted text

On 07/24/2015 11:06 AM, Jim Nasby wrote:

On 7/23/15 5:18 AM, Thakur, Sameer wrote:

Hello,

logged > 25 times

Sorry, it is much lower at 7 times. Does not change overall point though

I think it's related to the problem of figuring out how many dead tuples
you expect to find in the overall heap, which you need to do to have any
hope of this being a comprehensive estimate.

What about just reporting scanned pages/total pages ? That would be
easy and cheap to track. It would result in some herky-jerky
"progress", but would still be an improvement over the feedback we don't
have now.

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#48Pavel Stehule
pavel.stehule@gmail.com
In reply to: Josh Berkus (#46)
Re: [PROPOSAL] VACUUM Progress Checker.

2015-07-25 0:41 GMT+02:00 Josh Berkus <josh@agliodbs.com>:

On 07/24/2015 11:06 AM, Jim Nasby wrote:

On 7/23/15 5:18 AM, Thakur, Sameer wrote:

Hello,

logged > 25 times

Sorry, it is much lower at 7 times. Does not change overall point though

I think it's related to the problem of figuring out how many dead tuples
you expect to find in the overall heap, which you need to do to have any
hope of this being a comprehensive estimate.

What about just reporting scanned pages/total pages ? That would be
easy and cheap to track. It would result in some herky-jerky
"progress", but would still be an improvement over the feedback we don't
have now.

I like this idea.

Regards

Pavel

Show quoted text

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#49Rahila Syed
rahilasyed90@gmail.com
In reply to: Jim Nasby (#45)
Re: [PROPOSAL] VACUUM Progress Checker.

I think it's related to the problem of figuring out how many dead tuples

you expect to find in the overall heap, which you need to do to have >any
hope of this being a comprehensive estimate.

An estimate of number of index scans while vacuuming can be done using
estimate of total dead tuples in the relation and maintenance work mem.
n_dead_tuples in pg_stat_all_tables can be used as an estimate of dead
tuples.

Following can be a way to estimate,

if nindexes == 0
index_scans =0
else if pages_all_visible
index_scans =0
else
index_scans = Max((n_dead_tuples * space occupied by single dead
tuple)/m_w_m,1)

This estimates index_scans = 1 if n_dead_tuples = 0 assuming lazy scan heap
is likely to find some dead_tuples.
If n_dead_tuples is non zero the above estimate gives a lower bound on
number of index scans possible.

Thank you,
Rahila Syed

#50Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Rahila Syed (#49)
Re: [PROPOSAL] VACUUM Progress Checker.

I think the only way to produce usable estimates is to report more than
one number. And in the particular case of lazy vacuuming, ISTM the way
to do it is to consider heap scanning as one phase, index cleanup as
another phase; these two phases can be interleaved. And there's a final
heap scan which is a third phase, which can only run after phases one
and two are complete.

So you would report either "we're in phases one/two" or "we're in phase
three". If we're in phases one/two, then we need to report

1. what's the page number of heap scan (i.e. how much more do we have to
go yet?)
2. how many index scans have we done so far
3. if phase two, how many index pages have we scanned (total, i.e.
across all indexes).

The total number of heap pages is known, and the total number of index
pages is also known, so it's possible to derive a percentage out of
this part. Maybe it would be useful to report how much time it's been
in phases one and two respectively; with that, I think it is possible to
extrapolate the total time.

If we're in third phase, we report the heap page number we're in.

This looks pretty complicated to understand from the user POV, but
anything other than this seems to me too simplistic to be of any use.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#51Joshua D. Drake
jd@commandprompt.com
In reply to: Alvaro Herrera (#50)
Re: [PROPOSAL] VACUUM Progress Checker.

On 07/31/2015 11:21 AM, Alvaro Herrera wrote:

This looks pretty complicated to understand from the user POV, but
anything other than this seems to me too simplistic to be of any use.

I would agree and I don't think it is all that complicated. This is an
RDBMS not a web browser downloading a file.

JD

--
Command Prompt, Inc. - http://www.commandprompt.com/ 503-667-4564
PostgreSQL Centered full stack support, consulting and development.
Announcing "I'm offended" is basically telling the world you can't
control your own emotions, so everyone else should do it for you.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#52Robert Haas
robertmhaas@gmail.com
In reply to: Alvaro Herrera (#50)
Re: [PROPOSAL] VACUUM Progress Checker.

On Fri, Jul 31, 2015 at 2:21 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

I think the only way to produce usable estimates is to report more than
one number. And in the particular case of lazy vacuuming, ISTM the way
to do it is to consider heap scanning as one phase, index cleanup as
another phase; these two phases can be interleaved. And there's a final
heap scan which is a third phase, which can only run after phases one
and two are complete.

That's not really right. There's a phase three for each phase two.

Put in terms of the code, what we're calling phase one is
lazy_scan_heap(), which prunes all pages, sets hint bits, collects
dead TIDs, and maybe marks the page all-visible.

When lazy_scan_heap() fills up maintenance_work_mem, or when it
reaches the end of the heap, it does phase two, which is
lazy_vacuum_index(), and phase three, which is lazy_vacuum_heap().
Phase one - lazy_scan_heap() - then keeps going from where it left
off.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#53Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Robert Haas (#52)
Re: [PROPOSAL] VACUUM Progress Checker.

Robert Haas wrote:

On Fri, Jul 31, 2015 at 2:21 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

I think the only way to produce usable estimates is to report more than
one number. And in the particular case of lazy vacuuming, ISTM the way
to do it is to consider heap scanning as one phase, index cleanup as
another phase; these two phases can be interleaved. And there's a final
heap scan which is a third phase, which can only run after phases one
and two are complete.

That's not really right. There's a phase three for each phase two.

Put in terms of the code, what we're calling phase one is
lazy_scan_heap(), which prunes all pages, sets hint bits, collects
dead TIDs, and maybe marks the page all-visible.

When lazy_scan_heap() fills up maintenance_work_mem, or when it
reaches the end of the heap, it does phase two, which is
lazy_vacuum_index(), and phase three, which is lazy_vacuum_heap().
Phase one - lazy_scan_heap() - then keeps going from where it left
off.

Hmm, you're right. I don't think it changes the essence of what I
suggest, though.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#54Rahila Syed
rahilasyed90@gmail.com
In reply to: Alvaro Herrera (#50)
Re: [PROPOSAL] VACUUM Progress Checker.

The total number of heap pages is known, and the total number of index
pages is also known, so it's possible to derive a percentage out of
this part.

The total number of index pages scanned during entire vacuum will depend on
number
of index scans that happens.
In order to extrapolate percent complete for phase two(index scan) we need
number of index scans * total index pages.
The number of index scans can vary from 1 to n (n depending on
maintenance_work_mem)

Summarizing suggestions in previous mails, following information can be
reported
Phase 1.heap pages scanned / total heap pages
Phase 2.index pages scanned / total index pages (across all indexes)
Phase 3.count of heap pages vacuumed

Additional info like number of index scans so far, number of dead tuples
being vacuumed in one batch can also be provided.

A combined estimate for vacuum percent complete can be provided by summing
up heap pages scanned, index pages scanned against total heap pages, total
index pages * number of index scans.

Thank you,
Rahila Syed

#55Rahila Syed
rahilasyed90@gmail.com
In reply to: Robert Haas (#37)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

Say, 6 bigint counters, 6 float8
counters, and 3 strings up to 80 characters each. So we have a
fixed-size chunk of shared memory per backend, and each backend that
wants to expose progress information can fill in those fields however
it likes, and we expose the results.
This would be sorta like the way pg_statistic works: the same columns
can be used for different purposes depending on what estimator will be
used to access them.

After thinking more on this suggestion, I came up with following generic
structure which can be used to store progress of any command per backend in
shared memory.

Struct PgBackendProgress
{
int32 *counter[COMMAND_NUM_SLOTS];
float8 *counter_float[COMMAND_NUM_SLOTS];

char *progress_message[COMMAND_NUM_SLOTS];
}

COMMAND_NUM_SLOTS will define maximum number of slots(phases) for any
command.
Progress of command will be measured using progress of each phase in
command.
For some command the number of phases can be singular and rest of the slots
will be NULL.

Each phase will report n integer counters, n float counters and a progress
message.
For some phases , any of the above fields can be NULL.

For VACUUM , there can 3 phases as discussed in the earlier mails.

Phase 1. Report 2 integer counters: heap pages scanned and total heap
pages, 1 float counter: percentage_complete and progress message.
Phase 2. Report 2 integer counters: index pages scanned and total index
pages(across all indexes) and progress message.
Phase 3. 1 integer counter: heap pages vacuumed.

This structure can be accessed by statistics collector to display progress
via new view.

Thank you,
Rahila Syed

#56Masahiko Sawada
sawada.mshk@gmail.com
In reply to: Rahila Syed (#55)
Re: [PROPOSAL] VACUUM Progress Checker.

On Mon, Aug 10, 2015 at 1:36 PM, Rahila Syed <rahilasyed90@gmail.com> wrote:

Hello,

Say, 6 bigint counters, 6 float8
counters, and 3 strings up to 80 characters each. So we have a
fixed-size chunk of shared memory per backend, and each backend that
wants to expose progress information can fill in those fields however
it likes, and we expose the results.
This would be sorta like the way pg_statistic works: the same columns
can be used for different purposes depending on what estimator will be
used to access them.

After thinking more on this suggestion, I came up with following generic
structure which can be used to store progress of any command per backend in
shared memory.

Struct PgBackendProgress
{
int32 *counter[COMMAND_NUM_SLOTS];
float8 *counter_float[COMMAND_NUM_SLOTS];

char *progress_message[COMMAND_NUM_SLOTS];
}

COMMAND_NUM_SLOTS will define maximum number of slots(phases) for any
command.
Progress of command will be measured using progress of each phase in
command.
For some command the number of phases can be singular and rest of the slots
will be NULL.

Each phase will report n integer counters, n float counters and a progress
message.
For some phases , any of the above fields can be NULL.

For VACUUM , there can 3 phases as discussed in the earlier mails.

Phase 1. Report 2 integer counters: heap pages scanned and total heap pages,
1 float counter: percentage_complete and progress message.
Phase 2. Report 2 integer counters: index pages scanned and total index
pages(across all indexes) and progress message.
Phase 3. 1 integer counter: heap pages vacuumed.

This structure can be accessed by statistics collector to display progress
via new view.

I have one question about this.

When we're in Phase2 or 3, don't we need to report the number of total
page scanned or percentage of how many table pages scanned, as well?
As Robert said, Phase2(means lazy_vacuum_index here) and 3(means
lazy_vacuum_heap here) could be called whenever lazy_scan_heap fills
up the maintenance_work_mem. And phase 3 could be called at the end of
scanning single page if table doesn't have index.
So if vacuum progress checker reports the only current phase
information, we would not be able to know where we are in now.

Regards,

--
Masahiko Sawada

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#57Syed, Rahila
Rahila.Syed@nttdata.com
In reply to: Masahiko Sawada (#56)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

When we're in Phase2 or 3, don't we need to report the number of total page scanned or percentage of how many table pages scanned, as well?

The total heap pages scanned need to be reported with phase 2 or 3. Complete progress report need to have numbers from each phase when applicable.

Phase 1. Report 2 integer counters: heap pages scanned and total heap
pages,
1 float counter: percentage_complete and progress message.
Phase 2. Report 2 integer counters: index pages scanned and total
index pages(across all indexes) and progress message.
Phase 3. 1 integer counter: heap pages vacuumed.

Sorry for being unclear here. What I meant to say is, each phase of a command will correspond to a slot in COMMAND_NUM_SLOTS. Each phase will be a separate array element and
will comprise of n integers, n floats, string. So , in the view reporting progress, VACUUM command can have 3 entries one for each phase.

Thank you,
Rahila Syed

______________________________________________________________________
Disclaimer: This email and any attachments are sent in strictest confidence
for the sole use of the addressee and may contain legally privileged,
confidential, and proprietary data. If you are not the intended recipient,
please advise the sender by replying promptly to this email and then delete
and destroy this email and any attachments without any further use, copying
or forwarding.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#58Simon Riggs
simon@2ndQuadrant.com
In reply to: Syed, Rahila (#57)
Re: [PROPOSAL] VACUUM Progress Checker.

On 10 August 2015 at 15:59, Syed, Rahila <Rahila.Syed@nttdata.com> wrote:

Hello,

When we're in Phase2 or 3, don't we need to report the number of total

page scanned or percentage of how many table pages scanned, as well?
The total heap pages scanned need to be reported with phase 2 or 3.
Complete progress report need to have numbers from each phase when
applicable.

Phase 1. Report 2 integer counters: heap pages scanned and total heap
pages,
1 float counter: percentage_complete and progress message.
Phase 2. Report 2 integer counters: index pages scanned and total
index pages(across all indexes) and progress message.
Phase 3. 1 integer counter: heap pages vacuumed.

Sorry for being unclear here. What I meant to say is, each phase of a
command will correspond to a slot in COMMAND_NUM_SLOTS. Each phase will be
a separate array element and
will comprise of n integers, n floats, string. So , in the view reporting
progress, VACUUM command can have 3 entries one for each phase.

VACUUM has 3 phases now, but since phases 2 and 3 repeat, you can have an
unbounded number of phases. But that assumes that we don't count truncation
as a 4th phase of VACUUM...

SELECT statements also have a variable number of phases, hash, materialize,
sorts all act as blocking nodes where you cannot progress to next phase
until it is complete and you don't know for certain how much data will come
in later phases.

I think the best you'll do is an array of pairs of values [(current blocks,
total blocks), ... ]

Knowing how many phases there are is a tough problem. I think the only way
forwards is to admit that we will publish our best initial estimate of
total workload size and then later we may realise it was wrong and publish
a better number (do until complete). It's not wonderful, but la vida es
loca.

--
Simon Riggs http://www.2ndQuadrant.com/
<http://www.2ndquadrant.com/&gt;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#59Masahiko Sawada
sawada.mshk@gmail.com
In reply to: Simon Riggs (#58)
Re: [PROPOSAL] VACUUM Progress Checker.

On Tue, Aug 11, 2015 at 12:20 AM, Simon Riggs <simon@2ndquadrant.com> wrote:

On 10 August 2015 at 15:59, Syed, Rahila <Rahila.Syed@nttdata.com> wrote:

Hello,

When we're in Phase2 or 3, don't we need to report the number of total
page scanned or percentage of how many table pages scanned, as well?

The total heap pages scanned need to be reported with phase 2 or 3.
Complete progress report need to have numbers from each phase when
applicable.

Phase 1. Report 2 integer counters: heap pages scanned and total heap
pages,
1 float counter: percentage_complete and progress message.
Phase 2. Report 2 integer counters: index pages scanned and total
index pages(across all indexes) and progress message.
Phase 3. 1 integer counter: heap pages vacuumed.

Sorry for being unclear here. What I meant to say is, each phase of a
command will correspond to a slot in COMMAND_NUM_SLOTS. Each phase will be a
separate array element and
will comprise of n integers, n floats, string. So , in the view reporting
progress, VACUUM command can have 3 entries one for each phase.

VACUUM has 3 phases now, but since phases 2 and 3 repeat, you can have an
unbounded number of phases. But that assumes that we don't count truncation
as a 4th phase of VACUUM...

Yeah.
This topic may have been already discussed but, why don't we use just
total scanned pages and total pages?

The mechanism of VACUUM is complicated a bit today, and other
maintenance command is as well.
It would be tough to trace these processing, and these might be
changed in the future.
But basically, we can trace total scanned pages of target relation
easily, and such information would be enough at many case.

Regards,

--
Masahiko Sawada

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#60Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Masahiko Sawada (#59)
Re: [PROPOSAL] VACUUM Progress Checker.

Masahiko Sawada wrote:

This topic may have been already discussed but, why don't we use just
total scanned pages and total pages?

Because those numbers don't extrapolate nicely. If the density of dead
tuples is irregular across the table, such absolute numbers might be
completely meaningless: you could scan 90% of the table without seeing
any index scan, and then at the final 10% be hit by many index scans
cleaning dead tuples. Thus you would see progress go up to 90% very
quickly and then take hours to have it go to 91%. (Additionally, and a
comparatively minor point: since you don't know how many index scans are
going to happen, there's no way to know the total number of blocks
scanned, unless you don't count index blocks at all, and then the
numbers become a lie.)

If you instead track number of heap pages separately from index pages,
and indicate how many index scans have taken place, you have a chance of
actually figuring out how many heap pages are left to scan and how many
more index scans will occur.

The mechanism of VACUUM is complicated a bit today,

Understatement of the week ...

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#61Masahiko Sawada
sawada.mshk@gmail.com
In reply to: Alvaro Herrera (#60)
Re: [PROPOSAL] VACUUM Progress Checker.

On Tue, Aug 11, 2015 at 1:50 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Masahiko Sawada wrote:

This topic may have been already discussed but, why don't we use just
total scanned pages and total pages?

Because those numbers don't extrapolate nicely. If the density of dead
tuples is irregular across the table, such absolute numbers might be
completely meaningless: you could scan 90% of the table without seeing
any index scan, and then at the final 10% be hit by many index scans
cleaning dead tuples. Thus you would see progress go up to 90% very
quickly and then take hours to have it go to 91%. (Additionally, and a
comparatively minor point: since you don't know how many index scans are
going to happen, there's no way to know the total number of blocks
scanned, unless you don't count index blocks at all, and then the
numbers become a lie.)
If you instead track number of heap pages separately from index pages,
and indicate how many index scans have taken place, you have a chance of
actually figuring out how many heap pages are left to scan and how many
more index scans will occur.

Thank you for your explanation!
I understood about this.

VACUUM has 3 phases now, but since phases 2 and 3 repeat, you can have an unbounded number of phases. But that assumes that we don't count truncation as a 4th phase of VACUUM...

In case of vacuum, I think we need to track the number of scanned heap
pages at least, and the information about index scan is the additional
information.
The another idea for displaying progress is to have two kind of
information: essential information and additional information.

Essential information has one numeric data, which is stored
essentially information regarding of its processing.
Additional information has two data: text and numeric. These data is
free-style data which is stored by each backend as it like.
And these three data are output at same time.

For example, In case of vacuum, essential information is the number of
total scanned heap page.

* When lazy_scan_heap starts, the two additional data are NULL.

* When lazy_vacuum_index starts, the backend set additional data like
followings.
- "Index vacuuming" into text data which describes what we're doing
now actually.
- "50" into numeric data which describes how many index pages we scanned.

* And when lazy_vacuum_index is done, backend sets additional data NULL again.

Regards,

--
Masahiko Sawada

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#62Simon Riggs
simon@2ndQuadrant.com
In reply to: Alvaro Herrera (#60)
Re: [PROPOSAL] VACUUM Progress Checker.

On 10 August 2015 at 17:50, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

Masahiko Sawada wrote:

This topic may have been already discussed but, why don't we use just
total scanned pages and total pages?

Because those numbers don't extrapolate nicely. If the density of dead
tuples is irregular across the table, such absolute numbers might be
completely meaningless: you could scan 90% of the table without seeing
any index scan, and then at the final 10% be hit by many index scans
cleaning dead tuples. Thus you would see progress go up to 90% very
quickly and then take hours to have it go to 91%. (Additionally, and a
comparatively minor point: since you don't know how many index scans are
going to happen, there's no way to know the total number of blocks
scanned, unless you don't count index blocks at all, and then the
numbers become a lie.)

If you instead track number of heap pages separately from index pages,
and indicate how many index scans have taken place, you have a chance of
actually figuring out how many heap pages are left to scan and how many
more index scans will occur.

I think this overstates the difficulty.

Autovacuum knows what % of a table needs to be cleaned - that is how it is
triggered. When a vacuum runs we should calculate how many TIDs we will
collect and therefore how many trips to the indexes we need for given
memory. We can use the VM to find out how many blocks we'll need to scan in
the table. So overall, we know how many blocks we need to scan.

I think just storing (total num blocks, scanned blocks) is sufficiently
accurate to be worth holding, rather than make it even more complex.

--
Simon Riggs http://www.2ndQuadrant.com/
<http://www.2ndquadrant.com/&gt;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#63Syed, Rahila
Rahila.Syed@nttdata.com
In reply to: Simon Riggs (#62)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

Autovacuum knows what % of a table needs to be cleaned - that is how it is triggered.
When a vacuum runs we should calculate how many TIDs we will collect and therefore how many trips to the indexes we need for given memory.
We can use the VM to find out how many blocks we'll need to scan in the table. So overall, we know how many blocks we need to scan.

Total heap pages to be scanned can be obtained from VM as mentioned. To figure out number of index scans we need an estimate of dead tuples.

IIUC, autovacuum acquires information that a table has to be cleaned by looking at pgstat entry for the table. i.e n_dead_tuples.
Hence,initial estimate of dead tuple TIDs can be made using n_dead_tuples in pgstat.
n_dead_tuples in pgstat table entry is the value updated by last analyze and may not be up to date.
In cases where pgstat entry for table is NULL, number of dead tuples TIDs cannot be estimated.
In such cases where TIDs cannot be estimated , we can start with an initial estimate of 1 index scan and later go on adding number of index pages to the total count of pages(heap+index) if count of index scan exceeds.

Thank you,
Rahila Syed.

______________________________________________________________________
Disclaimer: This email and any attachments are sent in strictest confidence
for the sole use of the addressee and may contain legally privileged,
confidential, and proprietary data. If you are not the intended recipient,
please advise the sender by replying promptly to this email and then delete
and destroy this email and any attachments without any further use, copying
or forwarding.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#64Rahila Syed
rahilasyed90@gmail.com
In reply to: Masahiko Sawada (#61)
Re: [PROPOSAL] VACUUM Progress Checker.

In case of vacuum, I think we need to track the number of scanned heap
pages at least, and the information about index scan is the additional
information

Actually the progress of heap pages scan depend on index scans. So complete
VACUUM progress
needs to have a count of index pages scanned too. So, progress can be
calculated by measuring index_pages_scanned + heap_pages_scanned
against total_index_pages + total_heap_pages. This can make essential
information.

This can be followed by additional individual phase information.

Following fields common across different commands can be used to display
progress

Command work done total work percent complete message

VACUUM x y z
total progress

u v w
phase 1

The command code can be divided into distinct phases and each phase
progress can be represented separately. With a summary of entire command
progress as the first entry. The summary can be the summation of individual
phase entries.

If the phase repeats during command execution the previous entry for the
phase will be replaced.(for ex. index scan in vacuum)

Essential information has one numeric data, which is stored
essentially information regarding of its processing.

We may need more than one numeric data as mentioned above to represent
scanned blocks versus total blocks.

Additional information has two data: text and numeric. These data is
free-style data which is stored by each backend as it like.

If I understand your point correctly, I think you are missing following,
The amount of additional information for each command can be different. We
may need an array of text and numeric data to represent more additional
information.

Thank you,
Rahila Syed

#65Jim Nasby
Jim.Nasby@BlueTreble.com
In reply to: Rahila Syed (#64)
Re: [PROPOSAL] VACUUM Progress Checker.

On 8/17/15 5:07 PM, Rahila Syed wrote:

In case ofvacuum, I think we need to track the number of scanned heap
pages at least, and the information about index scan is the additional
information

Actually the progress of heap pages scan depend on index scans. So
complete VACUUM progress
needs to have a count of index pages scanned too. So, progress can be
calculated by measuring index_pages_scanned + heap_pages_scanned
against total_index_pages + total_heap_pages. This can make essential
information.

There's absolutely no way to get a reasonable status report in the case
of multiple index passes unless you somehow count the passes, especially
since index cleanup is frequently MUCH longer than the heap cleanup.

What should work is exporting the number of index passes we've already
made. If > 0 we know we're in a multiple scan situation. At the end of
each index pass, do index_passes++; index_pages=0; index_pages_scanned=0.

Personally, I think we should use SIGINFO to signal a backend to output
status data to a file in pg_stat_tmp/ (but not the main stats file) and
be done with it. That allows us to easily handle variable length stuff
with minimal fuss. No normal user is going to hammer away at that, and
anyone that's really worried about performance will have that directory
sitting on a ramdisk anyway.
--
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Data in Trouble? Get it in Treble! http://BlueTreble.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#66Robert Haas
robertmhaas@gmail.com
In reply to: Rahila Syed (#55)
Re: [PROPOSAL] VACUUM Progress Checker.

On Mon, Aug 10, 2015 at 12:36 AM, Rahila Syed <rahilasyed90@gmail.com> wrote:

Hello,

Say, 6 bigint counters, 6 float8
counters, and 3 strings up to 80 characters each. So we have a
fixed-size chunk of shared memory per backend, and each backend that
wants to expose progress information can fill in those fields however
it likes, and we expose the results.
This would be sorta like the way pg_statistic works: the same columns
can be used for different purposes depending on what estimator will be
used to access them.

After thinking more on this suggestion, I came up with following generic
structure which can be used to store progress of any command per backend in
shared memory.

Struct PgBackendProgress
{
int32 *counter[COMMAND_NUM_SLOTS];
float8 *counter_float[COMMAND_NUM_SLOTS];

char *progress_message[COMMAND_NUM_SLOTS];
}

This can't actually work, because we don't have a dynamic allocator
for shared memory. What you need to do is something like this:

struct PgBackendProgress
{
uint64 progress_integer[N_PROGRESS_INTEGER];
float8 progress_float[N_PROGRESS_FLOAT];
char progress_string[PROGRESS_STRING_LENGTH][N_PROGRESS_STRING];
};

You probably want to protect this with the st_changecount protocol, or
just put the fields in PgBackendStatus.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#67Rahila Syed
rahilasyed90@gmail.com
In reply to: Rahila Syed (#19)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

On Jul 16, 2015 1:48 AM, "Rahila Syed" <rahilasyed90@gmail.com> wrote:

Hello,

Please find attached updated patch >with an interface to calculate

command progress in pgstat.c. This interface currently
implements VACUUM progress tracking .

I have added this patch to CommitFest 2015-09. It is marked as
Waiting on author . I will post an updated patch as per review comments
soon.

Thank you,
Rahila Syed

#68Syed, Rahila
Rahila.Syed@nttdata.com
In reply to: Rahila Syed (#67)
1 attachment(s)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

Please find attached updated VACUUM progress checker patch.
Following have been accomplished in the patch

1. Accounts for index pages count while calculating total progress of VACUUM.
2. Common location for storing progress parameters for any command. Idea is every command which needs to report progress can populate and interpret the shared variables in its own way.
Each can display progress by implementing separate views.
3. Separate VACUUM progress view to display various progress parameters has been implemented . Progress of various phases like heap scan, index scan, total pages scanned along with
completion percentage is reported.
4.This view can display progress for all active backends running VACUUM.

Basic testing has been performed. Thorough testing is yet to be done. Marking it as Needs Review in Sept-Commitfest.

ToDo:
Display count of heap pages actually vacuumed(marking line pointers unused)
Display percentage of work_mem being used to store dead tuples.

Thank you,
Rahila Syed

______________________________________________________________________
Disclaimer: This email and any attachments are sent in strictest confidence
for the sole use of the addressee and may contain legally privileged,
confidential, and proprietary data. If you are not the intended recipient,
please advise the sender by replying promptly to this email and then delete
and destroy this email and any attachments without any further use, copying
or forwarding.

Attachments:

Vacuum_progress_checker_v2.patchapplication/octet-stream; name=Vacuum_progress_checker_v2.patchDownload
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ccc030f..beedf90 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -631,6 +631,18 @@ CREATE VIEW pg_stat_activity AS
     WHERE S.datid = D.oid AND
             S.usesysid = U.oid;
 
+CREATE VIEW pg_stat_vacuum_progress AS
+    SELECT
+			S.pid,
+            S.total_pages,
+            S.scanned_pages,
+			S.total_heap_pages,
+			S.scanned_heap_pages,
+            S.total_index_pages,
+            S.scanned_index_pages,
+			S.percent_complete
+    FROM pg_stat_get_vacuum_progress(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index a01cfb4..d4a31ef 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -439,7 +439,13 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			   Relation *Irel, int nindexes, bool scan_all)
 {
 	BlockNumber nblocks,
-				blkno;
+				blkno,
+				total_pages,
+				scanned_total_pages = 0,
+				total_heap_pages,
+				rel_index_pages = 0,
+				total_index_pages = 0,
+				scanned_index_pages = 0;
 	HeapTupleData tuple;
 	char	   *relname;
 	BlockNumber empty_pages,
@@ -471,7 +477,14 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	indstats = (IndexBulkDeleteResult **)
 		palloc0(nindexes * sizeof(IndexBulkDeleteResult *));
 
-	nblocks = RelationGetNumberOfBlocks(onerel);
+	total_heap_pages = nblocks = RelationGetNumberOfBlocks(onerel);
+
+	for (i = 0; i < nindexes; i++)
+		rel_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+
+	total_index_pages = rel_index_pages;
+	total_pages = total_heap_pages + rel_index_pages;
+
 	vacrelstats->rel_pages = nblocks;
 	vacrelstats->scanned_pages = 0;
 	vacrelstats->nonempty_pages = 0;
@@ -520,7 +533,11 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		vacuum_delay_point();
 	}
 	if (next_not_all_visible_block >= SKIP_PAGES_THRESHOLD)
+	{
 		skipping_all_visible_blocks = true;
+		total_heap_pages = total_heap_pages - next_not_all_visible_block;
+		total_pages = total_pages - next_not_all_visible_block;
+	}
 	else
 		skipping_all_visible_blocks = false;
 
@@ -559,7 +576,11 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			 * following blocks.
 			 */
 			if (next_not_all_visible_block - blkno > SKIP_PAGES_THRESHOLD)
+			{
 				skipping_all_visible_blocks = true;
+				total_heap_pages = total_heap_pages - (next_not_all_visible_block - blkno);
+				total_pages = total_pages - (next_not_all_visible_block - blkno);
+			}
 			else
 				skipping_all_visible_blocks = false;
 			all_visible_according_to_vm = false;
@@ -596,11 +617,30 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			/* Log cleanup info before we touch indexes */
 			vacuum_log_cleanup_info(onerel, vacrelstats);
 
+			/*
+			 * If passes through indexes exceed 1 add
+			 * pages equal to rel_index_pages to the count of
+			 * total pages to be scanned.
+			 */
+			if (vacrelstats->num_index_scans >= 1)
+			{
+				total_index_pages = total_index_pages + rel_index_pages;
+				total_pages = total_heap_pages + total_index_pages;
+			}
+
 			/* Remove index entries */
 			for (i = 0; i < nindexes; i++)
+			{
 				lazy_vacuum_index(Irel[i],
 								  &indstats[i],
 								  vacrelstats);
+				scanned_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+				scanned_total_pages = scanned_total_pages + RelationGetNumberOfBlocks(Irel[i]);
+				/* Report progress to the statistics collector */
+				pgstat_report_progress(total_pages, scanned_total_pages, total_heap_pages,
+										vacrelstats->scanned_pages, total_index_pages,
+										scanned_index_pages);
+			}
 			/* Remove tuples from heap */
 			lazy_vacuum_heap(onerel, vacrelstats);
 
@@ -658,6 +698,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 				UnlockReleaseBuffer(buf);
 				vacrelstats->scanned_pages++;
 				vacrelstats->pinskipped_pages++;
+				scanned_total_pages++;
 				continue;
 			}
 			LockBuffer(buf, BUFFER_LOCK_UNLOCK);
@@ -666,6 +707,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		}
 
 		vacrelstats->scanned_pages++;
+		scanned_total_pages++;
 
 		page = BufferGetPage(buf);
 
@@ -1062,6 +1104,13 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		 */
 		if (vacrelstats->num_dead_tuples == prev_dead_count)
 			RecordPageWithFreeSpace(onerel, blkno, freespace);
+
+		/*
+		 * Reporting vacuum progress to statistics collector
+		 */
+		pgstat_report_progress(total_pages, scanned_total_pages, total_heap_pages,
+								vacrelstats->scanned_pages, total_index_pages,
+								scanned_index_pages);
 	}
 
 	pfree(frozen);
@@ -1093,11 +1142,30 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		/* Log cleanup info before we touch indexes */
 		vacuum_log_cleanup_info(onerel, vacrelstats);
 
+		/*
+		 * If passes through indexes exceed 1 add
+		 * pages equal to rel_index_pages to the count of
+		 * total pages to be scanned.
+		 */
+		if (vacrelstats->num_index_scans >= 1)
+		{
+			total_index_pages = total_index_pages + rel_index_pages;
+			total_pages = total_heap_pages + total_index_pages;
+		}
+
 		/* Remove index entries */
 		for (i = 0; i < nindexes; i++)
+		{
 			lazy_vacuum_index(Irel[i],
 							  &indstats[i],
 							  vacrelstats);
+			scanned_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+			scanned_total_pages = scanned_total_pages + RelationGetNumberOfBlocks(Irel[i]);
+			/* Report progress to the statistics collector */
+			pgstat_report_progress(total_pages, scanned_total_pages, total_heap_pages,
+									vacrelstats->scanned_pages, total_index_pages,
+									scanned_index_pages);
+		}
 		/* Remove tuples from heap */
 		lazy_vacuum_heap(onerel, vacrelstats);
 		vacrelstats->num_index_scans++;
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index ab018c4..793fee5 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -2731,7 +2731,6 @@ pgstat_bestart(void)
 	beentry->st_clienthostname[NAMEDATALEN - 1] = '\0';
 	beentry->st_appname[NAMEDATALEN - 1] = '\0';
 	beentry->st_activity[pgstat_track_activity_query_size - 1] = '\0';
-
 	pgstat_increment_changecount_after(beentry);
 
 	/* Update app name to current GUC setting */
@@ -2851,6 +2850,37 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
 	pgstat_increment_changecount_after(beentry);
 }
 
+/* ---------------------------------------------
+ * Called from VACUUM  after every heap page scan or index scan
+ * to report progress
+ * ---------------------------------------------
+ */
+
+void
+pgstat_report_progress(BlockNumber total_pages, BlockNumber scanned_pages,
+						BlockNumber heap_total_pages, BlockNumber heap_scanned_pages,
+						BlockNumber index_total_pages, BlockNumber index_scanned_pages)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	if (!beentry)
+		return;
+
+	if (!pgstat_track_activities)
+		return;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->progress_param_float[0] = scanned_pages * 100 / total_pages;
+	beentry->progress_param[0] = total_pages;
+	beentry->progress_param[1] = scanned_pages;
+	beentry->progress_param[2] = heap_total_pages;
+	beentry->progress_param[3] = heap_scanned_pages;
+	beentry->progress_param[4] = index_total_pages;
+	beentry->progress_param[5] = index_scanned_pages;
+	pgstat_increment_changecount_after(beentry);
+
+}
+
 /* ----------
  * pgstat_report_appname() -
  *
@@ -3005,6 +3035,7 @@ pgstat_read_current_status(void)
 				strcpy(localactivity, (char *) beentry->st_activity);
 				localentry->backendStatus.st_activity = localactivity;
 				localentry->backendStatus.st_ssl = beentry->st_ssl;
+
 #ifdef USE_SSL
 				if (beentry->st_ssl)
 				{
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index f7c9bf6..197aa52 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -53,6 +53,7 @@ extern Datum pg_stat_get_function_self_time(PG_FUNCTION_ARGS);
 
 extern Datum pg_stat_get_backend_idset(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_activity(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_vacuum_progress(PG_FUNCTION_ARGS);
 extern Datum pg_backend_pid(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_pid(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_dbid(PG_FUNCTION_ARGS);
@@ -523,7 +524,110 @@ pg_stat_get_backend_idset(PG_FUNCTION_ARGS)
 		SRF_RETURN_DONE(funcctx);
 	}
 }
+/*
+ * Returns VACUUM progress values stored by each backend
+ * executing VACUUM.
+ */
+Datum
+pg_stat_get_vacuum_progress(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_PROGRESS_COLS	30
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+
+	/* 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);
+
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		Datum		values[PG_STAT_GET_PROGRESS_COLS];
+		bool		nulls[PG_STAT_GET_PROGRESS_COLS];
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+
+		MemSet(values, 0, sizeof(values));
+		MemSet(nulls, 0, sizeof(nulls));
+
+		local_beentry = pgstat_fetch_stat_local_beentry(curr_backend);
+		if (!local_beentry)
+			continue;
+
+		beentry = &local_beentry->backendStatus;
+		/* Report values for only those backends which are running VACUUM */
+		if(!beentry || (strncmp(beentry->st_activity,"VACUUM",6)
+						&& strncmp(beentry->st_activity,"vacuum",6)))
+			continue;
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+		if (beentry->progress_param[0] != 0)
+			values[1] = UInt32GetDatum(beentry->progress_param[0]);
+		else
+			nulls[1] = true;
+
+		if (beentry->progress_param[1] != 0)
+			values[2] = UInt32GetDatum(beentry->progress_param[1]);
+		else
+			nulls[2] = true;
 
+		if (beentry->progress_param[2] != 0)
+			values[3] = UInt32GetDatum(beentry->progress_param[2]);
+		else
+			nulls[3] = true;
+
+		if (beentry->progress_param[3] != 0)
+			values[4] = UInt32GetDatum(beentry->progress_param[3]);
+		else
+			nulls[4] = true;
+
+		if (beentry->progress_param[4] != 0)
+			values[5] = UInt32GetDatum(beentry->progress_param[4]);
+		else
+			nulls[5] = true;
+
+		if (beentry->progress_param[5] != 0)
+			values[6] = UInt32GetDatum(beentry->progress_param[5]);
+		else
+			nulls[6] = true;
+
+		if (beentry->progress_param_float[0] != 0)
+			values[7] = Float8GetDatum(beentry->progress_param_float[0]);
+		else
+			nulls[7] = true;
+
+		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+	}
+
+	/* clean up and return the tuplestore */
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
 /*
  * Returns activity of PG backends.
  */
@@ -584,7 +688,6 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 
 			if (!be || be->st_procpid != pid)
 				continue;
-
 		}
 
 		/* Get the next one in the list */
@@ -790,7 +893,6 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
-
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
 {
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 8ebf424..5ccce10 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -2308,7 +2308,6 @@ static struct config_int ConfigureNamesInt[] =
 		-1, -1, INT_MAX,
 		NULL, NULL, NULL
 	},
-
 	{
 		{"log_autovacuum_min_duration", PGC_SIGHUP, LOGGING_WHAT,
 			gettext_noop("Sets the minimum execution time above which "
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index ddf7c67..b0845d3 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2779,6 +2779,9 @@ DATA(insert OID = 1936 (  pg_stat_get_backend_idset		PGNSP PGUID 12 1 100 0 0 f
 DESCR("statistics: currently active backend IDs");
 DATA(insert OID = 2022 (  pg_stat_get_activity			PGNSP PGUID 12 1 100 0 0 f f f f f t s 1 0 2249 "23" "{23,26,23,26,25,25,25,16,1184,1184,1184,1184,869,25,23,28,28,16,25,25,23,16,25}" "{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{pid,datid,pid,usesysid,application_name,state,query,waiting,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,ssl,sslversion,sslcipher,sslbits,sslcompression,sslclientdn}" _null_ _null_ pg_stat_get_activity _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active backends");
+
+DATA(insert OID = 3308 (  pg_stat_get_vacuum_progress			PGNSP PGUID 12 1 1 0 0 f f f f f t s 1 0 2249 "23" "{23,23,23,23,23,23,23,23,701}" "{i,o,o,o,o,o,o,o,o}" "{pid,pid,total_pages,scanned_pages,total_heap_pages,scanned_heap_pages,total_index_pages,scanned_index_pages,percent_complete}" _null_ _null_ pg_stat_get_vacuum_progress _null_ _null_ _null_ ));
+
 DATA(insert OID = 3099 (  pg_stat_get_wal_senders	PGNSP PGUID 12 1 10 0 0 f f f f f t s 0 0 2249 "" "{23,25,3220,3220,3220,3220,23,25}" "{o,o,o,o,o,o,o,o}" "{pid,state,sent_location,write_location,flush_location,replay_location,sync_priority,sync_state}" _null_ _null_ pg_stat_get_wal_senders _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active replication");
 DATA(insert OID = 2026 (  pg_backend_pid				PGNSP PGUID 12 1 0 0 0 f f f f t f s 0 0 23 "" _null_ _null_ _null_ _null_ _null_ pg_backend_pid _null_ _null_ _null_ ));
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index e3a31af..e5f1ac9 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -155,7 +155,6 @@ extern int	vacuum_freeze_table_age;
 extern int	vacuum_multixact_freeze_min_age;
 extern int	vacuum_multixact_freeze_table_age;
 
-
 /* in commands/vacuum.c */
 extern void ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(int options, RangeVar *relation, Oid relid,
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 9ecc163..c4a665a 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -20,6 +20,7 @@
 #include "utils/hsearch.h"
 #include "utils/relcache.h"
 
+#include "storage/block.h"
 
 /* ----------
  * Paths for the statistics files (relative to installation's $PGDATA).
@@ -205,6 +206,8 @@ typedef struct PgStat_MsgHdr
 #define PGSTAT_MAX_MSG_SIZE 1000
 #define PGSTAT_MSG_PAYLOAD	(PGSTAT_MAX_MSG_SIZE - sizeof(PgStat_MsgHdr))
 
+#define N_PROGRESS_PARAM 10
+#define PROGRESS_MESSAGE_LENGTH 20
 
 /* ----------
  * PgStat_MsgDummy				A dummy message, ignored by the collector
@@ -776,6 +779,11 @@ typedef struct PgBackendStatus
 
 	/* current command string; MUST be null-terminated */
 	char	   *st_activity;
+
+	uint32 progress_param[N_PROGRESS_PARAM];
+	double	progress_param_float[N_PROGRESS_PARAM];
+	char progress_message[PROGRESS_MESSAGE_LENGTH][N_PROGRESS_PARAM];
+
 } PgBackendStatus;
 
 /*
@@ -928,6 +936,9 @@ extern void pgstat_initialize(void);
 extern void pgstat_bestart(void);
 
 extern void pgstat_report_activity(BackendState state, const char *cmd_str);
+extern void pgstat_report_progress(BlockNumber total_pages, BlockNumber scanned_pages,
+									BlockNumber total_heap_pages, BlockNumber scanned_heap_pages,
+									BlockNumber total_index_pages, BlockNumber scanned_index_pages);
 extern void pgstat_report_tempfile(size_t filesize);
 extern void pgstat_report_appname(const char *appname);
 extern void pgstat_report_xact_timestamp(TimestampTz tstamp);
#69Thom Brown
thom@linux.com
In reply to: Syed, Rahila (#68)
Re: [PROPOSAL] VACUUM Progress Checker.

On 11 September 2015 at 15:43, Syed, Rahila <Rahila.Syed@nttdata.com> wrote:

Hello,

Please find attached updated VACUUM progress checker patch.
Following have been accomplished in the patch

1. Accounts for index pages count while calculating total progress of
VACUUM.
2. Common location for storing progress parameters for any command. Idea
is every command which needs to report progress can populate and interpret
the shared variables in its own way.
Each can display progress by implementing separate views.
3. Separate VACUUM progress view to display various progress parameters
has been implemented . Progress of various phases like heap scan, index
scan, total pages scanned along with
completion percentage is reported.
4.This view can display progress for all active backends running VACUUM.

Basic testing has been performed. Thorough testing is yet to be done.
Marking it as Needs Review in Sept-Commitfest.

ToDo:
Display count of heap pages actually vacuumed(marking line pointers unused)
Display percentage of work_mem being used to store dead tuples.

Thank you,
Rahila Syed

This doesn't seem to compile:

make[4]: Leaving directory `/home/thom/Development/postgresql/src/backend'
make[3]: Leaving directory `/home/thom/Development/postgresql/src/common'
make -C catalog schemapg.h
make[3]: Entering directory
`/home/thom/Development/postgresql/src/backend/catalog'
cd ../../../src/include/catalog && '/usr/bin/perl' ./duplicate_oids
3308
make[3]: *** [postgres.bki] Error 1
make[3]: Leaving directory
`/home/thom/Development/postgresql/src/backend/catalog'
make[2]: *** [submake-schemapg] Error 2
make[2]: Leaving directory `/home/thom/Development/postgresql/src/backend'
make[1]: *** [install-backend-recurse] Error 2
make[1]: Leaving directory `/home/thom/Development/postgresql/src'
make: *** [install-src-recurse] Error 2
make: Leaving directory `/home/thom/Development/postgresql'

Thom

#70Rahila Syed
rahilasyed90@gmail.com
In reply to: Thom Brown (#69)
1 attachment(s)
Re: [PROPOSAL] VACUUM Progress Checker.

This doesn't seem to compile

Oh. It compiled successfully when applied on HEAD on my machine. Anyways,
the OID is changed to 3309 in the attached patch. 3308 / 3309 both are part
of OIDs in unused OID list.

Thank you,
Rahila Syed

Attachments:

Vacuum_progress_checker_v2.patchapplication/octet-stream; name=Vacuum_progress_checker_v2.patchDownload
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ccc030f..beedf90 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -631,6 +631,18 @@ CREATE VIEW pg_stat_activity AS
     WHERE S.datid = D.oid AND
             S.usesysid = U.oid;
 
+CREATE VIEW pg_stat_vacuum_progress AS
+    SELECT
+			S.pid,
+            S.total_pages,
+            S.scanned_pages,
+			S.total_heap_pages,
+			S.scanned_heap_pages,
+            S.total_index_pages,
+            S.scanned_index_pages,
+			S.percent_complete
+    FROM pg_stat_get_vacuum_progress(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index a01cfb4..d4a31ef 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -439,7 +439,13 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			   Relation *Irel, int nindexes, bool scan_all)
 {
 	BlockNumber nblocks,
-				blkno;
+				blkno,
+				total_pages,
+				scanned_total_pages = 0,
+				total_heap_pages,
+				rel_index_pages = 0,
+				total_index_pages = 0,
+				scanned_index_pages = 0;
 	HeapTupleData tuple;
 	char	   *relname;
 	BlockNumber empty_pages,
@@ -471,7 +477,14 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	indstats = (IndexBulkDeleteResult **)
 		palloc0(nindexes * sizeof(IndexBulkDeleteResult *));
 
-	nblocks = RelationGetNumberOfBlocks(onerel);
+	total_heap_pages = nblocks = RelationGetNumberOfBlocks(onerel);
+
+	for (i = 0; i < nindexes; i++)
+		rel_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+
+	total_index_pages = rel_index_pages;
+	total_pages = total_heap_pages + rel_index_pages;
+
 	vacrelstats->rel_pages = nblocks;
 	vacrelstats->scanned_pages = 0;
 	vacrelstats->nonempty_pages = 0;
@@ -520,7 +533,11 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		vacuum_delay_point();
 	}
 	if (next_not_all_visible_block >= SKIP_PAGES_THRESHOLD)
+	{
 		skipping_all_visible_blocks = true;
+		total_heap_pages = total_heap_pages - next_not_all_visible_block;
+		total_pages = total_pages - next_not_all_visible_block;
+	}
 	else
 		skipping_all_visible_blocks = false;
 
@@ -559,7 +576,11 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			 * following blocks.
 			 */
 			if (next_not_all_visible_block - blkno > SKIP_PAGES_THRESHOLD)
+			{
 				skipping_all_visible_blocks = true;
+				total_heap_pages = total_heap_pages - (next_not_all_visible_block - blkno);
+				total_pages = total_pages - (next_not_all_visible_block - blkno);
+			}
 			else
 				skipping_all_visible_blocks = false;
 			all_visible_according_to_vm = false;
@@ -596,11 +617,30 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			/* Log cleanup info before we touch indexes */
 			vacuum_log_cleanup_info(onerel, vacrelstats);
 
+			/*
+			 * If passes through indexes exceed 1 add
+			 * pages equal to rel_index_pages to the count of
+			 * total pages to be scanned.
+			 */
+			if (vacrelstats->num_index_scans >= 1)
+			{
+				total_index_pages = total_index_pages + rel_index_pages;
+				total_pages = total_heap_pages + total_index_pages;
+			}
+
 			/* Remove index entries */
 			for (i = 0; i < nindexes; i++)
+			{
 				lazy_vacuum_index(Irel[i],
 								  &indstats[i],
 								  vacrelstats);
+				scanned_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+				scanned_total_pages = scanned_total_pages + RelationGetNumberOfBlocks(Irel[i]);
+				/* Report progress to the statistics collector */
+				pgstat_report_progress(total_pages, scanned_total_pages, total_heap_pages,
+										vacrelstats->scanned_pages, total_index_pages,
+										scanned_index_pages);
+			}
 			/* Remove tuples from heap */
 			lazy_vacuum_heap(onerel, vacrelstats);
 
@@ -658,6 +698,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 				UnlockReleaseBuffer(buf);
 				vacrelstats->scanned_pages++;
 				vacrelstats->pinskipped_pages++;
+				scanned_total_pages++;
 				continue;
 			}
 			LockBuffer(buf, BUFFER_LOCK_UNLOCK);
@@ -666,6 +707,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		}
 
 		vacrelstats->scanned_pages++;
+		scanned_total_pages++;
 
 		page = BufferGetPage(buf);
 
@@ -1062,6 +1104,13 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		 */
 		if (vacrelstats->num_dead_tuples == prev_dead_count)
 			RecordPageWithFreeSpace(onerel, blkno, freespace);
+
+		/*
+		 * Reporting vacuum progress to statistics collector
+		 */
+		pgstat_report_progress(total_pages, scanned_total_pages, total_heap_pages,
+								vacrelstats->scanned_pages, total_index_pages,
+								scanned_index_pages);
 	}
 
 	pfree(frozen);
@@ -1093,11 +1142,30 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		/* Log cleanup info before we touch indexes */
 		vacuum_log_cleanup_info(onerel, vacrelstats);
 
+		/*
+		 * If passes through indexes exceed 1 add
+		 * pages equal to rel_index_pages to the count of
+		 * total pages to be scanned.
+		 */
+		if (vacrelstats->num_index_scans >= 1)
+		{
+			total_index_pages = total_index_pages + rel_index_pages;
+			total_pages = total_heap_pages + total_index_pages;
+		}
+
 		/* Remove index entries */
 		for (i = 0; i < nindexes; i++)
+		{
 			lazy_vacuum_index(Irel[i],
 							  &indstats[i],
 							  vacrelstats);
+			scanned_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+			scanned_total_pages = scanned_total_pages + RelationGetNumberOfBlocks(Irel[i]);
+			/* Report progress to the statistics collector */
+			pgstat_report_progress(total_pages, scanned_total_pages, total_heap_pages,
+									vacrelstats->scanned_pages, total_index_pages,
+									scanned_index_pages);
+		}
 		/* Remove tuples from heap */
 		lazy_vacuum_heap(onerel, vacrelstats);
 		vacrelstats->num_index_scans++;
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index ab018c4..793fee5 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -2731,7 +2731,6 @@ pgstat_bestart(void)
 	beentry->st_clienthostname[NAMEDATALEN - 1] = '\0';
 	beentry->st_appname[NAMEDATALEN - 1] = '\0';
 	beentry->st_activity[pgstat_track_activity_query_size - 1] = '\0';
-
 	pgstat_increment_changecount_after(beentry);
 
 	/* Update app name to current GUC setting */
@@ -2851,6 +2850,37 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
 	pgstat_increment_changecount_after(beentry);
 }
 
+/* ---------------------------------------------
+ * Called from VACUUM  after every heap page scan or index scan
+ * to report progress
+ * ---------------------------------------------
+ */
+
+void
+pgstat_report_progress(BlockNumber total_pages, BlockNumber scanned_pages,
+						BlockNumber heap_total_pages, BlockNumber heap_scanned_pages,
+						BlockNumber index_total_pages, BlockNumber index_scanned_pages)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	if (!beentry)
+		return;
+
+	if (!pgstat_track_activities)
+		return;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->progress_param_float[0] = scanned_pages * 100 / total_pages;
+	beentry->progress_param[0] = total_pages;
+	beentry->progress_param[1] = scanned_pages;
+	beentry->progress_param[2] = heap_total_pages;
+	beentry->progress_param[3] = heap_scanned_pages;
+	beentry->progress_param[4] = index_total_pages;
+	beentry->progress_param[5] = index_scanned_pages;
+	pgstat_increment_changecount_after(beentry);
+
+}
+
 /* ----------
  * pgstat_report_appname() -
  *
@@ -3005,6 +3035,7 @@ pgstat_read_current_status(void)
 				strcpy(localactivity, (char *) beentry->st_activity);
 				localentry->backendStatus.st_activity = localactivity;
 				localentry->backendStatus.st_ssl = beentry->st_ssl;
+
 #ifdef USE_SSL
 				if (beentry->st_ssl)
 				{
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index f7c9bf6..197aa52 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -53,6 +53,7 @@ extern Datum pg_stat_get_function_self_time(PG_FUNCTION_ARGS);
 
 extern Datum pg_stat_get_backend_idset(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_activity(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_vacuum_progress(PG_FUNCTION_ARGS);
 extern Datum pg_backend_pid(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_pid(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_dbid(PG_FUNCTION_ARGS);
@@ -523,7 +524,110 @@ pg_stat_get_backend_idset(PG_FUNCTION_ARGS)
 		SRF_RETURN_DONE(funcctx);
 	}
 }
+/*
+ * Returns VACUUM progress values stored by each backend
+ * executing VACUUM.
+ */
+Datum
+pg_stat_get_vacuum_progress(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_PROGRESS_COLS	30
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+
+	/* 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);
+
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		Datum		values[PG_STAT_GET_PROGRESS_COLS];
+		bool		nulls[PG_STAT_GET_PROGRESS_COLS];
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+
+		MemSet(values, 0, sizeof(values));
+		MemSet(nulls, 0, sizeof(nulls));
+
+		local_beentry = pgstat_fetch_stat_local_beentry(curr_backend);
+		if (!local_beentry)
+			continue;
+
+		beentry = &local_beentry->backendStatus;
+		/* Report values for only those backends which are running VACUUM */
+		if(!beentry || (strncmp(beentry->st_activity,"VACUUM",6)
+						&& strncmp(beentry->st_activity,"vacuum",6)))
+			continue;
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+		if (beentry->progress_param[0] != 0)
+			values[1] = UInt32GetDatum(beentry->progress_param[0]);
+		else
+			nulls[1] = true;
+
+		if (beentry->progress_param[1] != 0)
+			values[2] = UInt32GetDatum(beentry->progress_param[1]);
+		else
+			nulls[2] = true;
 
+		if (beentry->progress_param[2] != 0)
+			values[3] = UInt32GetDatum(beentry->progress_param[2]);
+		else
+			nulls[3] = true;
+
+		if (beentry->progress_param[3] != 0)
+			values[4] = UInt32GetDatum(beentry->progress_param[3]);
+		else
+			nulls[4] = true;
+
+		if (beentry->progress_param[4] != 0)
+			values[5] = UInt32GetDatum(beentry->progress_param[4]);
+		else
+			nulls[5] = true;
+
+		if (beentry->progress_param[5] != 0)
+			values[6] = UInt32GetDatum(beentry->progress_param[5]);
+		else
+			nulls[6] = true;
+
+		if (beentry->progress_param_float[0] != 0)
+			values[7] = Float8GetDatum(beentry->progress_param_float[0]);
+		else
+			nulls[7] = true;
+
+		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+	}
+
+	/* clean up and return the tuplestore */
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
 /*
  * Returns activity of PG backends.
  */
@@ -584,7 +688,6 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 
 			if (!be || be->st_procpid != pid)
 				continue;
-
 		}
 
 		/* Get the next one in the list */
@@ -790,7 +893,6 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
-
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
 {
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 8ebf424..5ccce10 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -2308,7 +2308,6 @@ static struct config_int ConfigureNamesInt[] =
 		-1, -1, INT_MAX,
 		NULL, NULL, NULL
 	},
-
 	{
 		{"log_autovacuum_min_duration", PGC_SIGHUP, LOGGING_WHAT,
 			gettext_noop("Sets the minimum execution time above which "
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index ddf7c67..b4d9f9b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2779,6 +2779,9 @@ DATA(insert OID = 1936 (  pg_stat_get_backend_idset		PGNSP PGUID 12 1 100 0 0 f
 DESCR("statistics: currently active backend IDs");
 DATA(insert OID = 2022 (  pg_stat_get_activity			PGNSP PGUID 12 1 100 0 0 f f f f f t s 1 0 2249 "23" "{23,26,23,26,25,25,25,16,1184,1184,1184,1184,869,25,23,28,28,16,25,25,23,16,25}" "{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{pid,datid,pid,usesysid,application_name,state,query,waiting,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,ssl,sslversion,sslcipher,sslbits,sslcompression,sslclientdn}" _null_ _null_ pg_stat_get_activity _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active backends");
+
+DATA(insert OID = 3309 (  pg_stat_get_vacuum_progress			PGNSP PGUID 12 1 1 0 0 f f f f f t s 1 0 2249 "23" "{23,23,23,23,23,23,23,23,701}" "{i,o,o,o,o,o,o,o,o}" "{pid,pid,total_pages,scanned_pages,total_heap_pages,scanned_heap_pages,total_index_pages,scanned_index_pages,percent_complete}" _null_ _null_ pg_stat_get_vacuum_progress _null_ _null_ _null_ ));
+
 DATA(insert OID = 3099 (  pg_stat_get_wal_senders	PGNSP PGUID 12 1 10 0 0 f f f f f t s 0 0 2249 "" "{23,25,3220,3220,3220,3220,23,25}" "{o,o,o,o,o,o,o,o}" "{pid,state,sent_location,write_location,flush_location,replay_location,sync_priority,sync_state}" _null_ _null_ pg_stat_get_wal_senders _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active replication");
 DATA(insert OID = 2026 (  pg_backend_pid				PGNSP PGUID 12 1 0 0 0 f f f f t f s 0 0 23 "" _null_ _null_ _null_ _null_ _null_ pg_backend_pid _null_ _null_ _null_ ));
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index e3a31af..e5f1ac9 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -155,7 +155,6 @@ extern int	vacuum_freeze_table_age;
 extern int	vacuum_multixact_freeze_min_age;
 extern int	vacuum_multixact_freeze_table_age;
 
-
 /* in commands/vacuum.c */
 extern void ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(int options, RangeVar *relation, Oid relid,
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 9ecc163..c4a665a 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -20,6 +20,7 @@
 #include "utils/hsearch.h"
 #include "utils/relcache.h"
 
+#include "storage/block.h"
 
 /* ----------
  * Paths for the statistics files (relative to installation's $PGDATA).
@@ -205,6 +206,8 @@ typedef struct PgStat_MsgHdr
 #define PGSTAT_MAX_MSG_SIZE 1000
 #define PGSTAT_MSG_PAYLOAD	(PGSTAT_MAX_MSG_SIZE - sizeof(PgStat_MsgHdr))
 
+#define N_PROGRESS_PARAM 10
+#define PROGRESS_MESSAGE_LENGTH 20
 
 /* ----------
  * PgStat_MsgDummy				A dummy message, ignored by the collector
@@ -776,6 +779,11 @@ typedef struct PgBackendStatus
 
 	/* current command string; MUST be null-terminated */
 	char	   *st_activity;
+
+	uint32 progress_param[N_PROGRESS_PARAM];
+	double	progress_param_float[N_PROGRESS_PARAM];
+	char progress_message[PROGRESS_MESSAGE_LENGTH][N_PROGRESS_PARAM];
+
 } PgBackendStatus;
 
 /*
@@ -928,6 +936,9 @@ extern void pgstat_initialize(void);
 extern void pgstat_bestart(void);
 
 extern void pgstat_report_activity(BackendState state, const char *cmd_str);
+extern void pgstat_report_progress(BlockNumber total_pages, BlockNumber scanned_pages,
+									BlockNumber total_heap_pages, BlockNumber scanned_heap_pages,
+									BlockNumber total_index_pages, BlockNumber scanned_index_pages);
 extern void pgstat_report_tempfile(size_t filesize);
 extern void pgstat_report_appname(const char *appname);
 extern void pgstat_report_xact_timestamp(TimestampTz tstamp);
#71Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Rahila Syed (#70)
Re: [PROPOSAL] VACUUM Progress Checker.

Rahila Syed wrote:

This doesn't seem to compile

Oh. It compiled successfully when applied on HEAD on my machine. Anyways,
the OID is changed to 3309 in the attached patch. 3308 / 3309 both are part
of OIDs in unused OID list.

I think Thom may have patched on top of some other patch.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#72Thom Brown
thom@linux.com
In reply to: Alvaro Herrera (#71)
Re: [PROPOSAL] VACUUM Progress Checker.

On 11 September 2015 at 22:34, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:

Rahila Syed wrote:

This doesn't seem to compile

Oh. It compiled successfully when applied on HEAD on my machine. Anyways,
the OID is changed to 3309 in the attached patch. 3308 / 3309 both are

part

of OIDs in unused OID list.

I think Thom may have patched on top of some other patch.

I think you might be right. I had run "git stash" and thought that would
be sufficient, but it seems "git clean -f" was necessary.

It builds fine now.

Thom

#73Thom Brown
thom@linux.com
In reply to: Syed, Rahila (#68)
Re: [PROPOSAL] VACUUM Progress Checker.

On 11 September 2015 at 15:43, Syed, Rahila <Rahila.Syed@nttdata.com> wrote:

Hello,

Please find attached updated VACUUM progress checker patch.
Following have been accomplished in the patch

1. Accounts for index pages count while calculating total progress of
VACUUM.
2. Common location for storing progress parameters for any command. Idea
is every command which needs to report progress can populate and interpret
the shared variables in its own way.
Each can display progress by implementing separate views.
3. Separate VACUUM progress view to display various progress parameters
has been implemented . Progress of various phases like heap scan, index
scan, total pages scanned along with
completion percentage is reported.
4.This view can display progress for all active backends running VACUUM.

Basic testing has been performed. Thorough testing is yet to be done.
Marking it as Needs Review in Sept-Commitfest.

ToDo:
Display count of heap pages actually vacuumed(marking line pointers unused)
Display percentage of work_mem being used to store dead tuples.

Okay, I've just tested this with a newly-loaded table (1,252,973 of jsonb
data), and it works fine during a vacuum. I can see the scanned_pages,
scanned_heap_pages and percent_complete increasing, but after it's
finished, I end up with this:

json=# select * from pg_stat_vacuum_progress;
-[ RECORD 1 ]-------+-------
pid | 5569
total_pages | 217941
scanned_pages | 175243
total_heap_pages | 175243
scanned_heap_pages | 175243
total_index_pages | 42698
scanned_index_pages |
percent_complete | 80

This was running with a VACUUM ANALYZE. This output seems to suggest that
it didn't complete.

After, I ran VACUUM FULL. pg_stat_vacuum_progress didn't change from
before, so that doesn't appear to show up in the view.

I then deleted 40,000 rows from my table, and ran VACUUM ANALYZE again.
This time it progressed and percent_complete reached 100.

--
Thom

#74Syed, Rahila
Rahila.Syed@nttdata.com
In reply to: Thom Brown (#73)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello Thom,

Okay, I've just tested this with a newly-loaded table (1,252,973 of jsonb data),

Thanks a lot!

but after it's finished, I end up with this:
json=# select * from pg_stat_vacuum_progress;
-[ RECORD 1 ]-------+-------
pid                 | 5569
total_pages         | 217941
scanned_pages       | 175243
total_heap_pages    | 175243
scanned_heap_pages  | 175243
total_index_pages   | 42698
scanned_index_pages |
percent_complete    | 80
This was running with a VACUUM ANALYZE.  This output seems to suggest that it didn't complete.

Ok. The patch fails here because 'total pages to be scanned' takes into account index pages and no index pages are actually scanned.
So the scanned pages count does not reach total pages count . I will fix this.
It seems that no index pages were scanned during this because there were no dead tuples to be cleaned as the table was newly loaded.

After, I ran VACUUM FULL.  pg_stat_vacuum_progress didn't change from before, so that doesn't appear to show up in the view.

The scope of this patch is to report progress of basic VACUUM . It does not take into account VACUUM FULL yet. I think this can be included after basic VACUUM progress is done.

I then deleted 40,000 rows from my table, and ran VACUUM ANALYZE again. This time it progressed and percent_complete reached 100

OK.

Thank you,
Rahila Syed.

______________________________________________________________________
Disclaimer: This email and any attachments are sent in strictest confidence
for the sole use of the addressee and may contain legally privileged,
confidential, and proprietary data. If you are not the intended recipient,
please advise the sender by replying promptly to this email and then delete
and destroy this email and any attachments without any further use, copying
or forwarding.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#75Masahiko Sawada
sawada.mshk@gmail.com
In reply to: Syed, Rahila (#74)
Re: [PROPOSAL] VACUUM Progress Checker.

On Tue, Sep 15, 2015 at 11:35 PM, Syed, Rahila <Rahila.Syed@nttdata.com> wrote:

Hello Thom,

Okay, I've just tested this with a newly-loaded table (1,252,973 of jsonb data),

Thanks a lot!

but after it's finished, I end up with this:
json=# select * from pg_stat_vacuum_progress;
-[ RECORD 1 ]-------+-------
pid | 5569
total_pages | 217941
scanned_pages | 175243
total_heap_pages | 175243
scanned_heap_pages | 175243
total_index_pages | 42698
scanned_index_pages |
percent_complete | 80
This was running with a VACUUM ANALYZE. This output seems to suggest that it didn't complete.

Ok. The patch fails here because 'total pages to be scanned' takes into account index pages and no index pages are actually scanned.
So the scanned pages count does not reach total pages count . I will fix this.
It seems that no index pages were scanned during this because there were no dead tuples to be cleaned as the table was newly loaded.

After, I ran VACUUM FULL. pg_stat_vacuum_progress didn't change from before, so that doesn't appear to show up in the view.

The scope of this patch is to report progress of basic VACUUM . It does not take into account VACUUM FULL yet. I think this can be included after basic VACUUM progress is done.

I then deleted 40,000 rows from my table, and ran VACUUM ANALYZE again. This time it progressed and percent_complete reached 100

OK.

I tested this patch with some cases.
And the followings seems to be bug.

* After running "pgbench -i -s 100" and "VACUUM FREEZE
pgbench_accounts", the pg_stat_vacuum_progress is,

-[ RECORD 1 ]-------+-------
pid | 2298
total_pages | 27422
scanned_pages | 163935
total_heap_pages |
scanned_heap_pages | 163935
total_index_pages | 27422
scanned_index_pages |
percent_complete | 597

The value of percent_complete column exceeds 100%.
And, why are the total_heap_pages and scanned_index_pages columns NULL?

* Also, after dropping primary key of pgbench_accounts, I got
assertion error when I execute "VACUUM FREEZE pgbench_accounts".

=# VACUUM FREEZE pgbench_accounts;
ERROR: floating-point exception
DETAIL: An invalid floating-point operation was signaled. This
probably means an out-of-range result or an invalid operation, such as
division by zero.
STATEMENT: vacuum freeze pgbench_accounts ;
TRAP: FailedAssertion("!((beentry->st_changecount & 1) == 0)", File:
"pgstat.c", Line: 2934)

* The progress of vacuum by autovacuum seems not to be displayed.

Regards,

--
Masahiko Sawada

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#76Syed, Rahila
Rahila.Syed@nttdata.com
In reply to: Masahiko Sawada (#75)
1 attachment(s)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

Please find attached patch with bugs reported by Thom and Sawada-san solved.

* The progress of vacuum by autovacuum seems not to be displayed.

The progress is stored in shared variables during autovacuum. I guess the reason they are not visible is that the entries are deleted as soon as the process exits.
But the progress can be viewed while autovacuum worker is running.

Thank you,
Rahila Syed

______________________________________________________________________
Disclaimer: This email and any attachments are sent in strictest confidence
for the sole use of the addressee and may contain legally privileged,
confidential, and proprietary data. If you are not the intended recipient,
please advise the sender by replying promptly to this email and then delete
and destroy this email and any attachments without any further use, copying
or forwarding.

Attachments:

Vacuum_progress_checker_v3.patchapplication/octet-stream; name=Vacuum_progress_checker_v3.patchDownload
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ccc030f..beedf90 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -631,6 +631,18 @@ CREATE VIEW pg_stat_activity AS
     WHERE S.datid = D.oid AND
             S.usesysid = U.oid;
 
+CREATE VIEW pg_stat_vacuum_progress AS
+    SELECT
+			S.pid,
+            S.total_pages,
+            S.scanned_pages,
+			S.total_heap_pages,
+			S.scanned_heap_pages,
+            S.total_index_pages,
+            S.scanned_index_pages,
+			S.percent_complete
+    FROM pg_stat_get_vacuum_progress(NULL) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index a01cfb4..24832eb 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -439,7 +439,13 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			   Relation *Irel, int nindexes, bool scan_all)
 {
 	BlockNumber nblocks,
-				blkno;
+				blkno,
+				total_pages,
+				scanned_total_pages = 0,
+				total_heap_pages,
+				rel_index_pages = 0,
+				total_index_pages = 0,
+				scanned_index_pages = 0;
 	HeapTupleData tuple;
 	char	   *relname;
 	BlockNumber empty_pages,
@@ -471,7 +477,14 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	indstats = (IndexBulkDeleteResult **)
 		palloc0(nindexes * sizeof(IndexBulkDeleteResult *));
 
-	nblocks = RelationGetNumberOfBlocks(onerel);
+	total_heap_pages = nblocks = RelationGetNumberOfBlocks(onerel);
+
+	for (i = 0; i < nindexes; i++)
+		rel_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+
+	total_index_pages = rel_index_pages;
+	total_pages = total_heap_pages + rel_index_pages;
+
 	vacrelstats->rel_pages = nblocks;
 	vacrelstats->scanned_pages = 0;
 	vacrelstats->nonempty_pages = 0;
@@ -520,7 +533,14 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		vacuum_delay_point();
 	}
 	if (next_not_all_visible_block >= SKIP_PAGES_THRESHOLD)
+	{
 		skipping_all_visible_blocks = true;
+		if(!scan_all)
+		{
+			total_heap_pages = total_heap_pages - next_not_all_visible_block;
+			total_pages = total_pages - next_not_all_visible_block;
+		}
+	}
 	else
 		skipping_all_visible_blocks = false;
 
@@ -559,7 +579,14 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			 * following blocks.
 			 */
 			if (next_not_all_visible_block - blkno > SKIP_PAGES_THRESHOLD)
+			{
 				skipping_all_visible_blocks = true;
+				if(!scan_all)
+				{
+					total_heap_pages = total_heap_pages - (next_not_all_visible_block - blkno);
+					total_pages = total_pages - (next_not_all_visible_block - blkno);
+				}
+			}
 			else
 				skipping_all_visible_blocks = false;
 			all_visible_according_to_vm = false;
@@ -596,11 +623,30 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			/* Log cleanup info before we touch indexes */
 			vacuum_log_cleanup_info(onerel, vacrelstats);
 
+			/*
+			 * If passes through indexes exceed 1 add
+			 * pages equal to rel_index_pages to the count of
+			 * total pages to be scanned.
+			 */
+			if (vacrelstats->num_index_scans >= 1)
+			{
+				total_index_pages = total_index_pages + rel_index_pages;
+				total_pages = total_heap_pages + total_index_pages;
+			}
+
 			/* Remove index entries */
 			for (i = 0; i < nindexes; i++)
+			{
 				lazy_vacuum_index(Irel[i],
 								  &indstats[i],
 								  vacrelstats);
+				scanned_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+				scanned_total_pages = scanned_total_pages + RelationGetNumberOfBlocks(Irel[i]);
+				/* Report progress to the statistics collector */
+				pgstat_report_progress(total_pages, scanned_total_pages, total_heap_pages,
+										vacrelstats->scanned_pages, total_index_pages,
+										scanned_index_pages);
+			}
 			/* Remove tuples from heap */
 			lazy_vacuum_heap(onerel, vacrelstats);
 
@@ -658,6 +704,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 				UnlockReleaseBuffer(buf);
 				vacrelstats->scanned_pages++;
 				vacrelstats->pinskipped_pages++;
+				scanned_total_pages++;
 				continue;
 			}
 			LockBuffer(buf, BUFFER_LOCK_UNLOCK);
@@ -666,6 +713,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		}
 
 		vacrelstats->scanned_pages++;
+		scanned_total_pages++;
 
 		page = BufferGetPage(buf);
 
@@ -1062,6 +1110,17 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		 */
 		if (vacrelstats->num_dead_tuples == prev_dead_count)
 			RecordPageWithFreeSpace(onerel, blkno, freespace);
+
+		/*
+		 * Reporting vacuum progress to statistics collector
+		 */
+		if (blkno == nblocks - 1 && vacrelstats->num_dead_tuples == 0 && nindexes != 0
+			&& vacrelstats->num_index_scans == 0)
+			total_pages = total_pages - total_index_pages;
+
+		pgstat_report_progress(total_pages, scanned_total_pages, total_heap_pages,
+								vacrelstats->scanned_pages, total_index_pages,
+								scanned_index_pages);
 	}
 
 	pfree(frozen);
@@ -1093,11 +1152,30 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		/* Log cleanup info before we touch indexes */
 		vacuum_log_cleanup_info(onerel, vacrelstats);
 
+		/*
+		 * If passes through indexes exceed 1 add
+		 * pages equal to rel_index_pages to the count of
+		 * total pages to be scanned.
+		 */
+		if (vacrelstats->num_index_scans >= 1)
+		{
+			total_index_pages = total_index_pages + rel_index_pages;
+			total_pages = total_heap_pages + total_index_pages;
+		}
+
 		/* Remove index entries */
 		for (i = 0; i < nindexes; i++)
+		{
 			lazy_vacuum_index(Irel[i],
 							  &indstats[i],
 							  vacrelstats);
+			scanned_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+			scanned_total_pages = scanned_total_pages + RelationGetNumberOfBlocks(Irel[i]);
+			/* Report progress to the statistics collector */
+			pgstat_report_progress(total_pages, scanned_total_pages, total_heap_pages,
+									vacrelstats->scanned_pages, total_index_pages,
+									scanned_index_pages);
+		}
 		/* Remove tuples from heap */
 		lazy_vacuum_heap(onerel, vacrelstats);
 		vacrelstats->num_index_scans++;
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index ab018c4..a531fb8 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -2731,7 +2731,6 @@ pgstat_bestart(void)
 	beentry->st_clienthostname[NAMEDATALEN - 1] = '\0';
 	beentry->st_appname[NAMEDATALEN - 1] = '\0';
 	beentry->st_activity[pgstat_track_activity_query_size - 1] = '\0';
-
 	pgstat_increment_changecount_after(beentry);
 
 	/* Update app name to current GUC setting */
@@ -2851,6 +2850,36 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
 	pgstat_increment_changecount_after(beentry);
 }
 
+/* ---------------------------------------------
+ * Called from VACUUM  after every heap page scan or index scan
+ * to report progress
+ * ---------------------------------------------
+ */
+
+void
+pgstat_report_progress(BlockNumber total_pages, BlockNumber scanned_pages,
+						BlockNumber heap_total_pages, BlockNumber heap_scanned_pages,
+						BlockNumber index_total_pages, BlockNumber index_scanned_pages)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	if (!beentry)
+		return;
+
+	if (!pgstat_track_activities)
+		return;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->progress_param_float[0] = scanned_pages * 100 / total_pages;
+	beentry->progress_param[0] = total_pages;
+	beentry->progress_param[1] = scanned_pages;
+	beentry->progress_param[2] = heap_total_pages;
+	beentry->progress_param[3] = heap_scanned_pages;
+	beentry->progress_param[4] = index_total_pages;
+	beentry->progress_param[5] = index_scanned_pages;
+	pgstat_increment_changecount_after(beentry);
+}
+
 /* ----------
  * pgstat_report_appname() -
  *
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index f7c9bf6..197aa52 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -53,6 +53,7 @@ extern Datum pg_stat_get_function_self_time(PG_FUNCTION_ARGS);
 
 extern Datum pg_stat_get_backend_idset(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_activity(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_vacuum_progress(PG_FUNCTION_ARGS);
 extern Datum pg_backend_pid(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_pid(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_dbid(PG_FUNCTION_ARGS);
@@ -523,7 +524,110 @@ pg_stat_get_backend_idset(PG_FUNCTION_ARGS)
 		SRF_RETURN_DONE(funcctx);
 	}
 }
+/*
+ * Returns VACUUM progress values stored by each backend
+ * executing VACUUM.
+ */
+Datum
+pg_stat_get_vacuum_progress(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_PROGRESS_COLS	30
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+
+	/* 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);
+
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		Datum		values[PG_STAT_GET_PROGRESS_COLS];
+		bool		nulls[PG_STAT_GET_PROGRESS_COLS];
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+
+		MemSet(values, 0, sizeof(values));
+		MemSet(nulls, 0, sizeof(nulls));
+
+		local_beentry = pgstat_fetch_stat_local_beentry(curr_backend);
+		if (!local_beentry)
+			continue;
+
+		beentry = &local_beentry->backendStatus;
+		/* Report values for only those backends which are running VACUUM */
+		if(!beentry || (strncmp(beentry->st_activity,"VACUUM",6)
+						&& strncmp(beentry->st_activity,"vacuum",6)))
+			continue;
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+		if (beentry->progress_param[0] != 0)
+			values[1] = UInt32GetDatum(beentry->progress_param[0]);
+		else
+			nulls[1] = true;
+
+		if (beentry->progress_param[1] != 0)
+			values[2] = UInt32GetDatum(beentry->progress_param[1]);
+		else
+			nulls[2] = true;
 
+		if (beentry->progress_param[2] != 0)
+			values[3] = UInt32GetDatum(beentry->progress_param[2]);
+		else
+			nulls[3] = true;
+
+		if (beentry->progress_param[3] != 0)
+			values[4] = UInt32GetDatum(beentry->progress_param[3]);
+		else
+			nulls[4] = true;
+
+		if (beentry->progress_param[4] != 0)
+			values[5] = UInt32GetDatum(beentry->progress_param[4]);
+		else
+			nulls[5] = true;
+
+		if (beentry->progress_param[5] != 0)
+			values[6] = UInt32GetDatum(beentry->progress_param[5]);
+		else
+			nulls[6] = true;
+
+		if (beentry->progress_param_float[0] != 0)
+			values[7] = Float8GetDatum(beentry->progress_param_float[0]);
+		else
+			nulls[7] = true;
+
+		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+	}
+
+	/* clean up and return the tuplestore */
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
 /*
  * Returns activity of PG backends.
  */
@@ -584,7 +688,6 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 
 			if (!be || be->st_procpid != pid)
 				continue;
-
 		}
 
 		/* Get the next one in the list */
@@ -790,7 +893,6 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
-
 Datum
 pg_backend_pid(PG_FUNCTION_ARGS)
 {
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index fcba3c5..f6d5897 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -2299,7 +2299,6 @@ static struct config_int ConfigureNamesInt[] =
 		-1, -1, INT_MAX,
 		NULL, NULL, NULL
 	},
-
 	{
 		{"log_autovacuum_min_duration", PGC_SIGHUP, LOGGING_WHAT,
 			gettext_noop("Sets the minimum execution time above which "
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index eb55b3a..e5bac75 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2781,6 +2781,7 @@ DATA(insert OID = 1936 (  pg_stat_get_backend_idset		PGNSP PGUID 12 1 100 0 0 f
 DESCR("statistics: currently active backend IDs");
 DATA(insert OID = 2022 (  pg_stat_get_activity			PGNSP PGUID 12 1 100 0 0 f f f f f t s r 1 0 2249 "23" "{23,26,23,26,25,25,25,16,1184,1184,1184,1184,869,25,23,28,28,16,25,25,23,16,25}" "{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{pid,datid,pid,usesysid,application_name,state,query,waiting,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,ssl,sslversion,sslcipher,sslbits,sslcompression,sslclientdn}" _null_ _null_ pg_stat_get_activity _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active backends");
+DATA(insert OID = 3308 (  pg_stat_get_vacuum_progress			PGNSP PGUID 12 1 1 0 0 f f f f f t s r 1 0 2249 "23" "{23,23,23,23,23,23,23,23,701}" "{i,o,o,o,o,o,o,o,o}" "{pid,pid,total_pages,scanned_pages,total_heap_pages,scanned_heap_pages,total_index_pages,scanned_index_pages,percent_complete}" _null_ _null_ pg_stat_get_vacuum_progress _null_ _null_ _null_ ));
 DATA(insert OID = 3099 (  pg_stat_get_wal_senders	PGNSP PGUID 12 1 10 0 0 f f f f f t s r 0 0 2249 "" "{23,25,3220,3220,3220,3220,23,25}" "{o,o,o,o,o,o,o,o}" "{pid,state,sent_location,write_location,flush_location,replay_location,sync_priority,sync_state}" _null_ _null_ pg_stat_get_wal_senders _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active replication");
 DATA(insert OID = 2026 (  pg_backend_pid				PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 23 "" _null_ _null_ _null_ _null_ _null_ pg_backend_pid _null_ _null_ _null_ ));
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index e3a31af..e5f1ac9 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -155,7 +155,6 @@ extern int	vacuum_freeze_table_age;
 extern int	vacuum_multixact_freeze_min_age;
 extern int	vacuum_multixact_freeze_table_age;
 
-
 /* in commands/vacuum.c */
 extern void ExecVacuum(VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(int options, RangeVar *relation, Oid relid,
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 9ecc163..c4a665a 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -20,6 +20,7 @@
 #include "utils/hsearch.h"
 #include "utils/relcache.h"
 
+#include "storage/block.h"
 
 /* ----------
  * Paths for the statistics files (relative to installation's $PGDATA).
@@ -205,6 +206,8 @@ typedef struct PgStat_MsgHdr
 #define PGSTAT_MAX_MSG_SIZE 1000
 #define PGSTAT_MSG_PAYLOAD	(PGSTAT_MAX_MSG_SIZE - sizeof(PgStat_MsgHdr))
 
+#define N_PROGRESS_PARAM 10
+#define PROGRESS_MESSAGE_LENGTH 20
 
 /* ----------
  * PgStat_MsgDummy				A dummy message, ignored by the collector
@@ -776,6 +779,11 @@ typedef struct PgBackendStatus
 
 	/* current command string; MUST be null-terminated */
 	char	   *st_activity;
+
+	uint32 progress_param[N_PROGRESS_PARAM];
+	double	progress_param_float[N_PROGRESS_PARAM];
+	char progress_message[PROGRESS_MESSAGE_LENGTH][N_PROGRESS_PARAM];
+
 } PgBackendStatus;
 
 /*
@@ -928,6 +936,9 @@ extern void pgstat_initialize(void);
 extern void pgstat_bestart(void);
 
 extern void pgstat_report_activity(BackendState state, const char *cmd_str);
+extern void pgstat_report_progress(BlockNumber total_pages, BlockNumber scanned_pages,
+									BlockNumber total_heap_pages, BlockNumber scanned_heap_pages,
+									BlockNumber total_index_pages, BlockNumber scanned_index_pages);
 extern void pgstat_report_tempfile(size_t filesize);
 extern void pgstat_report_appname(const char *appname);
 extern void pgstat_report_xact_timestamp(TimestampTz tstamp);
#77Masahiko Sawada
sawada.mshk@gmail.com
In reply to: Syed, Rahila (#76)
Re: [PROPOSAL] VACUUM Progress Checker.

On Wed, Sep 23, 2015 at 12:24 AM, Syed, Rahila <Rahila.Syed@nttdata.com> wrote:

Hello,

Please find attached patch with bugs reported by Thom and Sawada-san solved.

* The progress of vacuum by autovacuum seems not to be displayed.

The progress is stored in shared variables during autovacuum. I guess the reason they are not visible is that the entries are deleted as soon as the process exits.
But the progress can be viewed while autovacuum worker is running.

Thank you for updating the patch.

I tested the latest version patch.
The followings are my review comments and questions.

* pg_stat_vacuum_progress should have the oid of relation being vacuumed.

When we run "VACUUM;", the all tables of current database will be vacuumed.
So pg_stat_vacuum_progress should have these oid in order to show
which table is vacuumed now.

* progress_message variable in PgBackendStatus is not used at all.
IIRC, progress_message variable is set the description of processing.

* The progress of VACUUM FULL seems wrong.
When I run VACUUM FULL for a table, I got following progress.

postgres(1)=# select * from pg_stat_vacuum_progress ;
-[ RECORD 1 ]-------+------
pid | 19190
total_pages | 1
scanned_pages | 1
total_heap_pages | 1
scanned_heap_pages | 1
total_index_pages |
scanned_index_pages |
percent_complete | 100

The table being vacuumed is 400MB, so it's not 1 page table.

* The vacuum by autovacuum is not displayed.
I tested about this by the executing the following queries in a row,
but the vacuum by autovacuum is not displayed,

postgres(1)=# select datname, pid, backend_start, query, state from
pg_stat_activity ;
datname | pid | backend_start |
query | state
----------+-------+-------------------------------+--------------------------------------------------------------------------+--------
postgres | 20123 | 2015-09-24 17:44:26.467021+09 | autovacuum: VACUUM
ANALYZE public.hoge | active
postgres | 19779 | 2015-09-24 17:42:31.57918+09 | select datname,
pid, backend_start, query, state from pg_stat_activity ; | active
(3 rows)

postgres(1)=# selecT * from pg_stat_vacuum_progress ;
pid | total_pages | scanned_pages | total_heap_pages |
scanned_heap_pages | total_index_pages | scanned_index_pages |
percent_complete
-----+-------------+---------------+------------------+--------------------+-------------------+---------------------+------------------
(0 rows)

postgres(1)=# select datname, pid, backend_start, query, state from
pg_stat_activity ;
datname | pid | backend_start |
query | state
----------+-------+-------------------------------+--------------------------------------------------------------------------+--------
postgres | 20123 | 2015-09-24 17:44:26.467021+09 | autovacuum: VACUUM
ANALYZE public.hoge | active
postgres | 19779 | 2015-09-24 17:42:31.57918+09 | select datname,
pid, backend_start, query, state from pg_stat_activity ; | active
(3 rows)

The vacuuming for hoge table took about 2min, but the progress of
vacuum is never displayed.
Could you check this on your environment?

Regards,

--
Masahiko Sawada

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#78Fujii Masao
masao.fujii@gmail.com
In reply to: Syed, Rahila (#76)
Re: [PROPOSAL] VACUUM Progress Checker.

On Wed, Sep 23, 2015 at 12:24 AM, Syed, Rahila <Rahila.Syed@nttdata.com> wrote:

Hello,

Please find attached patch with bugs reported by Thom and Sawada-san solved.

The regression test failed on my machine, so you need to update the
regression test,
I think.

Regards,

--
Fujii Masao

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#79Jim Nasby
Jim.Nasby@BlueTreble.com
In reply to: Masahiko Sawada (#77)
Re: [PROPOSAL] VACUUM Progress Checker.

On 9/24/15 7:37 AM, Masahiko Sawada wrote:

* The progress of VACUUM FULL seems wrong.
When I run VACUUM FULL for a table, I got following progress.

It never occurred to me that this patch was attempting to measure the
progress of a CLUSTER (aka VACUUM FULL). I'm not sure that's such a
great idea, as the progress estimation presumably needs to be
significantly different.

More to the point, you can't estimate a CLUSTER unless you can estimate
the progress of an index build. That'd be a cool feature to have as
well, but it seems like a bad idea to mix that in with this patch.

Keep in mind that running a VACUUM FULL is presumably a LOT less common
than regular vacuums, so I don't think leaving it out for now is that
big a deal.

* The vacuum by autovacuum is not displayed.
I tested about this by the executing the following queries in a row,
but the vacuum by autovacuum is not displayed,

IIRC this is the second problem related to autovacuum... is there some
way to regression test that? Maybe disable autovac on a table, dirty it,
then re-enable (all with an absurdly low autovacuum naptime)?
--
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Experts in Analytics, Data Architecture and PostgreSQL
Data in Trouble? Get it in Treble! http://BlueTreble.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#80Robert Haas
robertmhaas@gmail.com
In reply to: Masahiko Sawada (#77)
Re: [PROPOSAL] VACUUM Progress Checker.

On Thu, Sep 24, 2015 at 8:37 AM, Masahiko Sawada <sawada.mshk@gmail.com> wrote:

When we run "VACUUM;", the all tables of current database will be vacuumed.
So pg_stat_vacuum_progress should have these oid in order to show
which table is vacuumed now.

Hmm, I would tend to instead show the schema & table name, like "foo"."bar".

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#81Masahiko Sawada
sawada.mshk@gmail.com
In reply to: Robert Haas (#80)
Re: [PROPOSAL] VACUUM Progress Checker.

On Mon, Sep 28, 2015 at 11:03 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Thu, Sep 24, 2015 at 8:37 AM, Masahiko Sawada <sawada.mshk@gmail.com> wrote:

When we run "VACUUM;", the all tables of current database will be vacuumed.
So pg_stat_vacuum_progress should have these oid in order to show
which table is vacuumed now.

Hmm, I would tend to instead show the schema & table name, like "foo"."bar".

Yes, it looks better.

Regards,

--
Masahiko Sawada

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#82Fujii Masao
masao.fujii@gmail.com
In reply to: Fujii Masao (#78)
Re: [PROPOSAL] VACUUM Progress Checker.

On Fri, Sep 25, 2015 at 2:03 AM, Fujii Masao <masao.fujii@gmail.com> wrote:

On Wed, Sep 23, 2015 at 12:24 AM, Syed, Rahila <Rahila.Syed@nttdata.com> wrote:

Hello,

Please find attached patch with bugs reported by Thom and Sawada-san solved.

The regression test failed on my machine, so you need to update the
regression test,
I think.

Here are another review comments.

You removed some empty lines, for example, in vacuum.h.
Which seems useless to me.

+ uint32 progress_param[N_PROGRESS_PARAM];

Why did you use an array to store the progress information of VACUUM?
I think that it's better to use separate specific variables for them for
better code readability, for example, variables scanned_pages,
heap_total_pages, etc.

+ double progress_param_float[N_PROGRESS_PARAM];

Currently only progress_param_float[0] is used. So there is no need to
use an array here.

progress_param_float[0] saves the percetage of VACUUM progress.
But why do we need to save that information into shared memory?
We can just calculate the percentage whenever pg_stat_get_vacuum_progress()
is executed, instead. There seems to be no need to save that information.

+ char progress_message[PROGRESS_MESSAGE_LENGTH][N_PROGRESS_PARAM];

As Sawada pointed out, there is no user of this variable.

+#define PG_STAT_GET_PROGRESS_COLS 30

Why did you use 30?

+ FROM pg_stat_get_vacuum_progress(NULL) AS S;

You defined pg_stat_get_vacuum_progress() so that it accepts one argument.
But as far as I read the function, it doesn't use any argument at all.
I think that pg_stat_get_vacuum_progress() should be defined as a function
having no argument.

+        /* Report values for only those backends which are running VACUUM */
+        if(!beentry || (strncmp(beentry->st_activity,"VACUUM",6)
+                        && strncmp(beentry->st_activity,"vacuum",6)))
+            continue;

This design looks bad to me. There is no guarantee that st_activity of
the backend running VACUUM displays "VACUUM" or "vacuum".
For example, st_activity of autovacuum worker displays "autovacuum ...".
So as Sawada reported, he could not find any entry for autovacuum in
pg_stat_vacuum_progress.

I think that you should add the flag or something which indicates
whether this backend is running VACUUM or not, into PgBackendStatus.
pg_stat_vacuum_progress should display the entries of only backends
with that flag set true. This design means that you need to set the flag
to true when starting VACUUM and reset at the end of VACUUM progressing.

Non-superuser cannot see some columns of the superuser's entry in
pg_stat_activity, for permission reason. We should treat
pg_stat_vacuum_progress in the same way? That is, non-superuser
should not be allowed to see the pg_stat_vacuum_progress entry
of superuser running VACUUM?

+                if(!scan_all)
+                {
+                    total_heap_pages = total_heap_pages -
(next_not_all_visible_block - blkno);
+                    total_pages = total_pages -
(next_not_all_visible_block - blkno);
+                }

This code may cause total_pages and total_heap_pages to decrease
while VACUUM is running. This sounds strange and confusing. I think
that total values basically should be fixed. And heap_scanned_pages
should increase, instead.

Regards,

--
Fujii Masao

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#83Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Fujii Masao (#82)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2015/10/02 15:38, Fujii Masao wrote:

+ uint32 progress_param[N_PROGRESS_PARAM];

Why did you use an array to store the progress information of VACUUM?
I think that it's better to use separate specific variables for them for
better code readability, for example, variables scanned_pages,
heap_total_pages, etc.

+ double progress_param_float[N_PROGRESS_PARAM];

Currently only progress_param_float[0] is used. So there is no need to
use an array here.

I think this kind of design may have come from the ideas expressed here
(especially the last paragraph):

/messages/by-id/CA+TgmoYnWtNJRmVWAJ+wGLOB_x8vNOTrZnEDio=GaPi5HK73oQ@mail.gmail.com

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#84Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#83)
Re: [PROPOSAL] VACUUM Progress Checker.

On Fri, Oct 2, 2015 at 3:14 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2015/10/02 15:38, Fujii Masao wrote:

+ uint32 progress_param[N_PROGRESS_PARAM];

Why did you use an array to store the progress information of VACUUM?
I think that it's better to use separate specific variables for them for
better code readability, for example, variables scanned_pages,
heap_total_pages, etc.

+ double progress_param_float[N_PROGRESS_PARAM];

Currently only progress_param_float[0] is used. So there is no need to
use an array here.

I think this kind of design may have come from the ideas expressed here
(especially the last paragraph):

/messages/by-id/CA+TgmoYnWtNJRmVWAJ+wGLOB_x8vNOTrZnEDio=GaPi5HK73oQ@mail.gmail.com

Right. This design is obviously silly if we only care about exposing
VACUUM progress. But if we want to be able to expose progress from
many utility commands, and slightly different kinds of information for
each one, then I think it could be quite useful.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#85Andres Freund
andres@anarazel.de
In reply to: Syed, Rahila (#76)
Re: [PROPOSAL] VACUUM Progress Checker.

Hi!

On 2015-09-22 15:24:38 +0000, Syed, Rahila wrote:

Please find attached patch with bugs reported by Thom and Sawada-san solved.

This thread has seen a bunch of reviews and new patch versions, but
doesnt yet seem to have arrived in a committable state. As the
commitfest ended and this patch has gotten attention, I'm moving the
entry to the next fest.

Greetings,

Andres Freund

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#86Syed, Rahila
Rahila.Syed@nttdata.com
In reply to: Fujii Masao (#82)
1 attachment(s)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello Fujii-san,

Here are another review comments

Thank you for review. Please find attached an updated patch.

You removed some empty lines, for example, in vacuum.h.
Which seems useless to me.

Has been corrected in the attached.

Why did you use an array to store the progress information of VACUUM?
I think that it's better to use separate specific variables for them for better code readability, for example, variables scanned_pages, heap_total_pages, etc.

It is designed this way to keep it generic for all the commands which can store different progress parameters in shared memory.

Currently only progress_param_float[0] is used. So there is no need to use an array here.

Same as before . This is for later use by other commands.

progress_param_float[0] saves the percetage of VACUUM progress.
But why do we need to save that information into shared memory?
We can just calculate the percentage whenever pg_stat_get_vacuum_progress() is executed, instead. There seems to be no need to save that information.

This has been corrected in the attached.

char progress_message[PROGRESS_MESSAGE_LENGTH][N_PROGRESS_PARAM];
As Sawada pointed out, there is no user of this variable.

Have used it to store table name in the updated patch. It can also be used to display index names, current phase of VACUUM.
This has not been included in the patch yet to avoid cluttering the display with too much information.

For example, st_activity of autovacuum worker displays "autovacuum ...".
So as Sawada reported, he could not find any entry for autovacuum in pg_stat_vacuum_progress.

In the attached patch , I have performed check for autovacuum also.

I think that you should add the flag or something which indicates whether this backend is running VACUUM or not, into PgBackendStatus.
pg_stat_vacuum_progress should display the entries of only backends with that flag set true. This design means that you need to set the flag to true when starting VACUUM and reset at the end of VACUUM progressing.

This design seems better in the sense that we don’t rely on st_activity entry to display progress values.
A variable which stores flags for running commands can be created in PgBackendStatus. These flags can be used at the time of display of progress of particular command.

That is, non-superuser should not be allowed to see the pg_stat_vacuum_progress entry of superuser running VACUUM?

This has been included in the updated patch.

This code may cause total_pages and total_heap_pages to decrease while VACUUM is running.

Yes. This is because the initial count of total pages to be vacuumed and the pages which are actually vacuumed can vary depending on visibility of tuples.
The pages which are all visible are skipped and hence have been subtracted from total page count.

Thank you,
Rahila Syed

______________________________________________________________________
Disclaimer: This email and any attachments are sent in strictest confidence
for the sole use of the addressee and may contain legally privileged,
confidential, and proprietary data. If you are not the intended recipient,
please advise the sender by replying promptly to this email and then delete
and destroy this email and any attachments without any further use, copying
or forwarding.

Attachments:

Vacuum_progress_checker_v4.patchapplication/octet-stream; name=Vacuum_progress_checker_v4.patchDownload
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ccc030f..9b8024c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -631,6 +631,19 @@ CREATE VIEW pg_stat_activity AS
     WHERE S.datid = D.oid AND
             S.usesysid = U.oid;
 
+CREATE VIEW pg_stat_vacuum_progress AS
+    SELECT
+			S.pid,
+            S.total_pages,
+            S.scanned_pages,
+			S.total_heap_pages,
+			S.scanned_heap_pages,
+            S.total_index_pages,
+            S.scanned_index_pages,
+			S.percent_complete,
+			S.table_name
+    FROM pg_stat_get_vacuum_progress() AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index a01cfb4..b69af45 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -439,9 +439,16 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			   Relation *Irel, int nindexes, bool scan_all)
 {
 	BlockNumber nblocks,
-				blkno;
+				blkno,
+				total_pages,
+				scanned_total_pages = 0,
+				total_heap_pages,
+				rel_index_pages = 0,
+				total_index_pages = 0,
+				scanned_index_pages = 0;
 	HeapTupleData tuple;
 	char	   *relname;
+	char		*schemaname;
 	BlockNumber empty_pages,
 				vacuumed_pages;
 	double		num_tuples,
@@ -460,6 +467,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	pg_rusage_init(&ru0);
 
 	relname = RelationGetRelationName(onerel);
+	schemaname = get_namespace_name(RelationGetNamespace(onerel));
 	ereport(elevel,
 			(errmsg("vacuuming \"%s.%s\"",
 					get_namespace_name(RelationGetNamespace(onerel)),
@@ -471,7 +479,14 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	indstats = (IndexBulkDeleteResult **)
 		palloc0(nindexes * sizeof(IndexBulkDeleteResult *));
 
-	nblocks = RelationGetNumberOfBlocks(onerel);
+	total_heap_pages = nblocks = RelationGetNumberOfBlocks(onerel);
+
+	for (i = 0; i < nindexes; i++)
+		rel_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+
+	total_index_pages = rel_index_pages;
+	total_pages = total_heap_pages + rel_index_pages;
+
 	vacrelstats->rel_pages = nblocks;
 	vacrelstats->scanned_pages = 0;
 	vacrelstats->nonempty_pages = 0;
@@ -520,7 +535,14 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		vacuum_delay_point();
 	}
 	if (next_not_all_visible_block >= SKIP_PAGES_THRESHOLD)
+	{
 		skipping_all_visible_blocks = true;
+		if(!scan_all)
+		{
+			total_heap_pages = total_heap_pages - next_not_all_visible_block;
+			total_pages = total_pages - next_not_all_visible_block;
+		}
+	}
 	else
 		skipping_all_visible_blocks = false;
 
@@ -559,7 +581,14 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			 * following blocks.
 			 */
 			if (next_not_all_visible_block - blkno > SKIP_PAGES_THRESHOLD)
+			{
 				skipping_all_visible_blocks = true;
+				if(!scan_all)
+				{
+					total_heap_pages = total_heap_pages - (next_not_all_visible_block - blkno);
+					total_pages = total_pages - (next_not_all_visible_block - blkno);
+				}
+			}
 			else
 				skipping_all_visible_blocks = false;
 			all_visible_according_to_vm = false;
@@ -596,11 +625,30 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			/* Log cleanup info before we touch indexes */
 			vacuum_log_cleanup_info(onerel, vacrelstats);
 
+			/*
+			 * If passes through indexes exceed 1 add
+			 * pages equal to rel_index_pages to the count of
+			 * total pages to be scanned.
+			 */
+			if (vacrelstats->num_index_scans >= 1)
+			{
+				total_index_pages = total_index_pages + rel_index_pages;
+				total_pages = total_heap_pages + total_index_pages;
+			}
+
 			/* Remove index entries */
 			for (i = 0; i < nindexes; i++)
+			{
 				lazy_vacuum_index(Irel[i],
 								  &indstats[i],
 								  vacrelstats);
+				scanned_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+				scanned_total_pages = scanned_total_pages + RelationGetNumberOfBlocks(Irel[i]);
+				/* Report progress to the statistics collector */
+				pgstat_report_progress(relname, schemaname, total_pages, scanned_total_pages, total_heap_pages,
+										vacrelstats->scanned_pages, total_index_pages,
+										scanned_index_pages);
+			}
 			/* Remove tuples from heap */
 			lazy_vacuum_heap(onerel, vacrelstats);
 
@@ -658,6 +706,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 				UnlockReleaseBuffer(buf);
 				vacrelstats->scanned_pages++;
 				vacrelstats->pinskipped_pages++;
+				scanned_total_pages++;
 				continue;
 			}
 			LockBuffer(buf, BUFFER_LOCK_UNLOCK);
@@ -666,6 +715,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		}
 
 		vacrelstats->scanned_pages++;
+		scanned_total_pages++;
 
 		page = BufferGetPage(buf);
 
@@ -1062,6 +1112,17 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		 */
 		if (vacrelstats->num_dead_tuples == prev_dead_count)
 			RecordPageWithFreeSpace(onerel, blkno, freespace);
+
+		/*
+		 * Reporting vacuum progress to statistics collector
+		 */
+		if (blkno == nblocks - 1 && vacrelstats->num_dead_tuples == 0 && nindexes != 0
+			&& vacrelstats->num_index_scans == 0)
+			total_pages = total_pages - total_index_pages;
+
+		pgstat_report_progress(relname, schemaname, total_pages, scanned_total_pages, total_heap_pages,
+								vacrelstats->scanned_pages, total_index_pages,
+								scanned_index_pages);
 	}
 
 	pfree(frozen);
@@ -1093,11 +1154,30 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		/* Log cleanup info before we touch indexes */
 		vacuum_log_cleanup_info(onerel, vacrelstats);
 
+		/*
+		 * If passes through indexes exceed 1 add
+		 * pages equal to rel_index_pages to the count of
+		 * total pages to be scanned.
+		 */
+		if (vacrelstats->num_index_scans >= 1)
+		{
+			total_index_pages = total_index_pages + rel_index_pages;
+			total_pages = total_heap_pages + total_index_pages;
+		}
+
 		/* Remove index entries */
 		for (i = 0; i < nindexes; i++)
+		{
 			lazy_vacuum_index(Irel[i],
 							  &indstats[i],
 							  vacrelstats);
+			scanned_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+			scanned_total_pages = scanned_total_pages + RelationGetNumberOfBlocks(Irel[i]);
+			/* Report progress to the statistics collector */
+			pgstat_report_progress(relname, schemaname, total_pages, scanned_total_pages, total_heap_pages,
+									vacrelstats->scanned_pages, total_index_pages,
+									scanned_index_pages);
+		}
 		/* Remove tuples from heap */
 		lazy_vacuum_heap(onerel, vacrelstats);
 		vacrelstats->num_index_scans++;
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index ab018c4..f1c87cb 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -2851,6 +2851,45 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
 	pgstat_increment_changecount_after(beentry);
 }
 
+/* ---------------------------------------------
+ * Called from VACUUM  after every heap page scan or index scan
+ * to report progress
+ * ---------------------------------------------
+ */
+
+void
+pgstat_report_progress(char *relname, char *schemaname, BlockNumber total_pages, BlockNumber scanned_pages,
+						BlockNumber heap_total_pages, BlockNumber heap_scanned_pages,
+						BlockNumber index_total_pages, BlockNumber index_scanned_pages)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+	int relname_len, schemaname_len;
+
+	if (!beentry)
+		return;
+
+	if (!pgstat_track_activities)
+		return;
+
+	relname_len = strlen(relname);
+	schemaname_len = strlen(schemaname);
+	pgstat_increment_changecount_before(beentry);
+
+	beentry->progress_param[0] = total_pages;
+	beentry->progress_param[1] = scanned_pages;
+	beentry->progress_param[2] = heap_total_pages;
+	beentry->progress_param[3] = heap_scanned_pages;
+	beentry->progress_param[4] = index_total_pages;
+	beentry->progress_param[5] = index_scanned_pages;
+
+	memcpy((char *) beentry->progress_message[0], schemaname, schemaname_len);
+    beentry->progress_message[0][schemaname_len] = '\0';
+	strcat(beentry->progress_message[0],".");
+	strcat(beentry->progress_message[0],relname);
+
+	pgstat_increment_changecount_after(beentry);
+}
+
 /* ----------
  * pgstat_report_appname() -
  *
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index f7c9bf6..2ee256c 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -53,6 +53,7 @@ extern Datum pg_stat_get_function_self_time(PG_FUNCTION_ARGS);
 
 extern Datum pg_stat_get_backend_idset(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_activity(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_vacuum_progress(PG_FUNCTION_ARGS);
 extern Datum pg_backend_pid(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_pid(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_dbid(PG_FUNCTION_ARGS);
@@ -523,7 +524,132 @@ pg_stat_get_backend_idset(PG_FUNCTION_ARGS)
 		SRF_RETURN_DONE(funcctx);
 	}
 }
+/*
+ * Returns VACUUM progress values stored by each backend
+ * executing VACUUM.
+ */
+Datum
+pg_stat_get_vacuum_progress(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_PROGRESS_COLS	30
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+
+	/* 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);
+
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		Datum		values[PG_STAT_GET_PROGRESS_COLS];
+		bool		nulls[PG_STAT_GET_PROGRESS_COLS];
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+
+		MemSet(values, 0, sizeof(values));
+		MemSet(nulls, 0, sizeof(nulls));
+
+		local_beentry = pgstat_fetch_stat_local_beentry(curr_backend);
+		if (!local_beentry)
+			continue;
+		beentry = &local_beentry->backendStatus;
+
+		/* Report values for only those backends which are running VACUUM */
+		if(!beentry || (strncmp(beentry->st_activity,"VACUUM",6)
+						&& strncmp(beentry->st_activity,"vacuum",6)
+						&& strncmp(beentry->st_activity,"autovacuum",10)))
+			continue;
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		/* Progress can only be viewed by role member */
+		if (has_privs_of_role(GetUserId(), beentry->st_userid))
+		{
+			if (beentry->progress_param[0] != 0)
+				values[1] = UInt32GetDatum(beentry->progress_param[0]);
+			else
+				nulls[1] = true;
+
+			if (beentry->progress_param[1] != 0)
+				values[2] = UInt32GetDatum(beentry->progress_param[1]);
+			else
+				nulls[2] = true;
+
+			if (beentry->progress_param[2] != 0)
+				values[3] = UInt32GetDatum(beentry->progress_param[2]);
+			else
+				nulls[3] = true;
+
+			if (beentry->progress_param[3] != 0)
+				values[4] = UInt32GetDatum(beentry->progress_param[3]);
+			else
+				nulls[4] = true;
+
+			if (beentry->progress_param[4] != 0)
+				values[5] = UInt32GetDatum(beentry->progress_param[4]);
+			else
+				nulls[5] = true;
+
+			if (beentry->progress_param[5] != 0)
+				values[6] = UInt32GetDatum(beentry->progress_param[5]);
+			else
+				nulls[6] = true;
+
+			if (beentry->progress_param[0] != 0)
+				values[7] = Float8GetDatum(beentry->progress_param[1] * 100 / beentry->progress_param[0]);
+			else
+				nulls[7] = true;
+
+			if(beentry->progress_message[0])
+				values[8] = CStringGetTextDatum(beentry->progress_message[0]);
+			else
+				nulls[8] = true;
+		}
+		else
+		{
+			values[1] = CStringGetTextDatum("<insufficient privilege>");
+			nulls[2] = true;
+			nulls[3] = true;
+			nulls[4] = true;
+			nulls[5] = true;
+			nulls[6] = true;
+			nulls[7] = true;
+			nulls[8] = true;
+		}
+
+		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+	}
 
+	/* clean up and return the tuplestore */
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
 /*
  * Returns activity of PG backends.
  */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index eb55b3a..a19f325 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2781,6 +2781,8 @@ DATA(insert OID = 1936 (  pg_stat_get_backend_idset		PGNSP PGUID 12 1 100 0 0 f
 DESCR("statistics: currently active backend IDs");
 DATA(insert OID = 2022 (  pg_stat_get_activity			PGNSP PGUID 12 1 100 0 0 f f f f f t s r 1 0 2249 "23" "{23,26,23,26,25,25,25,16,1184,1184,1184,1184,869,25,23,28,28,16,25,25,23,16,25}" "{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{pid,datid,pid,usesysid,application_name,state,query,waiting,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,ssl,sslversion,sslcipher,sslbits,sslcompression,sslclientdn}" _null_ _null_ pg_stat_get_activity _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active backends");
+DATA(insert OID = 3308 (  pg_stat_get_vacuum_progress			PGNSP PGUID 12 1 1 0 0 f f f f f t s r 0 0 2249 "" "{23,23,23,23,23,23,23,701,25}" "{o,o,o,o,o,o,o,o,o}" "{pid,total_pages,scanned_pages,total_heap_pages,scanned_heap_pages,total_index_pages,scanned_index_pages,percent_complete,table_name}" _null_ _null_ pg_stat_get_vacuum_progress _null_ _null_ _null_ ));
+DESCR("statistics: information about progress of backends running VACUUM");
 DATA(insert OID = 3099 (  pg_stat_get_wal_senders	PGNSP PGUID 12 1 10 0 0 f f f f f t s r 0 0 2249 "" "{23,25,3220,3220,3220,3220,23,25}" "{o,o,o,o,o,o,o,o}" "{pid,state,sent_location,write_location,flush_location,replay_location,sync_priority,sync_state}" _null_ _null_ pg_stat_get_wal_senders _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active replication");
 DATA(insert OID = 2026 (  pg_backend_pid				PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 23 "" _null_ _null_ _null_ _null_ _null_ pg_backend_pid _null_ _null_ _null_ ));
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 9ecc163..d195714 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -20,6 +20,7 @@
 #include "utils/hsearch.h"
 #include "utils/relcache.h"
 
+#include "storage/block.h"
 
 /* ----------
  * Paths for the statistics files (relative to installation's $PGDATA).
@@ -205,6 +206,8 @@ typedef struct PgStat_MsgHdr
 #define PGSTAT_MAX_MSG_SIZE 1000
 #define PGSTAT_MSG_PAYLOAD	(PGSTAT_MAX_MSG_SIZE - sizeof(PgStat_MsgHdr))
 
+#define N_PROGRESS_PARAM 10
+#define PROGRESS_MESSAGE_LENGTH 30
 
 /* ----------
  * PgStat_MsgDummy				A dummy message, ignored by the collector
@@ -776,6 +779,11 @@ typedef struct PgBackendStatus
 
 	/* current command string; MUST be null-terminated */
 	char	   *st_activity;
+
+	uint32 progress_param[N_PROGRESS_PARAM];
+	double	progress_param_float[N_PROGRESS_PARAM];
+	char progress_message[PROGRESS_MESSAGE_LENGTH][N_PROGRESS_PARAM];
+
 } PgBackendStatus;
 
 /*
@@ -928,6 +936,9 @@ extern void pgstat_initialize(void);
 extern void pgstat_bestart(void);
 
 extern void pgstat_report_activity(BackendState state, const char *cmd_str);
+extern void pgstat_report_progress(char *relname, char *schemaname, BlockNumber total_pages, BlockNumber scanned_pages,
+									BlockNumber total_heap_pages, BlockNumber scanned_heap_pages,
+									BlockNumber total_index_pages, BlockNumber scanned_index_pages);
 extern void pgstat_report_tempfile(size_t filesize);
 extern void pgstat_report_appname(const char *appname);
 extern void pgstat_report_xact_timestamp(TimestampTz tstamp);
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 80374e4..da75e8f 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1848,6 +1848,16 @@ pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
     pg_stat_all_tables.autoanalyze_count
    FROM pg_stat_all_tables
   WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
+pg_stat_vacuum_progress| SELECT s.pid,
+    s.total_pages,
+    s.scanned_pages,
+    s.total_heap_pages,
+    s.scanned_heap_pages,
+    s.total_index_pages,
+    s.scanned_index_pages,
+    s.percent_complete,
+	s.table_name
+   FROM pg_stat_get_vacuum_progress() s(pid, total_pages, scanned_pages, total_heap_pages, scanned_heap_pages, total_index_pages, scanned_index_pages, percent_complete, table_name);
 pg_stat_xact_all_tables| SELECT c.oid AS relid,
     n.nspname AS schemaname,
     c.relname,
#87Syed, Rahila
Rahila.Syed@nttdata.com
In reply to: Syed, Rahila (#86)
1 attachment(s)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

Please check the attached patch as the earlier one had typo in regression test output.

+#define PG_STAT_GET_PROGRESS_COLS 30
Why did you use 30?

That has come from N_PROGRESS_PARAM * 3 where N_PROGRESS_PARAM = 10 is the number of progress parameters of each type stored in shared memory.
There are three such types (int, float, string) hence total number of progress parameters can be 30.

Thank you,
Rahila Syed

______________________________________________________________________
Disclaimer: This email and any attachments are sent in strictest confidence
for the sole use of the addressee and may contain legally privileged,
confidential, and proprietary data. If you are not the intended recipient,
please advise the sender by replying promptly to this email and then delete
and destroy this email and any attachments without any further use, copying
or forwarding.

Attachments:

Vacuum_progress_checker_v4.patchapplication/octet-stream; name=Vacuum_progress_checker_v4.patchDownload
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ccc030f..9b8024c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -631,6 +631,19 @@ CREATE VIEW pg_stat_activity AS
     WHERE S.datid = D.oid AND
             S.usesysid = U.oid;
 
+CREATE VIEW pg_stat_vacuum_progress AS
+    SELECT
+			S.pid,
+            S.total_pages,
+            S.scanned_pages,
+			S.total_heap_pages,
+			S.scanned_heap_pages,
+            S.total_index_pages,
+            S.scanned_index_pages,
+			S.percent_complete,
+			S.table_name
+    FROM pg_stat_get_vacuum_progress() AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index a01cfb4..b69af45 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -439,9 +439,16 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			   Relation *Irel, int nindexes, bool scan_all)
 {
 	BlockNumber nblocks,
-				blkno;
+				blkno,
+				total_pages,
+				scanned_total_pages = 0,
+				total_heap_pages,
+				rel_index_pages = 0,
+				total_index_pages = 0,
+				scanned_index_pages = 0;
 	HeapTupleData tuple;
 	char	   *relname;
+	char		*schemaname;
 	BlockNumber empty_pages,
 				vacuumed_pages;
 	double		num_tuples,
@@ -460,6 +467,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	pg_rusage_init(&ru0);
 
 	relname = RelationGetRelationName(onerel);
+	schemaname = get_namespace_name(RelationGetNamespace(onerel));
 	ereport(elevel,
 			(errmsg("vacuuming \"%s.%s\"",
 					get_namespace_name(RelationGetNamespace(onerel)),
@@ -471,7 +479,14 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	indstats = (IndexBulkDeleteResult **)
 		palloc0(nindexes * sizeof(IndexBulkDeleteResult *));
 
-	nblocks = RelationGetNumberOfBlocks(onerel);
+	total_heap_pages = nblocks = RelationGetNumberOfBlocks(onerel);
+
+	for (i = 0; i < nindexes; i++)
+		rel_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+
+	total_index_pages = rel_index_pages;
+	total_pages = total_heap_pages + rel_index_pages;
+
 	vacrelstats->rel_pages = nblocks;
 	vacrelstats->scanned_pages = 0;
 	vacrelstats->nonempty_pages = 0;
@@ -520,7 +535,14 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		vacuum_delay_point();
 	}
 	if (next_not_all_visible_block >= SKIP_PAGES_THRESHOLD)
+	{
 		skipping_all_visible_blocks = true;
+		if(!scan_all)
+		{
+			total_heap_pages = total_heap_pages - next_not_all_visible_block;
+			total_pages = total_pages - next_not_all_visible_block;
+		}
+	}
 	else
 		skipping_all_visible_blocks = false;
 
@@ -559,7 +581,14 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			 * following blocks.
 			 */
 			if (next_not_all_visible_block - blkno > SKIP_PAGES_THRESHOLD)
+			{
 				skipping_all_visible_blocks = true;
+				if(!scan_all)
+				{
+					total_heap_pages = total_heap_pages - (next_not_all_visible_block - blkno);
+					total_pages = total_pages - (next_not_all_visible_block - blkno);
+				}
+			}
 			else
 				skipping_all_visible_blocks = false;
 			all_visible_according_to_vm = false;
@@ -596,11 +625,30 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			/* Log cleanup info before we touch indexes */
 			vacuum_log_cleanup_info(onerel, vacrelstats);
 
+			/*
+			 * If passes through indexes exceed 1 add
+			 * pages equal to rel_index_pages to the count of
+			 * total pages to be scanned.
+			 */
+			if (vacrelstats->num_index_scans >= 1)
+			{
+				total_index_pages = total_index_pages + rel_index_pages;
+				total_pages = total_heap_pages + total_index_pages;
+			}
+
 			/* Remove index entries */
 			for (i = 0; i < nindexes; i++)
+			{
 				lazy_vacuum_index(Irel[i],
 								  &indstats[i],
 								  vacrelstats);
+				scanned_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+				scanned_total_pages = scanned_total_pages + RelationGetNumberOfBlocks(Irel[i]);
+				/* Report progress to the statistics collector */
+				pgstat_report_progress(relname, schemaname, total_pages, scanned_total_pages, total_heap_pages,
+										vacrelstats->scanned_pages, total_index_pages,
+										scanned_index_pages);
+			}
 			/* Remove tuples from heap */
 			lazy_vacuum_heap(onerel, vacrelstats);
 
@@ -658,6 +706,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 				UnlockReleaseBuffer(buf);
 				vacrelstats->scanned_pages++;
 				vacrelstats->pinskipped_pages++;
+				scanned_total_pages++;
 				continue;
 			}
 			LockBuffer(buf, BUFFER_LOCK_UNLOCK);
@@ -666,6 +715,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		}
 
 		vacrelstats->scanned_pages++;
+		scanned_total_pages++;
 
 		page = BufferGetPage(buf);
 
@@ -1062,6 +1112,17 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		 */
 		if (vacrelstats->num_dead_tuples == prev_dead_count)
 			RecordPageWithFreeSpace(onerel, blkno, freespace);
+
+		/*
+		 * Reporting vacuum progress to statistics collector
+		 */
+		if (blkno == nblocks - 1 && vacrelstats->num_dead_tuples == 0 && nindexes != 0
+			&& vacrelstats->num_index_scans == 0)
+			total_pages = total_pages - total_index_pages;
+
+		pgstat_report_progress(relname, schemaname, total_pages, scanned_total_pages, total_heap_pages,
+								vacrelstats->scanned_pages, total_index_pages,
+								scanned_index_pages);
 	}
 
 	pfree(frozen);
@@ -1093,11 +1154,30 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		/* Log cleanup info before we touch indexes */
 		vacuum_log_cleanup_info(onerel, vacrelstats);
 
+		/*
+		 * If passes through indexes exceed 1 add
+		 * pages equal to rel_index_pages to the count of
+		 * total pages to be scanned.
+		 */
+		if (vacrelstats->num_index_scans >= 1)
+		{
+			total_index_pages = total_index_pages + rel_index_pages;
+			total_pages = total_heap_pages + total_index_pages;
+		}
+
 		/* Remove index entries */
 		for (i = 0; i < nindexes; i++)
+		{
 			lazy_vacuum_index(Irel[i],
 							  &indstats[i],
 							  vacrelstats);
+			scanned_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+			scanned_total_pages = scanned_total_pages + RelationGetNumberOfBlocks(Irel[i]);
+			/* Report progress to the statistics collector */
+			pgstat_report_progress(relname, schemaname, total_pages, scanned_total_pages, total_heap_pages,
+									vacrelstats->scanned_pages, total_index_pages,
+									scanned_index_pages);
+		}
 		/* Remove tuples from heap */
 		lazy_vacuum_heap(onerel, vacrelstats);
 		vacrelstats->num_index_scans++;
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index ab018c4..f1c87cb 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -2851,6 +2851,45 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
 	pgstat_increment_changecount_after(beentry);
 }
 
+/* ---------------------------------------------
+ * Called from VACUUM  after every heap page scan or index scan
+ * to report progress
+ * ---------------------------------------------
+ */
+
+void
+pgstat_report_progress(char *relname, char *schemaname, BlockNumber total_pages, BlockNumber scanned_pages,
+						BlockNumber heap_total_pages, BlockNumber heap_scanned_pages,
+						BlockNumber index_total_pages, BlockNumber index_scanned_pages)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+	int relname_len, schemaname_len;
+
+	if (!beentry)
+		return;
+
+	if (!pgstat_track_activities)
+		return;
+
+	relname_len = strlen(relname);
+	schemaname_len = strlen(schemaname);
+	pgstat_increment_changecount_before(beentry);
+
+	beentry->progress_param[0] = total_pages;
+	beentry->progress_param[1] = scanned_pages;
+	beentry->progress_param[2] = heap_total_pages;
+	beentry->progress_param[3] = heap_scanned_pages;
+	beentry->progress_param[4] = index_total_pages;
+	beentry->progress_param[5] = index_scanned_pages;
+
+	memcpy((char *) beentry->progress_message[0], schemaname, schemaname_len);
+    beentry->progress_message[0][schemaname_len] = '\0';
+	strcat(beentry->progress_message[0],".");
+	strcat(beentry->progress_message[0],relname);
+
+	pgstat_increment_changecount_after(beentry);
+}
+
 /* ----------
  * pgstat_report_appname() -
  *
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index f7c9bf6..2ee256c 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -53,6 +53,7 @@ extern Datum pg_stat_get_function_self_time(PG_FUNCTION_ARGS);
 
 extern Datum pg_stat_get_backend_idset(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_activity(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_vacuum_progress(PG_FUNCTION_ARGS);
 extern Datum pg_backend_pid(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_pid(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_dbid(PG_FUNCTION_ARGS);
@@ -523,7 +524,132 @@ pg_stat_get_backend_idset(PG_FUNCTION_ARGS)
 		SRF_RETURN_DONE(funcctx);
 	}
 }
+/*
+ * Returns VACUUM progress values stored by each backend
+ * executing VACUUM.
+ */
+Datum
+pg_stat_get_vacuum_progress(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_PROGRESS_COLS	30
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+
+	/* 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);
+
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		Datum		values[PG_STAT_GET_PROGRESS_COLS];
+		bool		nulls[PG_STAT_GET_PROGRESS_COLS];
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+
+		MemSet(values, 0, sizeof(values));
+		MemSet(nulls, 0, sizeof(nulls));
+
+		local_beentry = pgstat_fetch_stat_local_beentry(curr_backend);
+		if (!local_beentry)
+			continue;
+		beentry = &local_beentry->backendStatus;
+
+		/* Report values for only those backends which are running VACUUM */
+		if(!beentry || (strncmp(beentry->st_activity,"VACUUM",6)
+						&& strncmp(beentry->st_activity,"vacuum",6)
+						&& strncmp(beentry->st_activity,"autovacuum",10)))
+			continue;
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		/* Progress can only be viewed by role member */
+		if (has_privs_of_role(GetUserId(), beentry->st_userid))
+		{
+			if (beentry->progress_param[0] != 0)
+				values[1] = UInt32GetDatum(beentry->progress_param[0]);
+			else
+				nulls[1] = true;
+
+			if (beentry->progress_param[1] != 0)
+				values[2] = UInt32GetDatum(beentry->progress_param[1]);
+			else
+				nulls[2] = true;
+
+			if (beentry->progress_param[2] != 0)
+				values[3] = UInt32GetDatum(beentry->progress_param[2]);
+			else
+				nulls[3] = true;
+
+			if (beentry->progress_param[3] != 0)
+				values[4] = UInt32GetDatum(beentry->progress_param[3]);
+			else
+				nulls[4] = true;
+
+			if (beentry->progress_param[4] != 0)
+				values[5] = UInt32GetDatum(beentry->progress_param[4]);
+			else
+				nulls[5] = true;
+
+			if (beentry->progress_param[5] != 0)
+				values[6] = UInt32GetDatum(beentry->progress_param[5]);
+			else
+				nulls[6] = true;
+
+			if (beentry->progress_param[0] != 0)
+				values[7] = Float8GetDatum(beentry->progress_param[1] * 100 / beentry->progress_param[0]);
+			else
+				nulls[7] = true;
+
+			if(beentry->progress_message[0])
+				values[8] = CStringGetTextDatum(beentry->progress_message[0]);
+			else
+				nulls[8] = true;
+		}
+		else
+		{
+			values[1] = CStringGetTextDatum("<insufficient privilege>");
+			nulls[2] = true;
+			nulls[3] = true;
+			nulls[4] = true;
+			nulls[5] = true;
+			nulls[6] = true;
+			nulls[7] = true;
+			nulls[8] = true;
+		}
+
+		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+	}
 
+	/* clean up and return the tuplestore */
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
 /*
  * Returns activity of PG backends.
  */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index eb55b3a..a19f325 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2781,6 +2781,8 @@ DATA(insert OID = 1936 (  pg_stat_get_backend_idset		PGNSP PGUID 12 1 100 0 0 f
 DESCR("statistics: currently active backend IDs");
 DATA(insert OID = 2022 (  pg_stat_get_activity			PGNSP PGUID 12 1 100 0 0 f f f f f t s r 1 0 2249 "23" "{23,26,23,26,25,25,25,16,1184,1184,1184,1184,869,25,23,28,28,16,25,25,23,16,25}" "{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{pid,datid,pid,usesysid,application_name,state,query,waiting,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,ssl,sslversion,sslcipher,sslbits,sslcompression,sslclientdn}" _null_ _null_ pg_stat_get_activity _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active backends");
+DATA(insert OID = 3308 (  pg_stat_get_vacuum_progress			PGNSP PGUID 12 1 1 0 0 f f f f f t s r 0 0 2249 "" "{23,23,23,23,23,23,23,701,25}" "{o,o,o,o,o,o,o,o,o}" "{pid,total_pages,scanned_pages,total_heap_pages,scanned_heap_pages,total_index_pages,scanned_index_pages,percent_complete,table_name}" _null_ _null_ pg_stat_get_vacuum_progress _null_ _null_ _null_ ));
+DESCR("statistics: information about progress of backends running VACUUM");
 DATA(insert OID = 3099 (  pg_stat_get_wal_senders	PGNSP PGUID 12 1 10 0 0 f f f f f t s r 0 0 2249 "" "{23,25,3220,3220,3220,3220,23,25}" "{o,o,o,o,o,o,o,o}" "{pid,state,sent_location,write_location,flush_location,replay_location,sync_priority,sync_state}" _null_ _null_ pg_stat_get_wal_senders _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active replication");
 DATA(insert OID = 2026 (  pg_backend_pid				PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 23 "" _null_ _null_ _null_ _null_ _null_ pg_backend_pid _null_ _null_ _null_ ));
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 9ecc163..d195714 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -20,6 +20,7 @@
 #include "utils/hsearch.h"
 #include "utils/relcache.h"
 
+#include "storage/block.h"
 
 /* ----------
  * Paths for the statistics files (relative to installation's $PGDATA).
@@ -205,6 +206,8 @@ typedef struct PgStat_MsgHdr
 #define PGSTAT_MAX_MSG_SIZE 1000
 #define PGSTAT_MSG_PAYLOAD	(PGSTAT_MAX_MSG_SIZE - sizeof(PgStat_MsgHdr))
 
+#define N_PROGRESS_PARAM 10
+#define PROGRESS_MESSAGE_LENGTH 30
 
 /* ----------
  * PgStat_MsgDummy				A dummy message, ignored by the collector
@@ -776,6 +779,11 @@ typedef struct PgBackendStatus
 
 	/* current command string; MUST be null-terminated */
 	char	   *st_activity;
+
+	uint32 progress_param[N_PROGRESS_PARAM];
+	double	progress_param_float[N_PROGRESS_PARAM];
+	char progress_message[PROGRESS_MESSAGE_LENGTH][N_PROGRESS_PARAM];
+
 } PgBackendStatus;
 
 /*
@@ -928,6 +936,9 @@ extern void pgstat_initialize(void);
 extern void pgstat_bestart(void);
 
 extern void pgstat_report_activity(BackendState state, const char *cmd_str);
+extern void pgstat_report_progress(char *relname, char *schemaname, BlockNumber total_pages, BlockNumber scanned_pages,
+									BlockNumber total_heap_pages, BlockNumber scanned_heap_pages,
+									BlockNumber total_index_pages, BlockNumber scanned_index_pages);
 extern void pgstat_report_tempfile(size_t filesize);
 extern void pgstat_report_appname(const char *appname);
 extern void pgstat_report_xact_timestamp(TimestampTz tstamp);
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 80374e4..323002d 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1848,6 +1848,16 @@ pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
     pg_stat_all_tables.autoanalyze_count
    FROM pg_stat_all_tables
   WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
+pg_stat_vacuum_progress| SELECT s.pid,
+    s.total_pages,
+    s.scanned_pages,
+    s.total_heap_pages,
+    s.scanned_heap_pages,
+    s.total_index_pages,
+    s.scanned_index_pages,
+    s.percent_complete,
+    s.table_name
+   FROM pg_stat_get_vacuum_progress() s(pid, total_pages, scanned_pages, total_heap_pages, scanned_heap_pages, total_index_pages, scanned_index_pages, percent_complete, table_name);
 pg_stat_xact_all_tables| SELECT c.oid AS relid,
     n.nspname AS schemaname,
     c.relname,
#88Syed, Rahila
Rahila.Syed@nttdata.com
In reply to: Syed, Rahila (#86)
1 attachment(s)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

I think that you should add the flag or something which indicates whether this backend is running VACUUM or not, into PgBackendStatus.
pg_stat_vacuum_progress should display the entries of only backends with that flag set true. This design means that you need to set the flag to true when starting VACUUM and reset at the end of VACUUM progressing.

Please find attached updated patch which adds a flag in PgBackendStatus which indicates whether this backend in running VACUUM.
Also, pgstat_report_progress function is changed to make it generic for all commands reporting progress.

Thank you,
Rahila Syed

______________________________________________________________________
Disclaimer: This email and any attachments are sent in strictest confidence
for the sole use of the addressee and may contain legally privileged,
confidential, and proprietary data. If you are not the intended recipient,
please advise the sender by replying promptly to this email and then delete
and destroy this email and any attachments without any further use, copying
or forwarding.

Attachments:

Vacuum_progress_checker_v5.patchapplication/octet-stream; name=Vacuum_progress_checker_v5.patchDownload
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ccc030f..9b8024c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -631,6 +631,19 @@ CREATE VIEW pg_stat_activity AS
     WHERE S.datid = D.oid AND
             S.usesysid = U.oid;
 
+CREATE VIEW pg_stat_vacuum_progress AS
+    SELECT
+			S.pid,
+            S.total_pages,
+            S.scanned_pages,
+			S.total_heap_pages,
+			S.scanned_heap_pages,
+            S.total_index_pages,
+            S.scanned_index_pages,
+			S.percent_complete,
+			S.table_name
+    FROM pg_stat_get_vacuum_progress() AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 6d55148..b9ed102 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -284,6 +284,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
 		VacuumPageMiss = 0;
 		VacuumPageDirty = 0;
 
+		pgstat_report_activityflag(ACTIVITY_IS_VACUUM);
 		/*
 		 * Loop to process each selected relation.
 		 */
@@ -355,6 +356,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
 		vac_update_datfrozenxid();
 	}
 
+	pgstat_reset_activityflag;
 	/*
 	 * Clean up working storage --- note we must do this after
 	 * StartTransactionCommand, else we might be trying to delete the active
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index a01cfb4..e2cb2b3 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -439,9 +439,17 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			   Relation *Irel, int nindexes, bool scan_all)
 {
 	BlockNumber nblocks,
-				blkno;
+				blkno,
+				total_pages,
+				scanned_total_pages = 0,
+				total_heap_pages,
+				rel_index_pages = 0,
+				total_index_pages = 0,
+				scanned_index_pages = 0;
 	HeapTupleData tuple;
 	char	   *relname;
+	char		*schemaname;
+	int			schemaname_len;
 	BlockNumber empty_pages,
 				vacuumed_pages;
 	double		num_tuples,
@@ -456,14 +464,19 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	bool		skipping_all_visible_blocks;
 	xl_heap_freeze_tuple *frozen;
 	StringInfoData buf;
+	uint32 progress_param[N_PROGRESS_PARAM];
+	double	progress_param_float[N_PROGRESS_PARAM];
+	char	progress_message[PROGRESS_MESSAGE_LENGTH][N_PROGRESS_PARAM];
 
 	pg_rusage_init(&ru0);
 
 	relname = RelationGetRelationName(onerel);
+	schemaname = get_namespace_name(RelationGetNamespace(onerel));
 	ereport(elevel,
 			(errmsg("vacuuming \"%s.%s\"",
 					get_namespace_name(RelationGetNamespace(onerel)),
 					relname)));
+	schemaname_len = strlen(schemaname);
 
 	empty_pages = vacuumed_pages = 0;
 	num_tuples = tups_vacuumed = nkeep = nunused = 0;
@@ -471,7 +484,14 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	indstats = (IndexBulkDeleteResult **)
 		palloc0(nindexes * sizeof(IndexBulkDeleteResult *));
 
-	nblocks = RelationGetNumberOfBlocks(onerel);
+	total_heap_pages = nblocks = RelationGetNumberOfBlocks(onerel);
+
+	for (i = 0; i < nindexes; i++)
+		rel_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+
+	total_index_pages = rel_index_pages;
+	total_pages = total_heap_pages + rel_index_pages;
+
 	vacrelstats->rel_pages = nblocks;
 	vacrelstats->scanned_pages = 0;
 	vacrelstats->nonempty_pages = 0;
@@ -520,7 +540,14 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		vacuum_delay_point();
 	}
 	if (next_not_all_visible_block >= SKIP_PAGES_THRESHOLD)
+	{
 		skipping_all_visible_blocks = true;
+		if(!scan_all)
+		{
+			total_heap_pages = total_heap_pages - next_not_all_visible_block;
+			total_pages = total_pages - next_not_all_visible_block;
+		}
+	}
 	else
 		skipping_all_visible_blocks = false;
 
@@ -559,7 +586,14 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			 * following blocks.
 			 */
 			if (next_not_all_visible_block - blkno > SKIP_PAGES_THRESHOLD)
+			{
 				skipping_all_visible_blocks = true;
+				if(!scan_all)
+				{
+					total_heap_pages = total_heap_pages - (next_not_all_visible_block - blkno);
+					total_pages = total_pages - (next_not_all_visible_block - blkno);
+				}
+			}
 			else
 				skipping_all_visible_blocks = false;
 			all_visible_according_to_vm = false;
@@ -596,11 +630,42 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			/* Log cleanup info before we touch indexes */
 			vacuum_log_cleanup_info(onerel, vacrelstats);
 
+			/*
+			 * If passes through indexes exceed 1 add
+			 * pages equal to rel_index_pages to the count of
+			 * total pages to be scanned.
+			 */
+			if (vacrelstats->num_index_scans >= 1)
+			{
+				total_index_pages = total_index_pages + rel_index_pages;
+				total_pages = total_heap_pages + total_index_pages;
+			}
+
 			/* Remove index entries */
 			for (i = 0; i < nindexes; i++)
+			{
 				lazy_vacuum_index(Irel[i],
 								  &indstats[i],
 								  vacrelstats);
+				scanned_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+				scanned_total_pages = scanned_total_pages + RelationGetNumberOfBlocks(Irel[i]);
+
+				/* Report progress to the statistics collector */
+				progress_param[0] = total_pages;
+				progress_param[1] = scanned_total_pages;
+				progress_param[2] = total_heap_pages;
+				progress_param[3] = vacrelstats->scanned_pages;
+				progress_param[4] = total_index_pages;
+				progress_param[5] = scanned_index_pages;
+
+				memcpy((char *) progress_message[0], schemaname, schemaname_len);
+				progress_message[0][schemaname_len] = '\0';
+				strcat(progress_message[0],".");
+				strcat(progress_message[0],relname);
+
+				pgstat_report_progress(progress_param, 6, progress_param_float,
+										0, progress_message, 1);
+			}
 			/* Remove tuples from heap */
 			lazy_vacuum_heap(onerel, vacrelstats);
 
@@ -658,6 +723,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 				UnlockReleaseBuffer(buf);
 				vacrelstats->scanned_pages++;
 				vacrelstats->pinskipped_pages++;
+				scanned_total_pages++;
 				continue;
 			}
 			LockBuffer(buf, BUFFER_LOCK_UNLOCK);
@@ -666,6 +732,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		}
 
 		vacrelstats->scanned_pages++;
+		scanned_total_pages++;
 
 		page = BufferGetPage(buf);
 
@@ -1062,6 +1129,28 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		 */
 		if (vacrelstats->num_dead_tuples == prev_dead_count)
 			RecordPageWithFreeSpace(onerel, blkno, freespace);
+
+		/*
+		 * Reporting vacuum progress to statistics collector
+		 */
+		if (blkno == nblocks - 1 && vacrelstats->num_dead_tuples == 0 && nindexes != 0
+			&& vacrelstats->num_index_scans == 0)
+			total_pages = total_pages - total_index_pages;
+
+		progress_param[0] = total_pages;
+		progress_param[1] = scanned_total_pages;
+		progress_param[2] = total_heap_pages;
+		progress_param[3] = vacrelstats->scanned_pages;
+		progress_param[4] = total_index_pages;
+		progress_param[5] = scanned_index_pages;
+
+		memcpy((char *) progress_message[0], schemaname, schemaname_len);
+		progress_message[0][schemaname_len] = '\0';
+		strcat(progress_message[0],".");
+		strcat(progress_message[0],relname);
+
+		pgstat_report_progress(progress_param, 6, progress_param_float, 0,
+								progress_message, 1);
 	}
 
 	pfree(frozen);
@@ -1093,11 +1182,41 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		/* Log cleanup info before we touch indexes */
 		vacuum_log_cleanup_info(onerel, vacrelstats);
 
+		/*
+		 * If passes through indexes exceed 1 add
+		 * pages equal to rel_index_pages to the count of
+		 * total pages to be scanned.
+		 */
+		if (vacrelstats->num_index_scans >= 1)
+		{
+			total_index_pages = total_index_pages + rel_index_pages;
+			total_pages = total_heap_pages + total_index_pages;
+		}
+
 		/* Remove index entries */
 		for (i = 0; i < nindexes; i++)
+		{
 			lazy_vacuum_index(Irel[i],
 							  &indstats[i],
 							  vacrelstats);
+			scanned_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+			scanned_total_pages = scanned_total_pages + RelationGetNumberOfBlocks(Irel[i]);
+			/* Report progress to the statistics collector */
+			progress_param[0] = total_pages;
+			progress_param[1] = scanned_total_pages;
+			progress_param[2] = total_heap_pages;
+			progress_param[3] = vacrelstats->scanned_pages;
+			progress_param[4] = total_index_pages;
+			progress_param[5] = scanned_index_pages;
+
+			memcpy((char *) progress_message[0], schemaname, schemaname_len);
+			progress_message[0][schemaname_len] = '\0';
+			strcat(progress_message[0],".");
+			strcat(progress_message[0],relname);
+
+			pgstat_report_progress(progress_param, 6, progress_param_float, 0,
+								progress_message, 1);
+		}
 		/* Remove tuples from heap */
 		lazy_vacuum_heap(onerel, vacrelstats);
 		vacrelstats->num_index_scans++;
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index ab018c4..f97759e 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -2851,6 +2851,55 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
 	pgstat_increment_changecount_after(beentry);
 }
 
+/* ---------------------------------------------
+ * Called from VACUUM  after every heap page scan or index scan
+ * to report progress
+ * ---------------------------------------------
+ */
+
+void
+pgstat_report_progress(uint *param1, int num_of_int, double *param2, int num_of_float,
+						char param3[PROGRESS_MESSAGE_LENGTH][N_PROGRESS_PARAM],
+						int num_of_string)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+	int i;
+
+	if (!beentry)
+		return;
+
+	if (!pgstat_track_activities)
+		return;
+
+	pgstat_increment_changecount_before(beentry);
+
+	for(i = 0; i < num_of_int; i++)
+	{
+		beentry->progress_param[i] = param1[i];
+	}
+	for (i = 0; i < num_of_float; i++)
+	{
+		beentry->progress_param_float[i] = param2[i];
+	}
+	for (i = 0; i < num_of_string; i++)
+	{
+		strcpy((char *)beentry->progress_message[i], param3[i]);
+	}
+	pgstat_increment_changecount_after(beentry);
+}
+
+void
+pgstat_report_activityflag(activity_flag)
+{
+	PgBackendStatus *beentry = MyBEEntry;
+	beentry->flag_activity = activity_flag;
+}
+void
+pgstat_reset_activityflag()
+{
+	PgBackendStatus *beentry = MyBEEntry;
+	beentry->flag_activity = 0;
+}
 /* ----------
  * pgstat_report_appname() -
  *
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index f7c9bf6..e113361 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -53,6 +53,7 @@ extern Datum pg_stat_get_function_self_time(PG_FUNCTION_ARGS);
 
 extern Datum pg_stat_get_backend_idset(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_activity(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_vacuum_progress(PG_FUNCTION_ARGS);
 extern Datum pg_backend_pid(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_pid(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_dbid(PG_FUNCTION_ARGS);
@@ -523,7 +524,130 @@ pg_stat_get_backend_idset(PG_FUNCTION_ARGS)
 		SRF_RETURN_DONE(funcctx);
 	}
 }
+/*
+ * Returns VACUUM progress values stored by each backend
+ * executing VACUUM.
+ */
+Datum
+pg_stat_get_vacuum_progress(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_PROGRESS_COLS	30
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+
+	/* 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);
+
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		Datum		values[PG_STAT_GET_PROGRESS_COLS];
+		bool		nulls[PG_STAT_GET_PROGRESS_COLS];
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+
+		MemSet(values, 0, sizeof(values));
+		MemSet(nulls, 0, sizeof(nulls));
+
+		local_beentry = pgstat_fetch_stat_local_beentry(curr_backend);
+		if (!local_beentry)
+			continue;
+		beentry = &local_beentry->backendStatus;
+
+		/* Report values for only those backends which are running VACUUM */
+		if(!beentry || beentry->flag_activity != ACTIVITY_IS_VACUUM)
+			continue;
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		/* Progress can only be viewed by role member */
+		if (has_privs_of_role(GetUserId(), beentry->st_userid))
+		{
+			if (beentry->progress_param[0] != 0)
+				values[1] = UInt32GetDatum(beentry->progress_param[0]);
+			else
+				nulls[1] = true;
+
+			if (beentry->progress_param[1] != 0)
+				values[2] = UInt32GetDatum(beentry->progress_param[1]);
+			else
+				nulls[2] = true;
+
+			if (beentry->progress_param[2] != 0)
+				values[3] = UInt32GetDatum(beentry->progress_param[2]);
+			else
+				nulls[3] = true;
+
+			if (beentry->progress_param[3] != 0)
+				values[4] = UInt32GetDatum(beentry->progress_param[3]);
+			else
+				nulls[4] = true;
+
+			if (beentry->progress_param[4] != 0)
+				values[5] = UInt32GetDatum(beentry->progress_param[4]);
+			else
+				nulls[5] = true;
+
+			if (beentry->progress_param[5] != 0)
+				values[6] = UInt32GetDatum(beentry->progress_param[5]);
+			else
+				nulls[6] = true;
+
+			if (beentry->progress_param[0] != 0)
+				values[7] = Float8GetDatum(beentry->progress_param[1] * 100 / beentry->progress_param[0]);
+			else
+				nulls[7] = true;
+
+			if(beentry->progress_message[0])
+				values[8] = CStringGetTextDatum(beentry->progress_message[0]);
+			else
+				nulls[8] = true;
+		}
+		else
+		{
+			values[1] = CStringGetTextDatum("<insufficient privilege>");
+			nulls[2] = true;
+			nulls[3] = true;
+			nulls[4] = true;
+			nulls[5] = true;
+			nulls[6] = true;
+			nulls[7] = true;
+			nulls[8] = true;
+		}
+
+		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+	}
 
+	/* clean up and return the tuplestore */
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
 /*
  * Returns activity of PG backends.
  */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index eb55b3a..a19f325 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2781,6 +2781,8 @@ DATA(insert OID = 1936 (  pg_stat_get_backend_idset		PGNSP PGUID 12 1 100 0 0 f
 DESCR("statistics: currently active backend IDs");
 DATA(insert OID = 2022 (  pg_stat_get_activity			PGNSP PGUID 12 1 100 0 0 f f f f f t s r 1 0 2249 "23" "{23,26,23,26,25,25,25,16,1184,1184,1184,1184,869,25,23,28,28,16,25,25,23,16,25}" "{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{pid,datid,pid,usesysid,application_name,state,query,waiting,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,ssl,sslversion,sslcipher,sslbits,sslcompression,sslclientdn}" _null_ _null_ pg_stat_get_activity _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active backends");
+DATA(insert OID = 3308 (  pg_stat_get_vacuum_progress			PGNSP PGUID 12 1 1 0 0 f f f f f t s r 0 0 2249 "" "{23,23,23,23,23,23,23,701,25}" "{o,o,o,o,o,o,o,o,o}" "{pid,total_pages,scanned_pages,total_heap_pages,scanned_heap_pages,total_index_pages,scanned_index_pages,percent_complete,table_name}" _null_ _null_ pg_stat_get_vacuum_progress _null_ _null_ _null_ ));
+DESCR("statistics: information about progress of backends running VACUUM");
 DATA(insert OID = 3099 (  pg_stat_get_wal_senders	PGNSP PGUID 12 1 10 0 0 f f f f f t s r 0 0 2249 "" "{23,25,3220,3220,3220,3220,23,25}" "{o,o,o,o,o,o,o,o}" "{pid,state,sent_location,write_location,flush_location,replay_location,sync_priority,sync_state}" _null_ _null_ pg_stat_get_wal_senders _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active replication");
 DATA(insert OID = 2026 (  pg_backend_pid				PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 23 "" _null_ _null_ _null_ _null_ _null_ pg_backend_pid _null_ _null_ _null_ ));
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 9ecc163..4214b3d 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -20,6 +20,7 @@
 #include "utils/hsearch.h"
 #include "utils/relcache.h"
 
+#include "storage/block.h"
 
 /* ----------
  * Paths for the statistics files (relative to installation's $PGDATA).
@@ -205,6 +206,8 @@ typedef struct PgStat_MsgHdr
 #define PGSTAT_MAX_MSG_SIZE 1000
 #define PGSTAT_MSG_PAYLOAD	(PGSTAT_MAX_MSG_SIZE - sizeof(PgStat_MsgHdr))
 
+#define N_PROGRESS_PARAM 10
+#define PROGRESS_MESSAGE_LENGTH 30
 
 /* ----------
  * PgStat_MsgDummy				A dummy message, ignored by the collector
@@ -776,6 +779,12 @@ typedef struct PgBackendStatus
 
 	/* current command string; MUST be null-terminated */
 	char	   *st_activity;
+
+	uint32		flag_activity;
+	uint32		progress_param[N_PROGRESS_PARAM];
+	double		progress_param_float[N_PROGRESS_PARAM];
+	char		progress_message[PROGRESS_MESSAGE_LENGTH][N_PROGRESS_PARAM];
+
 } PgBackendStatus;
 
 /*
@@ -815,6 +824,7 @@ typedef struct PgBackendStatus
 		save_changecount = beentry->st_changecount; \
 	} while (0)
 
+#define ACTIVITY_IS_VACUUM 0x01
 /* ----------
  * LocalPgBackendStatus
  *
@@ -928,6 +938,12 @@ extern void pgstat_initialize(void);
 extern void pgstat_bestart(void);
 
 extern void pgstat_report_activity(BackendState state, const char *cmd_str);
+extern void pgstat_report_activityflag(int activity_flag);
+extern void pgstat_reset_activityflag();
+extern void pgstat_report_progress(uint *param1, int num_of_int, double *param2,
+									int int_num_float,
+									char param3[PROGRESS_MESSAGE_LENGTH][N_PROGRESS_PARAM],
+									int num_of_string);
 extern void pgstat_report_tempfile(size_t filesize);
 extern void pgstat_report_appname(const char *appname);
 extern void pgstat_report_xact_timestamp(TimestampTz tstamp);
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 80374e4..323002d 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1848,6 +1848,16 @@ pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
     pg_stat_all_tables.autoanalyze_count
    FROM pg_stat_all_tables
   WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
+pg_stat_vacuum_progress| SELECT s.pid,
+    s.total_pages,
+    s.scanned_pages,
+    s.total_heap_pages,
+    s.scanned_heap_pages,
+    s.total_index_pages,
+    s.scanned_index_pages,
+    s.percent_complete,
+    s.table_name
+   FROM pg_stat_get_vacuum_progress() s(pid, total_pages, scanned_pages, total_heap_pages, scanned_heap_pages, total_index_pages, scanned_index_pages, percent_complete, table_name);
 pg_stat_xact_all_tables| SELECT c.oid AS relid,
     n.nspname AS schemaname,
     c.relname,
#89Robert Haas
robertmhaas@gmail.com
In reply to: Syed, Rahila (#88)
Re: [PROPOSAL] VACUUM Progress Checker.

On Tue, Oct 20, 2015 at 4:58 AM, Syed, Rahila <Rahila.Syed@nttdata.com> wrote:

I think that you should add the flag or something which indicates whether this backend is running VACUUM or not, into PgBackendStatus.
pg_stat_vacuum_progress should display the entries of only backends with that flag set true. This design means that you need to set the flag to true when starting VACUUM and reset at the end of VACUUM progressing.

Please find attached updated patch which adds a flag in PgBackendStatus which indicates whether this backend in running VACUUM.

Flag isn't reset on error.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#90Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Syed, Rahila (#88)
Re: [PROPOSAL] VACUUM Progress Checker.

Syed, Rahila wrote:

@@ -355,6 +356,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
vac_update_datfrozenxid();
}

+ pgstat_reset_activityflag;
/*
* Clean up working storage --- note we must do this after
* StartTransactionCommand, else we might be trying to delete the active

Does this actually compile?

@@ -596,11 +630,42 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
/* Log cleanup info before we touch indexes */
vacuum_log_cleanup_info(onerel, vacrelstats);

+			/*
+			 * If passes through indexes exceed 1 add
+			 * pages equal to rel_index_pages to the count of
+			 * total pages to be scanned.
+			 */
+			if (vacrelstats->num_index_scans >= 1)
+			{
+				total_index_pages = total_index_pages + rel_index_pages;
+				total_pages = total_heap_pages + total_index_pages;
+			}

Having the keep total_pages updated each time you change one of the
summands seems tedious and error-prone. Why can't it be computed
whenever it is going to be used instead?

+				memcpy((char *) progress_message[0], schemaname, schemaname_len);
+				progress_message[0][schemaname_len] = '\0';
+				strcat(progress_message[0],".");
+				strcat(progress_message[0],relname);

snprintf()? I don't think you need to keep track of schemaname_len at
all.

+			scanned_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+			scanned_total_pages = scanned_total_pages + RelationGetNumberOfBlocks(Irel[i]);
+			/* Report progress to the statistics collector */
+			progress_param[0] = total_pages;
+			progress_param[1] = scanned_total_pages;
+			progress_param[2] = total_heap_pages;
+			progress_param[3] = vacrelstats->scanned_pages;
+			progress_param[4] = total_index_pages;
+			progress_param[5] = scanned_index_pages;

In fact, I wonder if you need to send total_pages at all -- surely the
client can add both total_heap_pages and total_index_pages by itself ...

+			memcpy((char *) progress_message[0], schemaname, schemaname_len);
+			progress_message[0][schemaname_len] = '\0';
+			strcat(progress_message[0],".");
+			strcat(progress_message[0],relname);

snprintf().

diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index ab018c4..f97759e 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -2851,6 +2851,55 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
pgstat_increment_changecount_after(beentry);
}
+/* ---------------------------------------------
+ * Called from VACUUM  after every heap page scan or index scan
+ * to report progress
+ * ---------------------------------------------
+ */
+
+void
+pgstat_report_progress(uint *param1, int num_of_int, double *param2, int num_of_float,
+						char param3[PROGRESS_MESSAGE_LENGTH][N_PROGRESS_PARAM],
+						int num_of_string)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+	int i;
+
+	if (!beentry)
+		return;
+
+	if (!pgstat_track_activities)
+		return;
+
+	pgstat_increment_changecount_before(beentry);
+
+	for(i = 0; i < num_of_int; i++)
+	{
+		beentry->progress_param[i] = param1[i];
+	}
+	for (i = 0; i < num_of_float; i++)
+	{
+		beentry->progress_param_float[i] = param2[i];
+	}
+	for (i = 0; i < num_of_string; i++)
+	{
+		strcpy((char *)beentry->progress_message[i], param3[i]);
+	}
+	pgstat_increment_changecount_after(beentry);
+}

It seems a bit strange that the remaining progress_param entries are not
initialized to anything. Also, why aren't the number of params of each
type saved too? In the receiving code you check whether each value
equals 0, and if it does then report NULL, but imagine vacuuming a table
with no indexes where the number of index pages is going to be zero.
Shouldn't we display zero there rather than null? Maybe I'm missing
something and that does work fine.

This patch lacks a comment somewhere explaining how this whole thing
works.

diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 9ecc163..4214b3d 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -20,6 +20,7 @@
#include "utils/hsearch.h"
#include "utils/relcache.h"

+#include "storage/block.h"

I believe you don't need this include.

@@ -776,6 +779,12 @@ typedef struct PgBackendStatus

/* current command string; MUST be null-terminated */
char	   *st_activity;
+
+	uint32		flag_activity;
+	uint32		progress_param[N_PROGRESS_PARAM];
+	double		progress_param_float[N_PROGRESS_PARAM];
+	char		progress_message[PROGRESS_MESSAGE_LENGTH][N_PROGRESS_PARAM];
+
} PgBackendStatus;

This not only adds an unnecessary empty line at the end of the struct
declaration, but also fails to preserve the "st_" prefix used in all the
other fields.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#91Syed, Rahila
Rahila.Syed@nttdata.com
In reply to: Alvaro Herrera (#90)
1 attachment(s)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

Please find attached an updated patch.

Flag isn't reset on error.

Corrected in the attached.

+ pgstat_reset_activityflag;
Does this actually compile?

It does compile but with no effect. It has been corrected.

snprintf()? I don't think you need to keep track of schemaname_len at all.

memcpy() has been replaced by snprintf() to avoid calculating schemaname_len.

In fact, I wonder if you need to send total_pages at all -- surely the client can add both total_heap_pages and total_index_pages by itself ...

This has been corrected in the attached patch.

It seems a bit strange that the remaining progress_param entries are not initialized to anything. Also, why aren't the number of params of each type saved too?

The number of params for each command remains constant hence it has been hardcoded.

In the receiving code you check whether each value equals 0, and if it does then report NULL, but imagine vacuuming a table with no indexes where the number of index pages is going to be zero.
Shouldn't we display zero there rather than null?

Agree. IIUC, NULL should rather be used when a value is invalid. But for valid values like 'zero index pages' it is clearer to display 0. It has been corrected in the attached.

This patch lacks a comment somewhere explaining how this whole thing works.

Have added few lines in pgstat.h inside PgBackendStatus struct.

I believe you don't need this include.

Corrected.

This not only adds an unnecessary empty line at the end of the struct declaration, but also fails to preserve the "st_" prefix used in all the other fields

Corrected.

Thank you,
Rahila Syed

______________________________________________________________________
Disclaimer: This email and any attachments are sent in strictest confidence
for the sole use of the addressee and may contain legally privileged,
confidential, and proprietary data. If you are not the intended recipient,
please advise the sender by replying promptly to this email and then delete
and destroy this email and any attachments without any further use, copying
or forwarding.

Attachments:

Vacuum_progress_checker_v6.patchapplication/octet-stream; name=Vacuum_progress_checker_v6.patchDownload
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ccc030f..32064d0 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -631,6 +631,19 @@ CREATE VIEW pg_stat_activity AS
     WHERE S.datid = D.oid AND
             S.usesysid = U.oid;
 
+CREATE VIEW pg_stat_vacuum_progress AS
+    SELECT
+			S.pid,
+			S.table_name,
+			S.total_heap_pages,
+			S.scanned_heap_pages,
+            S.total_index_pages,
+            S.scanned_index_pages,
+            S.total_pages,
+            S.scanned_pages,
+			S.percent_complete
+    FROM pg_stat_get_vacuum_progress() AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 6d55148..a0df890 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -284,6 +284,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
 		VacuumPageMiss = 0;
 		VacuumPageDirty = 0;
 
+		pgstat_report_activityflag(ACTIVITY_IS_VACUUM);
 		/*
 		 * Loop to process each selected relation.
 		 */
@@ -325,6 +326,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
 	{
 		in_vacuum = false;
 		VacuumCostActive = false;
+		pgstat_reset_activityflag();
 		PG_RE_THROW();
 	}
 	PG_END_TRY();
@@ -355,6 +357,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
 		vac_update_datfrozenxid();
 	}
 
+	pgstat_reset_activityflag();
 	/*
 	 * Clean up working storage --- note we must do this after
 	 * StartTransactionCommand, else we might be trying to delete the active
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index a01cfb4..bb94833 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -439,9 +439,14 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			   Relation *Irel, int nindexes, bool scan_all)
 {
 	BlockNumber nblocks,
-				blkno;
+				blkno,
+				total_heap_pages,
+				rel_index_pages = 0,
+				total_index_pages = 0,
+				scanned_index_pages = 0;
 	HeapTupleData tuple;
 	char	   *relname;
+	char		*schemaname;
 	BlockNumber empty_pages,
 				vacuumed_pages;
 	double		num_tuples,
@@ -456,14 +461,21 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	bool		skipping_all_visible_blocks;
 	xl_heap_freeze_tuple *frozen;
 	StringInfoData buf;
+	uint32 progress_param[N_PROGRESS_PARAM];
+	double	progress_param_float[N_PROGRESS_PARAM];
+	char	progress_message[PROGRESS_MESSAGE_LENGTH][N_PROGRESS_PARAM];
 
 	pg_rusage_init(&ru0);
 
 	relname = RelationGetRelationName(onerel);
+	schemaname = get_namespace_name(RelationGetNamespace(onerel));
 	ereport(elevel,
 			(errmsg("vacuuming \"%s.%s\"",
 					get_namespace_name(RelationGetNamespace(onerel)),
 					relname)));
+    snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s", schemaname);
+	strcat(progress_message[0],".");
+	strcat(progress_message[0],relname);
 
 	empty_pages = vacuumed_pages = 0;
 	num_tuples = tups_vacuumed = nkeep = nunused = 0;
@@ -471,7 +483,12 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	indstats = (IndexBulkDeleteResult **)
 		palloc0(nindexes * sizeof(IndexBulkDeleteResult *));
 
-	nblocks = RelationGetNumberOfBlocks(onerel);
+	total_heap_pages = nblocks = RelationGetNumberOfBlocks(onerel);
+
+	for (i = 0; i < nindexes; i++)
+		rel_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+	total_index_pages = rel_index_pages;
+
 	vacrelstats->rel_pages = nblocks;
 	vacrelstats->scanned_pages = 0;
 	vacrelstats->nonempty_pages = 0;
@@ -520,7 +537,11 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		vacuum_delay_point();
 	}
 	if (next_not_all_visible_block >= SKIP_PAGES_THRESHOLD)
+	{
 		skipping_all_visible_blocks = true;
+		if(!scan_all)
+			total_heap_pages = total_heap_pages - next_not_all_visible_block;
+	}
 	else
 		skipping_all_visible_blocks = false;
 
@@ -559,7 +580,11 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			 * following blocks.
 			 */
 			if (next_not_all_visible_block - blkno > SKIP_PAGES_THRESHOLD)
+			{
 				skipping_all_visible_blocks = true;
+				if(!scan_all)
+					total_heap_pages = total_heap_pages - (next_not_all_visible_block - blkno);
+			}
 			else
 				skipping_all_visible_blocks = false;
 			all_visible_according_to_vm = false;
@@ -596,11 +621,31 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			/* Log cleanup info before we touch indexes */
 			vacuum_log_cleanup_info(onerel, vacrelstats);
 
+			/*
+			 * If passes through indexes exceed 1 add
+			 * pages equal to rel_index_pages to the count of
+			 * total pages to be scanned.
+			 */
+			if (vacrelstats->num_index_scans >= 1)
+				total_index_pages = total_index_pages + rel_index_pages;
+
 			/* Remove index entries */
 			for (i = 0; i < nindexes; i++)
+			{
 				lazy_vacuum_index(Irel[i],
 								  &indstats[i],
 								  vacrelstats);
+				scanned_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+
+				/* Report progress to the statistics collector */
+				progress_param[0] = total_heap_pages;
+				progress_param[1] = vacrelstats->scanned_pages;
+				progress_param[2] = total_index_pages;
+				progress_param[3] = scanned_index_pages;
+
+				pgstat_report_progress(progress_param, 4, progress_param_float,
+										0, progress_message, 1);
+			}
 			/* Remove tuples from heap */
 			lazy_vacuum_heap(onerel, vacrelstats);
 
@@ -1062,6 +1107,21 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		 */
 		if (vacrelstats->num_dead_tuples == prev_dead_count)
 			RecordPageWithFreeSpace(onerel, blkno, freespace);
+
+		/*
+		 * Reporting vacuum progress to statistics collector
+		 */
+		if (blkno == nblocks - 1 && vacrelstats->num_dead_tuples == 0 && nindexes != 0
+			&& vacrelstats->num_index_scans == 0)
+			total_index_pages = 0;
+
+		progress_param[0] = total_heap_pages;
+		progress_param[1] = vacrelstats->scanned_pages;
+		progress_param[2] = total_index_pages;
+		progress_param[3] = scanned_index_pages;
+
+		pgstat_report_progress(progress_param, 4, progress_param_float, 0,
+								progress_message, 1);
 	}
 
 	pfree(frozen);
@@ -1093,11 +1153,30 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		/* Log cleanup info before we touch indexes */
 		vacuum_log_cleanup_info(onerel, vacrelstats);
 
+		/*
+		 * If passes through indexes exceed 1 add
+		 * pages equal to rel_index_pages to the count of
+		 * total pages to be scanned.
+		 */
+		if (vacrelstats->num_index_scans >= 1)
+			total_index_pages = total_index_pages + rel_index_pages;
+
 		/* Remove index entries */
 		for (i = 0; i < nindexes; i++)
+		{
 			lazy_vacuum_index(Irel[i],
 							  &indstats[i],
 							  vacrelstats);
+			scanned_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+			/* Report progress to the statistics collector */
+			progress_param[0] = total_heap_pages;
+			progress_param[1] = vacrelstats->scanned_pages;
+			progress_param[2] = total_index_pages;
+			progress_param[3] = scanned_index_pages;
+
+			pgstat_report_progress(progress_param, 4, progress_param_float, 0,
+								progress_message, 1);
+		}
 		/* Remove tuples from heap */
 		lazy_vacuum_heap(onerel, vacrelstats);
 		vacrelstats->num_index_scans++;
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index ab018c4..480b753 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -2851,6 +2851,55 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
 	pgstat_increment_changecount_after(beentry);
 }
 
+/* ---------------------------------------------
+ * Called from VACUUM  after every heap page scan or index scan
+ * to report progress
+ * ---------------------------------------------
+ */
+
+void
+pgstat_report_progress(uint32 *param1, int num_of_int, double *param2, int num_of_float,
+						char param3[PROGRESS_MESSAGE_LENGTH][N_PROGRESS_PARAM],
+						int num_of_string)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+	int i;
+
+	if (!beentry)
+		return;
+
+	if (!pgstat_track_activities)
+		return;
+
+	pgstat_increment_changecount_before(beentry);
+
+	for(i = 0; i < num_of_int; i++)
+	{
+		beentry->st_progress_param[i] = param1[i];
+	}
+	for (i = 0; i < num_of_float; i++)
+	{
+		beentry->st_progress_param_float[i] = param2[i];
+	}
+	for (i = 0; i < num_of_string; i++)
+	{
+		strcpy((char *)beentry->st_progress_message[i], param3[i]);
+	}
+	pgstat_increment_changecount_after(beentry);
+}
+
+void
+pgstat_report_activityflag(activity_flag)
+{
+	PgBackendStatus *beentry = MyBEEntry;
+	beentry->st_activity_flag = activity_flag;
+}
+void
+pgstat_reset_activityflag()
+{
+	PgBackendStatus *beentry = MyBEEntry;
+	beentry->st_activity_flag = 0;
+}
 /* ----------
  * pgstat_report_appname() -
  *
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index f7c9bf6..d1cec76 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -53,6 +53,7 @@ extern Datum pg_stat_get_function_self_time(PG_FUNCTION_ARGS);
 
 extern Datum pg_stat_get_backend_idset(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_activity(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_vacuum_progress(PG_FUNCTION_ARGS);
 extern Datum pg_backend_pid(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_pid(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_dbid(PG_FUNCTION_ARGS);
@@ -523,7 +524,110 @@ pg_stat_get_backend_idset(PG_FUNCTION_ARGS)
 		SRF_RETURN_DONE(funcctx);
 	}
 }
+/*
+ * Returns VACUUM progress values stored by each backend
+ * executing VACUUM.
+ */
+Datum
+pg_stat_get_vacuum_progress(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_PROGRESS_COLS	30
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+
+	/* 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);
+
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		Datum		values[PG_STAT_GET_PROGRESS_COLS];
+		bool		nulls[PG_STAT_GET_PROGRESS_COLS];
+		uint32		total_pages;
+		uint32		scanned_pages;
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+
+		MemSet(values, 0, sizeof(values));
+		MemSet(nulls, 0, sizeof(nulls));
+
+		local_beentry = pgstat_fetch_stat_local_beentry(curr_backend);
+		if (!local_beentry)
+			continue;
+		beentry = &local_beentry->backendStatus;
+
+		/* Report values for only those backends which are running VACUUM */
+		if(!beentry || beentry->st_activity_flag != ACTIVITY_IS_VACUUM)
+			continue;
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+		if(beentry->st_progress_message[0])
+			values[1] = CStringGetTextDatum(beentry->st_progress_message[0]);
+		else
+			nulls[1] = true;
+
+		total_pages = beentry->st_progress_param[0] + beentry->st_progress_param[2];
+		scanned_pages = beentry->st_progress_param[1] + beentry->st_progress_param[3];
+
+		/* Progress can only be viewed by role member */
+		if (has_privs_of_role(GetUserId(), beentry->st_userid))
+		{
+			values[2] = UInt32GetDatum(beentry->st_progress_param[0]);
+			values[3] = UInt32GetDatum(beentry->st_progress_param[1]);
+			values[4] = UInt32GetDatum(beentry->st_progress_param[2]);
+			values[5] = UInt32GetDatum(beentry->st_progress_param[3]);
+			values[6] = UInt32GetDatum(total_pages);
+			values[7] = UInt32GetDatum(scanned_pages);
+
+			if (total_pages != 0)
+				values[8] = Float8GetDatum(scanned_pages * 100 / total_pages);
+			else
+				nulls[8] = true;
+		}
+		else
+		{
+			values[2] = CStringGetTextDatum("<insufficient privilege>");
+			nulls[3] = true;
+			nulls[4] = true;
+			nulls[5] = true;
+			nulls[6] = true;
+			nulls[7] = true;
+			nulls[8] = true;
+		}
+
+		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+	}
+
+	/* clean up and return the tuplestore */
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
 /*
  * Returns activity of PG backends.
  */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index f688454..95afcdb 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2781,6 +2781,8 @@ DATA(insert OID = 1936 (  pg_stat_get_backend_idset		PGNSP PGUID 12 1 100 0 0 f
 DESCR("statistics: currently active backend IDs");
 DATA(insert OID = 2022 (  pg_stat_get_activity			PGNSP PGUID 12 1 100 0 0 f f f f f t s r 1 0 2249 "23" "{23,26,23,26,25,25,25,16,1184,1184,1184,1184,869,25,23,28,28,16,25,25,23,16,25}" "{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{pid,datid,pid,usesysid,application_name,state,query,waiting,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,ssl,sslversion,sslcipher,sslbits,sslcompression,sslclientdn}" _null_ _null_ pg_stat_get_activity _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active backends");
+DATA(insert OID = 3308 (  pg_stat_get_vacuum_progress			PGNSP PGUID 12 1 1 0 0 f f f f f t s r 0 0 2249 "" "{23,25,23,23,23,23,23,23,701}" "{o,o,o,o,o,o,o,o,o}" "{pid,table_name,total_heap_pages,scanned_heap_pages,total_index_pages,scanned_index_pages,total_pages,scanned_pages,percent_complete}" _null_ _null_ pg_stat_get_vacuum_progress _null_ _null_ _null_ ));
+DESCR("statistics: information about progress of backends running VACUUM");
 DATA(insert OID = 3099 (  pg_stat_get_wal_senders	PGNSP PGUID 12 1 10 0 0 f f f f f t s r 0 0 2249 "" "{23,25,3220,3220,3220,3220,23,25}" "{o,o,o,o,o,o,o,o}" "{pid,state,sent_location,write_location,flush_location,replay_location,sync_priority,sync_state}" _null_ _null_ pg_stat_get_wal_senders _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active replication");
 DATA(insert OID = 2026 (  pg_backend_pid				PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 23 "" _null_ _null_ _null_ _null_ _null_ pg_backend_pid _null_ _null_ _null_ ));
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 9ecc163..43ba610 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -205,6 +205,8 @@ typedef struct PgStat_MsgHdr
 #define PGSTAT_MAX_MSG_SIZE 1000
 #define PGSTAT_MSG_PAYLOAD	(PGSTAT_MAX_MSG_SIZE - sizeof(PgStat_MsgHdr))
 
+#define N_PROGRESS_PARAM 10
+#define PROGRESS_MESSAGE_LENGTH 30
 
 /* ----------
  * PgStat_MsgDummy				A dummy message, ignored by the collector
@@ -776,6 +778,20 @@ typedef struct PgBackendStatus
 
 	/* current command string; MUST be null-terminated */
 	char	   *st_activity;
+
+	/*
+	 * Information about the progress of activity/command being run by the backend.
+	 * The progress parameters indicate progress of a command. Different
+	 * commands can report different number of parameters of each type.
+	 *
+	 * st_activity_flag reports which activity/command is being run by the backend.
+	 * This is used in the SQL callable functions to display progress values
+	 * for respective commands.
+	 */
+	uint32		st_activity_flag;
+	uint32		st_progress_param[N_PROGRESS_PARAM];
+	double		st_progress_param_float[N_PROGRESS_PARAM];
+	char		st_progress_message[PROGRESS_MESSAGE_LENGTH][N_PROGRESS_PARAM];
 } PgBackendStatus;
 
 /*
@@ -815,6 +831,7 @@ typedef struct PgBackendStatus
 		save_changecount = beentry->st_changecount; \
 	} while (0)
 
+#define ACTIVITY_IS_VACUUM 0x01
 /* ----------
  * LocalPgBackendStatus
  *
@@ -928,6 +945,12 @@ extern void pgstat_initialize(void);
 extern void pgstat_bestart(void);
 
 extern void pgstat_report_activity(BackendState state, const char *cmd_str);
+extern void pgstat_report_activityflag(int activity_flag);
+extern void pgstat_reset_activityflag();
+extern void pgstat_report_progress(uint32 *param1, int num_of_int, double *param2,
+									int int_num_float,
+									char param3[PROGRESS_MESSAGE_LENGTH][N_PROGRESS_PARAM],
+									int num_of_string);
 extern void pgstat_report_tempfile(size_t filesize);
 extern void pgstat_report_appname(const char *appname);
 extern void pgstat_report_xact_timestamp(TimestampTz tstamp);
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 80374e4..8404842 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1848,6 +1848,16 @@ pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
     pg_stat_all_tables.autoanalyze_count
    FROM pg_stat_all_tables
   WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
+pg_stat_vacuum_progress| SELECT s.pid,
+    s.table_name,
+    s.total_heap_pages,
+    s.scanned_heap_pages,
+    s.total_index_pages,
+    s.scanned_index_pages,
+    s.total_pages,
+    s.scanned_pages,
+    s.percent_complete
+   FROM pg_stat_get_vacuum_progress() s(pid, table_name, total_heap_pages, scanned_heap_pages, total_index_pages, scanned_index_pages, total_pages, scanned_pages, percent_complete);
 pg_stat_xact_all_tables| SELECT c.oid AS relid,
     n.nspname AS schemaname,
     c.relname,
#92Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Syed, Rahila (#91)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2015/10/29 23:22, Syed, Rahila wrote:

Please find attached an updated patch.

Thanks for the v6. A few quick comments:

- duplicate_oids error in HEAD.

- a compiler warning:

pgstat.c:2898: warning: no previous prototype for �pgstat_reset_activityflag�

To fix that use void for empty parameter list -

-extern void pgstat_reset_activityflag();
+extern void pgstat_reset_activityflag(void);

One more change you could do is 's/activityflag/activity_flag/g', which I
guess is a naming related guideline in place.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#93Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Amit Langote (#92)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello, I have some random comments on this patch addition to
Amit's comments.

- Type of the flag of vacuum activity.

ACTIVITY_IS_VACUUM is the alone entry in the enum, and the
variable to store it is named as *flag. If you don't have any
plan to extend this information, the name of this variable would
seems better to be something like pgstat_report_vacuum_running
and in the type of boolean.

- Type of st_progress_param and so.

The variable st_progress_param has very generic name but as
looking the pg_stat_get_vacuum_progress, every elements of it is
in a definite role. If so, the variable should be a struct.

st_progress_param_float is currently totally useless.

- Definition of progress_message.

The definition of progress_message in lazy_scan_heap is "char
[PROGRESS_MESSAGE_LENGTH][N_PROGRESS_PARAM]" which looks to be
inversed. The following snprintf,

| snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s", schemaname);

certainly destroys the data already stored in it if any.

- snprintf()

You are so carefully to use snprintf,

+    snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s", schemaname);
+	strcat(progress_message[0],".");
+	strcat(progress_message[0],relname);

but the strcats following ruin it.

- Calculation of total_heap_pages in lazy_scan_heap.

The current code subtracts the number of blocks when
skipping_all_visible_blocks is set in two places. But I think
it is enough to decrement when skipping.

I'll be happy if this can be of any help.

regards,

At Tue, 10 Nov 2015 14:44:23 +0900, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote in <56418437.5080003@lab.ntt.co.jp>

Thanks for the v6. A few quick comments:

- duplicate_oids error in HEAD.

- a compiler warning:

pgstat.c:2898: warning: no previous prototype for ‘pgstat_reset_activityflag’

To fix that use void for empty parameter list -

-extern void pgstat_reset_activityflag();
+extern void pgstat_reset_activityflag(void);

One more change you could do is 's/activityflag/activity_flag/g', which I
guess is a naming related guideline in place.

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#94Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Syed, Rahila (#91)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2015/10/29 23:22, Syed, Rahila wrote:

Please find attached an updated patch.

A few more comments on v6:

relname = RelationGetRelationName(onerel);
+	schemaname = get_namespace_name(RelationGetNamespace(onerel));
ereport(elevel,
(errmsg("vacuuming \"%s.%s\"",
get_namespace_name(RelationGetNamespace(onerel)),
relname)));
+    snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s", schemaname);
+	strcat(progress_message[0],".");
+	strcat(progress_message[0],relname);

How about the following instead -

+ snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s",
+ 					generate_relation_name(onerel));
if (next_not_all_visible_block >= SKIP_PAGES_THRESHOLD)
+	{
skipping_all_visible_blocks = true;
+		if(!scan_all)
+			total_heap_pages = total_heap_pages - next_not_all_visible_block;
+	}
else
skipping_all_visible_blocks = false;

...

*/
if (next_not_all_visible_block - blkno > SKIP_PAGES_THRESHOLD)
+			{
skipping_all_visible_blocks = true;
+				if(!scan_all)
+					total_heap_pages = total_heap_pages - (next_not_all_visible_block - blkno);
+			}

Fujii-san's review comment about these code blocks does not seem to be
addressed. He suggested to keep total_heap_pages fixed while adding number
of skipped pages to that of scanned pages. For that, why not add a
scanned_heap_pages variable instead of using vacrelstats->scanned_pages.

+		if (has_privs_of_role(GetUserId(), beentry->st_userid))
+		{
+			values[2] = UInt32GetDatum(beentry->st_progress_param[0]);
+			values[3] = UInt32GetDatum(beentry->st_progress_param[1]);
+			values[4] = UInt32GetDatum(beentry->st_progress_param[2]);
+			values[5] = UInt32GetDatum(beentry->st_progress_param[3]);
+			values[6] = UInt32GetDatum(total_pages);
+			values[7] = UInt32GetDatum(scanned_pages);
+
+			if (total_pages != 0)
+				values[8] = Float8GetDatum(scanned_pages * 100 / total_pages);
+			else
+				nulls[8] = true;
+		}
+		else
+		{
+			values[2] = CStringGetTextDatum("<insufficient privilege>");
+			nulls[3] = true;
+			nulls[4] = true;
+			nulls[5] = true;
+			nulls[6] = true;
+			nulls[7] = true;
+			nulls[8] = true;
+		}

This is most likely not correct, that is, putting a text datum into
supposedly int4 column. I see this when I switch to a unprivileged user:

pgbench=# \x
pgbench=# \c - other
pgbench=> SELECT * FROM pg_stat_vacuum_progress;
-[ RECORD 1 ]-------+------------------------
pid | 20395
table_name | public.pgbench_accounts
total_heap_pages | 44895488
scanned_heap_pages |
total_index_pages |
scanned_index_pages |
total_pages |
scanned_pages |
percent_complete |

I'm not sure if applying the privilege check for columns of
pg_stat_vacuum_progress is necessary, but I may be wrong.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#95Amit Langote
amitlangote09@gmail.com
In reply to: Amit Langote (#94)
Re: [PROPOSAL] VACUUM Progress Checker.

On Tue, Nov 10, 2015 at 5:02 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2015/10/29 23:22, Syed, Rahila wrote:
How about the following instead -

+ snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s",
+                                       generate_relation_name(onerel));

That was a useless suggestion, sorry. It still could be rewritten as -

+ snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s.%s",
+                          get_namespace_name(RelationGetNamespace(rel)),
+                          relname);

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#96Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Amit Langote (#94)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2015/11/10 17:02, Amit Langote wrote:

On 2015/10/29 23:22, Syed, Rahila wrote:

Please find attached an updated patch.

A few more comments on v6:

I backed up a little, studied the proposal and the patch in little some
more detail. Here are still more comments -

Going through the thread, it seems there are following problems being solved -

1) General purpose interface for (maintenance?) commands to report a set
of internal values of different internal types using shared memory as IPC;
values are exposed to users as is and/or some derived values like
percent_done using view/functions

2) For a start, instrumenting lazy vacuum to report such internal values

And maybe,

3) Estimating the amount of work to be done and time required based on
historical statistics like n_dead_tup, visibility map and run-time
resources available like maintenance_work_mem

Latest version of the patch (v6) implements 1 and 2. The code is starting
to look good though see some comments below.

* Regarding (2): Some random thoughts on the patch and in general -

For lazy vacuum, lazy_scan_heap() seems like the best place which can
provide granular progress report in terms of the heap block number (of
total number of heap blocks in the relation) currently undergoing
per-block pass 1 processing. About pass 2, ie, lazy_index_vacuum() and
lazy_vacuum_heap(), I don't see how we can do better than reporting its
progress only after finishing all of it without any finer-grained
instrumentation. They are essentially block-box as far as the proposed
instrumentation approach is concerned. Being able to report progress per
index seems good but as a whole, a user would have to wait arbitrarily
long before numbers move forward. We might as well just report a bool
saying we're about to enter a potentially time-consuming index vacuum
round with possibly multiple indexes followed by lazy_vacuum_heap()
processing. Additionally, we can report the incremented count of the
vacuuming round (pass 2) once we are through it. So, we'd report two
values viz. waiting_vacuum_pass (bool) and num_vacuum_pass (int). The
former is reported twice - 'true' as we are about to begin the round and
'false' once done. We can keep the total_index_pages (counting all
indexes) and index_pages_done as the patch currently reports. The latter
moves forward for every index we finish processing, and also should be
reset for every pass 2 round. Note that we can leave them out of
percent_done of overall vacuum progress. Until we have a good solution for
number (3) above, it seems to difficult to incorporate index pages into
overall progress.

As someone pointed out upthread, the final heap truncate phase can take
arbitrarily long and is outside the scope of lazy_scan_heap() to
instrument. Perhaps a bool, say, waiting_heap_trunc could be reported for
the same. Note that, it would have to be reported from lazy_vacuum_rel().

I spotted a potential oversight regarding report of scanned_pages. It
seems pages that are skipped because of not getting a pin, being new,
being empty could be left out of the progress equation.

* Regarding (1): These are mostly code comments -

IMHO, float progress parameters (st_progress_param_float[]) can be taken
out. They are currently unused and it's unlikely that some command would
want to report them. OTOH, as suggested in above paragraph, why not have
bool parameters? In addition to a few I mentioned in the context of lazy
vacuum instrumentation, it seems likely that they would be useful for
other commands, too.

Instead of st_activity_flag, how about st_command and calling
ACTIVITY_IS_VACUUM, say, COMMAND_LAZY_VACUUM?
pgstat_report_activity_flag() then would become pgstat_report_command().

Like floats, I would think we could take out st_progress_message[][]. I
see that it is currently used to report table name. For that, we might as
well add a single st_command_target[NAMEDATALEN] string which is set at
the beginning of command processing using, say,
pgstat_report_command_target(). It stores the name of relation/object that
the command is going to work on.

Maybe, we don't need each command to proactively pgstat_reset_command().
That would be similar to how st_activity is not proactively cleared but is
rather reset by the next query/command or when some other backend uses the
shared memory slot. Also, we could have a SQL function
pg_stat_reset_local_progress() which clears the st_command after which the
backend is no longer shown in the progress view.

I think it would be better to report only changed parameter arrays when
performing pgstat_report_progress(). So, if we have following shared
memory progress parameters and the reporting function signature:

typedef struct PgBackendStatus
{
...
uint16 st_command;
char st_command[NAMEDATALEN];
uint32 st_progress_uint32_param[N_PROGRESS_PARAM];
bool st_progress_bool_param[N_PROGRESS_PARAM];
} PgBackendStatus;

void pgstat_report_progress(uint32 *uint32_param, int num_uint32_param,
bool *bool_param, int num_bool_param);

and if we need to report a bool parameter change, say, waiting_vacuum_pass
in lazy_scan_heap(), we do -

pgstat_report_progress(NULL, 0, progress_bool_param, 2);

That is, no need for pgstat_report_progress() to overwrite the shared
st_progress_uint32_param if none of its members have changed since the
last report.

Currently, ACTIVITY_IS_VACUUM is reported even for VACOPT_ANALYZE and
VACOPT_FULL commands. They are not covered by lazy_scan_heap(), so such
commands are needlessly shown in the progress view with 0s in most of the
fields.

Regarding pg_stat_get_vacuum_progress(): I think a backend can simply be
skipped if (!has_privs_of_role(GetUserId(), beentry->st_userid)) (cannot
put that in plain English, :))

Please add documentation for the newly added view and SQL functions, if any.

I'm marking this as "Waiting on author" in the commitfest app. Also, let's
hear from more people.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#97Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Amit Langote (#96)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2015/11/19 16:18, Amit Langote wrote:

I'm marking this as "Waiting on author" in the commitfest app. Also, let's
hear from more people.

Well, it seems Michael beat me to it.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#98Michael Paquier
michael.paquier@gmail.com
In reply to: Amit Langote (#97)
Re: [PROPOSAL] VACUUM Progress Checker.

On Thu, Nov 19, 2015 at 4:30 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2015/11/19 16:18, Amit Langote wrote:

I'm marking this as "Waiting on author" in the commitfest app. Also, let's
hear from more people.

Well, it seems Michael beat me to it.

Yeah, some other folks provided feedback that's why I did it.
@Rahila: are you planning more work on this patch? It seems that at
this point we're waiting for you. If not, and because I have a couple
of times waited for long VACUUM jobs to finish on some relations
without having much information about their progress, I would be fine
to spend time hacking this thing. That's up to you.
Regards,
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#99Rahila Syed
rahilasyed90@gmail.com
In reply to: Michael Paquier (#98)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello Michael,

I am planning to continue contributing to this feature in any way be it by
reviewing the patch or making one. Though I haven't been able to reply to
the comments or post an updated patch lately. I plan to do that soon.

Thank you,
Rahila

On Thu, Nov 19, 2015 at 1:09 PM, Michael Paquier <michael.paquier@gmail.com>
wrote:

Show quoted text

On Thu, Nov 19, 2015 at 4:30 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2015/11/19 16:18, Amit Langote wrote:

I'm marking this as "Waiting on author" in the commitfest app. Also,

let's

hear from more people.

Well, it seems Michael beat me to it.

Yeah, some other folks provided feedback that's why I did it.
@Rahila: are you planning more work on this patch? It seems that at
this point we're waiting for you. If not, and because I have a couple
of times waited for long VACUUM jobs to finish on some relations
without having much information about their progress, I would be fine
to spend time hacking this thing. That's up to you.
Regards,
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#100Jim Nasby
Jim.Nasby@BlueTreble.com
In reply to: Amit Langote (#96)
Re: [PROPOSAL] VACUUM Progress Checker.

On 11/19/15 1:18 AM, Amit Langote wrote:

1) General purpose interface for (maintenance?) commands to report a set

I'm surprised no one has picked up on using this for DML. Certainly
anyone works with ETL processes would love to be able to get some clue
on the status of a long running query...

About pass 2, ie, lazy_index_vacuum() and
lazy_vacuum_heap(), I don't see how we can do better than reporting its
progress only after finishing all of it without any finer-grained
instrumentation. They are essentially block-box as far as the proposed
instrumentation approach is concerned. Being able to report progress per
index seems good but as a whole, a user would have to wait arbitrarily
long before numbers move forward. We might as well just report a bool
saying we're about to enter a potentially time-consuming index vacuum
round with possibly multiple indexes followed by lazy_vacuum_heap()
processing. Additionally, we can report the incremented count of the
vacuuming round (pass 2) once we are through it.

Another option is to provide the means for the index scan routines to
report their progress. Maybe every index AM won't use it, but it'd
certainly be a lot better than staring at a long_running boolean.

Note that we can leave them out of
percent_done of overall vacuum progress. Until we have a good solution for
number (3) above, it seems to difficult to incorporate index pages into
overall progress.

IMHO we need to either put a big caution sign on any % estimate that it
could be wildly off, or just forgo it completely for now. I'll bet that
if we don't provide it some enterprising users will figure out the best
way to do this (similar to how the bloat estimate query has evolved over
time).

Even if we never get a % done indicator, just being able to see what
'position' a command is at will be very valuable.

As someone pointed out upthread, the final heap truncate phase can take
arbitrarily long and is outside the scope of lazy_scan_heap() to
instrument. Perhaps a bool, say, waiting_heap_trunc could be reported for
the same. Note that, it would have to be reported from lazy_vacuum_rel().

ISTM this is similar to the problem of reporting index status, namely
that a progress reporting method needs to accept reports from multiple
places in the code.
--
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Experts in Analytics, Data Architecture and PostgreSQL
Data in Trouble? Get it in Treble! http://BlueTreble.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#101Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Jim Nasby (#100)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2015/11/20 0:57, Jim Nasby wrote:

On 11/19/15 1:18 AM, Amit Langote wrote:

1) General purpose interface for (maintenance?) commands to report a set

I'm surprised no one has picked up on using this for DML. Certainly anyone
works with ETL processes would love to be able to get some clue on the
status of a long running query...

Instrumenting query execution for progress info would be a complex beast
though. Also, what kind of reporting interface it would require is also
not clear, at least to me. Jan Urbanski's PGCon presentation[1]http://www.pgcon.org/2013/schedule/events/576.en.html is a good
source on the matter I discovered in this thread, thanks! But IMHO, for
now, it would be worthwhile to focus our resources on the modest goal of
implementing a reporting interface for utility commands. Sure it would be
nice to investigate how much the requirements of the two overlap.

About pass 2, ie, lazy_index_vacuum() and
lazy_vacuum_heap(), I don't see how we can do better than reporting its
progress only after finishing all of it without any finer-grained
instrumentation. They are essentially block-box as far as the proposed
instrumentation approach is concerned. Being able to report progress per
index seems good but as a whole, a user would have to wait arbitrarily
long before numbers move forward. We might as well just report a bool
saying we're about to enter a potentially time-consuming index vacuum
round with possibly multiple indexes followed by lazy_vacuum_heap()
processing. Additionally, we can report the incremented count of the
vacuuming round (pass 2) once we are through it.

Another option is to provide the means for the index scan routines to
report their progress. Maybe every index AM won't use it, but it'd
certainly be a lot better than staring at a long_running boolean.

The boolean would be a workaround for sure. I'm also slightly tempted by
the idea of instrumenting vacuum scans of individual index AM's bulkdelete
methods. One precedent is how vacuum_delay_point() are sprinkled around in
the code. Another problem to solve would be to figure out how to pass
progress parameters around - via some struct or could they be globals just
like VacuumCost* variables are...

Note that we can leave them out of
percent_done of overall vacuum progress. Until we have a good solution for
number (3) above, it seems to difficult to incorporate index pages into
overall progress.

IMHO we need to either put a big caution sign on any % estimate that it
could be wildly off, or just forgo it completely for now. I'll bet that if
we don't provide it some enterprising users will figure out the best way
to do this (similar to how the bloat estimate query has evolved over time).

Even if we never get a % done indicator, just being able to see what
'position' a command is at will be very valuable.

Agreed. If we provide enough information in whatever view we choose to
expose, that would be a good start.

As someone pointed out upthread, the final heap truncate phase can take
arbitrarily long and is outside the scope of lazy_scan_heap() to
instrument. Perhaps a bool, say, waiting_heap_trunc could be reported for
the same. Note that, it would have to be reported from lazy_vacuum_rel().

ISTM this is similar to the problem of reporting index status, namely that
a progress reporting method needs to accept reports from multiple places
in the code.

Yes.

Thanks,
Amit

[1]: http://www.pgcon.org/2013/schedule/events/576.en.html

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#102Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#96)
Re: [PROPOSAL] VACUUM Progress Checker.

On Thu, Nov 19, 2015 at 2:18 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

As someone pointed out upthread, the final heap truncate phase can take
arbitrarily long and is outside the scope of lazy_scan_heap() to
instrument. Perhaps a bool, say, waiting_heap_trunc could be reported for
the same. Note that, it would have to be reported from lazy_vacuum_rel().

I don't think reporting booleans is a very good idea. It's better to
report that some other way, like use one of the strings to report a
"phase" of processing that we're currently performing.

IMHO, float progress parameters (st_progress_param_float[]) can be taken
out. They are currently unused and it's unlikely that some command would
want to report them.

If they are not used, they shouldn't be included in this patch, but we
should be open to adding them later if it proves useful.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#103Jim Nasby
Jim.Nasby@BlueTreble.com
In reply to: Amit Langote (#101)
Re: [PROPOSAL] VACUUM Progress Checker.

On 11/19/15 7:29 PM, Amit Langote wrote:

Another option is to provide the means for the index scan routines to

report their progress. Maybe every index AM won't use it, but it'd
certainly be a lot better than staring at a long_running boolean.

The boolean would be a workaround for sure. I'm also slightly tempted by
the idea of instrumenting vacuum scans of individual index AM's bulkdelete
methods. One precedent is how vacuum_delay_point() are sprinkled around in
the code. Another problem to solve would be to figure out how to pass
progress parameters around - via some struct or could they be globals just
like VacuumCost* variables are...

It just occurred to me that we could do the instrumentation in
lazy_tid_reaped(). It might seem bad to do in increment for every tuple
in an index, but we're already doing a bsearch over the dead tuple list.
Presumably that's going to be a lot more expensive than an increment
operation.
--
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Experts in Analytics, Data Architecture and PostgreSQL
Data in Trouble? Get it in Treble! http://BlueTreble.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#104Robert Haas
robertmhaas@gmail.com
In reply to: Jim Nasby (#103)
Re: [PROPOSAL] VACUUM Progress Checker.

On Sat, Nov 21, 2015 at 12:38 AM, Jim Nasby <Jim.Nasby@bluetreble.com> wrote:

On 11/19/15 7:29 PM, Amit Langote wrote:

Another option is to provide the means for the index scan routines to

report their progress. Maybe every index AM won't use it, but it'd
certainly be a lot better than staring at a long_running boolean.

The boolean would be a workaround for sure. I'm also slightly tempted by
the idea of instrumenting vacuum scans of individual index AM's bulkdelete
methods. One precedent is how vacuum_delay_point() are sprinkled around in
the code. Another problem to solve would be to figure out how to pass
progress parameters around - via some struct or could they be globals just
like VacuumCost* variables are...

It just occurred to me that we could do the instrumentation in
lazy_tid_reaped(). It might seem bad to do in increment for every tuple in
an index, but we're already doing a bsearch over the dead tuple list.
Presumably that's going to be a lot more expensive than an increment
operation.

I think the cost of doing an increment there would be negligible. I'm
not quite sure whether that's the right place to instrument - though
it looks like it might be - but I think the cost of ++something in
that function isn't gonna be a problem at all.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#105Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Jim Nasby (#103)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2015/11/21 14:38, Jim Nasby wrote:

On 11/19/15 7:29 PM, Amit Langote wrote:

Another option is to provide the means for the index scan routines to

report their progress. Maybe every index AM won't use it, but it'd
certainly be a lot better than staring at a long_running boolean.

The boolean would be a workaround for sure. I'm also slightly tempted by
the idea of instrumenting vacuum scans of individual index AM's bulkdelete
methods. One precedent is how vacuum_delay_point() are sprinkled around in
the code. Another problem to solve would be to figure out how to pass
progress parameters around - via some struct or could they be globals just
like VacuumCost* variables are...

It just occurred to me that we could do the instrumentation in
lazy_tid_reaped(). It might seem bad to do in increment for every tuple in
an index, but we're already doing a bsearch over the dead tuple list.
Presumably that's going to be a lot more expensive than an increment
operation.

Just to clarify, does this mean we report index vacuum progress in terms
of index items processed (not pages)? If so, how do we get total number of
index items to process (presumably across all indexes) for a given phase 2
round? As a context, we'd report phase 1 progress in terms of heap pages
processed of total heap pages.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#106Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#102)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2015/11/21 5:46, Robert Haas wrote:

On Thu, Nov 19, 2015 at 2:18 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

As someone pointed out upthread, the final heap truncate phase can take
arbitrarily long and is outside the scope of lazy_scan_heap() to
instrument. Perhaps a bool, say, waiting_heap_trunc could be reported for
the same. Note that, it would have to be reported from lazy_vacuum_rel().

I don't think reporting booleans is a very good idea. It's better to
report that some other way, like use one of the strings to report a
"phase" of processing that we're currently performing.

Yeah, that might be better. One possible downside of booleans I didn't
foresee is that too many of them might clutter the progress view. What
would've been the names of boolean columns in the progress view are better
reported as strings as the value of a single column, as you seem to suggest.

IMHO, float progress parameters (st_progress_param_float[]) can be taken
out. They are currently unused and it's unlikely that some command would
want to report them.

If they are not used, they shouldn't be included in this patch, but we
should be open to adding them later if it proves useful.

Certainly.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#107Jim Nasby
Jim.Nasby@BlueTreble.com
In reply to: Amit Langote (#105)
Re: [PROPOSAL] VACUUM Progress Checker.

On 11/24/15 2:02 AM, Amit Langote wrote:

It just occurred to me that we could do the instrumentation in

lazy_tid_reaped(). It might seem bad to do in increment for every tuple in
an index, but we're already doing a bsearch over the dead tuple list.
Presumably that's going to be a lot more expensive than an increment
operation.

Just to clarify, does this mean we report index vacuum progress in terms
of index items processed (not pages)? If so, how do we get total number of
index items to process (presumably across all indexes) for a given phase 2
round? As a context, we'd report phase 1 progress in terms of heap pages
processed of total heap pages.

You'd get it from pg_class.reltuples for each index. Since all index
vacuuming is done strictly on a per-index-tuple basis, that's probably
the most accurate way to do it anyway.

Also, while it might be interesting to look at the total number of index
tuples, I think it's probably best to always report on a per-index
basis, as well as which index is being processed. I suspect there could
be a very large variance of tuple processing speed for different index
types. Eventually it might be worth it to allow index AMs to provide
their own vacuuming feedback, but I think that's way out of scope for
this patch. :)
--
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Experts in Analytics, Data Architecture and PostgreSQL
Data in Trouble? Get it in Treble! http://BlueTreble.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#108Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Jim Nasby (#107)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2015/11/25 9:32, Jim Nasby wrote:

On 11/24/15 2:02 AM, Amit Langote wrote:

Just to clarify, does this mean we report index vacuum progress in terms
of index items processed (not pages)? If so, how do we get total number of
index items to process (presumably across all indexes) for a given phase 2
round? As a context, we'd report phase 1 progress in terms of heap pages
processed of total heap pages.

You'd get it from pg_class.reltuples for each index. Since all index
vacuuming is done strictly on a per-index-tuple basis, that's probably the
most accurate way to do it anyway.

Important to remember though that the reltuples would be latest as of the
last VACUUM/ANALYZE.

Also, while it might be interesting to look at the total number of index
tuples, I think it's probably best to always report on a per-index basis,
as well as which index is being processed. I suspect there could be a very
large variance of tuple processing speed for different index types.
Eventually it might be worth it to allow index AMs to provide their own
vacuuming feedback, but I think that's way out of scope for this patch. :)

Agreed.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#109Jim Nasby
Jim.Nasby@BlueTreble.com
In reply to: Amit Langote (#108)
Re: [PROPOSAL] VACUUM Progress Checker.

On 11/24/15 7:02 PM, Amit Langote wrote:

You'd get it from pg_class.reltuples for each index. Since all index

vacuuming is done strictly on a per-index-tuple basis, that's probably the
most accurate way to do it anyway.

Important to remember though that the reltuples would be latest as of the
last VACUUM/ANALYZE.

True, but in cases where you care about monitoring a vacuum I suspect
it'll be close enough.

Might be worth a little extra effort to handle the 0 case though. If you
really wanted to get fancy you could see how the current heap
tuples/page count compares to reltuples/relpages from pg_class for the
heap... but I suspect that's pretty serious overkill.
--
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Experts in Analytics, Data Architecture and PostgreSQL
Data in Trouble? Get it in Treble! http://BlueTreble.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#110Rahila Syed
rahilasyed90@gmail.com
In reply to: Jim Nasby (#109)
1 attachment(s)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

Thank you for your comments.
Please find attached patch addressing following comments ,

- duplicate_oids error in HEAD.

Check.

- a compiler warning:
pgstat.c:2898: warning: no previous prototype for

‘pgstat_reset_activityflag’
Check.

One more change you could do is 's/activityflag/activity_flag/g',

Check.

Type of the flag of vacuum activity.

The flag variable is an integer to incorporate more commands in future.

Type of st_progress_param and so.

st_progress_param is also given a generic name to incorporate different
parameters reported from various commands.

st_progress_param_float is currently totally useless.

Float parameter has currently been removed from the patch.

Definition of progress_message.
The definition of progress_message in lazy_scan_heap is "char
[PROGRESS_MESSAGE_LENGTH][N_PROGRESS_PARAM]" which looks to be
inversed.

Corrected.

The current code subtracts the number of blocks when
skipping_all_visible_blocks is set in two places. But I think
it is enough to decrement when skipping.

In both the places, the pages are being skipped hence the total count was
decremented.

He suggested to keep total_heap_pages fixed while adding number
of skipped pages to that of scanned pages.

This has been done in the attached.

snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s.%s",
get_namespace_name(RelationGetNamespace(rel)),
relname);

Check.

The previous implementation used to add total number of pages across all
indexes of a relation to total_index_pages in every scan of
indexes to account for total pages scanned. Thus, it was equal to number
of scans * total_index_pages.

In the attached patch, total_index_pages reflects total number of pages
across all indexes of a relation.
And the column to report passes through indexes (phase 2) has been added to
account for number of passes for index and heap vacuuming.
Number of scanned index pages is reset at the end of each pass.
This makes the reporting clearer.
The percent complete does not account for index pages. It just denotes
percentage of heap scanned.

Spotted a potential oversight regarding report of scanned_pages. It
seems pages that are skipped because of not getting a pin, being new,
being empty could be left out of the progress equation.

Corrected.

It's better to
report that some other way, like use one of the strings to report a
"phase" of processing that we're currently performing.

Has been included in the attached.

Some more comments need to be addressed which include name change of
activity flag, reporting only changed parameters to shared memory,
ACTIVITY_IS_VACUUM flag being set unnecessarily for ANALYZE and FULL
commands ,documentation for new view.
Also, finer grain reporting from indexes and heap truncate phase is yet to
be incorporated into the patch

Thank you,
Rahila Syed

Attachments:

Vacuum_progress_checker_v7.patchtext/x-patch; charset=US-ASCII; name=Vacuum_progress_checker_v7.patchDownload
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ccc030f..d53833e 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -631,6 +631,19 @@ CREATE VIEW pg_stat_activity AS
     WHERE S.datid = D.oid AND
             S.usesysid = U.oid;
 
+CREATE VIEW pg_stat_vacuum_progress AS
+    SELECT
+			S.pid,
+			S.table_name,
+			S.phase,
+			S.total_heap_pages,
+			S.scanned_heap_pages,
+            S.percent_complete,
+            S.total_index_pages,
+            S.scanned_index_pages,
+            S.index_scan_count
+    FROM pg_stat_get_vacuum_progress() AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7c4ef58..e27a8f3 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -284,6 +284,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
 		VacuumPageMiss = 0;
 		VacuumPageDirty = 0;
 
+		pgstat_report_activity_flag(ACTIVITY_IS_VACUUM);
 		/*
 		 * Loop to process each selected relation.
 		 */
@@ -325,6 +326,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
 	{
 		in_vacuum = false;
 		VacuumCostActive = false;
+		pgstat_reset_activity_flag();
 		PG_RE_THROW();
 	}
 	PG_END_TRY();
@@ -355,6 +357,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
 		vac_update_datfrozenxid();
 	}
 
+	pgstat_reset_activity_flag();
 	/*
 	 * Clean up working storage --- note we must do this after
 	 * StartTransactionCommand, else we might be trying to delete the active
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index 2429889..1c74b51 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -439,9 +439,14 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			   Relation *Irel, int nindexes, bool scan_all)
 {
 	BlockNumber nblocks,
-				blkno;
+				blkno,
+				total_heap_pages,
+				scanned_heap_pages = 0,
+				total_index_pages = 0,
+				scanned_index_pages = 0;
 	HeapTupleData tuple;
 	char	   *relname;
+	char		*schemaname;
 	BlockNumber empty_pages,
 				vacuumed_pages;
 	double		num_tuples,
@@ -456,14 +461,20 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	bool		skipping_all_visible_blocks;
 	xl_heap_freeze_tuple *frozen;
 	StringInfoData buf;
+	uint32 progress_param[N_PROGRESS_PARAM];
+	char	progress_message[N_PROGRESS_PARAM][PROGRESS_MESSAGE_LENGTH];
+	const char *phase1="Scanning Heap";
+	const char *phase2="Vacuuming Index and Heap";
 
 	pg_rusage_init(&ru0);
 
 	relname = RelationGetRelationName(onerel);
+	schemaname = get_namespace_name(RelationGetNamespace(onerel));
 	ereport(elevel,
 			(errmsg("vacuuming \"%s.%s\"",
 					get_namespace_name(RelationGetNamespace(onerel)),
 					relname)));
+	snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s.%s", schemaname,relname);
 
 	empty_pages = vacuumed_pages = 0;
 	num_tuples = tups_vacuumed = nkeep = nunused = 0;
@@ -471,7 +482,11 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	indstats = (IndexBulkDeleteResult **)
 		palloc0(nindexes * sizeof(IndexBulkDeleteResult *));
 
-	nblocks = RelationGetNumberOfBlocks(onerel);
+	total_heap_pages = nblocks = RelationGetNumberOfBlocks(onerel);
+
+	for (i = 0; i < nindexes; i++)
+		total_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+
 	vacrelstats->rel_pages = nblocks;
 	vacrelstats->scanned_pages = 0;
 	vacrelstats->nonempty_pages = 0;
@@ -520,10 +535,15 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		vacuum_delay_point();
 	}
 	if (next_not_all_visible_block >= SKIP_PAGES_THRESHOLD)
+	{
 		skipping_all_visible_blocks = true;
+		if(!scan_all)
+			scanned_heap_pages = scanned_heap_pages + next_not_all_visible_block;
+	}
 	else
 		skipping_all_visible_blocks = false;
 
+	snprintf(progress_message[1], PROGRESS_MESSAGE_LENGTH, "%s", phase1);
 	for (blkno = 0; blkno < nblocks; blkno++)
 	{
 		Buffer		buf;
@@ -559,7 +579,11 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			 * following blocks.
 			 */
 			if (next_not_all_visible_block - blkno > SKIP_PAGES_THRESHOLD)
+			{
 				skipping_all_visible_blocks = true;
+				if(!scan_all)
+					scanned_heap_pages = scanned_heap_pages + next_not_all_visible_block;
+			}
 			else
 				skipping_all_visible_blocks = false;
 			all_visible_according_to_vm = false;
@@ -596,11 +620,25 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			/* Log cleanup info before we touch indexes */
 			vacuum_log_cleanup_info(onerel, vacrelstats);
 
+			snprintf(progress_message[1], PROGRESS_MESSAGE_LENGTH, "%s", phase2);
+
 			/* Remove index entries */
 			for (i = 0; i < nindexes; i++)
+			{
 				lazy_vacuum_index(Irel[i],
 								  &indstats[i],
 								  vacrelstats);
+				scanned_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+
+				/* Report progress to the statistics collector */
+				progress_param[0] = total_heap_pages;
+				progress_param[1] = scanned_heap_pages;
+				progress_param[2] = total_index_pages;
+				progress_param[3] = scanned_index_pages;
+				progress_param[4] = vacrelstats->num_index_scans + 1;
+
+				pgstat_report_progress(progress_param, 5, progress_message, 2);
+			}
 			/* Remove tuples from heap */
 			lazy_vacuum_heap(onerel, vacrelstats);
 
@@ -610,8 +648,12 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			 * valid.
 			 */
 			vacrelstats->num_dead_tuples = 0;
+			scanned_index_pages = 0;
 			vacrelstats->num_index_scans++;
 		}
+		snprintf(progress_message[1], PROGRESS_MESSAGE_LENGTH, "%s", phase1);
+		pgstat_report_progress(progress_param, 5, progress_message, 2);
+
 
 		/*
 		 * Pin the visibility map page in case we need to mark the page
@@ -637,6 +679,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			if (!scan_all)
 			{
 				ReleaseBuffer(buf);
+				scanned_heap_pages++;
 				vacrelstats->pinskipped_pages++;
 				continue;
 			}
@@ -657,6 +700,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			{
 				UnlockReleaseBuffer(buf);
 				vacrelstats->scanned_pages++;
+				scanned_heap_pages++;
 				vacrelstats->pinskipped_pages++;
 				continue;
 			}
@@ -666,6 +710,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		}
 
 		vacrelstats->scanned_pages++;
+		scanned_heap_pages++;
 
 		page = BufferGetPage(buf);
 
@@ -1062,8 +1107,22 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		 */
 		if (vacrelstats->num_dead_tuples == prev_dead_count)
 			RecordPageWithFreeSpace(onerel, blkno, freespace);
-	}
 
+		/*
+		 * Reporting vacuum progress to statistics collector
+		 */
+		if (blkno == nblocks - 1 && vacrelstats->num_dead_tuples == 0 && nindexes != 0
+			&& vacrelstats->num_index_scans == 0)
+			total_index_pages = 0;
+
+		progress_param[0] = total_heap_pages;
+		progress_param[1] = scanned_heap_pages;
+		progress_param[2] = total_index_pages;
+		progress_param[3] = scanned_index_pages;
+		progress_param[4] = vacrelstats->num_index_scans;
+
+		pgstat_report_progress(progress_param, 5, progress_message, 2);
+	}
 	pfree(frozen);
 
 	/* save stats for use later */
@@ -1093,16 +1152,29 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		/* Log cleanup info before we touch indexes */
 		vacuum_log_cleanup_info(onerel, vacrelstats);
 
+		snprintf(progress_message[1], PROGRESS_MESSAGE_LENGTH, "%s", phase2);
+
 		/* Remove index entries */
 		for (i = 0; i < nindexes; i++)
+		{
 			lazy_vacuum_index(Irel[i],
 							  &indstats[i],
 							  vacrelstats);
+			scanned_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+			/* Report progress to the statistics collector */
+			progress_param[0] = total_heap_pages;
+			progress_param[1] = scanned_heap_pages;
+			progress_param[2] = total_index_pages;
+			progress_param[3] = scanned_index_pages;
+			progress_param[4] = vacrelstats->num_index_scans + 1;
+
+			pgstat_report_progress(progress_param, 5, progress_message, 2);
+		}
 		/* Remove tuples from heap */
 		lazy_vacuum_heap(onerel, vacrelstats);
 		vacrelstats->num_index_scans++;
+		scanned_index_pages = 0;
 	}
-
 	/* Do post-vacuum cleanup and statistics update for each index */
 	for (i = 0; i < nindexes; i++)
 		lazy_cleanup_index(Irel[i], indstats[i], vacrelstats);
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index ab018c4..ff64959 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -2851,6 +2851,51 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
 	pgstat_increment_changecount_after(beentry);
 }
 
+/* ---------------------------------------------
+ * Called from VACUUM  after every heap page scan or index scan
+ * to report progress
+ * ---------------------------------------------
+ */
+
+void
+pgstat_report_progress(uint32 *param1, int num_of_int, char param2[N_PROGRESS_PARAM][PROGRESS_MESSAGE_LENGTH],
+						int num_of_string)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+	int i;
+
+	if (!beentry)
+		return;
+
+	if (!pgstat_track_activities)
+		return;
+
+	pgstat_increment_changecount_before(beentry);
+
+	for(i = 0; i < num_of_int; i++)
+	{
+		beentry->st_progress_param[i] = param1[i];
+	}
+
+	for (i = 0; i < num_of_string; i++)
+	{
+		strcpy((char *)beentry->st_progress_message[i], param2[i]);
+	}
+	pgstat_increment_changecount_after(beentry);
+}
+
+void
+pgstat_report_activity_flag(activity_flag)
+{
+	PgBackendStatus *beentry = MyBEEntry;
+	beentry->st_activity_flag = activity_flag;
+}
+void
+pgstat_reset_activity_flag()
+{
+	PgBackendStatus *beentry = MyBEEntry;
+	beentry->st_activity_flag = 0;
+}
 /* ----------
  * pgstat_report_appname() -
  *
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index f7c9bf6..d9f1c3a 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -53,6 +53,7 @@ extern Datum pg_stat_get_function_self_time(PG_FUNCTION_ARGS);
 
 extern Datum pg_stat_get_backend_idset(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_activity(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_vacuum_progress(PG_FUNCTION_ARGS);
 extern Datum pg_backend_pid(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_pid(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_dbid(PG_FUNCTION_ARGS);
@@ -523,7 +524,106 @@ pg_stat_get_backend_idset(PG_FUNCTION_ARGS)
 		SRF_RETURN_DONE(funcctx);
 	}
 }
+/*
+ * Returns VACUUM progress values stored by each backend
+ * executing VACUUM.
+ */
+Datum
+pg_stat_get_vacuum_progress(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_PROGRESS_COLS	30
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+
+	/* 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);
+
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		Datum		values[PG_STAT_GET_PROGRESS_COLS];
+		bool		nulls[PG_STAT_GET_PROGRESS_COLS];
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+
+		MemSet(values, 0, sizeof(values));
+		MemSet(nulls, 0, sizeof(nulls));
+
+		local_beentry = pgstat_fetch_stat_local_beentry(curr_backend);
+		if (!local_beentry)
+			continue;
+		beentry = &local_beentry->backendStatus;
+
+		/* Report values for only those backends which are running VACUUM */
+		if(!beentry || beentry->st_activity_flag != ACTIVITY_IS_VACUUM)
+			continue;
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+		if(beentry->st_progress_message[0])
+			values[1] = CStringGetTextDatum(beentry->st_progress_message[0]);
+		else
+			nulls[1] = true;
+
+
+		/* Progress can only be viewed by role member */
+		if (has_privs_of_role(GetUserId(), beentry->st_userid))
+		{
+			values[2] = CStringGetTextDatum(beentry->st_progress_message[1]);
+			values[3] = UInt32GetDatum(beentry->st_progress_param[0]);
+			values[4] = UInt32GetDatum(beentry->st_progress_param[1]);
+			if (beentry->st_progress_param[0] != 0)
+				values[5] = Float8GetDatum(beentry->st_progress_param[1] * 100 / beentry->st_progress_param[0]);
+			else
+				nulls[5] = true;
+			values[6] = UInt32GetDatum(beentry->st_progress_param[2]);
+			values[7] = UInt32GetDatum(beentry->st_progress_param[3]);
+			values[8] = UInt32GetDatum(beentry->st_progress_param[4]);
+
+		}
+		else
+		{
+			values[2] = CStringGetTextDatum("<insufficient privilege>");
+			nulls[3] = true;
+			nulls[4] = true;
+			nulls[5] = true;
+			nulls[6] = true;
+			nulls[7] = true;
+			nulls[8] = true;
+		}
+
+		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+	}
+
+	/* clean up and return the tuplestore */
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
 /*
  * Returns activity of PG backends.
  */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index d8640db..ae03c15 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2783,6 +2783,8 @@ DATA(insert OID = 1936 (  pg_stat_get_backend_idset		PGNSP PGUID 12 1 100 0 0 f
 DESCR("statistics: currently active backend IDs");
 DATA(insert OID = 2022 (  pg_stat_get_activity			PGNSP PGUID 12 1 100 0 0 f f f f f t s r 1 0 2249 "23" "{23,26,23,26,25,25,25,16,1184,1184,1184,1184,869,25,23,28,28,16,25,25,23,16,25}" "{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{pid,datid,pid,usesysid,application_name,state,query,waiting,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,ssl,sslversion,sslcipher,sslbits,sslcompression,sslclientdn}" _null_ _null_ pg_stat_get_activity _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active backends");
+DATA(insert OID = 3319 (  pg_stat_get_vacuum_progress			PGNSP PGUID 12 1 1 0 0 f f f f f t s r 0 0 2249 "" "{23,25,25,23,23,701,23,23,23}" "{o,o,o,o,o,o,o,o,o}" "{pid,table_name,phase,total_heap_pages,scanned_heap_pages,percent_complete,total_index_pages,scanned_index_pages,index_scan_count}" _null_ _null_ pg_stat_get_vacuum_progress _null_ _null_ _null_ ));
+DESCR("statistics: information about progress of backends running VACUUM");
 DATA(insert OID = 3099 (  pg_stat_get_wal_senders	PGNSP PGUID 12 1 10 0 0 f f f f f t s r 0 0 2249 "" "{23,25,3220,3220,3220,3220,23,25}" "{o,o,o,o,o,o,o,o}" "{pid,state,sent_location,write_location,flush_location,replay_location,sync_priority,sync_state}" _null_ _null_ pg_stat_get_wal_senders _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active replication");
 DATA(insert OID = 2026 (  pg_backend_pid				PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 23 "" _null_ _null_ _null_ _null_ _null_ pg_backend_pid _null_ _null_ _null_ ));
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 9ecc163..576ffbd 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -205,6 +205,8 @@ typedef struct PgStat_MsgHdr
 #define PGSTAT_MAX_MSG_SIZE 1000
 #define PGSTAT_MSG_PAYLOAD	(PGSTAT_MAX_MSG_SIZE - sizeof(PgStat_MsgHdr))
 
+#define N_PROGRESS_PARAM 10
+#define PROGRESS_MESSAGE_LENGTH 30
 
 /* ----------
  * PgStat_MsgDummy				A dummy message, ignored by the collector
@@ -776,6 +778,19 @@ typedef struct PgBackendStatus
 
 	/* current command string; MUST be null-terminated */
 	char	   *st_activity;
+
+	/*
+	 * Information about the progress of activity/command being run by the backend.
+	 * The progress parameters indicate progress of a command. Different
+	 * commands can report different number of parameters of each type.
+	 *
+	 * st_activity_flag reports which activity/command is being run by the backend.
+	 * This is used in the SQL callable functions to display progress values
+	 * for respective commands.
+	 */
+	uint32		st_activity_flag;
+	uint32		st_progress_param[N_PROGRESS_PARAM];
+	char		st_progress_message[N_PROGRESS_PARAM][PROGRESS_MESSAGE_LENGTH];
 } PgBackendStatus;
 
 /*
@@ -815,6 +830,7 @@ typedef struct PgBackendStatus
 		save_changecount = beentry->st_changecount; \
 	} while (0)
 
+#define ACTIVITY_IS_VACUUM 0x01
 /* ----------
  * LocalPgBackendStatus
  *
@@ -928,6 +944,10 @@ extern void pgstat_initialize(void);
 extern void pgstat_bestart(void);
 
 extern void pgstat_report_activity(BackendState state, const char *cmd_str);
+extern void pgstat_report_activity_flag(int activity_flag);
+extern void pgstat_reset_activity_flag(void);
+extern void pgstat_report_progress(uint32 *param1, int num_of_int, char param2[N_PROGRESS_PARAM][PROGRESS_MESSAGE_LENGTH],
+									int num_of_string);
 extern void pgstat_report_tempfile(size_t filesize);
 extern void pgstat_report_appname(const char *appname);
 extern void pgstat_report_xact_timestamp(TimestampTz tstamp);
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 80374e4..8404842 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1848,6 +1848,16 @@ pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
     pg_stat_all_tables.autoanalyze_count
    FROM pg_stat_all_tables
   WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
+pg_stat_vacuum_progress| SELECT s.pid,
+    s.table_name,
+    s.total_heap_pages,
+    s.scanned_heap_pages,
+    s.total_index_pages,
+    s.scanned_index_pages,
+    s.total_pages,
+    s.scanned_pages,
+    s.percent_complete
+   FROM pg_stat_get_vacuum_progress() s(pid, table_name, total_heap_pages, scanned_heap_pages, total_index_pages, scanned_index_pages, total_pages, scanned_pages, percent_complete);
 pg_stat_xact_all_tables| SELECT c.oid AS relid,
     n.nspname AS schemaname,
     c.relname,
#111Vinayak
vinpokale@gmail.com
In reply to: Rahila Syed (#110)
Re: [PROPOSAL] VACUUM Progress Checker.

Thanks for the v7.
Please check the comment below.
-Table name in the vacuum progress

+ snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s.%s",
schemaname,relname);

In the vacuum progress, column table_name is showing first 30 characters of
table name.
postgres=# create table test_vacuum_progress_in_postgresql(c1 int,c2 text);
postgres=# select * from pg_stat_vacuum_progress ;
-[ RECORD 1 ]-------+------------------------------
pid | 12284
table_name | public.test_vacuum_progress_i
phase | Scanning Heap
total_heap_pages | 41667
scanned_heap_pages | 25185
percent_complete | 60
total_index_pages | 0
scanned_index_pages | 0
index_scan_count | 0

-----
Regards,
Vinayak,

--
View this message in context: http://postgresql.nabble.com/PROPOSAL-VACUUM-Progress-Checker-tp5855849p5875614.html
Sent from the PostgreSQL - hackers mailing list archive at Nabble.com.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#112Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Vinayak (#111)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

At Mon, 30 Nov 2015 19:10:44 -0700 (MST), Vinayak <vinpokale@gmail.com> wrote in <1448935844520-5875614.post@n5.nabble.com>

Thanks for the v7.
Please check the comment below.
-Table name in the vacuum progress

+ snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s.%s",
schemaname,relname);

In the vacuum progress, column table_name is showing first 30 characters of
table name.

Yeah, it is actually restricted in that length. But if we allow
the buffer to store whole the qualified names, it will need 64 *
2 + 1 +1 = 130 bytes * 10 1300 bytes for each beentry... It might
be acceptable by others, but I don't think that is preferable..

Separating namespace and relation name as below reduces the
required length of the field but 62 bytes is still too long for
most of the information and in turn too short for longer messages
in some cases.

As a more dractic change in design, since these fields are
written/read in sequential manner, providing one free buffer of
the size of.. so.. about 128 bytes for each beentry and storing
strings delimiting with '\0' and numbers in binary format, as an
example, would do. Additional functions to write into/read from
this buffer safely would be needed but this gives both the
ability to store longer messages and relatively short total
buffer size, and allows arbitrary number of parameters limited
only by the length of the free buffer.

What do you think about this?

By the way, how about giving separate columns for relname and
namespace? I think it is more usual way to designate a relation
in this kind of view and it makes the snprintf to concatenate
name and schema unnecessary(it's not significant, though). (The
following example is after pg_stat_all_tables)

postgres=# create table test_vacuum_progress_in_postgresql(c1 int,c2 text);
postgres=# select * from pg_stat_vacuum_progress ;
pid | 12284
schemaname | public
relname | test_vacuum_progress_i...
phase | Scanning Heap
total_heap_pages | 41667

...

And I have some comments about code.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#113Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Kyotaro HORIGUCHI (#112)
Re: [PROPOSAL] VACUUM Progress Checker.

Sorry for the confusing description and the chopped sentsnce.

At Tue, 01 Dec 2015 16:25:57 +0900 (Tokyo Standard Time), Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp> wrote in <20151201.162557.184519961.horiguchi.kyotaro@lab.ntt.co.jp>

Hello,

At Mon, 30 Nov 2015 19:10:44 -0700 (MST), Vinayak <vinpokale@gmail.com> wrote in <1448935844520-5875614.post@n5.nabble.com>

Thanks for the v7.
Please check the comment below.
-Table name in the vacuum progress

+ snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s.%s",
schemaname,relname);

In the vacuum progress, column table_name is showing first 30 characters of
table name.

Yeah, it is actually restricted in that length. But if we allow
the buffer to store whole the qualified names, it will need 64 *
2 + 1 +1 = 130 bytes * 10 1300 bytes for each beentry... It might
be acceptable by others, but I don't think that is preferable..

Separating namespace and relation name as below reduces the
required length of the field but 62 bytes is still too long for
most of the information and in turn too short for longer messages
in some cases.

As a more dractic change in design, since these fields are
written/read in sequential manner, providing one free buffer of
the size of.. so.. about 128 bytes for each beentry and storing
strings delimiting with '\0' and numbers in binary format, as an
example, would do.

This would fail to make sense.. I suppose this can be called
'packed format', as opposed to fixed-length format. Sorry for
poor wording.

Additional functions to write into/read from
this buffer safely would be needed but this gives both the
ability to store longer messages and relatively short total
buffer size, and allows arbitrary number of parameters limited
only by the length of the free buffer.

What do you think about this?

By the way, how about giving separate columns for relname and
namespace? I think it is more usual way to designate a relation
in this kind of view and it makes the snprintf to concatenate
name and schema unnecessary(it's not significant, though). (The
following example is after pg_stat_all_tables)

postgres=# create table test_vacuum_progress_in_postgresql(c1 int,c2 text);
postgres=# select * from pg_stat_vacuum_progress ;
pid | 12284
schemaname | public
relname | test_vacuum_progress_i...
phase | Scanning Heap
total_heap_pages | 41667

...

And I have some comments about code.

This is just what I forgot to delete. I'll mention them later if
necessary.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#114Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Kyotaro HORIGUCHI (#112)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2015/12/01 16:25, Kyotaro HORIGUCHI wrote:

At Mon, 30 Nov 2015 19:10:44 -0700 (MST), Vinayak <vinpokale@gmail.com> wrote

Thanks for the v7.
Please check the comment below.
-Table name in the vacuum progress

+ snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s.%s",
schemaname,relname);

In the vacuum progress, column table_name is showing first 30 characters of
table name.

Yeah, it is actually restricted in that length. But if we allow
the buffer to store whole the qualified names, it will need 64 *
2 + 1 +1 = 130 bytes * 10 1300 bytes for each beentry... It might
be acceptable by others, but I don't think that is preferable..

Separating namespace and relation name as below reduces the
required length of the field but 62 bytes is still too long for
most of the information and in turn too short for longer messages
in some cases.

As done in the patch, the table name is stored in one of the slots of
st_progress_message which has the width limit of PROGRESS_MESSAGE_LENGTH
bytes. Whereas users of pgstat_report_progress interface could make sure
that strings of their choosing to be stored in st_progress_param slots are
within the PROGRESS_MESSAGE_LENGTH limit, the same cannot be ensured for
the table name. Maybe, the table name is a different kind of information
than other reported parameters that it could be treated specially. How
about a separate st_* member, say, st_command_target[2*NAMDATALEN+1] for
the table name? It would be reported using a separate interface, say,
pgstat_report_command_target() once the name is determined. Moreover,
subsequent pgstat_report_progress() invocations need not copy the table
name needlessly as part of copying argument values to st_progress_param
(which is a separate suggestion in its own right though).

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#115Robert Haas
robertmhaas@gmail.com
In reply to: Vinayak (#111)
Re: [PROPOSAL] VACUUM Progress Checker.

On Mon, Nov 30, 2015 at 9:10 PM, Vinayak <vinpokale@gmail.com> wrote:

Thanks for the v7.
Please check the comment below.
-Table name in the vacuum progress

+ snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s.%s",
schemaname,relname);

Uh, I hope that line doesn't appear in the patch. We're scarcely
likely to commit anything that has such an obvious SQL-injection risk
built into it.

https://xkcd.com/327/

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#116Robert Haas
robertmhaas@gmail.com
In reply to: Kyotaro HORIGUCHI (#112)
Re: [PROPOSAL] VACUUM Progress Checker.

On Tue, Dec 1, 2015 at 2:25 AM, Kyotaro HORIGUCHI
<horiguchi.kyotaro@lab.ntt.co.jp> wrote:

Yeah, it is actually restricted in that length. But if we allow
the buffer to store whole the qualified names, it will need 64 *
2 + 1 +1 = 130 bytes * 10 1300 bytes for each beentry... It might
be acceptable by others, but I don't think that is preferable..

There's no such thing as a free lunch here, but we probably don't need
room for 10 strings. If we allowed say 4 strings per beentry and
limited each one to, say, 140 characters for Twitter-compatibility,
that's 560 bytes per backend. Throw in some int8 counters and you're
up to maybe 600 bytes per backend. So that's ~60kB of memory for 100
backends. That doesn't sound like a huge problem to me, but it
wouldn't be stupid to have a PGC_POSTMASTER GUC to turn this feature
on and off, for the benefit of people who may want to run this in
low-memory environments.

As a more dractic change in design, since these fields are
written/read in sequential manner, providing one free buffer of
the size of.. so.. about 128 bytes for each beentry and storing
strings delimiting with '\0' and numbers in binary format, as an
example, would do. Additional functions to write into/read from
this buffer safely would be needed but this gives both the
ability to store longer messages and relatively short total
buffer size, and allows arbitrary number of parameters limited
only by the length of the free buffer.

What do you think about this?

I think it sounds like a mess with uncertain benefits. Now instead of
having individual fields that maybe don't fit and have to be
truncated, you have to figure out what to leave out when the overall
message doesn't fit. That's likely to lead to a lot of messy logic on
the server side, and even messier logic for any clients that read the
data and try to parse it programmatically.

By the way, how about giving separate columns for relname and
namespace? I think it is more usual way to designate a relation
in this kind of view and it makes the snprintf to concatenate
name and schema unnecessary(it's not significant, though). (The
following example is after pg_stat_all_tables)

I could go either way on this.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#117Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Vinayak (#111)
Re: [PROPOSAL] VACUUM Progress Checker.

Vinayak wrote:

In the vacuum progress, column table_name is showing first 30 characters of
table name.
postgres=# create table test_vacuum_progress_in_postgresql(c1 int,c2 text);
postgres=# select * from pg_stat_vacuum_progress ;
-[ RECORD 1 ]-------+------------------------------
pid | 12284
table_name | public.test_vacuum_progress_i

Actually, do we really need to have the table name as a string at all
here? Why not just report the table OID? Surely whoever wants to check
the progress can connect to the database in question to figure out the
table name.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#118Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Robert Haas (#116)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello, sorry for the cloberred CC list.

# I restored it manually from upthread..

At Wed, 2 Dec 2015 13:42:01 -0500, Robert Haas <robertmhaas@gmail.com> wrote in <CA+TgmobcN=3qa9X7c8_G18x53HDCpEYbWP4tnR_es5d=tYvrkQ@mail.gmail.com>

On Tue, Dec 1, 2015 at 2:25 AM, Kyotaro HORIGUCHI
<horiguchi.kyotaro@lab.ntt.co.jp> wrote:

Yeah, it is actually restricted in that length. But if we allow
the buffer to store whole the qualified names, it will need 64 *
2 + 1 +1 = 130 bytes * 10 1300 bytes for each beentry... It might
be acceptable by others, but I don't think that is preferable..

There's no such thing as a free lunch here, but we probably don't need
room for 10 strings. If we allowed say 4 strings per beentry and
limited each one to, say, 140 characters for Twitter-compatibility,
that's 560 bytes per backend. Throw in some int8 counters and you're
up to maybe 600 bytes per backend. So that's ~60kB of memory for 100
backends. That doesn't sound like a huge problem to me, but it
wouldn't be stupid to have a PGC_POSTMASTER GUC to turn this feature
on and off, for the benefit of people who may want to run this in
low-memory environments.

This is similar to Amit-L's proposal and either sound fair for me.

As a more dractic change in design, since these fields are
written/read in sequential manner, providing one free buffer of
the size of.. so.. about 128 bytes for each beentry and storing
strings delimiting with '\0' and numbers in binary format, as an
example, would do. Additional functions to write into/read from
this buffer safely would be needed but this gives both the
ability to store longer messages and relatively short total
buffer size, and allows arbitrary number of parameters limited
only by the length of the free buffer.

What do you think about this?

I think it sounds like a mess with uncertain benefits. Now instead of
having individual fields that maybe don't fit and have to be
truncated, you have to figure out what to leave out when the overall
message doesn't fit. That's likely to lead to a lot of messy logic on
the server side, and even messier logic for any clients that read the
data and try to parse it programmatically.

Ok, I understood that the packed format itself is unaccetable.

By the way, how about giving separate columns for relname and
namespace? I think it is more usual way to designate a relation
in this kind of view and it makes the snprintf to concatenate
name and schema unnecessary(it's not significant, though). (The
following example is after pg_stat_all_tables)

I could go either way on this.

It would depends on the field length but 140 bytes can hold a
whole qualified names.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#119Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Alvaro Herrera (#117)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

At Wed, 2 Dec 2015 15:48:20 -0300, Alvaro Herrera <alvherre@2ndquadrant.com> wrote in <20151202184820.GL2763@alvherre.pgsql>

Vinayak wrote:

In the vacuum progress, column table_name is showing first 30 characters of
table name.
postgres=# create table test_vacuum_progress_in_postgresql(c1 int,c2 text);
postgres=# select * from pg_stat_vacuum_progress ;
-[ RECORD 1 ]-------+------------------------------
pid | 12284
table_name | public.test_vacuum_progress_i

Actually, do we really need to have the table name as a string at all
here? Why not just report the table OID? Surely whoever wants to check
the progress can connect to the database in question to figure out the
table name.

I thought the same thing but found that the same kind of view
(say, pg_stat_user_tables) has separate relanme and shcemaname in
string (not a qualified name, though).

Apart from the representation of the relation, OID would be
better as a field in beentry.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#120Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Kyotaro HORIGUCHI (#119)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2015/12/03 13:47, Kyotaro HORIGUCHI wrote:

At Wed, 2 Dec 2015 15:48:20 -0300, Alvaro Herrera <alvherre@2ndquadrant.com> wrote

Actually, do we really need to have the table name as a string at all
here? Why not just report the table OID? Surely whoever wants to check
the progress can connect to the database in question to figure out the
table name.

I thought the same thing but found that the same kind of view
(say, pg_stat_user_tables) has separate relanme and shcemaname in
string (not a qualified name, though).

Apart from the representation of the relation, OID would be
better as a field in beentry.

I wonder if the field should be a standalone field or as yet another
st_progress_* array?

IMHO, there are some values that a command would report that should not be
mixed with pgstat_report_progress()'s interface. That is, things like
command ID/name, command target (table name or OID) should not be mixed
with actual progress parameters like num_pages, num_indexes (integers),
processing "phase" (string) that are shared via st_progress_* fields. The
first of them already has its own reporting interface in proposed patch
in the form of pgstat_report_activity_flag(). Although, we must be careful
to choose these interfaces carefully.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#121Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Amit Langote (#120)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

At Thu, 3 Dec 2015 14:18:50 +0900, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote in <565FD0BA.5020202@lab.ntt.co.jp>

On 2015/12/03 13:47, Kyotaro HORIGUCHI wrote:

At Wed, 2 Dec 2015 15:48:20 -0300, Alvaro Herrera <alvherre@2ndquadrant.com> wrote

Actually, do we really need to have the table name as a string at all
here? Why not just report the table OID? Surely whoever wants to check
the progress can connect to the database in question to figure out the
table name.

I thought the same thing but found that the same kind of view
(say, pg_stat_user_tables) has separate relanme and shcemaname in
string (not a qualified name, though).

Apart from the representation of the relation, OID would be
better as a field in beentry.

I wonder if the field should be a standalone field or as yet another
st_progress_* array?

IMHO, there are some values that a command would report that should not be
mixed with pgstat_report_progress()'s interface. That is, things like
command ID/name, command target (table name or OID) should not be mixed
with actual progress parameters like num_pages, num_indexes (integers),
processing "phase" (string) that are shared via st_progress_* fields. The
first of them already has its own reporting interface in proposed patch
in the form of pgstat_report_activity_flag(). Although, we must be careful
to choose these interfaces carefully.

Sorry I misunderstood the patch.

Agreed. The patch already separates integer values and texts.
And re-reviewing the patch, there's no fields necessary to be
passed as string.

total_heap_pages, scanned_heap_pages, total_index_pages,
scanned_index_pages, vacrelstats->num_index_scans are currently
in int32.

Phase can be in integer, and schema_name and relname can be
represented by one OID, uint32.

Finally, *NO* text field is needed at least this usage. So
progress_message is totally useless regardless of other usages
unknown to us.

Am I missing somethig?

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#122Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Kyotaro HORIGUCHI (#121)
Re: [PROPOSAL] VACUUM Progress Checker.

Hi,

On 2015/12/03 15:27, Kyotaro HORIGUCHI wrote:

At Thu, 3 Dec 2015 14:18:50 +0900, Amit Langote wrote

On 2015/12/03 13:47, Kyotaro HORIGUCHI wrote:

Apart from the representation of the relation, OID would be
better as a field in beentry.

I wonder if the field should be a standalone field or as yet another
st_progress_* array?

IMHO, there are some values that a command would report that should not be
mixed with pgstat_report_progress()'s interface. That is, things like
command ID/name, command target (table name or OID) should not be mixed
with actual progress parameters like num_pages, num_indexes (integers),
processing "phase" (string) that are shared via st_progress_* fields. The
first of them already has its own reporting interface in proposed patch
in the form of pgstat_report_activity_flag(). Although, we must be careful
to choose these interfaces carefully.

Sorry I misunderstood the patch.

Agreed. The patch already separates integer values and texts.
And re-reviewing the patch, there's no fields necessary to be
passed as string.

total_heap_pages, scanned_heap_pages, total_index_pages,
scanned_index_pages, vacrelstats->num_index_scans are currently
in int32.

Phase can be in integer, and schema_name and relname can be
represented by one OID, uint32.

AIUI, st_progress_message (strings) are to be used to share certain
messages as progress information. I think the latest vacuum-progress patch
uses it to report which phase lazy_scan_heap() is in, for example,
"Scanning heap" for phase 1 of its processing and "Vacuuming index and
heap" for phase 2. Those values are shown to the user in a text column
named "phase" of the pg_stat_vacuum_progress view. That said, reporting
phase as an integer value may also be worth a consideration. Some other
command might choose to do that.

Finally, *NO* text field is needed at least this usage. So
progress_message is totally useless regardless of other usages
unknown to us.

I think it may be okay at this point to add just those st_progress_*
fields which are required by lazy vacuum progress reporting. If someone
comes up with instrumentation ideas for some other command, they could
post patches to add more st_progress_* fields and to implement
instrumentation and a progress view for that command. This is essentially
what Robert said in [1]/messages/by-id/CA+TgmoYdZk9nPDtS+_kOt4S6fDRQLW+1jnJBmG0pkRT4ynxO=Q@mail.gmail.com in relation to my suggestion of taking out
st_progress_param_float from this patch.

By the way, there are some non-st_progress_* fields that I was talking
about in my previous message. For example, st_activity_flag (which I have
suggested to rename to st_command instead). It needs to be set once at the
beginning of the command processing and need not be touched again. I think
it may be a better idea to do the same for table name or OID. It also
won't change over the duration of the command execution. So, we could set
it once at the beginning where that becomes known. Currently in the patch,
it's reported in st_progress_message[0] by every pgstat_report_progress()
invocation. So, the table name will be strcpy()'d to shared memory for
every scanned block that's reported.

Thanks,
Amit

[1]: /messages/by-id/CA+TgmoYdZk9nPDtS+_kOt4S6fDRQLW+1jnJBmG0pkRT4ynxO=Q@mail.gmail.com
/messages/by-id/CA+TgmoYdZk9nPDtS+_kOt4S6fDRQLW+1jnJBmG0pkRT4ynxO=Q@mail.gmail.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#123Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Amit Langote (#122)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

At Thu, 3 Dec 2015 16:24:32 +0900, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote in <565FEE30.8010906@lab.ntt.co.jp>

Agreed. The patch already separates integer values and texts.
And re-reviewing the patch, there's no fields necessary to be
passed as string.

total_heap_pages, scanned_heap_pages, total_index_pages,
scanned_index_pages, vacrelstats->num_index_scans are currently
in int32.

Phase can be in integer, and schema_name and relname can be
represented by one OID, uint32.

AIUI, st_progress_message (strings) are to be used to share certain
messages as progress information. I think the latest vacuum-progress patch
uses it to report which phase lazy_scan_heap() is in, for example,
"Scanning heap" for phase 1 of its processing and "Vacuuming index and
heap" for phase 2. Those values are shown to the user in a text column
named "phase" of the pg_stat_vacuum_progress view. That said, reporting
phase as an integer value may also be worth a consideration. Some other
command might choose to do that.

Finally, *NO* text field is needed at least this usage. So
progress_message is totally useless regardless of other usages
unknown to us.

I think it may be okay at this point to add just those st_progress_*
fields which are required by lazy vacuum progress reporting. If someone
comes up with instrumentation ideas for some other command, they could
post patches to add more st_progress_* fields and to implement
instrumentation and a progress view for that command. This is essentially
what Robert said in [1] in relation to my suggestion of taking out
st_progress_param_float from this patch.

Yes. After taking a detour, though.

By the way, there are some non-st_progress_* fields that I was talking
about in my previous message. For example, st_activity_flag (which I have
suggested to rename to st_command instead). It needs to be set once at the
beginning of the command processing and need not be touched again. I think
it may be a better idea to do the same for table name or OID. It also
won't change over the duration of the command execution. So, we could set
it once at the beginning where that becomes known. Currently in the patch,
it's reported in st_progress_message[0] by every pgstat_report_progress()
invocation. So, the table name will be strcpy()'d to shared memory for
every scanned block that's reported.

If we don't have dedicate reporting functions for each phase
(that is, static data phase and progress phase), the one function
pgstat_report_progress does that by having some instruction from
the caller and it would be a bitfield.

Reading the flags for making decision whether every field to copy
or not and branching by that seems too much for int32 (or maybe
64?) fields. Alhtough it would be in effective when we have
progress_message fields, I don't think it is a good idea without
having progress_message.

pgstat_report_progress(uint32 *param1, uint16 param1_bitmap,
char param2[][..], uint16 param2_bitmap)
{
...
for(i = 0; i < 16 ; i++)
{
if (param1_bitmap & (1 << i))
beentry->st_progress_param[i] = param1[i];
if (param2_bitmap & (1 << i))
strcpy(beentry->..., param2[i]);
}

Thoughts?

Thanks,
Amit

[1]
/messages/by-id/CA+TgmoYdZk9nPDtS+_kOt4S6fDRQLW+1jnJBmG0pkRT4ynxO=Q@mail.gmail.com

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#124Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Kyotaro HORIGUCHI (#123)
Re: [PROPOSAL] VACUUM Progress Checker.

Hi,

On 2015/12/03 19:05, Kyotaro HORIGUCHI wrote:

At Thu, 3 Dec 2015 16:24:32 +0900, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote

By the way, there are some non-st_progress_* fields that I was talking
about in my previous message. For example, st_activity_flag (which I have
suggested to rename to st_command instead). It needs to be set once at the
beginning of the command processing and need not be touched again. I think
it may be a better idea to do the same for table name or OID. It also
won't change over the duration of the command execution. So, we could set
it once at the beginning where that becomes known. Currently in the patch,
it's reported in st_progress_message[0] by every pgstat_report_progress()
invocation. So, the table name will be strcpy()'d to shared memory for
every scanned block that's reported.

If we don't have dedicate reporting functions for each phase
(that is, static data phase and progress phase), the one function
pgstat_report_progress does that by having some instruction from
the caller and it would be a bitfield.

Reading the flags for making decision whether every field to copy
or not and branching by that seems too much for int32 (or maybe
64?) fields. Alhtough it would be in effective when we have
progress_message fields, I don't think it is a good idea without
having progress_message.

pgstat_report_progress(uint32 *param1, uint16 param1_bitmap,
char param2[][..], uint16 param2_bitmap)
{
...
for(i = 0; i < 16 ; i++)
{
if (param1_bitmap & (1 << i))
beentry->st_progress_param[i] = param1[i];
if (param2_bitmap & (1 << i))
strcpy(beentry->..., param2[i]);
}

Thoughts?

Hm, I guess progress messages would not change that much over the course
of a long-running command. How about we pass only the array(s) that we
change and NULL for others:

With the following prototype for pgstat_report_progress:

void pgstat_report_progress(uint32 *uint32_param, int num_uint32_param,
bool *message_param[], int num_message_param);

If we just wanted to change, say scanned_heap_pages, then we report that
using:

uint32_param[1] = scanned_heap_pages;
pgstat_report_progress(uint32_param, 3, NULL, 0);

Also, pgstat_report_progress() should check each of its parameters for
NULL before iterating over to copy. So in most reports over the course of
a command, the message_param would be NULL and hence not copied.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#125Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#124)
Re: [PROPOSAL] VACUUM Progress Checker.

On Mon, Dec 7, 2015 at 2:41 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2015/12/03 19:05, Kyotaro HORIGUCHI wrote:

At Thu, 3 Dec 2015 16:24:32 +0900, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote

By the way, there are some non-st_progress_* fields that I was talking
about in my previous message. For example, st_activity_flag (which I have
suggested to rename to st_command instead). It needs to be set once at the
beginning of the command processing and need not be touched again. I think
it may be a better idea to do the same for table name or OID. It also
won't change over the duration of the command execution. So, we could set
it once at the beginning where that becomes known. Currently in the patch,
it's reported in st_progress_message[0] by every pgstat_report_progress()
invocation. So, the table name will be strcpy()'d to shared memory for
every scanned block that's reported.

If we don't have dedicate reporting functions for each phase
(that is, static data phase and progress phase), the one function
pgstat_report_progress does that by having some instruction from
the caller and it would be a bitfield.

Reading the flags for making decision whether every field to copy
or not and branching by that seems too much for int32 (or maybe
64?) fields. Alhtough it would be in effective when we have
progress_message fields, I don't think it is a good idea without
having progress_message.

pgstat_report_progress(uint32 *param1, uint16 param1_bitmap,
char param2[][..], uint16 param2_bitmap)
{
...
for(i = 0; i < 16 ; i++)
{
if (param1_bitmap & (1 << i))
beentry->st_progress_param[i] = param1[i];
if (param2_bitmap & (1 << i))
strcpy(beentry->..., param2[i]);
}

Thoughts?

Hm, I guess progress messages would not change that much over the course
of a long-running command. How about we pass only the array(s) that we
change and NULL for others:

With the following prototype for pgstat_report_progress:

void pgstat_report_progress(uint32 *uint32_param, int num_uint32_param,
bool *message_param[], int num_message_param);

If we just wanted to change, say scanned_heap_pages, then we report that
using:

uint32_param[1] = scanned_heap_pages;
pgstat_report_progress(uint32_param, 3, NULL, 0);

Also, pgstat_report_progress() should check each of its parameters for
NULL before iterating over to copy. So in most reports over the course of
a command, the message_param would be NULL and hence not copied.

It's going to be *really* important that this facility provides a
lightweight way of updating progress, so I think this whole API is
badly designed. VACUUM, for example, is going to want a way to update
the individual counter for the number of pages it's scanned after
every page. It should not have to pass all of the other information
that is part of a complete report. It should just be able to say
pgstat_report_progress_update_counter(1, pages_scanned) or something
of this sort. Don't marshal all of the data and then make
pgstat_report_progress figure out what's changed. Provide a series of
narrow APIs where the caller tells you exactly what they want to
update, and you update only that. It's fine to have a
pgstat_report_progress() function to update everything at once, for
the use at the beginning of a command, but the incremental updates
within the command should do something lighter-weight.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#126Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#125)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2015/12/10 4:40, Robert Haas wrote:

It's going to be *really* important that this facility provides a
lightweight way of updating progress, so I think this whole API is
badly designed. VACUUM, for example, is going to want a way to update
the individual counter for the number of pages it's scanned after
every page. It should not have to pass all of the other information
that is part of a complete report. It should just be able to say
pgstat_report_progress_update_counter(1, pages_scanned) or something
of this sort. Don't marshal all of the data and then make
pgstat_report_progress figure out what's changed. Provide a series of
narrow APIs where the caller tells you exactly what they want to
update, and you update only that. It's fine to have a
pgstat_report_progress() function to update everything at once, for
the use at the beginning of a command, but the incremental updates
within the command should do something lighter-weight.

How about something like the following:

/*
* index: in the array of uint32 counters in the beentry
* counter: new value of the (index+1)th counter
*/
void pgstat_report_progress_update_counter(int index, uint32 counter);

/*
* msg: new value of (index+1)the message (with trailing null byte)
*/
void pgstat_report_progress_update_message(int index, const char *msg);

Actually updating a counter or message would look like:

pgstat_increment_changecount_before(beentry);
// update the counter or message at index in beentry->st_progress_*
pgstat_increment_changecount_after(beentry);

Other interface functions which are called at the beginning:

void pgstat_report_progress_set_command(int commandId);
void pgstat_report_progress_set_command_target(const char *target_name);
or
void pgstat_report_progress_set_command_target(Oid target_oid);

And then a SQL-level,
void pgstat_reset_local_progress();

Which simply sets beentry->st_command to some invalid value which signals
a progress view function to ignore this backend.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#127Michael Paquier
michael.paquier@gmail.com
In reply to: Robert Haas (#125)
Re: [PROPOSAL] VACUUM Progress Checker.

On Thu, Dec 10, 2015 at 4:40 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, Dec 7, 2015 at 2:41 AM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:

Hm, I guess progress messages would not change that much over the course
of a long-running command. How about we pass only the array(s) that we
change and NULL for others:

With the following prototype for pgstat_report_progress:

void pgstat_report_progress(uint32 *uint32_param, int num_uint32_param,
bool *message_param[], int num_message_param);

If we just wanted to change, say scanned_heap_pages, then we report that
using:

uint32_param[1] = scanned_heap_pages;
pgstat_report_progress(uint32_param, 3, NULL, 0);

Also, pgstat_report_progress() should check each of its parameters for
NULL before iterating over to copy. So in most reports over the course of
a command, the message_param would be NULL and hence not copied.

It's going to be *really* important that this facility provides a
lightweight way of updating progress, so I think this whole API is
badly designed. VACUUM, for example, is going to want a way to update
the individual counter for the number of pages it's scanned after
every page. It should not have to pass all of the other information
that is part of a complete report. It should just be able to say
pgstat_report_progress_update_counter(1, pages_scanned) or something
of this sort. Don't marshal all of the data and then make
pgstat_report_progress figure out what's changed. Provide a series of
narrow APIs where the caller tells you exactly what they want to
update, and you update only that. It's fine to have a
pgstat_report_progress() function to update everything at once, for
the use at the beginning of a command, but the incremental updates
within the command should do something lighter-weight.

[first time looking really at the patch and catching up with this thread]

Agreed. As far as I can guess from the code, those should be as
followed to bloat pgstat message queue a minimum:

+ pgstat_report_activity_flag(ACTIVITY_IS_VACUUM);
/*
* Loop to process each selected relation.
*/
Defining a new routine for this purpose is a bit surprising. Cannot we
just use pgstat_report_activity with a new BackendState STATE_INVACUUM
or similar if we pursue the progress tracking approach?

A couple of comments:
- The relation OID should be reported and not its name. In case of a
relation rename that would not be cool for tracking, and most users
are surely going to join with other system tables using it.
- The progress tracking facility adds a whole level of complexity for
very little gain, and IMO this should *not* be part of PgBackendStatus
because in most cases its data finishes wasted. We don't expect
backends to run frequently such progress reports, do we? My opinion on
the matter if that we should define a different collector data for
vacuum, with something like PgStat_StatVacuumEntry, then have on top
of it a couple of routines dedicated at feeding up data with it when
some work is done on a vacuum job.

In short, it seems to me that this patch needs a rework, and should be
returned with feedback. Other opinions?
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#128Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Michael Paquier (#127)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

At Thu, 10 Dec 2015 15:28:14 +0900, Michael Paquier <michael.paquier@gmail.com> wrote in <CAB7nPqRNw=w4mt-W+gtq0ED0KTR=B8Qgu6D+4BN3XmzFRuAgXQ@mail.gmail.com>

On Thu, Dec 10, 2015 at 4:40 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, Dec 7, 2015 at 2:41 AM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:

Hm, I guess progress messages would not change that much over the course
of a long-running command. How about we pass only the array(s) that we
change and NULL for others:

With the following prototype for pgstat_report_progress:

void pgstat_report_progress(uint32 *uint32_param, int num_uint32_param,
bool *message_param[], int num_message_param);

If we just wanted to change, say scanned_heap_pages, then we report that
using:

uint32_param[1] = scanned_heap_pages;
pgstat_report_progress(uint32_param, 3, NULL, 0);

Also, pgstat_report_progress() should check each of its parameters for
NULL before iterating over to copy. So in most reports over the course of
a command, the message_param would be NULL and hence not copied.

It's going to be *really* important that this facility provides a
lightweight way of updating progress, so I think this whole API is
badly designed. VACUUM, for example, is going to want a way to update
the individual counter for the number of pages it's scanned after
every page. It should not have to pass all of the other information
that is part of a complete report. It should just be able to say
pgstat_report_progress_update_counter(1, pages_scanned) or something
of this sort. Don't marshal all of the data and then make
pgstat_report_progress figure out what's changed. Provide a series of
narrow APIs where the caller tells you exactly what they want to
update, and you update only that. It's fine to have a
pgstat_report_progress() function to update everything at once, for
the use at the beginning of a command, but the incremental updates
within the command should do something lighter-weight.

[first time looking really at the patch and catching up with this thread]

Agreed. As far as I can guess from the code, those should be as
followed to bloat pgstat message queue a minimum:

+ pgstat_report_activity_flag(ACTIVITY_IS_VACUUM);
/*
* Loop to process each selected relation.
*/
Defining a new routine for this purpose is a bit surprising. Cannot we
just use pgstat_report_activity with a new BackendState STATE_INVACUUM
or similar if we pursue the progress tracking approach?

The author might want to know vacuum status *after* it has been
finished. But it is reset just after the end of a vacuum. One
concern is that BackendState adds new value for
pg_stat_activiry.state and it might confuse someone using it but
I don't see other issue on it.

A couple of comments:
- The relation OID should be reported and not its name. In case of a
relation rename that would not be cool for tracking, and most users
are surely going to join with other system tables using it.

+1

- The progress tracking facility adds a whole level of complexity for
very little gain, and IMO this should *not* be part of PgBackendStatus
because in most cases its data finishes wasted. We don't expect
backends to run frequently such progress reports, do we?

I strongly thought the same thing but I haven't came up with
better place for it to be stored. but,

My opinion on
the matter if that we should define a different collector data for
vacuum, with something like PgStat_StatVacuumEntry, then have on top
of it a couple of routines dedicated at feeding up data with it when
some work is done on a vacuum job.

+many. But I can't guess the appropriate number of the entry of
it, or suitable replacing policy on excesive number of
vacuums. Although sane users won't run vacuum on so many
backends.

In short, it seems to me that this patch needs a rework, and should be
returned with feedback. Other opinions?

This is important feature for DBAs so I agree with you.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#129Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Michael Paquier (#127)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2015/12/10 15:28, Michael Paquier wrote:

On Thu, Dec 10, 2015 at 4:40 AM, Robert Haas <robertmhaas@gmail.com> wrote:

It's going to be *really* important that this facility provides a
lightweight way of updating progress, so I think this whole API is
badly designed. VACUUM, for example, is going to want a way to update
the individual counter for the number of pages it's scanned after
every page. It should not have to pass all of the other information
that is part of a complete report. It should just be able to say
pgstat_report_progress_update_counter(1, pages_scanned) or something
of this sort. Don't marshal all of the data and then make
pgstat_report_progress figure out what's changed. Provide a series of
narrow APIs where the caller tells you exactly what they want to
update, and you update only that. It's fine to have a
pgstat_report_progress() function to update everything at once, for
the use at the beginning of a command, but the incremental updates
within the command should do something lighter-weight.

[first time looking really at the patch and catching up with this thread]

Agreed. As far as I can guess from the code, those should be as
followed to bloat pgstat message queue a minimum:

+ pgstat_report_activity_flag(ACTIVITY_IS_VACUUM);
/*
* Loop to process each selected relation.
*/
Defining a new routine for this purpose is a bit surprising. Cannot we
just use pgstat_report_activity with a new BackendState STATE_INVACUUM
or similar if we pursue the progress tracking approach?

ISTM, PgBackendStatus.st_state is normally manipulated at quite different
sites (mostly tcop) than where a backend would be able to report that a
command like VACUUM is running. Also, the value 'active' of
pg_stat_activity.state has an established meaning as Horiguchi-san seems
to point out in his reply. IMHO, this patch shouldn't affect such meaning
of a pg_stat_activity field.

A couple of comments:
- The relation OID should be reported and not its name. In case of a
relation rename that would not be cool for tracking, and most users
are surely going to join with other system tables using it.

+1

- The progress tracking facility adds a whole level of complexity for
very little gain, and IMO this should *not* be part of PgBackendStatus
because in most cases its data finishes wasted. We don't expect
backends to run frequently such progress reports, do we? My opinion on
the matter if that we should define a different collector data for
vacuum, with something like PgStat_StatVacuumEntry, then have on top
of it a couple of routines dedicated at feeding up data with it when
some work is done on a vacuum job.

I assume your comment here means we should use stats collector to the
track/publish progress info, is that right?

AIUI, the counts published via stats collector are updated asynchronously
w.r.t. operations they count and mostly as aggregate figures. For example,
PgStat_StatTabEntry.blocks_fetched. IOW, we never see
pg_statio_all_tables.heap_blks_read updating as a scan reads blocks. Maybe
that helps keep traffic to pgstat collector to sane levels. But that is
not to mean that I think controlling stats collector levels was the only
design consideration behind how such counters are published.

In case of reporting counters as progress info, it seems we might have to
send too many PgStat_Msg's, for example, for every block we finish
processing during vacuum. That kind of message traffic may swamp the
collector. Then we need to see the updated counters from other counters in
near real-time though that may be possible with suitable (build?)
configuration.

Moreover, the counters reported as progress info seem to be of different
nature than those published via the stats collector. I would think of it
in terms of the distinction between track_activities and track_counts. I
find these lines on "The Statistics Collector" page somewhat related:

"PostgreSQL also supports reporting dynamic information about exactly what
is going on in the system right now, such as the exact command currently
being executed by other server processes, and which other connections
exist in the system. This facility is independent of the collector process."

Then,

"When using the statistics to monitor collected data, it is important to
realize that the information does not update instantaneously. Each
individual server process transmits new statistical counts to the
collector just before going idle; so a query or transaction still in
progress does not affect the displayed totals. Also, the collector itself
emits a new report at most once per PGSTAT_STAT_INTERVAL milliseconds (500
ms unless altered while building the server). So the displayed information
lags behind actual activity. However, current-query information collected
by track_activities is always up-to-date."

http://www.postgresql.org/docs/devel/static/monitoring-stats.html

Am I misunderstanding what you mean?

In short, it seems to me that this patch needs a rework, and should be
returned with feedback. Other opinions?

Yeah, some more thought needs to be put into design of the general
reporting interface. Then we also need to pay attention to another
important aspect of this patch - lazy vacuum instrumentation.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#130Michael Paquier
michael.paquier@gmail.com
In reply to: Amit Langote (#129)
Re: [PROPOSAL] VACUUM Progress Checker.

On Thu, Dec 10, 2015 at 7:23 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2015/12/10 15:28, Michael Paquier wrote:

- The progress tracking facility adds a whole level of complexity for
very little gain, and IMO this should *not* be part of PgBackendStatus
because in most cases its data finishes wasted. We don't expect
backends to run frequently such progress reports, do we? My opinion on
the matter if that we should define a different collector data for
vacuum, with something like PgStat_StatVacuumEntry, then have on top
of it a couple of routines dedicated at feeding up data with it when
some work is done on a vacuum job.

I assume your comment here means we should use stats collector to the
track/publish progress info, is that right?

Yep.

AIUI, the counts published via stats collector are updated asynchronously
w.r.t. operations they count and mostly as aggregate figures. For example,
PgStat_StatTabEntry.blocks_fetched. IOW, we never see
pg_statio_all_tables.heap_blks_read updating as a scan reads blocks. Maybe
that helps keep traffic to pgstat collector to sane levels. But that is
not to mean that I think controlling stats collector levels was the only
design consideration behind how such counters are published.

In case of reporting counters as progress info, it seems we might have to
send too many PgStat_Msg's, for example, for every block we finish
processing during vacuum. That kind of message traffic may swamp the
collector. Then we need to see the updated counters from other counters in
near real-time though that may be possible with suitable (build?)
configuration.

As far as I understand it, the basic reason why this patch exists is
to allow a DBA to have a hint of the progress of a VACUUM that may be
taking minutes, or say hours, which is something we don't have now. So
it seems perfectly fine to me to report this information
asynchronously with a bit of lag. Why would we need so much precision
in the report?

In short, it seems to me that this patch needs a rework, and should be
returned with feedback. Other opinions?

Yeah, some more thought needs to be put into design of the general
reporting interface. Then we also need to pay attention to another
important aspect of this patch - lazy vacuum instrumentation.

This patch has received a lot of feedback, and it is not in a
committable state, so I marked it as "Returned with feedback" for this
CF.
Regards,
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#131Robert Haas
robertmhaas@gmail.com
In reply to: Michael Paquier (#130)
Re: [PROPOSAL] VACUUM Progress Checker.

On Thu, Dec 10, 2015 at 6:46 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:

On Thu, Dec 10, 2015 at 7:23 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2015/12/10 15:28, Michael Paquier wrote:

- The progress tracking facility adds a whole level of complexity for
very little gain, and IMO this should *not* be part of PgBackendStatus
because in most cases its data finishes wasted. We don't expect
backends to run frequently such progress reports, do we? My opinion on
the matter if that we should define a different collector data for
vacuum, with something like PgStat_StatVacuumEntry, then have on top
of it a couple of routines dedicated at feeding up data with it when
some work is done on a vacuum job.

I assume your comment here means we should use stats collector to the
track/publish progress info, is that right?

Yep.

Oh, please, no. Gosh, this is supposed to be a lightweight facility!
Just have a chunk of shared memory and write the data in there. If
you try to feed this through the stats collector you're going to
increase the overhead by 100x or more, and there's no benefit. We've
got to do relation stats that way because there's no a priori bound on
the number of relations, so we can't just preallocate enough shared
memory for all of them. But there's no similar restriction here: the
number of backends IS fixed at startup time. As long as we limit the
amount of progress information that a backend can supply to some fixed
length, which IMHO we definitely should, there's no need to add the
expense of funneling this through the stats collector.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#132Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#131)
Re: [PROPOSAL] VACUUM Progress Checker.

Robert Haas <robertmhaas@gmail.com> writes:

Oh, please, no. Gosh, this is supposed to be a lightweight facility!
Just have a chunk of shared memory and write the data in there. If
you try to feed this through the stats collector you're going to
increase the overhead by 100x or more, and there's no benefit. We've
got to do relation stats that way because there's no a priori bound on
the number of relations, so we can't just preallocate enough shared
memory for all of them. But there's no similar restriction here: the
number of backends IS fixed at startup time. As long as we limit the
amount of progress information that a backend can supply to some fixed
length, which IMHO we definitely should, there's no need to add the
expense of funneling this through the stats collector.

I agree with this, and I'd further add that if we don't have a
fixed-length progress state, we've overdesigned the facility entirely.
People won't be able to make sense of anything that acts much more
complicated than "0% .. 100% done". So you need to find a way of
approximating progress of a given command in terms more or less
like that, even if it's a pretty crude approximation.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#133Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#132)
Re: [PROPOSAL] VACUUM Progress Checker.

On Thu, Dec 10, 2015 at 9:49 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

Oh, please, no. Gosh, this is supposed to be a lightweight facility!
Just have a chunk of shared memory and write the data in there. If
you try to feed this through the stats collector you're going to
increase the overhead by 100x or more, and there's no benefit. We've
got to do relation stats that way because there's no a priori bound on
the number of relations, so we can't just preallocate enough shared
memory for all of them. But there's no similar restriction here: the
number of backends IS fixed at startup time. As long as we limit the
amount of progress information that a backend can supply to some fixed
length, which IMHO we definitely should, there's no need to add the
expense of funneling this through the stats collector.

I agree with this, and I'd further add that if we don't have a
fixed-length progress state, we've overdesigned the facility entirely.
People won't be able to make sense of anything that acts much more
complicated than "0% .. 100% done". So you need to find a way of
approximating progress of a given command in terms more or less
like that, even if it's a pretty crude approximation.

That I don't agree with. Even for something like VACUUM, it's pretty
hard to approximate overall progress - because, for example, normally
we'll only have 1 index scan per index, but we might have multiple
index scans or none if maintenance_work_mem is too small or if there
aren't any dead tuples after all. I don't want our progress reporting
facility to end up with this reputation:

https://xkcd.com/612/

This point has already been discussed rather extensively upthread, but
to reiterate, I think it's much better to report slightly more
detailed information and let the user figure out what to do with it.
For example, for a VACUUM, I think we should report something like
this:

1. The number of heap pages scanned thus far.
2. The number of dead tuples found thus far.
3. The number of dead tuples we can store before we run out of
maintenance_work_mem.
4. The number of index pages processed by the current index vac cycle
(or a sentinel value if none is in progress).
5. The number of heap pages for which the "second heap pass" has been completed.

Now, if the user wants to flatten this out to a progress meter, they
can write an SQL expression which does that easily enough, folding the
sizes of the table and its indices and whatever assumptions they want
to make about what will happen down the road. If we all agree on how
that should be done, it can even ship as a built-in view. But I
*don't* think we should build those assumptions into the core progress
reporting facility. For one thing, that would make updating the
progress meter considerably more expensive - you'd have to recompute a
new completion percentage instead of just saying "heap pages processed
went up by one". For another thing, there are definitely going to be
some people that want the detailed information - and I can practically
guarantee that if we don't make it available, at least one person will
write a tool that tries to reverse-engineer the detailed progress
information from whatever we do report.

Heck, I might do it myself. If I find a long-running vacuum on a
customer system that doesn't seem to be making progress, knowing that
it's 69% complete and that the completion percentage isn't rising
doesn't help me much. What I want to know is are we stuck in a heap
vacuum phase, or an index vacuum phase, or a second heap pass. Are we
making progress so slowly that 69% takes forever to get to 70%, or are
we making absolutely no progress at all? I think if we don't report a
healthy amount of detail here people will still frequently have to
resort to what I do now, which is ask the customer to install strace
and attach it to the vacuum process. For many customers, that's not
so easy; and it never inspires any confidence.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#134Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Michael Paquier (#130)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2015/12/10 20:46, Michael Paquier wrote:

On Thu, Dec 10, 2015 at 7:23 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

AIUI, the counts published via stats collector are updated asynchronously
w.r.t. operations they count and mostly as aggregate figures. For example,
PgStat_StatTabEntry.blocks_fetched. IOW, we never see
pg_statio_all_tables.heap_blks_read updating as a scan reads blocks. Maybe
that helps keep traffic to pgstat collector to sane levels. But that is
not to mean that I think controlling stats collector levels was the only
design consideration behind how such counters are published.

In case of reporting counters as progress info, it seems we might have to
send too many PgStat_Msg's, for example, for every block we finish
processing during vacuum. That kind of message traffic may swamp the
collector. Then we need to see the updated counters from other counters in
near real-time though that may be possible with suitable (build?)
configuration.

As far as I understand it, the basic reason why this patch exists is
to allow a DBA to have a hint of the progress of a VACUUM that may be
taking minutes, or say hours, which is something we don't have now. So
it seems perfectly fine to me to report this information
asynchronously with a bit of lag. Why would we need so much precision
in the report?

Sorry, I didn't mean to overstate this requirement. I agree precise
real-time reporting of progress info is not such a stringent requirement
from the patch. The point regarding whether we should storm the collector
with progress info messages still holds, IMHO.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#135Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Amit Langote (#134)
Re: [PROPOSAL] VACUUM Progress Checker.

Sorry, I misunderstood the meaning of PgStat_*.

At Fri, 11 Dec 2015 09:41:04 +0900, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote in <566A1BA0.70707@lab.ntt.co.jp>

As far as I understand it, the basic reason why this patch exists is
to allow a DBA to have a hint of the progress of a VACUUM that may be
taking minutes, or say hours, which is something we don't have now. So
it seems perfectly fine to me to report this information
asynchronously with a bit of lag. Why would we need so much precision
in the report?

Sorry, I didn't mean to overstate this requirement. I agree precise
real-time reporting of progress info is not such a stringent requirement
from the patch. The point regarding whether we should storm the collector
with progress info messages still holds, IMHO.

Taking a few seconds interval between each messages would be
sufficient. I personaly think that gettimeofday() per processing
every buffer (or few buffers) is not so heavy-weight but I
suppose there's not such a consensus here. However,
IsCheckpointOnSchedule does that per writing one buffer.

vacuum_delay_point() seems to be a reasonable point to check the
interval and send stats since it would be designed to be called
with the interval also appropriate for this purpose.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#136Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Kyotaro HORIGUCHI (#135)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2015/12/11 14:41, Kyotaro HORIGUCHI wrote:

Sorry, I misunderstood the meaning of PgStat_*.

I should've just said "messages to the stats collector" instead of
"PgStat_Msg's".

At Fri, 11 Dec 2015 09:41:04 +0900, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote

As far as I understand it, the basic reason why this patch exists is
to allow a DBA to have a hint of the progress of a VACUUM that may be
taking minutes, or say hours, which is something we don't have now. So
it seems perfectly fine to me to report this information
asynchronously with a bit of lag. Why would we need so much precision
in the report?

Sorry, I didn't mean to overstate this requirement. I agree precise
real-time reporting of progress info is not such a stringent requirement
from the patch. The point regarding whether we should storm the collector
with progress info messages still holds, IMHO.

Taking a few seconds interval between each messages would be
sufficient. I personaly think that gettimeofday() per processing
every buffer (or few buffers) is not so heavy-weight but I
suppose there's not such a consensus here. However,
IsCheckpointOnSchedule does that per writing one buffer.

vacuum_delay_point() seems to be a reasonable point to check the
interval and send stats since it would be designed to be called
with the interval also appropriate for this purpose.

Interesting, vacuum_delay_point() may be worth considering.

It seems though that, overall, PgBackendStatus approach may be more suited
for progress tracking. Let's see what the author thinks.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#137Michael Paquier
michael.paquier@gmail.com
In reply to: Robert Haas (#133)
Re: [PROPOSAL] VACUUM Progress Checker.

On Fri, Dec 11, 2015 at 12:59 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Thu, Dec 10, 2015 at 9:49 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

Oh, please, no. Gosh, this is supposed to be a lightweight facility!
Just have a chunk of shared memory and write the data in there. If
you try to feed this through the stats collector you're going to
increase the overhead by 100x or more, and there's no benefit. We've
got to do relation stats that way because there's no a priori bound on
the number of relations, so we can't just preallocate enough shared
memory for all of them. But there's no similar restriction here: the
number of backends IS fixed at startup time. As long as we limit the
amount of progress information that a backend can supply to some fixed
length, which IMHO we definitely should, there's no need to add the
expense of funneling this through the stats collector.

I agree with this, and I'd further add that if we don't have a
fixed-length progress state, we've overdesigned the facility entirely.
People won't be able to make sense of anything that acts much more
complicated than "0% .. 100% done". So you need to find a way of
approximating progress of a given command in terms more or less
like that, even if it's a pretty crude approximation.

Check. My opinion is based on the fact that most of the backends are
not going to use the progress facility at all, and we actually do not
need a high level of precision for VACUUM reports: we could simply
send messages with a certain delay between two messages. And it looks
like a waste to allocate that for all the backends. But I am going to
withdraw here, two committers is by far too much pressure.

That I don't agree with. Even for something like VACUUM, it's pretty
hard to approximate overall progress - because, for example, normally
we'll only have 1 index scan per index, but we might have multiple
index scans or none if maintenance_work_mem is too small or if there
aren't any dead tuples after all. I don't want our progress reporting
facility to end up with this reputation:

https://xkcd.com/612/

This brings memories. Who has never faced that...

This point has already been discussed rather extensively upthread, but
to reiterate, I think it's much better to report slightly more
detailed information and let the user figure out what to do with it.
For example, for a VACUUM, I think we should report something like
this:
1. The number of heap pages scanned thus far.
2. The number of dead tuples found thus far.
3. The number of dead tuples we can store before we run out of
maintenance_work_mem.
4. The number of index pages processed by the current index vac cycle
(or a sentinel value if none is in progress).
5. The number of heap pages for which the "second heap pass" has been completed
Now, if the user wants to flatten this out to a progress meter, they
can write an SQL expression which does that easily enough, folding the
sizes of the table and its indices and whatever assumptions they want
to make about what will happen down the road. If we all agree on how
that should be done, it can even ship as a built-in view. But I
*don't* think we should build those assumptions into the core progress
reporting facility. For one thing, that would make updating the
progress meter considerably more expensive - you'd have to recompute a
new completion percentage instead of just saying "heap pages processed
went up by one".

This stuff I agree. Having global counters, and have user compute any
kind of percentage or progress bar is definitely the way to go.

For another thing, there are definitely going to be
some people that want the detailed information - and I can practically
guarantee that if we don't make it available, at least one person will
write a tool that tries to reverse-engineer the detailed progress
information from whatever we do report.

OK, so this justifies the fact of having detailed information, but
does it justify the fact of having precise and accurate data? ISTM
that having detailed information and precise information are two
different things. The level of details is defined depending on how
verbose we want the information to be, and the list you are giving
would fulfill this requirement nicely for VACUUM. The level of
precision/accuracy at which this information is provided though
depends at which frequency we want to send this information. For
long-running VACUUM it does not seem necessary to update the fields of
the progress tracker each time a counter needs to be incremented.
CLUSTER has been mentioned as well as a potential target for the
progress facility, but it seems that it enters as well in the category
of things that would need to be reported on a slower frequency pace
than "each-time-a-counter-is-incremented".

My impression is just based on the needs of VACUUM and CLUSTER.
Perhaps I am lacking imagination regarding the potential use cases of
the progress facility though in cases where we'd want to provide
extremely precise progress information :)
It just seems to me that this is not a requirement for VACUUM or
CLUSTER. That's all.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#138Robert Haas
robertmhaas@gmail.com
In reply to: Michael Paquier (#137)
Re: [PROPOSAL] VACUUM Progress Checker.

On Fri, Dec 11, 2015 at 1:25 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:

For another thing, there are definitely going to be
some people that want the detailed information - and I can practically
guarantee that if we don't make it available, at least one person will
write a tool that tries to reverse-engineer the detailed progress
information from whatever we do report.

OK, so this justifies the fact of having detailed information, but
does it justify the fact of having precise and accurate data? ISTM
that having detailed information and precise information are two
different things. The level of details is defined depending on how
verbose we want the information to be, and the list you are giving
would fulfill this requirement nicely for VACUUM. The level of
precision/accuracy at which this information is provided though
depends at which frequency we want to send this information. For
long-running VACUUM it does not seem necessary to update the fields of
the progress tracker each time a counter needs to be incremented.
CLUSTER has been mentioned as well as a potential target for the
progress facility, but it seems that it enters as well in the category
of things that would need to be reported on a slower frequency pace
than "each-time-a-counter-is-incremented".

My impression is just based on the needs of VACUUM and CLUSTER.
Perhaps I am lacking imagination regarding the potential use cases of
the progress facility though in cases where we'd want to provide
extremely precise progress information :)
It just seems to me that this is not a requirement for VACUUM or
CLUSTER. That's all.

It's not a hard requirement, but it should be quite easy to do without
adding any significant overhead. All you need to do is something
like:

foo->changecount++;
pg_write_barrier();
foo->count_of_blocks++;
pg_write_barrier();
foo->changecount++;

I suspect that's plenty cheap enough to do for every block.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#139Vinayak Pokale
vinpokale@gmail.com
In reply to: Robert Haas (#138)
1 attachment(s)
Re: [PROPOSAL] VACUUM Progress Checker.

Hi,
Please find attached patch addressing following comments.

The relation OID should be reported and not its name. In case of a
relation rename that would not be cool for tracking, and most users
are surely going to join with other system tables using it.

The relation OID is reported instead of relation name.
The following interface function is called at the beginning to report the
relation OID once.
void pgstat_report_command_target(Oid relid)

Regarding pg_stat_get_vacuum_progress(): I think a backend can simply be
skipped if (!has_privs_of_role(GetUserId(), beentry->st_userid)) (cannot
put that in plain English, :))

Updated in the attached patch.

In the previous patch, ACTIVITY_IS_VACUUM is set unnecessarily for
VACOPT_FULL and they are not covered by lazy_scan_heap().
I have updated it in attached patch and also renamed ACTIVITY_IS_VACUUM to
COMMAND_LAZY_VACUUM.

Added documentation for view.
Some more comments need to be addressed.

Regards,

Vinayak Pokale

On Sat, Dec 12, 2015 at 2:07 AM, Robert Haas <robertmhaas@gmail.com> wrote:

Show quoted text

On Fri, Dec 11, 2015 at 1:25 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:

For another thing, there are definitely going to be
some people that want the detailed information - and I can practically
guarantee that if we don't make it available, at least one person will
write a tool that tries to reverse-engineer the detailed progress
information from whatever we do report.

OK, so this justifies the fact of having detailed information, but
does it justify the fact of having precise and accurate data? ISTM
that having detailed information and precise information are two
different things. The level of details is defined depending on how
verbose we want the information to be, and the list you are giving
would fulfill this requirement nicely for VACUUM. The level of
precision/accuracy at which this information is provided though
depends at which frequency we want to send this information. For
long-running VACUUM it does not seem necessary to update the fields of
the progress tracker each time a counter needs to be incremented.
CLUSTER has been mentioned as well as a potential target for the
progress facility, but it seems that it enters as well in the category
of things that would need to be reported on a slower frequency pace
than "each-time-a-counter-is-incremented".

My impression is just based on the needs of VACUUM and CLUSTER.
Perhaps I am lacking imagination regarding the potential use cases of
the progress facility though in cases where we'd want to provide
extremely precise progress information :)
It just seems to me that this is not a requirement for VACUUM or
CLUSTER. That's all.

It's not a hard requirement, but it should be quite easy to do without
adding any significant overhead. All you need to do is something
like:

foo->changecount++;
pg_write_barrier();
foo->count_of_blocks++;
pg_write_barrier();
foo->changecount++;

I suspect that's plenty cheap enough to do for every block.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Attachments:

Vacuum_progress_checker_v8.patchapplication/octet-stream; name=Vacuum_progress_checker_v8.patchDownload
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index c503636..c0a2545 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -499,6 +499,13 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       yet included in <structname>pg_stat_user_functions</>).</entry>
      </row>
 
+     <row>
+      <entry><structname>pg_stat_vacuum_progress</><indexterm><primary>pg_stat_vacuum_progress</primary></indexterm></entry>
+      <entry>One row for each backend (including autovacuum worker processes) running
+      <command>VACUUM</>, showing current progress in terms of heap pages it
+      has finished processing. Backends running <command>VACUUM FULL</> are
+      not included.</entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -1731,6 +1738,74 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
    controls exactly which functions are tracked.
   </para>
 
+  <table id="pg-stat-vacuum-progress" xreflabel="pg_stat_vacuum_progress">
+   <title><structname>pg_stat_vacuum_progress</structname> View</title>
+   <tgroup cols="3">
+    <thead>
+    <row>
+      <entry>Column</entry>
+      <entry>Type</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+   <tbody>
+    <row>
+     <entry><structfield>pid</></entry>
+     <entry><type>integer</></entry>
+     <entry>Process ID of backend</entry>
+    </row>
+    <row>
+     <entry><structfield>relid</></entry>
+     <entry><type>oid</></entry>
+     <entry>OID of a table</entry>
+    </row>
+    <row>
+     <entry><structfield>table_name</></entry>
+     <entry><type>name</></entry>
+     <entry>Name of the table being vacuumed</entry>
+    </row>
+    <row>
+     <entry><structfield>total_heap_pages</></entry>
+     <entry><type>integer</></entry>
+     <entry>Total number of pages in this table</entry>
+    </row>
+    <row>
+     <entry><structfield>scanned_heap_pages</></entry>
+     <entry><type>integer</></entry>
+     <entry>Number of pages processed</entry>
+    </row>
+    <row>
+     <entry><structfield>percent_complete</></entry>
+     <entry><type>double precision</></entry>
+     <entry>Amount of work done in percent</entry>
+    </row>
+    <row>
+     <entry><structfield>total_index_pages</></entry>
+     <entry><type>integer</></entry>
+     <entry>Total number of index pages in this table</entry>
+    </row>
+    <row>
+     <entry><structfield>scanned_index_pages</></entry>
+     <entry><type>integer</></entry>
+     <entry>Number of index pages processed</entry>
+    </row>
+    <row>
+     <entry><structfield>index_scan_count</></entry>
+     <entry><type>integer</></entry>
+     <entry>Number of times index scan has been performed so far</entry>
+    </row>
+   </tbody>
+   </tgroup>
+  </table>
+
+  <para>
+   The <structname>pg_stat_vacuum_progress</structname> view will contain
+   one row for each backend (including autovacuum worker processes), showing
+   progress of <command>VACUUM</> running in it. Note that the backends
+   running <command>VACUUM FULL</> are not shown.
+  </para>
+
  </sect2>
 
  <sect2 id="monitoring-stats-functions">
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 536c805..d1f96d4 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -639,6 +639,19 @@ CREATE VIEW pg_stat_activity AS
     WHERE S.datid = D.oid AND
             S.usesysid = U.oid;
 
+CREATE VIEW pg_stat_vacuum_progress AS
+	SELECT
+			S.pid,
+			S.relid,
+			S.phase,
+			S.total_heap_pages,
+			S.scanned_heap_pages,
+			S.percent_complete,
+			S.total_index_pages,
+			S.scanned_index_pages,
+			S.index_scan_count
+	FROM pg_stat_get_vacuum_progress() AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7c4ef58..a8b865d 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -293,6 +293,9 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
 
 			if (options & VACOPT_VACUUM)
 			{
+				if (!(options & VACOPT_FULL))
+					pgstat_report_activity_flag(COMMAND_LAZY_VACUUM);
+
 				if (!vacuum_rel(relid, relation, options, params))
 					continue;
 			}
@@ -325,6 +328,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
 	{
 		in_vacuum = false;
 		VacuumCostActive = false;
+		pgstat_reset_activity_flag();
 		PG_RE_THROW();
 	}
 	PG_END_TRY();
@@ -355,6 +359,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
 		vac_update_datfrozenxid();
 	}
 
+	pgstat_reset_activity_flag();
 	/*
 	 * Clean up working storage --- note we must do this after
 	 * StartTransactionCommand, else we might be trying to delete the active
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index 2429889..5a6acd0 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -439,7 +439,11 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			   Relation *Irel, int nindexes, bool scan_all)
 {
 	BlockNumber nblocks,
-				blkno;
+				blkno,
+				total_heap_pages,
+				scanned_heap_pages = 0,
+				total_index_pages = 0,
+				scanned_index_pages = 0;
 	HeapTupleData tuple;
 	char	   *relname;
 	BlockNumber empty_pages,
@@ -456,14 +460,21 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	bool		skipping_all_visible_blocks;
 	xl_heap_freeze_tuple *frozen;
 	StringInfoData buf;
+	uint32 progress_param[N_PROGRESS_PARAM];
+	char	progress_message[N_PROGRESS_PARAM][PROGRESS_MESSAGE_LENGTH];
+	const char *phase1="Scanning Heap";
+	const char *phase2="Vacuuming Index and Heap";
+	Oid	relid;
 
 	pg_rusage_init(&ru0);
 
 	relname = RelationGetRelationName(onerel);
+	relid = RelationGetRelid(onerel);
 	ereport(elevel,
 			(errmsg("vacuuming \"%s.%s\"",
 					get_namespace_name(RelationGetNamespace(onerel)),
 					relname)));
+	pgstat_report_command_target(relid);
 
 	empty_pages = vacuumed_pages = 0;
 	num_tuples = tups_vacuumed = nkeep = nunused = 0;
@@ -471,7 +482,11 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	indstats = (IndexBulkDeleteResult **)
 		palloc0(nindexes * sizeof(IndexBulkDeleteResult *));
 
-	nblocks = RelationGetNumberOfBlocks(onerel);
+	total_heap_pages = nblocks = RelationGetNumberOfBlocks(onerel);
+
+	for (i = 0; i < nindexes; i++)
+		total_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+
 	vacrelstats->rel_pages = nblocks;
 	vacrelstats->scanned_pages = 0;
 	vacrelstats->nonempty_pages = 0;
@@ -520,10 +535,15 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		vacuum_delay_point();
 	}
 	if (next_not_all_visible_block >= SKIP_PAGES_THRESHOLD)
+	{
 		skipping_all_visible_blocks = true;
+		if(!scan_all)
+			scanned_heap_pages = scanned_heap_pages + next_not_all_visible_block;
+	}
 	else
 		skipping_all_visible_blocks = false;
 
+	snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s", phase1);
 	for (blkno = 0; blkno < nblocks; blkno++)
 	{
 		Buffer		buf;
@@ -559,7 +579,11 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			 * following blocks.
 			 */
 			if (next_not_all_visible_block - blkno > SKIP_PAGES_THRESHOLD)
+			{
 				skipping_all_visible_blocks = true;
+				if(!scan_all)
+					scanned_heap_pages = scanned_heap_pages + next_not_all_visible_block;
+			}
 			else
 				skipping_all_visible_blocks = false;
 			all_visible_according_to_vm = false;
@@ -596,11 +620,25 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			/* Log cleanup info before we touch indexes */
 			vacuum_log_cleanup_info(onerel, vacrelstats);
 
+			snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s", phase2);
+
 			/* Remove index entries */
 			for (i = 0; i < nindexes; i++)
+			{
 				lazy_vacuum_index(Irel[i],
 								  &indstats[i],
 								  vacrelstats);
+				scanned_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+
+				/* Report progress to the statistics collector */
+				progress_param[0] = total_heap_pages;
+				progress_param[1] = scanned_heap_pages;
+				progress_param[2] = total_index_pages;
+				progress_param[3] = scanned_index_pages;
+				progress_param[4] = vacrelstats->num_index_scans + 1;
+
+				pgstat_report_progress(progress_param, 5, progress_message, 1);
+			}
 			/* Remove tuples from heap */
 			lazy_vacuum_heap(onerel, vacrelstats);
 
@@ -610,8 +648,12 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			 * valid.
 			 */
 			vacrelstats->num_dead_tuples = 0;
+			scanned_index_pages = 0;
 			vacrelstats->num_index_scans++;
 		}
+		snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s", phase1);
+		pgstat_report_progress(progress_param, 5, progress_message, 1);
+
 
 		/*
 		 * Pin the visibility map page in case we need to mark the page
@@ -637,6 +679,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			if (!scan_all)
 			{
 				ReleaseBuffer(buf);
+				scanned_heap_pages++;
 				vacrelstats->pinskipped_pages++;
 				continue;
 			}
@@ -657,6 +700,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			{
 				UnlockReleaseBuffer(buf);
 				vacrelstats->scanned_pages++;
+				scanned_heap_pages++;
 				vacrelstats->pinskipped_pages++;
 				continue;
 			}
@@ -666,6 +710,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		}
 
 		vacrelstats->scanned_pages++;
+		scanned_heap_pages++;
 
 		page = BufferGetPage(buf);
 
@@ -1062,8 +1107,22 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		 */
 		if (vacrelstats->num_dead_tuples == prev_dead_count)
 			RecordPageWithFreeSpace(onerel, blkno, freespace);
-	}
 
+		/*
+		 * Reporting vacuum progress to statistics collector
+		 */
+		if (blkno == nblocks - 1 && vacrelstats->num_dead_tuples == 0 && nindexes != 0
+			&& vacrelstats->num_index_scans == 0)
+			total_index_pages = 0;
+
+		progress_param[0] = total_heap_pages;
+		progress_param[1] = scanned_heap_pages;
+		progress_param[2] = total_index_pages;
+		progress_param[3] = scanned_index_pages;
+		progress_param[4] = vacrelstats->num_index_scans;
+
+		pgstat_report_progress(progress_param, 5, progress_message, 1);
+	}
 	pfree(frozen);
 
 	/* save stats for use later */
@@ -1093,16 +1152,29 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		/* Log cleanup info before we touch indexes */
 		vacuum_log_cleanup_info(onerel, vacrelstats);
 
+		snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s", phase2);
+
 		/* Remove index entries */
 		for (i = 0; i < nindexes; i++)
+		{
 			lazy_vacuum_index(Irel[i],
 							  &indstats[i],
 							  vacrelstats);
+			scanned_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+			/* Report progress to the statistics collector */
+			progress_param[0] = total_heap_pages;
+			progress_param[1] = scanned_heap_pages;
+			progress_param[2] = total_index_pages;
+			progress_param[3] = scanned_index_pages;
+			progress_param[4] = vacrelstats->num_index_scans + 1;
+
+			pgstat_report_progress(progress_param, 5, progress_message, 1);
+		}
 		/* Remove tuples from heap */
 		lazy_vacuum_heap(onerel, vacrelstats);
 		vacrelstats->num_index_scans++;
+		scanned_index_pages = 0;
 	}
-
 	/* Do post-vacuum cleanup and statistics update for each index */
 	for (i = 0; i < nindexes; i++)
 		lazy_cleanup_index(Irel[i], indstats[i], vacrelstats);
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index ab018c4..f979876 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -2851,12 +2851,78 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
 	pgstat_increment_changecount_after(beentry);
 }
 
+/* ---------------------------------------------
+ * Called from VACUUM  after every heap page scan or index scan
+ * to report progress
+ * ---------------------------------------------
+ */
+
+void
+pgstat_report_progress(uint32 *param1, int num_of_int, char param2[N_PROGRESS_PARAM][PROGRESS_MESSAGE_LENGTH],
+						int num_of_string)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+	int i;
+
+	if (!beentry)
+		return;
+
+	if (!pgstat_track_activities)
+		return;
+
+	pgstat_increment_changecount_before(beentry);
+
+	for(i = 0; i < num_of_int; i++)
+	{
+		beentry->st_progress_param[i] = param1[i];
+	}
+
+	for (i = 0; i < num_of_string; i++)
+	{
+		strcpy((char *)beentry->st_progress_message[i], param2[i]);
+	}
+
+	pgstat_increment_changecount_after(beentry);
+}
+
+/* ----------
+ *	pgstat_report_command_target() -
+ *
+ *	Called to update command target relation oid.
+ * ----------
+ */
+void
+pgstat_report_command_target(Oid relid)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	if (!beentry)
+		return;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_relid = relid;
+	pgstat_increment_changecount_after(beentry);
+}
+
+void
+pgstat_report_activity_flag(activity_flag)
+{
+	PgBackendStatus *beentry = MyBEEntry;
+	beentry->st_activity_flag = activity_flag;
+}
+void
+pgstat_reset_activity_flag()
+{
+	PgBackendStatus *beentry = MyBEEntry;
+	beentry->st_activity_flag = 0;
+}
 /* ----------
  * pgstat_report_appname() -
  *
  *	Called to update our application name.
  * ----------
  */
+
 void
 pgstat_report_appname(const char *appname)
 {
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index f7c9bf6..63853c6 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -53,6 +53,7 @@ extern Datum pg_stat_get_function_self_time(PG_FUNCTION_ARGS);
 
 extern Datum pg_stat_get_backend_idset(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_activity(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_vacuum_progress(PG_FUNCTION_ARGS);
 extern Datum pg_backend_pid(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_pid(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_dbid(PG_FUNCTION_ARGS);
@@ -523,7 +524,93 @@ pg_stat_get_backend_idset(PG_FUNCTION_ARGS)
 		SRF_RETURN_DONE(funcctx);
 	}
 }
+/*
+ * Returns VACUUM progress values stored by each backend
+ * executing VACUUM.
+ */
+Datum
+pg_stat_get_vacuum_progress(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_PROGRESS_COLS	30
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
 
+	/* 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);
+
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		Datum		values[PG_STAT_GET_PROGRESS_COLS];
+		bool		nulls[PG_STAT_GET_PROGRESS_COLS];
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+
+		MemSet(values, 0, sizeof(values));
+		MemSet(nulls, 0, sizeof(nulls));
+
+		local_beentry = pgstat_fetch_stat_local_beentry(curr_backend);
+		if (!local_beentry)
+			continue;
+		beentry = &local_beentry->backendStatus;
+
+		/* Report values for only those backends which are running VACUUM */
+		if(!beentry || beentry->st_activity_flag != COMMAND_LAZY_VACUUM)
+			continue;
+
+		/* Skip backend if current user does not have privilege to see its activity info.*/
+		if (!has_privs_of_role(GetUserId(), beentry->st_userid))
+			continue;
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+		values[1] = ObjectIdGetDatum(beentry->st_relid);
+
+		values[2] = CStringGetTextDatum(beentry->st_progress_message[0]);
+		values[3] = UInt32GetDatum(beentry->st_progress_param[0]);
+		values[4] = UInt32GetDatum(beentry->st_progress_param[1]);
+
+		if (beentry->st_progress_param[0] != 0)
+			values[5] = Float8GetDatum(beentry->st_progress_param[1] * 100 / beentry->st_progress_param[0]);
+		else
+			nulls[5] = true;
+
+		values[6] = UInt32GetDatum(beentry->st_progress_param[2]);
+		values[7] = UInt32GetDatum(beentry->st_progress_param[3]);
+		values[8] = UInt32GetDatum(beentry->st_progress_param[4]);
+
+		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+	}
+
+	/* clean up and return the tuplestore */
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
 /*
  * Returns activity of PG backends.
  */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index d8640db..58e7c1f 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2783,6 +2783,8 @@ DATA(insert OID = 1936 (  pg_stat_get_backend_idset		PGNSP PGUID 12 1 100 0 0 f
 DESCR("statistics: currently active backend IDs");
 DATA(insert OID = 2022 (  pg_stat_get_activity			PGNSP PGUID 12 1 100 0 0 f f f f f t s r 1 0 2249 "23" "{23,26,23,26,25,25,25,16,1184,1184,1184,1184,869,25,23,28,28,16,25,25,23,16,25}" "{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{pid,datid,pid,usesysid,application_name,state,query,waiting,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,ssl,sslversion,sslcipher,sslbits,sslcompression,sslclientdn}" _null_ _null_ pg_stat_get_activity _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active backends");
+DATA(insert OID = 3319 (  pg_stat_get_vacuum_progress           PGNSP PGUID 12 1 1 0 0 f f f f f t s r 0 0 2249 "" "{23,26,25,23,23,701,23,23,23}" "{o,o,o,o,o,o,o,o,o}" "{pid,relid,phase,total_heap_pages,scanned_heap_pages,percent_complete,total_index_pages,scanned_index_pages,index_scan_count}" _null_ _null_ pg_stat_get_vacuum_progress _null_ _null_ _null_ ));
+DESCR("statistics: information about progress of backends running VACUUM");
 DATA(insert OID = 3099 (  pg_stat_get_wal_senders	PGNSP PGUID 12 1 10 0 0 f f f f f t s r 0 0 2249 "" "{23,25,3220,3220,3220,3220,23,25}" "{o,o,o,o,o,o,o,o}" "{pid,state,sent_location,write_location,flush_location,replay_location,sync_priority,sync_state}" _null_ _null_ pg_stat_get_wal_senders _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active replication");
 DATA(insert OID = 2026 (  pg_backend_pid				PGNSP PGUID 12 1 0 0 0 f f f f t f s r 0 0 23 "" _null_ _null_ _null_ _null_ _null_ pg_backend_pid _null_ _null_ _null_ ));
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 9ecc163..6b0cbde 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -205,6 +205,8 @@ typedef struct PgStat_MsgHdr
 #define PGSTAT_MAX_MSG_SIZE 1000
 #define PGSTAT_MSG_PAYLOAD	(PGSTAT_MAX_MSG_SIZE - sizeof(PgStat_MsgHdr))
 
+#define N_PROGRESS_PARAM 10
+#define PROGRESS_MESSAGE_LENGTH 30
 
 /* ----------
  * PgStat_MsgDummy				A dummy message, ignored by the collector
@@ -776,6 +778,20 @@ typedef struct PgBackendStatus
 
 	/* current command string; MUST be null-terminated */
 	char	   *st_activity;
+
+	/*
+	 * Information about the progress of activity/command being run by the backend.
+	 * The progress parameters indicate progress of a command. Different
+	 * commands can report different number of parameters of each type.
+	 *
+	 * st_activity_flag reports which activity/command is being run by the backend.
+	 * This is used in the SQL callable functions to display progress values
+	 * for respective commands.
+	 */
+	uint32		st_activity_flag;
+	Oid		st_relid;
+	uint32		st_progress_param[N_PROGRESS_PARAM];
+	char		st_progress_message[N_PROGRESS_PARAM][PROGRESS_MESSAGE_LENGTH];
 } PgBackendStatus;
 
 /*
@@ -815,6 +831,7 @@ typedef struct PgBackendStatus
 		save_changecount = beentry->st_changecount; \
 	} while (0)
 
+#define COMMAND_LAZY_VACUUM 0x01
 /* ----------
  * LocalPgBackendStatus
  *
@@ -928,6 +945,10 @@ extern void pgstat_initialize(void);
 extern void pgstat_bestart(void);
 
 extern void pgstat_report_activity(BackendState state, const char *cmd_str);
+extern void pgstat_report_activity_flag(int activity_flag);
+extern void pgstat_reset_activity_flag(void);
+extern void pgstat_report_progress(uint32 *param1, int num_of_int, char param2[N_PROGRESS_PARAM][PROGRESS_MESSAGE_LENGTH],
+									int num_of_string);
 extern void pgstat_report_tempfile(size_t filesize);
 extern void pgstat_report_appname(const char *appname);
 extern void pgstat_report_xact_timestamp(TimestampTz tstamp);
@@ -938,6 +959,7 @@ extern const char *pgstat_get_crashed_backend_activity(int pid, char *buffer,
 
 extern PgStat_TableStatus *find_tabstat_entry(Oid rel_id);
 extern PgStat_BackendFunctionEntry *find_funcstat_entry(Oid func_id);
+extern void pgstat_report_command_target(Oid relid);
 
 extern void pgstat_initstats(Relation rel);
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 80374e4..6767bfb 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1848,6 +1848,16 @@ pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
     pg_stat_all_tables.autoanalyze_count
    FROM pg_stat_all_tables
   WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
+pg_stat_vacuum_progress| SELECT s.pid,
+    s.relid,
+    s.total_heap_pages,
+    s.scanned_heap_pages,
+    s.total_index_pages,
+    s.scanned_index_pages,
+    s.total_pages,
+    s.scanned_pages,
+    s.percent_complete
+   FROM pg_stat_get_vacuum_progress() s(pid, relid, total_heap_pages, scanned_heap_pages, total_index_pages, scanned_index_pages, total_pages, scanned_pages, percent_complete);
 pg_stat_xact_all_tables| SELECT c.oid AS relid,
     n.nspname AS schemaname,
     c.relname,
#140Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Vinayak Pokale (#139)
Re: [PROPOSAL] VACUUM Progress Checker.

Hi Vinayak,

On 2015/12/25 21:46, Vinayak Pokale wrote:

Hi,
Please find attached patch addressing following comments.

The relation OID should be reported and not its name. In case of a
relation rename that would not be cool for tracking, and most users
are surely going to join with other system tables using it.

The relation OID is reported instead of relation name.
The following interface function is called at the beginning to report the
relation OID once.
void pgstat_report_command_target(Oid relid)

Regarding pg_stat_get_vacuum_progress(): I think a backend can simply be
skipped if (!has_privs_of_role(GetUserId(), beentry->st_userid)) (cannot
put that in plain English, :))

Updated in the attached patch.

In the previous patch, ACTIVITY_IS_VACUUM is set unnecessarily for
VACOPT_FULL and they are not covered by lazy_scan_heap().
I have updated it in attached patch and also renamed ACTIVITY_IS_VACUUM to
COMMAND_LAZY_VACUUM.

Added documentation for view.
Some more comments need to be addressed.

I suspect you need to create a new CF entry for this patch in CF 2016-01.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#141Sudhir Lonkar-2
sudhir.lonkar@nttdata.com
In reply to: Vinayak Pokale (#139)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

Please find attached patch addressing following comments

I have tested this patch.
It is showing empty (null) in phase column of pg_stat_vacuum_progress, when
I switched to a unprivileged user.
In the previous patch, it is showing <insufficient privilege> in phase
column.

Thanks and Regards,
Sudhir Lonkar

--
View this message in context: http://postgresql.nabble.com/PROPOSAL-VACUUM-Progress-Checker-tp5855849p5880544.html
Sent from the PostgreSQL - hackers mailing list archive at Nabble.com.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#142Rahila Syed
rahilasyed90@gmail.com
In reply to: Amit Langote (#140)
Re: [PROPOSAL] VACUUM Progress Checker.

I suspect you need to create a new CF entry for this patch in CF 2016-01.

Unless I am missing something, there seems to be no entry for this patch
into CF 2016-01 page: https://commitfest.postgresql.org/8/.
Regrettably, we have exceeded the deadline to add the patch into this
commitfest. Is there still some way to add it to the commitfest 2016-01? As
this feature has received lot of feedback in previous commitfest , adding
it to this commitfest will surely help in progressing it in order to make
it ready for PostgreSQL 9.6.

Thank you,
Rahila Syed

On Mon, Dec 28, 2015 at 6:01 AM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp

Show quoted text

wrote:

Hi Vinayak,

On 2015/12/25 21:46, Vinayak Pokale wrote:

Hi,
Please find attached patch addressing following comments.

The relation OID should be reported and not its name. In case of a
relation rename that would not be cool for tracking, and most users
are surely going to join with other system tables using it.

The relation OID is reported instead of relation name.
The following interface function is called at the beginning to report the
relation OID once.
void pgstat_report_command_target(Oid relid)

Regarding pg_stat_get_vacuum_progress(): I think a backend can simply be
skipped if (!has_privs_of_role(GetUserId(), beentry->st_userid)) (cannot
put that in plain English, :))

Updated in the attached patch.

In the previous patch, ACTIVITY_IS_VACUUM is set unnecessarily for
VACOPT_FULL and they are not covered by lazy_scan_heap().
I have updated it in attached patch and also renamed ACTIVITY_IS_VACUUM

to

COMMAND_LAZY_VACUUM.

Added documentation for view.
Some more comments need to be addressed.

I suspect you need to create a new CF entry for this patch in CF 2016-01.

Thanks,
Amit

#143Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Rahila Syed (#142)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2016/01/08 21:20, Rahila Syed wrote:

I suspect you need to create a new CF entry for this patch in CF 2016-01.

Unless I am missing something, there seems to be no entry for this patch
into CF 2016-01 page: https://commitfest.postgresql.org/8/.
Regrettably, we have exceeded the deadline to add the patch into this
commitfest. Is there still some way to add it to the commitfest 2016-01? As
this feature has received lot of feedback in previous commitfest , adding
it to this commitfest will surely help in progressing it in order to make
it ready for PostgreSQL 9.6.

I see that the patch has been added to the CF.

I'm slightly concerned that the latest patch doesn't incorporate any
revisions to the original pgstat interface per Robert's comments in [1]/messages/by-id/CA+TgmoZ5q4N4T0c0_-XKTencEWOAbfdKtoPPT8NUjjjV5OHMFQ@mail.gmail.com.

Thanks,
Amit

[1]: /messages/by-id/CA+TgmoZ5q4N4T0c0_-XKTencEWOAbfdKtoPPT8NUjjjV5OHMFQ@mail.gmail.com
/messages/by-id/CA+TgmoZ5q4N4T0c0_-XKTencEWOAbfdKtoPPT8NUjjjV5OHMFQ@mail.gmail.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#144Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Amit Langote (#143)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2016/01/12 10:30, Amit Langote wrote:

I'm slightly concerned that the latest patch doesn't incorporate any
revisions to the original pgstat interface per Robert's comments in [1].

I meant to say "originally proposed pgstat interface on this thread".

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#145Vinayak Pokale
vinpokale@gmail.com
In reply to: Amit Langote (#144)
Re: [PROPOSAL] VACUUM Progress Checker.

On Jan 12, 2016 11:22 AM, "Amit Langote" <Langote_Amit_f8@lab.ntt.co.jp>
wrote:

On 2016/01/12 10:30, Amit Langote wrote:

I'm slightly concerned that the latest patch doesn't incorporate any
revisions to the original pgstat interface per Robert's comments in [1].

I meant to say "originally proposed pgstat interface on this thread".

Yes.
Robert's comments related to pgstat interface needs to be address.
I will update it.

Regards,
Vinayak Pokale

#146Vinayak Pokale
vinpokale@gmail.com
In reply to: Sudhir Lonkar-2 (#141)
Re: [PROPOSAL] VACUUM Progress Checker.

Hi Sudhir,

On Jan 7, 2016 3:02 AM, "Sudhir Lonkar-2" <sudhir.lonkar@nttdata.com> wrote:

Hello,

Please find attached patch addressing following comments

I have tested this patch.
It is showing empty (null) in phase column of pg_stat_vacuum_progress,

when

I switched to a unprivileged user.
In the previous patch, it is showing <insufficient privilege> in phase
column.

Yes. I will update the patch.
Regards,
Vinayak Pokale

#147Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Vinayak Pokale (#145)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2016/01/12 11:28, Vinayak Pokale wrote:

On Jan 12, 2016 11:22 AM, "Amit Langote" <Langote_Amit_f8@lab.ntt.co.jp>
wrote:

On 2016/01/12 10:30, Amit Langote wrote:

I'm slightly concerned that the latest patch doesn't incorporate any
revisions to the original pgstat interface per Robert's comments in [1].

I meant to say "originally proposed pgstat interface on this thread".

Yes.
Robert's comments related to pgstat interface needs to be address.
I will update it.

So, I updated the patch status to "Waiting on Author".

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#148Vinayak Pokale
vinpokale@gmail.com
In reply to: Amit Langote (#147)
1 attachment(s)
Re: [PROPOSAL] VACUUM Progress Checker.

Hi,

Please find attached updated patch with an updated interface.

I added the below interface to update the
scanned_heap_pages,scanned_index_pages and index_scan_count only.
void pgstat_report_progress_scanned_pages(int num_of_int, uint32
*progress_scanned_pages_param)

Other interface functions which are called at the beginning:
void pgstat_report_progress_set_command_target(Oid relid)

Regards,
Vinayak

On Wed, Jan 13, 2016 at 3:16 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp

Show quoted text

wrote:

On 2016/01/12 11:28, Vinayak Pokale wrote:

On Jan 12, 2016 11:22 AM, "Amit Langote" <Langote_Amit_f8@lab.ntt.co.jp>
wrote:

On 2016/01/12 10:30, Amit Langote wrote:

I'm slightly concerned that the latest patch doesn't incorporate any
revisions to the original pgstat interface per Robert's comments in

[1].

I meant to say "originally proposed pgstat interface on this thread".

Yes.
Robert's comments related to pgstat interface needs to be address.
I will update it.

So, I updated the patch status to "Waiting on Author".

Thanks,
Amit

Attachments:

Vacuum_progress_checker_v9.patchapplication/octet-stream; name=Vacuum_progress_checker_v9.patchDownload
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 85459d0..e7e53bd 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -507,6 +507,13 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       yet included in <structname>pg_stat_user_functions</>).</entry>
      </row>
 
+     <row>
+      <entry><structname>pg_stat_vacuum_progress</><indexterm><primary>pg_stat_vacuum_progress</primary></indexterm></entry>
+      <entry>One row for each backend (including autovacuum worker processes) running
+      <command>VACUUM</>, showing current progress in terms of heap pages it
+      has finished processing. Backends running <command>VACUUM FULL</> are
+      not included.</entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -1822,6 +1829,74 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
    controls exactly which functions are tracked.
   </para>
 
+  <table id="pg-stat-vacuum-progress" xreflabel="pg_stat_vacuum_progress">
+   <title><structname>pg_stat_vacuum_progress</structname> View</title>
+   <tgroup cols="3">
+    <thead>
+    <row>
+      <entry>Column</entry>
+      <entry>Type</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+   <tbody>
+    <row>
+     <entry><structfield>pid</></entry>
+     <entry><type>integer</></entry>
+     <entry>Process ID of backend</entry>
+    </row>
+    <row>
+     <entry><structfield>relid</></entry>
+     <entry><type>oid</></entry>
+     <entry>OID of a table</entry>
+    </row>
+    <row>
+     <entry><structfield>table_name</></entry>
+     <entry><type>name</></entry>
+     <entry>Name of the table being vacuumed</entry>
+    </row>
+    <row>
+     <entry><structfield>total_heap_pages</></entry>
+     <entry><type>integer</></entry>
+     <entry>Total number of pages in this table</entry>
+    </row>
+    <row>
+     <entry><structfield>scanned_heap_pages</></entry>
+     <entry><type>integer</></entry>
+     <entry>Number of pages processed</entry>
+    </row>
+    <row>
+     <entry><structfield>percent_complete</></entry>
+     <entry><type>double precision</></entry>
+     <entry>Amount of work done in percent</entry>
+    </row>
+    <row>
+     <entry><structfield>total_index_pages</></entry>
+     <entry><type>integer</></entry>
+     <entry>Total number of index pages in this table</entry>
+    </row>
+    <row>
+     <entry><structfield>scanned_index_pages</></entry>
+     <entry><type>integer</></entry>
+     <entry>Number of index pages processed</entry>
+    </row>
+    <row>
+     <entry><structfield>index_scan_count</></entry>
+     <entry><type>integer</></entry>
+     <entry>Number of times index scan has been performed so far</entry>
+    </row>
+   </tbody>
+   </tgroup>
+  </table>
+
+  <para>
+   The <structname>pg_stat_vacuum_progress</structname> view will contain
+   one row for each backend (including autovacuum worker processes), showing
+   progress of <command>VACUUM</> running in it. Note that the backends
+   running <command>VACUUM FULL</> are not shown.
+  </para>
+
  </sect2>
 
  <sect2 id="monitoring-stats-functions">
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 923fe58..81c0fa9 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -639,6 +639,19 @@ CREATE VIEW pg_stat_activity AS
     WHERE S.datid = D.oid AND
             S.usesysid = U.oid;
 
+CREATE VIEW pg_stat_vacuum_progress AS
+	SELECT
+			S.pid,
+			S.relid,
+			S.phase,
+			S.total_heap_pages,
+			S.scanned_heap_pages,
+			S.percent_complete,
+			S.total_index_pages,
+			S.scanned_index_pages,
+			S.index_scan_count
+	FROM pg_stat_get_vacuum_progress() AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 4cb4acf..9ea014d 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -293,6 +293,9 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
 
 			if (options & VACOPT_VACUUM)
 			{
+				if (!(options & VACOPT_FULL))
+					pgstat_report_progress_set_command(COMMAND_LAZY_VACUUM);
+
 				if (!vacuum_rel(relid, relation, options, params))
 					continue;
 			}
@@ -325,6 +328,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
 	{
 		in_vacuum = false;
 		VacuumCostActive = false;
+		pgstat_reset_local_progress();
 		PG_RE_THROW();
 	}
 	PG_END_TRY();
@@ -355,6 +359,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
 		vac_update_datfrozenxid();
 	}
 
+	pgstat_reset_local_progress();
 	/*
 	 * Clean up working storage --- note we must do this after
 	 * StartTransactionCommand, else we might be trying to delete the active
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index 4f6f6e7..7a81f74 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -433,7 +433,11 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			   Relation *Irel, int nindexes, bool scan_all)
 {
 	BlockNumber nblocks,
-				blkno;
+				blkno,
+				total_heap_pages,
+				scanned_heap_pages = 0,
+				total_index_pages = 0,
+				scanned_index_pages = 0;
 	HeapTupleData tuple;
 	char	   *relname;
 	BlockNumber empty_pages,
@@ -450,14 +454,25 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	bool		skipping_all_visible_blocks;
 	xl_heap_freeze_tuple *frozen;
 	StringInfoData buf;
+	uint32 *progress_param;
+	uint32 *progress_scanned_pages_param;
+	char	progress_message[N_PROGRESS_PARAM][PROGRESS_MESSAGE_LENGTH];
+	const char *phase1="Scanning Heap";
+	const char *phase2="Vacuuming Index and Heap";
+	Oid	relid;
 
 	pg_rusage_init(&ru0);
 
 	relname = RelationGetRelationName(onerel);
+	relid = RelationGetRelid(onerel);
 	ereport(elevel,
 			(errmsg("vacuuming \"%s.%s\"",
 					get_namespace_name(RelationGetNamespace(onerel)),
 					relname)));
+	pgstat_report_progress_set_command_target(relid);
+
+	progress_param = (uint32 *) palloc0(2 * sizeof(uint32));
+	progress_scanned_pages_param = (uint32 *) palloc0(3 * sizeof(uint32));
 
 	empty_pages = vacuumed_pages = 0;
 	num_tuples = tups_vacuumed = nkeep = nunused = 0;
@@ -465,7 +480,11 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	indstats = (IndexBulkDeleteResult **)
 		palloc0(nindexes * sizeof(IndexBulkDeleteResult *));
 
-	nblocks = RelationGetNumberOfBlocks(onerel);
+	total_heap_pages = nblocks = RelationGetNumberOfBlocks(onerel);
+
+	for (i = 0; i < nindexes; i++)
+		total_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+
 	vacrelstats->rel_pages = nblocks;
 	vacrelstats->scanned_pages = 0;
 	vacrelstats->nonempty_pages = 0;
@@ -523,10 +542,15 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		vacuum_delay_point();
 	}
 	if (next_not_all_visible_block >= SKIP_PAGES_THRESHOLD)
+	{
 		skipping_all_visible_blocks = true;
+		if(!scan_all)
+			scanned_heap_pages = scanned_heap_pages + next_not_all_visible_block;
+	}
 	else
 		skipping_all_visible_blocks = false;
 
+	snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s", phase1);
 	for (blkno = 0; blkno < nblocks; blkno++)
 	{
 		Buffer		buf;
@@ -566,7 +590,11 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			 * following blocks.
 			 */
 			if (next_not_all_visible_block - blkno > SKIP_PAGES_THRESHOLD)
+			{
 				skipping_all_visible_blocks = true;
+				if(!scan_all)
+					scanned_heap_pages = scanned_heap_pages + next_not_all_visible_block;
+			}
 			else
 				skipping_all_visible_blocks = false;
 			all_visible_according_to_vm = false;
@@ -603,11 +631,25 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			/* Log cleanup info before we touch indexes */
 			vacuum_log_cleanup_info(onerel, vacrelstats);
 
+			snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s", phase2);
+
 			/* Remove index entries */
 			for (i = 0; i < nindexes; i++)
+			{
 				lazy_vacuum_index(Irel[i],
 								  &indstats[i],
 								  vacrelstats);
+				scanned_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+
+				/* Report progress to the statistics collector */
+				progress_param[0] = total_heap_pages;
+				progress_param[1] = total_index_pages;
+
+				progress_scanned_pages_param[0] = scanned_heap_pages;
+				progress_scanned_pages_param[1] = scanned_index_pages;
+				pgstat_report_progress(NULL, 0, progress_message, 1);
+				pgstat_report_progress_scanned_pages(2, progress_scanned_pages_param);
+			}
 			/* Remove tuples from heap */
 			lazy_vacuum_heap(onerel, vacrelstats);
 
@@ -617,8 +659,13 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			 * valid.
 			 */
 			vacrelstats->num_dead_tuples = 0;
+			scanned_index_pages = 0;
 			vacrelstats->num_index_scans++;
 		}
+		snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s", phase1);
+
+		pgstat_report_progress(progress_param, 2, progress_message, 1);
+		pgstat_report_progress_scanned_pages(3,progress_scanned_pages_param);
 
 		/*
 		 * Pin the visibility map page in case we need to mark the page
@@ -645,6 +692,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			if (!scan_all && !FORCE_CHECK_PAGE())
 			{
 				ReleaseBuffer(buf);
+				scanned_heap_pages++;
 				vacrelstats->pinskipped_pages++;
 				continue;
 			}
@@ -670,6 +718,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			{
 				UnlockReleaseBuffer(buf);
 				vacrelstats->scanned_pages++;
+				scanned_heap_pages++;
 				vacrelstats->pinskipped_pages++;
 				if (hastup)
 					vacrelstats->nonempty_pages = blkno + 1;
@@ -693,6 +742,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		}
 
 		vacrelstats->scanned_pages++;
+		scanned_heap_pages++;
 
 		page = BufferGetPage(buf);
 
@@ -1089,8 +1139,23 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		 */
 		if (vacrelstats->num_dead_tuples == prev_dead_count)
 			RecordPageWithFreeSpace(onerel, blkno, freespace);
-	}
 
+		/*
+		 * Reporting vacuum progress to statistics collector
+		 */
+		if (blkno == nblocks - 1 && vacrelstats->num_dead_tuples == 0 && nindexes != 0
+			&& vacrelstats->num_index_scans == 0)
+			total_index_pages = 0;
+
+		progress_param[0] = total_heap_pages;
+		progress_param[1] = total_index_pages;
+
+		progress_scanned_pages_param[0] = scanned_heap_pages;
+		progress_scanned_pages_param[1] = scanned_index_pages;
+
+		pgstat_report_progress(NULL, 0, progress_message, 1);
+		pgstat_report_progress_scanned_pages(2, progress_scanned_pages_param);
+	}
 	pfree(frozen);
 
 	/* save stats for use later */
@@ -1120,16 +1185,30 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		/* Log cleanup info before we touch indexes */
 		vacuum_log_cleanup_info(onerel, vacrelstats);
 
+		snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s", phase2);
+
 		/* Remove index entries */
 		for (i = 0; i < nindexes; i++)
+		{
 			lazy_vacuum_index(Irel[i],
 							  &indstats[i],
 							  vacrelstats);
+			scanned_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+			/* Report progress to the statistics collector */
+			progress_param[0] = total_heap_pages;
+			progress_param[1] = total_index_pages;
+			progress_scanned_pages_param[0] = scanned_heap_pages;
+			progress_scanned_pages_param[1] = scanned_index_pages;
+			progress_scanned_pages_param[2] = vacrelstats->num_index_scans + 1;
+
+			pgstat_report_progress(NULL, 0, progress_message, 1);
+			pgstat_report_progress_scanned_pages(3, progress_scanned_pages_param);
+		}
 		/* Remove tuples from heap */
 		lazy_vacuum_heap(onerel, vacrelstats);
 		vacrelstats->num_index_scans++;
+		scanned_index_pages = 0;
 	}
-
 	/* Do post-vacuum cleanup and statistics update for each index */
 	for (i = 0; i < nindexes; i++)
 		lazy_cleanup_index(Irel[i], indstats[i], vacrelstats);
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index da768c6..7454919 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -2851,6 +2851,128 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
 	pgstat_increment_changecount_after(beentry);
 }
 
+/* -------
+ * pgstat_report_progress() -
+ *
+ * Called from VACUUM to report total heap pages
+ * and total index pages
+ * -------
+ */
+void
+pgstat_report_progress(uint32 *param1, int num_of_int, char param2[N_PROGRESS_PARAM][PROGRESS_MESSAGE_LENGTH],
+						int num_of_string)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+	int i;
+
+	if (!beentry)
+		return;
+
+	if (!pgstat_track_activities)
+		return;
+
+	pgstat_increment_changecount_before(beentry);
+
+	for(i = 0; i < num_of_int; i++)
+	{
+		beentry->st_progress_param[i] = param1[i];
+	}
+
+	for (i = 0; i < num_of_string; i++)
+	{
+		strcpy((char *)beentry->st_progress_message[i], param2[i]);
+	}
+
+	pgstat_increment_changecount_after(beentry);
+}
+
+/*-----------
+ * pgstat_report_progress_scanned_pages()-
+ *
+ * Called to update heap pages scanned,index pages scanned
+ * and number of index scan
+ *-----------
+ */
+void
+pgstat_report_progress_scanned_pages(int num_of_int, uint32 *progress_scanned_pages_param)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+	int i;
+
+	if(!beentry)
+		return;
+
+	if (!pgstat_track_activities)
+		return;
+
+	pgstat_increment_changecount_before(beentry);
+	for(i = 0; i < num_of_int; i++)
+	{
+		beentry->st_progress_scanned_pages_param[i] = progress_scanned_pages_param[i];
+	}
+	pgstat_increment_changecount_after(beentry);
+}
+
+/* ----------
+ *	pgstat_report_progress_set_command_target() -
+ *
+ *	Called to update command target relation oid.
+ * ----------
+ */
+void
+pgstat_report_progress_set_command_target(Oid relid)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	if (!beentry)
+		return;
+
+	if (!pgstat_track_activities)
+		return;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_relid = relid;
+	pgstat_increment_changecount_after(beentry);
+}
+
+/*-----------
+ * pgstat_report_progress_set_command()-
+ *
+ * Called to update command the backend is about to start running.
+ *-----------
+ */
+void
+pgstat_report_progress_set_command(int16 commandId)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	if (!beentry)
+		return;
+
+	if (!pgstat_track_activities)
+		return;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_command = commandId;
+	pgstat_increment_changecount_after(beentry);
+}
+
+/*--------
+ * pgstat_reset_local_progress()-
+ *
+ * Reset local backend's progress parameters. Resetting st_command will do.
+ *--------
+ */
+void
+pgstat_reset_local_progress(void)
+{
+	PgBackendStatus *beentry = MyBEEntry;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_command = 0;
+	pgstat_increment_changecount_after(beentry);
+}
+
 /* ----------
  * pgstat_report_appname() -
  *
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 1b22fcc..c5db36c 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -53,6 +53,7 @@ extern Datum pg_stat_get_function_self_time(PG_FUNCTION_ARGS);
 
 extern Datum pg_stat_get_backend_idset(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_activity(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_vacuum_progress(PG_FUNCTION_ARGS);
 extern Datum pg_backend_pid(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_pid(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_dbid(PG_FUNCTION_ARGS);
@@ -523,7 +524,102 @@ pg_stat_get_backend_idset(PG_FUNCTION_ARGS)
 		SRF_RETURN_DONE(funcctx);
 	}
 }
+/*
+ * Returns VACUUM progress values stored by each backend
+ * executing VACUUM.
+ */
+Datum
+pg_stat_get_vacuum_progress(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_PROGRESS_COLS	30
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
 
+	/* 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);
+
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		Datum		values[PG_STAT_GET_PROGRESS_COLS];
+		bool		nulls[PG_STAT_GET_PROGRESS_COLS];
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+
+		MemSet(values, 0, sizeof(values));
+		MemSet(nulls, 0, sizeof(nulls));
+
+		local_beentry = pgstat_fetch_stat_local_beentry(curr_backend);
+		if (!local_beentry)
+			continue;
+		beentry = &local_beentry->backendStatus;
+
+		/* Report values for only those backends which are running VACUUM */
+		if(!beentry || beentry->st_command != COMMAND_LAZY_VACUUM)
+			continue;
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+		values[1] = ObjectIdGetDatum(beentry->st_relid);
+
+		/*Progress can only be viewed by role member.*/
+		if (has_privs_of_role(GetUserId(), beentry->st_userid))
+		{
+			values[2] = CStringGetTextDatum(beentry->st_progress_message[0]);
+			values[3] = UInt32GetDatum(beentry->st_progress_param[0]);
+			values[4] = UInt32GetDatum(beentry->st_progress_scanned_pages_param[0]);
+
+			if (beentry->st_progress_param[0] != 0)
+				values[5] = Float8GetDatum(beentry->st_progress_scanned_pages_param[0] * 100 / beentry->st_progress_param[0]);
+			else
+				nulls[5] = true;
+
+			values[6] = UInt32GetDatum(beentry->st_progress_param[1]);
+			values[7] = UInt32GetDatum(beentry->st_progress_scanned_pages_param[1]);
+			values[8] = UInt32GetDatum(beentry->st_progress_scanned_pages_param[2]);
+		}
+		else
+		{
+			values[2] = CStringGetTextDatum("<insufficient privilege>");
+			nulls[3] = true;
+			nulls[4] = true;
+			nulls[5] = true;
+			nulls[6] = true;
+			nulls[7] = true;
+			nulls[8] = true;
+		}
+		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+	}
+
+	/* clean up and return the tuplestore */
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
 /*
  * Returns activity of PG backends.
  */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 244aa4d..88451ab 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2684,6 +2684,8 @@ DATA(insert OID = 1936 (  pg_stat_get_backend_idset		PGNSP PGUID 12 1 100 0 0 f
 DESCR("statistics: currently active backend IDs");
 DATA(insert OID = 2022 (  pg_stat_get_activity			PGNSP PGUID 12 1 100 0 0 f f f f f t s r 1 0 2249 "23" "{23,26,23,26,25,25,25,16,1184,1184,1184,1184,869,25,23,28,28,16,25,25,23,16,25}" "{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{pid,datid,pid,usesysid,application_name,state,query,waiting,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,ssl,sslversion,sslcipher,sslbits,sslcompression,sslclientdn}" _null_ _null_ pg_stat_get_activity _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active backends");
+DATA(insert OID = 3319 (  pg_stat_get_vacuum_progress           PGNSP PGUID 12 1 1 0 0 f f f f f t s r 0 0 2249 "" "{23,26,25,23,23,701,23,23,23}" "{o,o,o,o,o,o,o,o,o}" "{pid,relid,phase,total_heap_pages,scanned_heap_pages,percent_complete,total_index_pages,scanned_index_pages,index_scan_count}" _null_ _null_ pg_stat_get_vacuum_progress _null_ _null_ _null_ ));
+DESCR("statistics: information about progress of backends running VACUUM");
 DATA(insert OID = 3099 (  pg_stat_get_wal_senders	PGNSP PGUID 12 1 10 0 0 f f f f f t s r 0 0 2249 "" "{23,25,3220,3220,3220,3220,23,25}" "{o,o,o,o,o,o,o,o}" "{pid,state,sent_location,write_location,flush_location,replay_location,sync_priority,sync_state}" _null_ _null_ pg_stat_get_wal_senders _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active replication");
 DATA(insert OID = 3317 (  pg_stat_get_wal_receiver	PGNSP PGUID 12 1 0 0 0 f f f f f f s r 0 0 2249 "" "{23,25,3220,23,3220,23,1184,1184,3220,1184,25}" "{o,o,o,o,o,o,o,o,o,o,o}" "{pid,status,receive_start_lsn,receive_start_tli,received_lsn,received_tli,last_msg_send_time,last_msg_receipt_time,latest_end_lsn,latest_end_time,slot_name}" _null_ _null_ pg_stat_get_wal_receiver _null_ _null_ _null_ ));
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 65e968e..90a09ad 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -205,6 +205,8 @@ typedef struct PgStat_MsgHdr
 #define PGSTAT_MAX_MSG_SIZE 1000
 #define PGSTAT_MSG_PAYLOAD	(PGSTAT_MAX_MSG_SIZE - sizeof(PgStat_MsgHdr))
 
+#define N_PROGRESS_PARAM 10
+#define PROGRESS_MESSAGE_LENGTH 30
 
 /* ----------
  * PgStat_MsgDummy				A dummy message, ignored by the collector
@@ -776,6 +778,21 @@ typedef struct PgBackendStatus
 
 	/* current command string; MUST be null-terminated */
 	char	   *st_activity;
+
+	/*
+	 * Information about the progress of activity/command being run by the backend.
+	 * The progress parameters indicate progress of a command. Different
+	 * commands can report different number of parameters of each type.
+	 *
+	 * st_command reports which activity/command is being run by the backend.
+	 * This is used in the SQL callable functions to display progress values
+	 * for respective commands.
+	 */
+	uint16		st_command;
+	uint32		st_progress_param[N_PROGRESS_PARAM];
+	uint32		st_progress_scanned_pages_param[N_PROGRESS_PARAM];
+	char		st_progress_message[N_PROGRESS_PARAM][PROGRESS_MESSAGE_LENGTH];
+	Oid		st_relid;
 } PgBackendStatus;
 
 /*
@@ -815,6 +832,7 @@ typedef struct PgBackendStatus
 		save_changecount = beentry->st_changecount; \
 	} while (0)
 
+#define COMMAND_LAZY_VACUUM 0x01
 /* ----------
  * LocalPgBackendStatus
  *
@@ -928,6 +946,11 @@ extern void pgstat_initialize(void);
 extern void pgstat_bestart(void);
 
 extern void pgstat_report_activity(BackendState state, const char *cmd_str);
+extern void pgstat_report_progress_set_command(int16 commandId);
+extern void pgstat_reset_local_progress(void);
+extern void pgstat_report_progress(uint32 *param1, int num_of_int, char param2[N_PROGRESS_PARAM][PROGRESS_MESSAGE_LENGTH],
+									int num_of_string);
+extern void pgstat_report_progress_scanned_pages(int num_of_int, uint32 *progress_scanned_pages_param);
 extern void pgstat_report_tempfile(size_t filesize);
 extern void pgstat_report_appname(const char *appname);
 extern void pgstat_report_xact_timestamp(TimestampTz tstamp);
@@ -938,6 +961,7 @@ extern const char *pgstat_get_crashed_backend_activity(int pid, char *buffer,
 
 extern PgStat_TableStatus *find_tabstat_entry(Oid rel_id);
 extern PgStat_BackendFunctionEntry *find_funcstat_entry(Oid func_id);
+extern void pgstat_report_progress_set_command_target(Oid relid);
 
 extern void pgstat_initstats(Relation rel);
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 28b061f..31dbcdc 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1848,6 +1848,17 @@ pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
     pg_stat_all_tables.autoanalyze_count
    FROM pg_stat_all_tables
   WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
+pg_stat_vacuum_progress| SELECT s.pid,
+	s.relid,
+	s.phase,
+	s.total_heap_pages,
+	s.scanned_heap_pages,
+	s.percent_complete,
+	s.total_index_pages,
+	s.scanned_index_pages,
+	s.index_scan_count
+	FROM pg_stat_get_vacuum_progress() s(pid, relid, phase, total_heap_pages, scanned_heap_pages, percent_complete, total_index_pages, scanned_index_pages, index_scan_count);
+	
 pg_stat_wal_receiver| SELECT s.pid,
     s.status,
     s.receive_start_lsn,
#149Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Vinayak Pokale (#148)
Re: [PROPOSAL] VACUUM Progress Checker.

Hi Vinayak,

On 2016/01/25 20:58, Vinayak Pokale wrote:

Hi,

Please find attached updated patch with an updated interface.

Thanks for updating the patch.

I added the below interface to update the
scanned_heap_pages,scanned_index_pages and index_scan_count only.
void pgstat_report_progress_scanned_pages(int num_of_int, uint32
*progress_scanned_pages_param)

I think it's still the same interface with the names changed. IIRC, what
was suggested was to provide a way to not have to pass the entire array
for the update of a single member of it. Just pass the index of the
updated member and its new value. Maybe, something like:

void pgstat_progress_update_counter(int index, uint32 newval);

The above function would presumably update the value of
beentry.st_progress_counter[index] or something like that.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#150Vinayak Pokale
vinpokale@gmail.com
In reply to: Amit Langote (#149)
Re: [PROPOSAL] VACUUM Progress Checker.

Hi Amit,

Thank you for reviewing the patch.
On Jan 26, 2016 9:51 AM, "Amit Langote" <Langote_Amit_f8@lab.ntt.co.jp>
wrote:

Hi Vinayak,

On 2016/01/25 20:58, Vinayak Pokale wrote:

Hi,

Please find attached updated patch with an updated interface.

Thanks for updating the patch.

I added the below interface to update the
scanned_heap_pages,scanned_index_pages and index_scan_count only.
void pgstat_report_progress_scanned_pages(int num_of_int, uint32
*progress_scanned_pages_param)

I think it's still the same interface with the names changed. IIRC, what
was suggested was to provide a way to not have to pass the entire array
for the update of a single member of it. Just pass the index of the
updated member and its new value. Maybe, something like:

void pgstat_progress_update_counter(int index, uint32 newval);

The above function would presumably update the value of
beentry.st_progress_counter[index] or something like that.

Understood. I will update the patch.

Regards,
Vinayak

#151Vinayak Pokale
vinpokale@gmail.com
In reply to: Vinayak Pokale (#150)
1 attachment(s)
Re: [PROPOSAL] VACUUM Progress Checker.

Hi,

Please find attached updated patch with an updated interface.

On Jan 26, 2016 11:22 AM, "Vinayak Pokale" <vinpokale@gmail.com> wrote:

Hi Amit,

Thank you for reviewing the patch.

On Jan 26, 2016 9:51 AM, "Amit Langote" <Langote_Amit_f8@lab.ntt.co.jp>

wrote:

Hi Vinayak,

On 2016/01/25 20:58, Vinayak Pokale wrote:

Hi,

Please find attached updated patch with an updated interface.

Thanks for updating the patch.

I added the below interface to update the
scanned_heap_pages,scanned_index_pages and index_scan_count only.
void pgstat_report_progress_scanned_pages(int num_of_int, uint32
*progress_scanned_pages_param)

I think it's still the same interface with the names changed. IIRC, what
was suggested was to provide a way to not have to pass the entire array
for the update of a single member of it. Just pass the index of the
updated member and its new value. Maybe, something like:

void pgstat_progress_update_counter(int index, uint32 newval);

The above function would presumably update the value of
beentry.st_progress_counter[index] or something like that.

Following interface functions are added:

/*
* index: in the array of uint32 counters in the beentry
* counter: new value of the (index)th counter
*/
void
pgstat_report_progress_update_counter(int index, uint32 counter)

/*
called to updatet the VACUUM progress phase.
msg: new value of (index)th message
*/
void
pgstat_report_progress_update_message(int index, char
msg[N_PROGRESS_PARAM][PROGRESS_MESSAGE_LENGTH])

Regards,
Vinayak

Attachments:

Vacuum_progress_checker_v10.patchapplication/octet-stream; name=Vacuum_progress_checker_v10.patchDownload
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 85459d0..e7e53bd 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -507,6 +507,13 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       yet included in <structname>pg_stat_user_functions</>).</entry>
      </row>
 
+     <row>
+      <entry><structname>pg_stat_vacuum_progress</><indexterm><primary>pg_stat_vacuum_progress</primary></indexterm></entry>
+      <entry>One row for each backend (including autovacuum worker processes) running
+      <command>VACUUM</>, showing current progress in terms of heap pages it
+      has finished processing. Backends running <command>VACUUM FULL</> are
+      not included.</entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -1822,6 +1829,74 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
    controls exactly which functions are tracked.
   </para>
 
+  <table id="pg-stat-vacuum-progress" xreflabel="pg_stat_vacuum_progress">
+   <title><structname>pg_stat_vacuum_progress</structname> View</title>
+   <tgroup cols="3">
+    <thead>
+    <row>
+      <entry>Column</entry>
+      <entry>Type</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+   <tbody>
+    <row>
+     <entry><structfield>pid</></entry>
+     <entry><type>integer</></entry>
+     <entry>Process ID of backend</entry>
+    </row>
+    <row>
+     <entry><structfield>relid</></entry>
+     <entry><type>oid</></entry>
+     <entry>OID of a table</entry>
+    </row>
+    <row>
+     <entry><structfield>table_name</></entry>
+     <entry><type>name</></entry>
+     <entry>Name of the table being vacuumed</entry>
+    </row>
+    <row>
+     <entry><structfield>total_heap_pages</></entry>
+     <entry><type>integer</></entry>
+     <entry>Total number of pages in this table</entry>
+    </row>
+    <row>
+     <entry><structfield>scanned_heap_pages</></entry>
+     <entry><type>integer</></entry>
+     <entry>Number of pages processed</entry>
+    </row>
+    <row>
+     <entry><structfield>percent_complete</></entry>
+     <entry><type>double precision</></entry>
+     <entry>Amount of work done in percent</entry>
+    </row>
+    <row>
+     <entry><structfield>total_index_pages</></entry>
+     <entry><type>integer</></entry>
+     <entry>Total number of index pages in this table</entry>
+    </row>
+    <row>
+     <entry><structfield>scanned_index_pages</></entry>
+     <entry><type>integer</></entry>
+     <entry>Number of index pages processed</entry>
+    </row>
+    <row>
+     <entry><structfield>index_scan_count</></entry>
+     <entry><type>integer</></entry>
+     <entry>Number of times index scan has been performed so far</entry>
+    </row>
+   </tbody>
+   </tgroup>
+  </table>
+
+  <para>
+   The <structname>pg_stat_vacuum_progress</structname> view will contain
+   one row for each backend (including autovacuum worker processes), showing
+   progress of <command>VACUUM</> running in it. Note that the backends
+   running <command>VACUUM FULL</> are not shown.
+  </para>
+
  </sect2>
 
  <sect2 id="monitoring-stats-functions">
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 923fe58..81c0fa9 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -639,6 +639,19 @@ CREATE VIEW pg_stat_activity AS
     WHERE S.datid = D.oid AND
             S.usesysid = U.oid;
 
+CREATE VIEW pg_stat_vacuum_progress AS
+	SELECT
+			S.pid,
+			S.relid,
+			S.phase,
+			S.total_heap_pages,
+			S.scanned_heap_pages,
+			S.percent_complete,
+			S.total_index_pages,
+			S.scanned_index_pages,
+			S.index_scan_count
+	FROM pg_stat_get_vacuum_progress() AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 4cb4acf..9ea014d 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -293,6 +293,9 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
 
 			if (options & VACOPT_VACUUM)
 			{
+				if (!(options & VACOPT_FULL))
+					pgstat_report_progress_set_command(COMMAND_LAZY_VACUUM);
+
 				if (!vacuum_rel(relid, relation, options, params))
 					continue;
 			}
@@ -325,6 +328,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
 	{
 		in_vacuum = false;
 		VacuumCostActive = false;
+		pgstat_reset_local_progress();
 		PG_RE_THROW();
 	}
 	PG_END_TRY();
@@ -355,6 +359,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
 		vac_update_datfrozenxid();
 	}
 
+	pgstat_reset_local_progress();
 	/*
 	 * Clean up working storage --- note we must do this after
 	 * StartTransactionCommand, else we might be trying to delete the active
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index 4f6f6e7..fbe4634 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -433,7 +433,11 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			   Relation *Irel, int nindexes, bool scan_all)
 {
 	BlockNumber nblocks,
-				blkno;
+				blkno,
+				total_heap_pages,
+				scanned_heap_pages = 0,
+				total_index_pages = 0,
+				scanned_index_pages = 0;
 	HeapTupleData tuple;
 	char	   *relname;
 	BlockNumber empty_pages,
@@ -450,14 +454,20 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	bool		skipping_all_visible_blocks;
 	xl_heap_freeze_tuple *frozen;
 	StringInfoData buf;
+	char	progress_message[N_PROGRESS_PARAM][PROGRESS_MESSAGE_LENGTH];
+	const char *phase1="Scanning Heap";
+	const char *phase2="Vacuuming Index and Heap";
+	Oid	relid;
 
 	pg_rusage_init(&ru0);
 
 	relname = RelationGetRelationName(onerel);
+	relid = RelationGetRelid(onerel);
 	ereport(elevel,
 			(errmsg("vacuuming \"%s.%s\"",
 					get_namespace_name(RelationGetNamespace(onerel)),
 					relname)));
+	pgstat_report_progress_set_command_target(relid);
 
 	empty_pages = vacuumed_pages = 0;
 	num_tuples = tups_vacuumed = nkeep = nunused = 0;
@@ -465,7 +475,11 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	indstats = (IndexBulkDeleteResult **)
 		palloc0(nindexes * sizeof(IndexBulkDeleteResult *));
 
-	nblocks = RelationGetNumberOfBlocks(onerel);
+	total_heap_pages = nblocks = RelationGetNumberOfBlocks(onerel);
+
+	for (i = 0; i < nindexes; i++)
+		total_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+
 	vacrelstats->rel_pages = nblocks;
 	vacrelstats->scanned_pages = 0;
 	vacrelstats->nonempty_pages = 0;
@@ -523,10 +537,15 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		vacuum_delay_point();
 	}
 	if (next_not_all_visible_block >= SKIP_PAGES_THRESHOLD)
+	{
 		skipping_all_visible_blocks = true;
+		if(!scan_all)
+			scanned_heap_pages = scanned_heap_pages + next_not_all_visible_block;
+	}
 	else
 		skipping_all_visible_blocks = false;
 
+	snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s", phase1);
 	for (blkno = 0; blkno < nblocks; blkno++)
 	{
 		Buffer		buf;
@@ -566,7 +585,11 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			 * following blocks.
 			 */
 			if (next_not_all_visible_block - blkno > SKIP_PAGES_THRESHOLD)
+			{
 				skipping_all_visible_blocks = true;
+				if(!scan_all)
+					scanned_heap_pages = scanned_heap_pages + next_not_all_visible_block;
+			}
 			else
 				skipping_all_visible_blocks = false;
 			all_visible_according_to_vm = false;
@@ -603,11 +626,22 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			/* Log cleanup info before we touch indexes */
 			vacuum_log_cleanup_info(onerel, vacrelstats);
 
+			snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s", phase2);
+
 			/* Remove index entries */
 			for (i = 0; i < nindexes; i++)
+			{
 				lazy_vacuum_index(Irel[i],
 								  &indstats[i],
 								  vacrelstats);
+				scanned_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+
+				/* Report progress to the statistics collector */
+				pgstat_report_progress_update_message(0, progress_message);
+				pgstat_report_progress_update_counter(1, scanned_heap_pages);
+				pgstat_report_progress_update_counter(3, scanned_index_pages);
+
+			}
 			/* Remove tuples from heap */
 			lazy_vacuum_heap(onerel, vacrelstats);
 
@@ -617,8 +651,15 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			 * valid.
 			 */
 			vacrelstats->num_dead_tuples = 0;
+			scanned_index_pages = 0;
 			vacrelstats->num_index_scans++;
 		}
+		snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s", phase1);
+
+		pgstat_report_progress_update_message(0, progress_message);
+		pgstat_report_progress_update_counter(0, total_heap_pages);
+		pgstat_report_progress_update_counter(1, scanned_heap_pages);
+		pgstat_report_progress_update_counter(2, total_index_pages);
 
 		/*
 		 * Pin the visibility map page in case we need to mark the page
@@ -645,6 +686,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			if (!scan_all && !FORCE_CHECK_PAGE())
 			{
 				ReleaseBuffer(buf);
+				scanned_heap_pages++;
 				vacrelstats->pinskipped_pages++;
 				continue;
 			}
@@ -670,6 +712,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			{
 				UnlockReleaseBuffer(buf);
 				vacrelstats->scanned_pages++;
+				scanned_heap_pages++;
 				vacrelstats->pinskipped_pages++;
 				if (hastup)
 					vacrelstats->nonempty_pages = blkno + 1;
@@ -693,6 +736,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		}
 
 		vacrelstats->scanned_pages++;
+		scanned_heap_pages++;
 
 		page = BufferGetPage(buf);
 
@@ -1089,8 +1133,18 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		 */
 		if (vacrelstats->num_dead_tuples == prev_dead_count)
 			RecordPageWithFreeSpace(onerel, blkno, freespace);
-	}
 
+		/*
+		 * Reporting vacuum progress to statistics collector
+		 */
+		if (blkno == nblocks - 1 && vacrelstats->num_dead_tuples == 0 && nindexes != 0
+			&& vacrelstats->num_index_scans == 0)
+			total_index_pages = 0;
+
+		pgstat_report_progress_update_message(0, progress_message);
+		pgstat_report_progress_update_counter(1, scanned_heap_pages);
+		pgstat_report_progress_update_counter(3, scanned_index_pages);
+	}
 	pfree(frozen);
 
 	/* save stats for use later */
@@ -1120,16 +1174,26 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		/* Log cleanup info before we touch indexes */
 		vacuum_log_cleanup_info(onerel, vacrelstats);
 
+		snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s", phase2);
+
 		/* Remove index entries */
 		for (i = 0; i < nindexes; i++)
+		{
 			lazy_vacuum_index(Irel[i],
 							  &indstats[i],
 							  vacrelstats);
+			scanned_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+			/* Report progress to the statistics collector */
+			pgstat_report_progress_update_message(0, progress_message);
+			pgstat_report_progress_update_counter(1, scanned_heap_pages);
+			pgstat_report_progress_update_counter(3, scanned_index_pages);
+			pgstat_report_progress_update_counter(4, vacrelstats->num_index_scans + 1);
+		}
 		/* Remove tuples from heap */
 		lazy_vacuum_heap(onerel, vacrelstats);
 		vacrelstats->num_index_scans++;
+		scanned_index_pages = 0;
 	}
-
 	/* Do post-vacuum cleanup and statistics update for each index */
 	for (i = 0; i < nindexes; i++)
 		lazy_cleanup_index(Irel[i], indstats[i], vacrelstats);
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index da768c6..139bc77 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -2851,6 +2851,109 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
 	pgstat_increment_changecount_after(beentry);
 }
 
+/*-----------
+ * pgstat_report_progress_update_counter()-
+ *
+ * Called to update different values of VACUUM progress
+ *-----------
+ */
+void
+pgstat_report_progress_update_counter(int index, uint32 counter)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	if(!beentry)
+		return;
+
+	if (!pgstat_track_activities)
+		return;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_progress_param[index] = counter;
+	pgstat_increment_changecount_after(beentry);
+}
+
+/*-----------
+ * pgstat_report_progress_update_message()-
+ *
+ *Called to update phase of VACUUM progress
+ *-----------
+ */
+void
+pgstat_report_progress_update_message(int index, char msg[N_PROGRESS_PARAM][PROGRESS_MESSAGE_LENGTH])
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	if(!beentry)
+		return;
+
+	if (!pgstat_track_activities)
+		return;
+
+	pgstat_increment_changecount_before(beentry);
+	strcpy((char *)beentry->st_progress_message[index], msg[index]);
+	pgstat_increment_changecount_after(beentry);
+}
+/* ----------
+ *	pgstat_report_progress_set_command_target() -
+ *
+ *	Called to update command target relation oid.
+ * ----------
+ */
+void
+pgstat_report_progress_set_command_target(Oid relid)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	if (!beentry)
+		return;
+
+	if (!pgstat_track_activities)
+		return;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_relid = relid;
+	pgstat_increment_changecount_after(beentry);
+}
+
+/*-----------
+ * pgstat_report_progress_set_command()-
+ *
+ * Called to update command the backend is about to start running.
+ *-----------
+ */
+void
+pgstat_report_progress_set_command(int16 commandId)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	if (!beentry)
+		return;
+
+	if (!pgstat_track_activities)
+		return;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_command = commandId;
+	pgstat_increment_changecount_after(beentry);
+}
+
+/*--------
+ * pgstat_reset_local_progress()-
+ *
+ * Reset local backend's progress parameters. Resetting st_command will do.
+ *--------
+ */
+void
+pgstat_reset_local_progress(void)
+{
+	PgBackendStatus *beentry = MyBEEntry;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_command = 0;
+	pgstat_increment_changecount_after(beentry);
+}
+
 /* ----------
  * pgstat_report_appname() -
  *
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 1b22fcc..e9c0655 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -53,6 +53,7 @@ extern Datum pg_stat_get_function_self_time(PG_FUNCTION_ARGS);
 
 extern Datum pg_stat_get_backend_idset(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_activity(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_vacuum_progress(PG_FUNCTION_ARGS);
 extern Datum pg_backend_pid(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_pid(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_dbid(PG_FUNCTION_ARGS);
@@ -523,7 +524,102 @@ pg_stat_get_backend_idset(PG_FUNCTION_ARGS)
 		SRF_RETURN_DONE(funcctx);
 	}
 }
+/*
+ * Returns VACUUM progress values stored by each backend
+ * executing VACUUM.
+ */
+Datum
+pg_stat_get_vacuum_progress(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_PROGRESS_COLS	30
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
 
+	/* 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);
+
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		Datum		values[PG_STAT_GET_PROGRESS_COLS];
+		bool		nulls[PG_STAT_GET_PROGRESS_COLS];
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+
+		MemSet(values, 0, sizeof(values));
+		MemSet(nulls, 0, sizeof(nulls));
+
+		local_beentry = pgstat_fetch_stat_local_beentry(curr_backend);
+		if (!local_beentry)
+			continue;
+		beentry = &local_beentry->backendStatus;
+
+		/* Report values for only those backends which are running VACUUM */
+		if(!beentry || beentry->st_command != COMMAND_LAZY_VACUUM)
+			continue;
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+		values[1] = ObjectIdGetDatum(beentry->st_relid);
+
+		/*Progress can only be viewed by role member.*/
+		if (has_privs_of_role(GetUserId(), beentry->st_userid))
+		{
+			values[2] = CStringGetTextDatum(beentry->st_progress_message[0]);
+			values[3] = UInt32GetDatum(beentry->st_progress_param[0]);
+			values[4] = UInt32GetDatum(beentry->st_progress_param[1]);
+
+			if (beentry->st_progress_param[0] != 0)
+				values[5] = Float8GetDatum(beentry->st_progress_param[1] * 100 / beentry->st_progress_param[0]);
+			else
+				nulls[5] = true;
+
+			values[6] = UInt32GetDatum(beentry->st_progress_param[2]);
+			values[7] = UInt32GetDatum(beentry->st_progress_param[3]);
+			values[8] = UInt32GetDatum(beentry->st_progress_param[4]);
+		}
+		else
+		{
+			values[2] = CStringGetTextDatum("<insufficient privilege>");
+			nulls[3] = true;
+			nulls[4] = true;
+			nulls[5] = true;
+			nulls[6] = true;
+			nulls[7] = true;
+			nulls[8] = true;
+		}
+		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+	}
+
+	/* clean up and return the tuplestore */
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
 /*
  * Returns activity of PG backends.
  */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 244aa4d..88451ab 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2684,6 +2684,8 @@ DATA(insert OID = 1936 (  pg_stat_get_backend_idset		PGNSP PGUID 12 1 100 0 0 f
 DESCR("statistics: currently active backend IDs");
 DATA(insert OID = 2022 (  pg_stat_get_activity			PGNSP PGUID 12 1 100 0 0 f f f f f t s r 1 0 2249 "23" "{23,26,23,26,25,25,25,16,1184,1184,1184,1184,869,25,23,28,28,16,25,25,23,16,25}" "{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{pid,datid,pid,usesysid,application_name,state,query,waiting,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,ssl,sslversion,sslcipher,sslbits,sslcompression,sslclientdn}" _null_ _null_ pg_stat_get_activity _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active backends");
+DATA(insert OID = 3319 (  pg_stat_get_vacuum_progress           PGNSP PGUID 12 1 1 0 0 f f f f f t s r 0 0 2249 "" "{23,26,25,23,23,701,23,23,23}" "{o,o,o,o,o,o,o,o,o}" "{pid,relid,phase,total_heap_pages,scanned_heap_pages,percent_complete,total_index_pages,scanned_index_pages,index_scan_count}" _null_ _null_ pg_stat_get_vacuum_progress _null_ _null_ _null_ ));
+DESCR("statistics: information about progress of backends running VACUUM");
 DATA(insert OID = 3099 (  pg_stat_get_wal_senders	PGNSP PGUID 12 1 10 0 0 f f f f f t s r 0 0 2249 "" "{23,25,3220,3220,3220,3220,23,25}" "{o,o,o,o,o,o,o,o}" "{pid,state,sent_location,write_location,flush_location,replay_location,sync_priority,sync_state}" _null_ _null_ pg_stat_get_wal_senders _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active replication");
 DATA(insert OID = 3317 (  pg_stat_get_wal_receiver	PGNSP PGUID 12 1 0 0 0 f f f f f f s r 0 0 2249 "" "{23,25,3220,23,3220,23,1184,1184,3220,1184,25}" "{o,o,o,o,o,o,o,o,o,o,o}" "{pid,status,receive_start_lsn,receive_start_tli,received_lsn,received_tli,last_msg_send_time,last_msg_receipt_time,latest_end_lsn,latest_end_time,slot_name}" _null_ _null_ pg_stat_get_wal_receiver _null_ _null_ _null_ ));
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 65e968e..2074fde 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -205,6 +205,8 @@ typedef struct PgStat_MsgHdr
 #define PGSTAT_MAX_MSG_SIZE 1000
 #define PGSTAT_MSG_PAYLOAD	(PGSTAT_MAX_MSG_SIZE - sizeof(PgStat_MsgHdr))
 
+#define N_PROGRESS_PARAM 10
+#define PROGRESS_MESSAGE_LENGTH 30
 
 /* ----------
  * PgStat_MsgDummy				A dummy message, ignored by the collector
@@ -776,6 +778,20 @@ typedef struct PgBackendStatus
 
 	/* current command string; MUST be null-terminated */
 	char	   *st_activity;
+
+	/*
+	 * Information about the progress of activity/command being run by the backend.
+	 * The progress parameters indicate progress of a command. Different
+	 * commands can report different number of parameters of each type.
+	 *
+	 * st_command reports which activity/command is being run by the backend.
+	 * This is used in the SQL callable functions to display progress values
+	 * for respective commands.
+	 */
+	uint16		st_command;
+	uint32		st_progress_param[N_PROGRESS_PARAM];
+	char		st_progress_message[N_PROGRESS_PARAM][PROGRESS_MESSAGE_LENGTH];
+	Oid		st_relid;
 } PgBackendStatus;
 
 /*
@@ -815,6 +831,7 @@ typedef struct PgBackendStatus
 		save_changecount = beentry->st_changecount; \
 	} while (0)
 
+#define COMMAND_LAZY_VACUUM 0x01
 /* ----------
  * LocalPgBackendStatus
  *
@@ -928,6 +945,10 @@ extern void pgstat_initialize(void);
 extern void pgstat_bestart(void);
 
 extern void pgstat_report_activity(BackendState state, const char *cmd_str);
+extern void pgstat_report_progress_set_command(int16 commandId);
+extern void pgstat_reset_local_progress(void);
+extern void pgstat_report_progress_update_counter(int index, uint32 counter);
+extern void pgstat_report_progress_update_message(int index, char msg[N_PROGRESS_PARAM][PROGRESS_MESSAGE_LENGTH]);
 extern void pgstat_report_tempfile(size_t filesize);
 extern void pgstat_report_appname(const char *appname);
 extern void pgstat_report_xact_timestamp(TimestampTz tstamp);
@@ -938,6 +959,7 @@ extern const char *pgstat_get_crashed_backend_activity(int pid, char *buffer,
 
 extern PgStat_TableStatus *find_tabstat_entry(Oid rel_id);
 extern PgStat_BackendFunctionEntry *find_funcstat_entry(Oid func_id);
+extern void pgstat_report_progress_set_command_target(Oid relid);
 
 extern void pgstat_initstats(Relation rel);
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 28b061f..c03146a 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1848,6 +1848,16 @@ pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
     pg_stat_all_tables.autoanalyze_count
    FROM pg_stat_all_tables
   WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
+pg_stat_vacuum_progress| SELECT s.pid,
+	s.relid,
+	s.phase,
+	s.total_heap_pages,
+	s.scanned_heap_pages,
+	s.percent_complete,
+	s.total_index_pages,
+	s.scanned_index_pages,
+	s.index_scan_count
+	FROM pg_stat_get_vacuum_progress() s(pid, relid, phase, total_heap_pages, scanned_heap_pages, percent_complete, total_index_pages, scanned_index_pages, index_scan_count);	
 pg_stat_wal_receiver| SELECT s.pid,
     s.status,
     s.receive_start_lsn,
#152Robert Haas
robertmhaas@gmail.com
In reply to: Vinayak Pokale (#151)
Re: [PROPOSAL] VACUUM Progress Checker.

On Tue, Jan 26, 2016 at 11:37 PM, Vinayak Pokale <vinpokale@gmail.com> wrote:

Hi,

Please find attached updated patch with an updated interface.

Well, this isn't right. You've got this sort of thing:

+            scanned_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+            /* Report progress to the statistics collector */
+            pgstat_report_progress_update_message(0, progress_message);
+            pgstat_report_progress_update_counter(1, scanned_heap_pages);
+            pgstat_report_progress_update_counter(3, scanned_index_pages);
+            pgstat_report_progress_update_counter(4,
vacrelstats->num_index_scans + 1);

The point of having pgstat_report_progress_update_counter() is so that
you can efficiently update a single counter without having to update
everything, when only one counter has changed. But here you are
calling this function a whole bunch of times in a row, which
completely misses the point - if you are updating all the counters,
it's more efficient to use an interface that does them all at once
instead of one at a time.

But there's a second problem here, too, which is that I think you've
got this code in the wrong place. The second point of the
pgstat_report_progress_update_counter interface is that this should be
cheap enough that we can do it every time the counter changes. That's
not what you are doing here. You're updating the counters at various
points in the code and just pushing new values for all of them
regardless of which ones have changed. I think you should find a way
that you can update the value immediately at the exact moment it
changes. If that seems like too much of a performance hit we can talk
about it, but I think the value of this feature will be greatly
weakened if users can't count on it to be fully and continuously up to
the moment. When something gets stuck, you want to know where it's
stuck, not approximately kinda where it's stuck.

+                if(!scan_all)
+                    scanned_heap_pages = scanned_heap_pages +
next_not_all_visible_block;

I don't want to be too much of a stickler for details here, but it
seems to me that this is an outright lie. The number of scanned pages
does not go up when we decide to skip some pages, because scanning and
skipping aren't the same thing. If we're only going to report one
number here, it needs to be called something like "current heap page",
and then you can just report blkno at the top of each iteration of
lazy_scan_heap's main loop. If you want to report the numbers of
scanned and skipped pages separately that'd be OK too, but you can't
call it the number of scanned pages and then actually report a value
that is not that.

+        /*
+         * Reporting vacuum progress to statistics collector
+         */

This patch doesn't report anything to the statistics collector, nor should it.

Instead of making the SQL-visible function
pg_stat_get_vacuum_progress(), I think it should be something more
generic like pg_stat_get_command_progress(). Maybe VACUUM will be the
only command that reports into that feature for right now, but I'd
hope for us to change that pretty soon after we get the first patch
committed.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#153Rahila Syed
rahilasyed90@gmail.com
In reply to: Robert Haas (#152)
Re: [PROPOSAL] VACUUM Progress Checker.
+                if(!scan_all)
+                    scanned_heap_pages = scanned_heap_pages +
next_not_all_visible_block;

I don't want to be too much of a stickler for details here, but it
seems to me that this is an outright lie.

Initially the scanned_heap_pages were meant to report just the scanned
pages and skipped pages were not added to the count. Instead the skipped
pages were deduced from number of total heap pages to be scanned to make
the number of scanned pages eventually add up to total heap pages. As per
comments received later total heap pages were kept constant and skipped
pages count was added to scanned pages to make the count add up to total
heap pages at the end of scan. That said, as suggested, scanned_heap_pages
should be renamed to current_heap_page to report current blkno in
lazy_scan_heap loop which will add up to total heap pages(nblocks) at the
end of scan. And scanned_heap_pages can be reported as a separate number
which wont contain skipped pages.

+        /*
+         * Reporting vacuum progress to statistics collector
+         */

This patch doesn't report anything to the statistics collector, nor should

it.
Yes. This was incorrectly added initially by referring to similar
pgstat_report interface functions.

Thank you,
Rahila Syed

On Thu, Jan 28, 2016 at 2:27 AM, Robert Haas <robertmhaas@gmail.com> wrote:

Show quoted text

On Tue, Jan 26, 2016 at 11:37 PM, Vinayak Pokale <vinpokale@gmail.com>
wrote:

Hi,

Please find attached updated patch with an updated interface.

Well, this isn't right. You've got this sort of thing:

+            scanned_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+            /* Report progress to the statistics collector */
+            pgstat_report_progress_update_message(0, progress_message);
+            pgstat_report_progress_update_counter(1, scanned_heap_pages);
+            pgstat_report_progress_update_counter(3, scanned_index_pages);
+            pgstat_report_progress_update_counter(4,
vacrelstats->num_index_scans + 1);

The point of having pgstat_report_progress_update_counter() is so that
you can efficiently update a single counter without having to update
everything, when only one counter has changed. But here you are
calling this function a whole bunch of times in a row, which
completely misses the point - if you are updating all the counters,
it's more efficient to use an interface that does them all at once
instead of one at a time.

But there's a second problem here, too, which is that I think you've
got this code in the wrong place. The second point of the
pgstat_report_progress_update_counter interface is that this should be
cheap enough that we can do it every time the counter changes. That's
not what you are doing here. You're updating the counters at various
points in the code and just pushing new values for all of them
regardless of which ones have changed. I think you should find a way
that you can update the value immediately at the exact moment it
changes. If that seems like too much of a performance hit we can talk
about it, but I think the value of this feature will be greatly
weakened if users can't count on it to be fully and continuously up to
the moment. When something gets stuck, you want to know where it's
stuck, not approximately kinda where it's stuck.

+                if(!scan_all)
+                    scanned_heap_pages = scanned_heap_pages +
next_not_all_visible_block;

I don't want to be too much of a stickler for details here, but it
seems to me that this is an outright lie. The number of scanned pages
does not go up when we decide to skip some pages, because scanning and
skipping aren't the same thing. If we're only going to report one
number here, it needs to be called something like "current heap page",
and then you can just report blkno at the top of each iteration of
lazy_scan_heap's main loop. If you want to report the numbers of
scanned and skipped pages separately that'd be OK too, but you can't
call it the number of scanned pages and then actually report a value
that is not that.

+        /*
+         * Reporting vacuum progress to statistics collector
+         */

This patch doesn't report anything to the statistics collector, nor should
it.

Instead of making the SQL-visible function
pg_stat_get_vacuum_progress(), I think it should be something more
generic like pg_stat_get_command_progress(). Maybe VACUUM will be the
only command that reports into that feature for right now, but I'd
hope for us to change that pretty soon after we get the first patch
committed.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#154Amit Langote
amitlangote09@gmail.com
In reply to: Rahila Syed (#153)
Re: [PROPOSAL] VACUUM Progress Checker.

Hi,

On Thu, Jan 28, 2016 at 7:38 PM, Rahila Syed <rahilasyed90@gmail.com> wrote:

+                if(!scan_all)
+                    scanned_heap_pages = scanned_heap_pages +
next_not_all_visible_block;

I don't want to be too much of a stickler for details here, but it
seems to me that this is an outright lie.

Initially the scanned_heap_pages were meant to report just the scanned pages
and skipped pages were not added to the count. Instead the skipped pages
were deduced from number of total heap pages to be scanned to make the
number of scanned pages eventually add up to total heap pages. As per
comments received later total heap pages were kept constant and skipped
pages count was added to scanned pages to make the count add up to total
heap pages at the end of scan. That said, as suggested, scanned_heap_pages
should be renamed to current_heap_page to report current blkno in
lazy_scan_heap loop which will add up to total heap pages(nblocks) at the
end of scan. And scanned_heap_pages can be reported as a separate number
which wont contain skipped pages.

Or keep scanned_heap_pages as is and add a skipped_pages (or
skipped_heap_pages). I guess the latter would be updated not only for
all visible skipped pages but also pin skipped pages. That is,
updating its counter right after vacrelstats->pinskipped_pages++ which
there are a couple of instances of. Likewise a good (and only?) time
to update the former's counter would be right after
vacrelstats->scanned_pages++. Although, I see at least one place where
both are incremented so maybe I'm not entirely correct about the last
two sentences.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#155Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#154)
Re: [PROPOSAL] VACUUM Progress Checker.

On Thu, Jan 28, 2016 at 8:41 AM, Amit Langote <amitlangote09@gmail.com> wrote:

Or keep scanned_heap_pages as is and add a skipped_pages (or
skipped_heap_pages). I guess the latter would be updated not only for
all visible skipped pages but also pin skipped pages. That is,
updating its counter right after vacrelstats->pinskipped_pages++ which
there are a couple of instances of. Likewise a good (and only?) time
to update the former's counter would be right after
vacrelstats->scanned_pages++. Although, I see at least one place where
both are incremented so maybe I'm not entirely correct about the last
two sentences.

So I've spent a fair amount of time debugging really-long-running
VACUUM processes with customers, and generally what I really want to
know is:

What block number are we at? <<<

Because, if I know that, and I can see how fast that's increasing,
then I can estimate whether the VACUUM is going to end in a reasonable
period of time or not. So my preference is to not bother breaking out
skipped pages, but just report the block number and call it good. I
will defer to a strong consensus on something else, but reporting the
block number has the advantage of being dead simple and, in my
experience, that would answer the question that I typically have.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#156Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#155)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2016/01/28 23:53, Robert Haas wrote:

On Thu, Jan 28, 2016 at 8:41 AM, Amit Langote <amitlangote09@gmail.com> wrote:

Or keep scanned_heap_pages as is and add a skipped_pages (or
skipped_heap_pages). I guess the latter would be updated not only for
all visible skipped pages but also pin skipped pages. That is,
updating its counter right after vacrelstats->pinskipped_pages++ which
there are a couple of instances of. Likewise a good (and only?) time
to update the former's counter would be right after
vacrelstats->scanned_pages++. Although, I see at least one place where
both are incremented so maybe I'm not entirely correct about the last
two sentences.

So I've spent a fair amount of time debugging really-long-running
VACUUM processes with customers, and generally what I really want to
know is:

What block number are we at? <<<

Because, if I know that, and I can see how fast that's increasing,
then I can estimate whether the VACUUM is going to end in a reasonable
period of time or not. So my preference is to not bother breaking out
skipped pages, but just report the block number and call it good. I
will defer to a strong consensus on something else, but reporting the
block number has the advantage of being dead simple and, in my
experience, that would answer the question that I typically have.

Okay, I agree that reporting just the current blkno is simple and good
enough. How about numbers of what we're going to report as the "Vacuuming
Index and Heap" phase? I guess we can still keep the scanned_index_pages
and index_scan_count. So we have:

+CREATE VIEW pg_stat_vacuum_progress AS
+	SELECT
+              S.pid,
+              S.relid,
+              S.phase,
+              S.total_heap_blks,
+              S.current_heap_blkno,
+              S.total_index_pages,
+              S.scanned_index_pages,
+              S.index_scan_count
+              S.percent_complete,
+	FROM pg_stat_get_vacuum_progress() AS S;

I guess it won't remain pg_stat_get_"vacuum"_progress(), though.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#157Rahila Syed
rahilasyed90@gmail.com
In reply to: Amit Langote (#156)
Re: [PROPOSAL] VACUUM Progress Checker.
Okay, I agree that reporting just the current blkno is simple and good
enough. How about numbers of what we're going to report as the "Vacuuming
Index and Heap" phase? I guess we can still keep the scanned_index_pages
and index_scan_count So we have:
+CREATE VIEW pg_stat_vacuum_progress AS
+       SELECT
+              S.pid,
+              S.relid,
+              S.phase,
+              S.total_heap_blks,
+              S.current_heap_blkno,
+              S.total_index_pages,
+              S.scanned_index_pages,
+              S.index_scan_count
+              S.percent_complete,
+       FROM pg_stat_get_vacuum_progress() AS S;
I guess it won't remain pg_stat_get_"vacuum"_progress(
), though.

Apart from these, as suggested in [1]. /messages/by-id/56500356.4070101@BlueTreble.com , finer grained reporting from index
vacuuming phase can provide better insight. Currently we report number of
blocks processed once at the end of vacuuming of each index.
IIUC, what was suggested in [1]. /messages/by-id/56500356.4070101@BlueTreble.com was instrumenting lazy_tid_reaped with a
counter to count number of index tuples processed so far as lazy_tid_reaped
is called for every index tuple to see if it matches any of the dead tuple
tids.

So additional parameters for each index can be,
scanned_index_tuples
total_index_tuples (from pg_class.reltuples entry)

Thank you,
Rahila Syed

[1]: . /messages/by-id/56500356.4070101@BlueTreble.com

#158Robert Haas
robertmhaas@gmail.com
In reply to: Rahila Syed (#157)
Re: [PROPOSAL] VACUUM Progress Checker.

On Fri, Jan 29, 2016 at 7:02 AM, Rahila Syed <rahilasyed90@gmail.com> wrote:

Apart from these, as suggested in [1] , finer grained reporting from index
vacuuming phase can provide better insight. Currently we report number of
blocks processed once at the end of vacuuming of each index.
IIUC, what was suggested in [1] was instrumenting lazy_tid_reaped with a
counter to count number of index tuples processed so far as lazy_tid_reaped
is called for every index tuple to see if it matches any of the dead tuple
tids.

So additional parameters for each index can be,
scanned_index_tuples
total_index_tuples (from pg_class.reltuples entry)

Let's report blocks, not tuples. The reason is that
pg_class.reltuples is only an estimate and might be wildly wrong on
occasion, but the length of the relation in blocks can be known with
certainty.

But other than that I agree with this. Fine-grained is key. If it's
not fine grained, then people really won't be able to tell what's
going on when VACUUM doesn't finish in a timely fashion. And the
whole point is we want to be able to know that.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#159Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Rahila Syed (#157)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2016/01/29 21:02, Rahila Syed wrote:

Okay, I agree that reporting just the current blkno is simple and good
enough. How about numbers of what we're going to report as the "Vacuuming
Index and Heap" phase? I guess we can still keep the scanned_index_pages
and index_scan_count So we have:
+CREATE VIEW pg_stat_vacuum_progress AS
+       SELECT
+              S.pid,
+              S.relid,
+              S.phase,
+              S.total_heap_blks,
+              S.current_heap_blkno,
+              S.total_index_pages,
+              S.scanned_index_pages,
+              S.index_scan_count
+              S.percent_complete,
+       FROM pg_stat_get_vacuum_progress() AS S;
I guess it won't remain pg_stat_get_"vacuum"_progress(
), though.

Apart from these, as suggested in [1] , finer grained reporting from index
vacuuming phase can provide better insight. Currently we report number of
blocks processed once at the end of vacuuming of each index.
IIUC, what was suggested in [1] was instrumenting lazy_tid_reaped with a
counter to count number of index tuples processed so far as lazy_tid_reaped
is called for every index tuple to see if it matches any of the dead tuple
tids.

Agreed. Although, as Robert already suggested, I too would vote for
counting pages, not tuples. I think there was an independent patch doing
something of that sort somewhere upthread.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#160Noname
pokurev@pm.nttdata.co.jp
In reply to: Amit Langote (#147)
1 attachment(s)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

Please find attached updated patch.

The point of having pgstat_report_progress_update_counter() is so that
you can efficiently update a single counter without having to update
everything, when only one counter has changed. But here you are
calling this function a whole bunch of times in a row, which
completely misses the point - if you are updating all the counters,
it's more efficient to use an interface that does them all at once
instead of one at a time.

The pgstat_report_progress_update_counter() is called at appropriate places in the attached patch.

So I've spent a fair amount of time debugging really-long-running
VACUUM processes with customers, and generally what I really want to
know is:

What block number are we at? <<<

Agreed. The attached patch reported current block number.

Regards,
Vinayak

Attachments:

Vacuum_progress_checker_v11.patchapplication/octet-stream; name=Vacuum_progress_checker_v11.patchDownload
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 85459d0..6838e67 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -507,6 +507,13 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       yet included in <structname>pg_stat_user_functions</>).</entry>
      </row>
 
+     <row>
+      <entry><structname>pg_stat_vacuum_progress</><indexterm><primary>pg_stat_vacuum_progress</primary></indexterm></entry>
+      <entry>One row for each backend (including autovacuum worker processes) running
+      <command>VACUUM</>, showing current progress in terms of heap pages it
+      has finished processing. Backends running <command>VACUUM FULL</> are
+      not included.</entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -1822,6 +1829,74 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
    controls exactly which functions are tracked.
   </para>
 
+  <table id="pg-stat-vacuum-progress" xreflabel="pg_stat_vacuum_progress">
+   <title><structname>pg_stat_vacuum_progress</structname> View</title>
+   <tgroup cols="3">
+    <thead>
+    <row>
+      <entry>Column</entry>
+      <entry>Type</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+   <tbody>
+    <row>
+     <entry><structfield>pid</></entry>
+     <entry><type>integer</></entry>
+     <entry>Process ID of backend</entry>
+    </row>
+    <row>
+     <entry><structfield>relid</></entry>
+     <entry><type>oid</></entry>
+     <entry>OID of a table</entry>
+    </row>
+    <row>
+     <entry><structfield>phase</></entry>
+     <entry><type>name</></entry>
+     <entry>Phase of vacuum</entry>
+    </row>
+    <row>
+     <entry><structfield>total_heap_blks</></entry>
+     <entry><type>integer</></entry>
+     <entry>Total number of pages in this table</entry>
+    </row>
+    <row>
+     <entry><structfield>current_heap_blkno</></entry>
+     <entry><type>integer</></entry>
+     <entry>Current heap block number</entry>
+    </row>
+    <row>
+     <entry><structfield>total_index_pages</></entry>
+     <entry><type>integer</></entry>
+     <entry>Total number of index pages in this table</entry>
+    </row>
+    <row>
+     <entry><structfield>scanned_index_pages</></entry>
+     <entry><type>integer</></entry>
+     <entry>Number of index pages processed</entry>
+    </row>
+    <row>
+     <entry><structfield>index_scan_count</></entry>
+     <entry><type>integer</></entry>
+     <entry>Number of times index scan has been performed so far</entry>
+    </row>
+    <row>
+     <entry><structfield>percent_complete</></entry>
+     <entry><type>double precision</></entry>
+     <entry>Amount of work done in percent</entry>
+    </row>
+   </tbody>
+   </tgroup>
+  </table>
+
+  <para>
+   The <structname>pg_stat_vacuum_progress</structname> view will contain
+   one row for each backend (including autovacuum worker processes), showing
+   progress of <command>VACUUM</> running in it. Note that the backends
+   running <command>VACUUM FULL</> are not shown.
+  </para>
+
  </sect2>
 
  <sect2 id="monitoring-stats-functions">
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 923fe58..b7392bc 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -639,6 +639,19 @@ CREATE VIEW pg_stat_activity AS
     WHERE S.datid = D.oid AND
             S.usesysid = U.oid;
 
+CREATE VIEW pg_stat_vacuum_progress AS
+	SELECT
+			S.pid,
+			S.relid,
+			S.phase,
+			S.total_heap_blks,
+			S.current_heap_blkno,
+			S.total_index_pages,
+			S.scanned_index_pages,
+			S.index_scan_count,
+			S.percent_complete
+	FROM pg_stat_get_command_progress() AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 4cb4acf..9ea014d 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -293,6 +293,9 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
 
 			if (options & VACOPT_VACUUM)
 			{
+				if (!(options & VACOPT_FULL))
+					pgstat_report_progress_set_command(COMMAND_LAZY_VACUUM);
+
 				if (!vacuum_rel(relid, relation, options, params))
 					continue;
 			}
@@ -325,6 +328,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
 	{
 		in_vacuum = false;
 		VacuumCostActive = false;
+		pgstat_reset_local_progress();
 		PG_RE_THROW();
 	}
 	PG_END_TRY();
@@ -355,6 +359,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
 		vac_update_datfrozenxid();
 	}
 
+	pgstat_reset_local_progress();
 	/*
 	 * Clean up working storage --- note we must do this after
 	 * StartTransactionCommand, else we might be trying to delete the active
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index 4f6f6e7..7944e61 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -433,7 +433,10 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			   Relation *Irel, int nindexes, bool scan_all)
 {
 	BlockNumber nblocks,
-				blkno;
+				blkno,
+				total_heap_blks,
+				total_index_pages = 0,
+				scanned_index_pages = 0;
 	HeapTupleData tuple;
 	char	   *relname;
 	BlockNumber empty_pages,
@@ -450,14 +453,21 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	bool		skipping_all_visible_blocks;
 	xl_heap_freeze_tuple *frozen;
 	StringInfoData buf;
+	char	progress_message[N_PROGRESS_PARAM][PROGRESS_MESSAGE_LENGTH];
+	const char *phase1="Scanning Heap";
+	const char *phase2="Vacuuming Index and Heap";
+	Oid	relid;
 
 	pg_rusage_init(&ru0);
 
 	relname = RelationGetRelationName(onerel);
+	relid = RelationGetRelid(onerel);
 	ereport(elevel,
 			(errmsg("vacuuming \"%s.%s\"",
 					get_namespace_name(RelationGetNamespace(onerel)),
 					relname)));
+	/* Report relid of the relation*/
+	pgstat_report_progress_set_command_target(relid);
 
 	empty_pages = vacuumed_pages = 0;
 	num_tuples = tups_vacuumed = nkeep = nunused = 0;
@@ -465,7 +475,11 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	indstats = (IndexBulkDeleteResult **)
 		palloc0(nindexes * sizeof(IndexBulkDeleteResult *));
 
-	nblocks = RelationGetNumberOfBlocks(onerel);
+	total_heap_blks = nblocks = RelationGetNumberOfBlocks(onerel);
+
+	for (i = 0; i < nindexes; i++)
+		total_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+
 	vacrelstats->rel_pages = nblocks;
 	vacrelstats->scanned_pages = 0;
 	vacrelstats->nonempty_pages = 0;
@@ -474,6 +488,10 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	lazy_space_alloc(vacrelstats, nblocks);
 	frozen = palloc(sizeof(xl_heap_freeze_tuple) * MaxHeapTuplesPerPage);
 
+	/* Report count of total heap blocks and total index pages of a relation*/
+	pgstat_report_progress_update_counter(0, total_heap_blks);
+	pgstat_report_progress_update_counter(2, total_index_pages);
+
 	/*
 	 * We want to skip pages that don't require vacuuming according to the
 	 * visibility map, but only when we can skip at least SKIP_PAGES_THRESHOLD
@@ -527,6 +545,8 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	else
 		skipping_all_visible_blocks = false;
 
+	snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s", phase1);
+	pgstat_report_progress_update_message(0, progress_message);
 	for (blkno = 0; blkno < nblocks; blkno++)
 	{
 		Buffer		buf;
@@ -547,6 +567,8 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 #define FORCE_CHECK_PAGE() \
 		(blkno == nblocks - 1 && should_attempt_truncation(vacrelstats))
 
+		/* Update current block number of the relation */
+		pgstat_report_progress_update_counter(1, blkno + 1);
 		if (blkno == next_not_all_visible_block)
 		{
 			/* Time to advance next_not_all_visible_block */
@@ -603,11 +625,18 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			/* Log cleanup info before we touch indexes */
 			vacuum_log_cleanup_info(onerel, vacrelstats);
 
+			snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s", phase2);
+			pgstat_report_progress_update_message(0, progress_message);
 			/* Remove index entries */
 			for (i = 0; i < nindexes; i++)
+			{
 				lazy_vacuum_index(Irel[i],
 								  &indstats[i],
 								  vacrelstats);
+				scanned_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+				/* Update scanned index pages of a relation*/
+				pgstat_report_progress_update_counter(3, scanned_index_pages);
+			}
 			/* Remove tuples from heap */
 			lazy_vacuum_heap(onerel, vacrelstats);
 
@@ -617,8 +646,13 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			 * valid.
 			 */
 			vacrelstats->num_dead_tuples = 0;
+			scanned_index_pages = 0;
 			vacrelstats->num_index_scans++;
+
+			pgstat_report_progress_update_counter(4, vacrelstats->num_index_scans);
 		}
+		snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s", phase1);
+		pgstat_report_progress_update_message(0, progress_message);
 
 		/*
 		 * Pin the visibility map page in case we need to mark the page
@@ -1089,8 +1123,11 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		 */
 		if (vacrelstats->num_dead_tuples == prev_dead_count)
 			RecordPageWithFreeSpace(onerel, blkno, freespace);
-	}
 
+		if (blkno == nblocks - 1 && vacrelstats->num_dead_tuples == 0 && nindexes != 0
+			&& vacrelstats->num_index_scans == 0)
+			total_index_pages = 0;
+	}
 	pfree(frozen);
 
 	/* save stats for use later */
@@ -1120,14 +1157,23 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		/* Log cleanup info before we touch indexes */
 		vacuum_log_cleanup_info(onerel, vacrelstats);
 
+		snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s", phase2);
+		pgstat_report_progress_update_message(0, progress_message);
 		/* Remove index entries */
 		for (i = 0; i < nindexes; i++)
+		{
 			lazy_vacuum_index(Irel[i],
 							  &indstats[i],
 							  vacrelstats);
+			scanned_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+			/* Update the scanned index pages and number of index scan */
+			pgstat_report_progress_update_counter(3, scanned_index_pages);
+			pgstat_report_progress_update_counter(4, vacrelstats->num_index_scans + 1);
+		}
 		/* Remove tuples from heap */
 		lazy_vacuum_heap(onerel, vacrelstats);
 		vacrelstats->num_index_scans++;
+		scanned_index_pages = 0;
 	}
 
 	/* Do post-vacuum cleanup and statistics update for each index */
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index da768c6..27c1d68 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -2851,6 +2851,109 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
 	pgstat_increment_changecount_after(beentry);
 }
 
+/*-----------
+ * pgstat_report_progress_update_counter()-
+ *
+ * Called to update different values of command progress
+ *-----------
+ */
+void
+pgstat_report_progress_update_counter(int index, uint32 counter)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	if(!beentry)
+		return;
+
+	if (!pgstat_track_activities)
+		return;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_progress_param[index] = counter;
+	pgstat_increment_changecount_after(beentry);
+}
+
+/*-----------
+ * pgstat_report_progress_update_message()-
+ *
+ *Called to update phase of VACUUM progress
+ *-----------
+ */
+void
+pgstat_report_progress_update_message(int index, char msg[N_PROGRESS_PARAM][PROGRESS_MESSAGE_LENGTH])
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	if(!beentry)
+		return;
+
+	if (!pgstat_track_activities)
+		return;
+
+	pgstat_increment_changecount_before(beentry);
+	strcpy((char *)beentry->st_progress_message[index], msg[index]);
+	pgstat_increment_changecount_after(beentry);
+}
+/* ----------
+ *	pgstat_report_progress_set_command_target() -
+ *
+ *	Called to update command target relation oid.
+ * ----------
+ */
+void
+pgstat_report_progress_set_command_target(Oid relid)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	if (!beentry)
+		return;
+
+	if (!pgstat_track_activities)
+		return;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_relid = relid;
+	pgstat_increment_changecount_after(beentry);
+}
+
+/*-----------
+ * pgstat_report_progress_set_command()-
+ *
+ * Called to update command the backend is about to start running.
+ *-----------
+ */
+void
+pgstat_report_progress_set_command(int16 commandId)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	if (!beentry)
+		return;
+
+	if (!pgstat_track_activities)
+		return;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_command = commandId;
+	pgstat_increment_changecount_after(beentry);
+}
+
+/*--------
+ * pgstat_reset_local_progress()-
+ *
+ * Reset local backend's progress parameters. Resetting st_command will do.
+ *--------
+ */
+void
+pgstat_reset_local_progress(void)
+{
+	PgBackendStatus *beentry = MyBEEntry;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_command = 0;
+	pgstat_increment_changecount_after(beentry);
+}
+
 /* ----------
  * pgstat_report_appname() -
  *
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 1b22fcc..4b7a4c6 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -53,6 +53,7 @@ extern Datum pg_stat_get_function_self_time(PG_FUNCTION_ARGS);
 
 extern Datum pg_stat_get_backend_idset(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_activity(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_command_progress(PG_FUNCTION_ARGS);
 extern Datum pg_backend_pid(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_pid(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_dbid(PG_FUNCTION_ARGS);
@@ -523,7 +524,99 @@ pg_stat_get_backend_idset(PG_FUNCTION_ARGS)
 		SRF_RETURN_DONE(funcctx);
 	}
 }
+/*
+ * Returns progress values of commands stored by each backend
+ * executing command.
+ */
+Datum
+pg_stat_get_command_progress(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_PROGRESS_COLS	30
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+
+	/* 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);
+
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		Datum		values[PG_STAT_GET_PROGRESS_COLS];
+		bool		nulls[PG_STAT_GET_PROGRESS_COLS];
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+
+		MemSet(values, 0, sizeof(values));
+		MemSet(nulls, 0, sizeof(nulls));
+
+		local_beentry = pgstat_fetch_stat_local_beentry(curr_backend);
+		if (!local_beentry)
+			continue;
+		beentry = &local_beentry->backendStatus;
+
+		/* Report values for only those backends which are running command */
+		if(!beentry || beentry->st_command != COMMAND_LAZY_VACUUM)
+			continue;
+
+		values[0] = Int32GetDatum(beentry->st_procpid);
+		values[1] = ObjectIdGetDatum(beentry->st_relid);
+
+		/*Progress can only be viewed by role member.*/
+		if (has_privs_of_role(GetUserId(), beentry->st_userid))
+		{
+			values[2] = CStringGetTextDatum(beentry->st_progress_message[0]);
+			values[3] = UInt32GetDatum(beentry->st_progress_param[0]);
+			values[4] = UInt32GetDatum(beentry->st_progress_param[1]);
+			values[5] = UInt32GetDatum(beentry->st_progress_param[2]);
+			values[6] = UInt32GetDatum(beentry->st_progress_param[3]);
+			values[7] = UInt32GetDatum(beentry->st_progress_param[4]);
+			if (beentry->st_progress_param[0] != 0)
+				values[8] = Float8GetDatum(beentry->st_progress_param[1] * 100 / beentry->st_progress_param[0]);
+			else
+				nulls[8] = true;
+		}
+		else
+		{
+			values[2] = CStringGetTextDatum("<insufficient privilege>");
+			nulls[3] = true;
+			nulls[4] = true;
+			nulls[5] = true;
+			nulls[6] = true;
+			nulls[7] = true;
+		}
+		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+	}
+
+	/* clean up and return the tuplestore */
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
 /*
  * Returns activity of PG backends.
  */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 244aa4d..c90ea05 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2684,6 +2684,8 @@ DATA(insert OID = 1936 (  pg_stat_get_backend_idset		PGNSP PGUID 12 1 100 0 0 f
 DESCR("statistics: currently active backend IDs");
 DATA(insert OID = 2022 (  pg_stat_get_activity			PGNSP PGUID 12 1 100 0 0 f f f f f t s r 1 0 2249 "23" "{23,26,23,26,25,25,25,16,1184,1184,1184,1184,869,25,23,28,28,16,25,25,23,16,25}" "{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{pid,datid,pid,usesysid,application_name,state,query,waiting,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,ssl,sslversion,sslcipher,sslbits,sslcompression,sslclientdn}" _null_ _null_ pg_stat_get_activity _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active backends");
+DATA(insert OID = 3319 (  pg_stat_get_command_progress           PGNSP PGUID 12 1 1 0 0 f f f f f t s r 0 0 2249 "" "{23,26,25,23,23,23,23,23,701}" "{o,o,o,o,o,o,o,o,o}" "{pid,relid,phase,total_heap_blks,current_heap_blkno,total_index_pages,scanned_index_pages,index_scan_count,percent_complete}" _null_ _null_ pg_stat_get_command_progress _null_ _null_ _null_ ));
+DESCR("statistics: information about progress of backends running VACUUM");
 DATA(insert OID = 3099 (  pg_stat_get_wal_senders	PGNSP PGUID 12 1 10 0 0 f f f f f t s r 0 0 2249 "" "{23,25,3220,3220,3220,3220,23,25}" "{o,o,o,o,o,o,o,o}" "{pid,state,sent_location,write_location,flush_location,replay_location,sync_priority,sync_state}" _null_ _null_ pg_stat_get_wal_senders _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active replication");
 DATA(insert OID = 3317 (  pg_stat_get_wal_receiver	PGNSP PGUID 12 1 0 0 0 f f f f f f s r 0 0 2249 "" "{23,25,3220,23,3220,23,1184,1184,3220,1184,25}" "{o,o,o,o,o,o,o,o,o,o,o}" "{pid,status,receive_start_lsn,receive_start_tli,received_lsn,received_tli,last_msg_send_time,last_msg_receipt_time,latest_end_lsn,latest_end_time,slot_name}" _null_ _null_ pg_stat_get_wal_receiver _null_ _null_ _null_ ));
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 65e968e..2074fde 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -205,6 +205,8 @@ typedef struct PgStat_MsgHdr
 #define PGSTAT_MAX_MSG_SIZE 1000
 #define PGSTAT_MSG_PAYLOAD	(PGSTAT_MAX_MSG_SIZE - sizeof(PgStat_MsgHdr))
 
+#define N_PROGRESS_PARAM 10
+#define PROGRESS_MESSAGE_LENGTH 30
 
 /* ----------
  * PgStat_MsgDummy				A dummy message, ignored by the collector
@@ -776,6 +778,20 @@ typedef struct PgBackendStatus
 
 	/* current command string; MUST be null-terminated */
 	char	   *st_activity;
+
+	/*
+	 * Information about the progress of activity/command being run by the backend.
+	 * The progress parameters indicate progress of a command. Different
+	 * commands can report different number of parameters of each type.
+	 *
+	 * st_command reports which activity/command is being run by the backend.
+	 * This is used in the SQL callable functions to display progress values
+	 * for respective commands.
+	 */
+	uint16		st_command;
+	uint32		st_progress_param[N_PROGRESS_PARAM];
+	char		st_progress_message[N_PROGRESS_PARAM][PROGRESS_MESSAGE_LENGTH];
+	Oid		st_relid;
 } PgBackendStatus;
 
 /*
@@ -815,6 +831,7 @@ typedef struct PgBackendStatus
 		save_changecount = beentry->st_changecount; \
 	} while (0)
 
+#define COMMAND_LAZY_VACUUM 0x01
 /* ----------
  * LocalPgBackendStatus
  *
@@ -928,6 +945,10 @@ extern void pgstat_initialize(void);
 extern void pgstat_bestart(void);
 
 extern void pgstat_report_activity(BackendState state, const char *cmd_str);
+extern void pgstat_report_progress_set_command(int16 commandId);
+extern void pgstat_reset_local_progress(void);
+extern void pgstat_report_progress_update_counter(int index, uint32 counter);
+extern void pgstat_report_progress_update_message(int index, char msg[N_PROGRESS_PARAM][PROGRESS_MESSAGE_LENGTH]);
 extern void pgstat_report_tempfile(size_t filesize);
 extern void pgstat_report_appname(const char *appname);
 extern void pgstat_report_xact_timestamp(TimestampTz tstamp);
@@ -938,6 +959,7 @@ extern const char *pgstat_get_crashed_backend_activity(int pid, char *buffer,
 
 extern PgStat_TableStatus *find_tabstat_entry(Oid rel_id);
 extern PgStat_BackendFunctionEntry *find_funcstat_entry(Oid func_id);
+extern void pgstat_report_progress_set_command_target(Oid relid);
 
 extern void pgstat_initstats(Relation rel);
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 28b061f..e47bb81 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1848,6 +1848,16 @@ pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
     pg_stat_all_tables.autoanalyze_count
    FROM pg_stat_all_tables
   WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
+pg_stat_vacuum_progress| SELECT s.pid,
+    s.relid,
+    s.phase,
+    s.total_heap_blks,
+    s.current_heap_blkno,
+    s.total_index_pages,
+    s.scanned_index_pages,
+    s.index_scan_count,
+    s.percent_complete
+   FROM pg_stat_get_command_progress() s(pid, relid, phase, total_heap_blks, current_heap_blkno, total_index_pages, scanned_index_pages, index_scan_count, percent_complete);
 pg_stat_wal_receiver| SELECT s.pid,
     s.status,
     s.receive_start_lsn,
#161Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Noname (#160)
Re: [PROPOSAL] VACUUM Progress Checker.

Hi Vinayak,

Thanks for updating the patch, a couple of comments:

On 2016/02/05 17:15, pokurev@pm.nttdata.co.jp wrote:

Hello,

Please find attached updated patch.

The point of having pgstat_report_progress_update_counter() is so that
you can efficiently update a single counter without having to update
everything, when only one counter has changed. But here you are
calling this function a whole bunch of times in a row, which
completely misses the point - if you are updating all the counters,
it's more efficient to use an interface that does them all at once
instead of one at a time.

The pgstat_report_progress_update_counter() is called at appropriate places in the attached patch.

+ char progress_message[N_PROGRESS_PARAM][PROGRESS_MESSAGE_LENGTH];

[ ... ]

+	snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s", phase1);
+	pgstat_report_progress_update_message(0, progress_message);

[ ... ]

+			snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s", phase2);
+			pgstat_report_progress_update_message(0, progress_message);

Instead of passing the array of char *'s, why not just pass a single char
*, because that's what it's doing - updating a single message. So,
something like:

+ char progress_message[PROGRESS_MESSAGE_LENGTH];

[ ... ]

+ snprintf(progress_message, PROGRESS_MESSAGE_LENGTH, "%s", phase1);
+ pgstat_report_progress_update_message(0, progress_message);

[ ... ]

+ snprintf(progress_message, PROGRESS_MESSAGE_LENGTH, "%s", phase2);
+ pgstat_report_progress_update_message(0, progress_message);

And also:

+/*-----------
+ * pgstat_report_progress_update_message()-
+ *
+ *Called to update phase of VACUUM progress
+ *-----------
+ */
+void
+pgstat_report_progress_update_message(int index, char *msg)
+{

[ ... ]

+	pgstat_increment_changecount_before(beentry);
+	strncpy((char *)beentry->st_progress_message[index], msg,
PROGRESS_MESSAGE_LENGTH);
+	pgstat_increment_changecount_after(beentry);

One more comment:

@@ -1120,14 +1157,23 @@ lazy_scan_heap(Relation onerel, LVRelStats
*vacrelstats,
/* Log cleanup info before we touch indexes */
vacuum_log_cleanup_info(onerel, vacrelstats);

+		snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s", phase2);
+		pgstat_report_progress_update_message(0, progress_message);
 		/* Remove index entries */
 		for (i = 0; i < nindexes; i++)
+		{
 			lazy_vacuum_index(Irel[i],
 							  &indstats[i],
 							  vacrelstats);
+			scanned_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+			/* Update the scanned index pages and number of index scan */
+			pgstat_report_progress_update_counter(3, scanned_index_pages);
+			pgstat_report_progress_update_counter(4, vacrelstats->num_index_scans
+ 1);
+		}
 		/* Remove tuples from heap */
 		lazy_vacuum_heap(onerel, vacrelstats);
 		vacrelstats->num_index_scans++;
+		scanned_index_pages = 0;

I guess num_index_scans could better be reported after all the indexes are
done, that is, after the for loop ends.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#162Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Amit Langote (#161)
Re: [PROPOSAL] VACUUM Progress Checker.

Since things are clearly still moving here, I closed it as
returned-with-feedback. Please submit to the next CF so that we don't
lose it.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#163Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Amit Langote (#161)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

At Mon, 8 Feb 2016 11:37:17 +0900, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote in <56B7FF5D.7030108@lab.ntt.co.jp>

Hi Vinayak,

Thanks for updating the patch, a couple of comments:

On 2016/02/05 17:15, pokurev@pm.nttdata.co.jp wrote:

Hello,

Please find attached updated patch.

The point of having pgstat_report_progress_update_counter() is so that
you can efficiently update a single counter without having to update
everything, when only one counter has changed. But here you are
calling this function a whole bunch of times in a row, which
completely misses the point - if you are updating all the counters,
it's more efficient to use an interface that does them all at once
instead of one at a time.

The pgstat_report_progress_update_counter() is called at appropriate places in the attached patch.

+ char progress_message[N_PROGRESS_PARAM][PROGRESS_MESSAGE_LENGTH];

[ ... ]

+     snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s", phase1);
+     pgstat_report_progress_update_message(0, progress_message);

[ ... ]

+                     snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s", phase2);
+                     pgstat_report_progress_update_message(0, progress_message);

Instead of passing the array of char *'s, why not just pass a single char
*, because that's what it's doing - updating a single message. So,
something like:

+ char progress_message[PROGRESS_MESSAGE_LENGTH];

[ ... ]

+ snprintf(progress_message, PROGRESS_MESSAGE_LENGTH, "%s", phase1);
+ pgstat_report_progress_update_message(0, progress_message);

[ ... ]

+ snprintf(progress_message, PROGRESS_MESSAGE_LENGTH, "%s", phase2);
+ pgstat_report_progress_update_message(0, progress_message);

And also:

+/*-----------
+ * pgstat_report_progress_update_message()-
+ *
+ *Called to update phase of VACUUM progress
+ *-----------
+ */
+void
+pgstat_report_progress_update_message(int index, char *msg)
+{

[ ... ]

+     pgstat_increment_changecount_before(beentry);
+     strncpy((char *)beentry->st_progress_message[index], msg,
PROGRESS_MESSAGE_LENGTH);
+     pgstat_increment_changecount_after(beentry);

As I might have written upthread, transferring the whole string
as a progress message is useless at least in this scenario. Since
they are a set of fixed messages, each of them can be represented
by an identifier, an integer number. I don't see a reason for
sending the whole of a string beyond a backend.

Next, the function pg_stat_get_command_progress() has a somewhat
generic name, but it seems to reuturn the data only for the
backends with beentry->st_command = COMMAND_LAZY_VACUUM and has
the column names specific for vucuum like process. If the
function is intended to be generic, it might be better to return
a set of integer[] for given type. Otherwise it should have a
name represents its objective.

CREATE FUNCTION
pg_stat_get_command_progress(IN cmdtype integer)
RETURNS SETOF integer[] as $$....

SELECT * from pg_stat_get_command_progress(PROGRESS_COMMAND_VACUUM) as x
x
---------------------_
{1233, 16233, 1, ....}
{3244, 16236, 2, ....}
....

CREATE VIEW pg_stat_vacuum_progress AS
SELECT S.s[1] as pid,
S.s[2] as relid,
CASE S.s[3]
WHEN 1 THEN 'Scanning Heap'
WHEN 2 THEN 'Vacuuming Index and Heap'
ELSE 'Unknown phase'
END,
....
FROM pg_stat_get_command_progress(PROGRESS_COMMAND_VACUUM) as S;

# The name of the function could be other than *_command_progress.

Any thoughts or opinions?

One more comment:

@@ -1120,14 +1157,23 @@ lazy_scan_heap(Relation onerel, LVRelStats
*vacrelstats,
/* Log cleanup info before we touch indexes */
vacuum_log_cleanup_info(onerel, vacrelstats);

+             snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s", phase2);
+             pgstat_report_progress_update_message(0, progress_message);
/* Remove index entries */
for (i = 0; i < nindexes; i++)
+             {
lazy_vacuum_index(Irel[i],
&indstats[i],
vacrelstats);
+                     scanned_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+                     /* Update the scanned index pages and number of index scan */
+                     pgstat_report_progress_update_counter(3, scanned_index_pages);
+                     pgstat_report_progress_update_counter(4, vacrelstats->num_index_scans
+ 1);
+             }
/* Remove tuples from heap */
lazy_vacuum_heap(onerel, vacrelstats);
vacrelstats->num_index_scans++;
+             scanned_index_pages = 0;

I guess num_index_scans could better be reported after all the indexes are
done, that is, after the for loop ends.

Precise reporting would be valuable if vacuuming indexes takes a
long time. It seems to me to be fine as it is since updating of
stat counters wouldn't add any significant overhead.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#164Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Kyotaro HORIGUCHI (#163)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

On 2016/02/15 20:21, Kyotaro HORIGUCHI wrote:

At Mon, 8 Feb 2016 11:37:17 +0900, Amit Langote wrote:

On 2016/02/05 17:15, pokurev@pm.nttdata.co.jp wrote:

Please find attached updated patch.

[ ... ]

Instead of passing the array of char *'s, why not just pass a single char
*, because that's what it's doing - updating a single message. So,
something like:

As I might have written upthread, transferring the whole string
as a progress message is useless at least in this scenario. Since
they are a set of fixed messages, each of them can be represented
by an identifier, an integer number. I don't see a reason for
sending the whole of a string beyond a backend.

This tends to make sense. Perhaps, they could be macros:

#define VACUUM_PHASE_SCAN_HEAP 1
#define VACUUM_PHASE_VACUUM_INDEX_HEAP 2

Next, the function pg_stat_get_command_progress() has a somewhat
generic name, but it seems to reuturn the data only for the
backends with beentry->st_command = COMMAND_LAZY_VACUUM and has
the column names specific for vucuum like process. If the
function is intended to be generic, it might be better to return
a set of integer[] for given type. Otherwise it should have a
name represents its objective.

Agreed.

CREATE FUNCTION
pg_stat_get_command_progress(IN cmdtype integer)
RETURNS SETOF integer[] as $$....

SELECT * from pg_stat_get_command_progress(PROGRESS_COMMAND_VACUUM) as x
x
---------------------_
{1233, 16233, 1, ....}
{3244, 16236, 2, ....}
....

I am not sure what we would pass as argument to the (SQL) function
pg_stat_get_command_progress() in the system view definition for
individual commands - what is PROGRESS_COMMAND_VACUUM exactly? Would
string literals like "vacuum", "cluster", etc. to represent command names
work?

CREATE VIEW pg_stat_vacuum_progress AS
SELECT S.s[1] as pid,
S.s[2] as relid,
CASE S.s[3]
WHEN 1 THEN 'Scanning Heap'
WHEN 2 THEN 'Vacuuming Index and Heap'
ELSE 'Unknown phase'
END,
....
FROM pg_stat_get_command_progress(PROGRESS_COMMAND_VACUUM) as S;

# The name of the function could be other than *_command_progress.

Any thoughts or opinions?

How about pg_stat_get_progress_info()?

+             snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s", phase2);
+             pgstat_report_progress_update_message(0, progress_message);
/* Remove index entries */
for (i = 0; i < nindexes; i++)
+             {
lazy_vacuum_index(Irel[i],
&indstats[i],
vacrelstats);
+                     scanned_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+                     /* Update the scanned index pages and number of index scan */
+                     pgstat_report_progress_update_counter(3, scanned_index_pages);
+                     pgstat_report_progress_update_counter(4, vacrelstats->num_index_scans
+ 1);
+             }
/* Remove tuples from heap */
lazy_vacuum_heap(onerel, vacrelstats);
vacrelstats->num_index_scans++;
+             scanned_index_pages = 0;

I guess num_index_scans could better be reported after all the indexes are
done, that is, after the for loop ends.

Precise reporting would be valuable if vacuuming indexes takes a
long time. It seems to me to be fine as it is since updating of
stat counters wouldn't add any significant overhead.

Sorry, my comment may be a bit unclear. vacrelstats->num_index_scans
doesn't count individual indexes vacuumed but rather the number of times
"all" the indexes of a table are vacuumed, IOW, the number of times the
vacuum phase runs. Purpose of counter #4 there seems to be to report the
latter. OTOH, reporting scanned_index_pages per index as is done in the
patch is alright.

That said, there is discussion upthread about more precise reporting on
index vacuuming by utilizing the lazy_tid_reaped() (the index bulk delete
callback) as a place where we can report what index block number we are
at. I think that would mean the current IndexBulkDeleteCallback signature
is insufficient, which is the following:

/* Typedef for callback function to determine if a tuple is bulk-deletable */
typedef bool (*IndexBulkDeleteCallback) (ItemPointer itemptr, void *state);

One more parameter would be necessary:

typedef bool (*IndexBulkDeleteCallback) (ItemPointer itemptr, BlockNumber
current_index_blkno, void *state);

That would also require changing all the am specific vacuumpage routines
(like btvacuumpage) to also pass the new argument. Needless to say, some
bookkeeping information would also need to be kept in LVRelStats (the
"state" in above signature).

Am I missing something?

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#165Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Amit Langote (#164)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

At Tue, 16 Feb 2016 10:39:27 +0900, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote in <56C27DCF.7020705@lab.ntt.co.jp>

Hello,

On 2016/02/15 20:21, Kyotaro HORIGUCHI wrote:

At Mon, 8 Feb 2016 11:37:17 +0900, Amit Langote wrote:

On 2016/02/05 17:15, pokurev@pm.nttdata.co.jp wrote:

Please find attached updated patch.

[ ... ]

Instead of passing the array of char *'s, why not just pass a single char
*, because that's what it's doing - updating a single message. So,
something like:

As I might have written upthread, transferring the whole string
as a progress message is useless at least in this scenario. Since
they are a set of fixed messages, each of them can be represented
by an identifier, an integer number. I don't see a reason for
sending the whole of a string beyond a backend.

This tends to make sense. Perhaps, they could be macros:

#define VACUUM_PHASE_SCAN_HEAP 1
#define VACUUM_PHASE_VACUUM_INDEX_HEAP 2

Exactly. Or an enum.

Next, the function pg_stat_get_command_progress() has a somewhat
generic name, but it seems to reuturn the data only for the
backends with beentry->st_command = COMMAND_LAZY_VACUUM and has
the column names specific for vucuum like process. If the
function is intended to be generic, it might be better to return
a set of integer[] for given type. Otherwise it should have a
name represents its objective.

Agreed.

CREATE FUNCTION
pg_stat_get_command_progress(IN cmdtype integer)
RETURNS SETOF integer[] as $$....

SELECT * from pg_stat_get_command_progress(PROGRESS_COMMAND_VACUUM) as x
x
---------------------_
{1233, 16233, 1, ....}
{3244, 16236, 2, ....}
....

I am not sure what we would pass as argument to the (SQL) function
pg_stat_get_command_progress() in the system view definition for
individual commands - what is PROGRESS_COMMAND_VACUUM exactly? Would
string literals like "vacuum", "cluster", etc. to represent command names
work?

Sorry, it is a symbol to tell pg_stat_get_command_progress() to
return stats numbers of backends running VACUUM. It should have
been COMMAND_LAZY_VACUUM for this patch. If we want progress of
CREATE INDEX, it would be COMMAND_CREATE_INDEX.

CREATE VIEW pg_stat_vacuum_progress AS
SELECT S.s[1] as pid,
S.s[2] as relid,
CASE S.s[3]
WHEN 1 THEN 'Scanning Heap'
WHEN 2 THEN 'Vacuuming Index and Heap'
ELSE 'Unknown phase'
END,
....
FROM pg_stat_get_command_progress(PROGRESS_COMMAND_VACUUM) as S;

# The name of the function could be other than *_command_progress.

Any thoughts or opinions?

How about pg_stat_get_progress_info()?

I think it's good.

+             snprintf(progress_message[0], PROGRESS_MESSAGE_LENGTH, "%s", phase2);
+             pgstat_report_progress_update_message(0, progress_message);
/* Remove index entries */
for (i = 0; i < nindexes; i++)
+             {
lazy_vacuum_index(Irel[i],
&indstats[i],
vacrelstats);
+                     scanned_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+                     /* Update the scanned index pages and number of index scan */
+                     pgstat_report_progress_update_counter(3, scanned_index_pages);
+                     pgstat_report_progress_update_counter(4, vacrelstats->num_index_scans
+ 1);
+             }
/* Remove tuples from heap */
lazy_vacuum_heap(onerel, vacrelstats);
vacrelstats->num_index_scans++;
+             scanned_index_pages = 0;

I guess num_index_scans could better be reported after all the indexes are
done, that is, after the for loop ends.

Precise reporting would be valuable if vacuuming indexes takes a
long time. It seems to me to be fine as it is since updating of
stat counters wouldn't add any significant overhead.

Sorry, my comment may be a bit unclear. vacrelstats->num_index_scans
doesn't count individual indexes vacuumed but rather the number of times
"all" the indexes of a table are vacuumed, IOW, the number of times the
vacuum phase runs. Purpose of counter #4 there seems to be to report the
latter. OTOH, reporting scanned_index_pages per index as is done in the
patch is alright.

I got it. Sorry for my misreading. Yes, you're
right. index_scan_count can take atmost 1 by the code. That's
odd.

That said, there is discussion upthread about more precise reporting on
index vacuuming by utilizing the lazy_tid_reaped() (the index bulk delete
callback) as a place where we can report what index block number we are
at. I think that would mean the current IndexBulkDeleteCallback signature
is insufficient, which is the following:

/* Typedef for callback function to determine if a tuple is bulk-deletable */
typedef bool (*IndexBulkDeleteCallback) (ItemPointer itemptr, void *state);

One more parameter would be necessary:

typedef bool (*IndexBulkDeleteCallback) (ItemPointer itemptr, BlockNumber
current_index_blkno, void *state);

It could work for btree but doesn't for, for example,
gin. ginbulkdelete finds the next page in the following way.

blkno = GinPageGetOpaque(page)->rightlink;

We should use another value to fagure the progress. If the
callback is called centainly the same or around the same number
of times with the total page numbers, the callback should just
increment a static counter for processed pages.

That would also require changing all the am specific vacuumpage routines
(like btvacuumpage) to also pass the new argument. Needless to say, some
bookkeeping information would also need to be kept in LVRelStats (the
"state" in above signature).

Am I missing something?

So, maybe missing the case of other than btree..

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#166Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Kyotaro HORIGUCHI (#165)
Re: [PROPOSAL] VACUUM Progress Checker.

Hi,

On 2016/02/16 18:25, Kyotaro HORIGUCHI wrote:

At Tue, 16 Feb 2016 10:39:27 +0900, Amit Langote wrote:

On 2016/02/15 20:21, Kyotaro HORIGUCHI wrote:

CREATE FUNCTION
pg_stat_get_command_progress(IN cmdtype integer)
RETURNS SETOF integer[] as $$....

SELECT * from pg_stat_get_command_progress(PROGRESS_COMMAND_VACUUM) as x
x
---------------------_
{1233, 16233, 1, ....}
{3244, 16236, 2, ....}
....

I am not sure what we would pass as argument to the (SQL) function
pg_stat_get_command_progress() in the system view definition for
individual commands - what is PROGRESS_COMMAND_VACUUM exactly? Would
string literals like "vacuum", "cluster", etc. to represent command names
work?

Sorry, it is a symbol to tell pg_stat_get_command_progress() to
return stats numbers of backends running VACUUM. It should have
been COMMAND_LAZY_VACUUM for this patch. If we want progress of
CREATE INDEX, it would be COMMAND_CREATE_INDEX.

Oh I see:

CREATE VIEW pg_stat_vacuum_prgress AS
SELECT * from pg_stat_get_command_progress(PROGRESS_COMMAND_VACUUM) as x

is actually:

CREATE VIEW pg_stat_vacuum_prgress AS
SELECT * from pg_stat_get_command_progress(1) as x

where PROGRESS_COMMAND_VACUUM is 1 in backend code (macro, enum,
whatever). I was confused because we never say relkind = RELKIND_INDEX in
SQL queries, :)

That said, there is discussion upthread about more precise reporting on
index vacuuming by utilizing the lazy_tid_reaped() (the index bulk delete
callback) as a place where we can report what index block number we are
at. I think that would mean the current IndexBulkDeleteCallback signature
is insufficient, which is the following:

/* Typedef for callback function to determine if a tuple is bulk-deletable */
typedef bool (*IndexBulkDeleteCallback) (ItemPointer itemptr, void *state);

One more parameter would be necessary:

typedef bool (*IndexBulkDeleteCallback) (ItemPointer itemptr, BlockNumber
current_index_blkno, void *state);

It could work for btree but doesn't for, for example,
gin. ginbulkdelete finds the next page in the following way.

blkno = GinPageGetOpaque(page)->rightlink;

We should use another value to fagure the progress. If the
callback is called centainly the same or around the same number
of times with the total page numbers, the callback should just
increment a static counter for processed pages.

That would also require changing all the am specific vacuumpage routines
(like btvacuumpage) to also pass the new argument. Needless to say, some
bookkeeping information would also need to be kept in LVRelStats (the
"state" in above signature).

Am I missing something?

So, maybe missing the case of other than btree..

More or less, the callback is called maxoffset number of times for all
index pages containing pointers to heap tuples. Robert said upthread that
counting in granularity lower than pages may not be useful:

"Let's report blocks, not tuples. The reason is that
pg_class.reltuples is only an estimate and might be wildly wrong on
occasion, but the length of the relation in blocks can be known with
certainty."

With the existing interface of the callback, it's difficult to keep the
count of pages, hence a proposal to enhance the interface. Also, now I
wonder whether scanned_index_pages will always converge to whatever
total_index_pages we get from RelationGetNumberOfBlocks(index), because
callback is not called for *every* index page and tends to differ per
index method (am). Thanks for pointing me to confirm so.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#167Noname
pokurev@pm.nttdata.co.jp
In reply to: Amit Langote (#166)
1 attachment(s)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

Thank you for your comments.
Please find attached patch addressing following comments.

As I might have written upthread, transferring the whole string
as a progress message is useless at least in this scenario. Since
they are a set of fixed messages, each of them can be represented
by an identifier, an integer number. I don't see a reason for
sending the whole of a string beyond a backend.

Agreed. I used following macros.
#define VACUUM_PHASE_SCAN_HEAP 1
#define VACUUM_PHASE_VACUUM_INDEX_HEAP 2

I guess num_index_scans could better be reported after all the indexes are
done, that is, after the for loop ends.

Agreed. I have corrected it.

CREATE VIEW pg_stat_vacuum_progress AS
SELECT S.s[1] as pid,
S.s[2] as relid,
CASE S.s[3]
WHEN 1 THEN 'Scanning Heap'
WHEN 2 THEN 'Vacuuming Index and Heap'
ELSE 'Unknown phase'
END,
....
FROM pg_stat_get_command_progress(PROGRESS_COMMAND_VACUUM) as S;

# The name of the function could be other than *_command_progress.

The name of function is updated as pg_stat_get_progress_info() and also updated the function.
Updated the pg_stat_vacuum_progress view as suggested.

Regards,
Vinayak

Attachments:

Vacuum_progress_checker_v12.patchapplication/octet-stream; name=Vacuum_progress_checker_v12.patchDownload
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 85459d0..4288992 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -507,6 +507,13 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       yet included in <structname>pg_stat_user_functions</>).</entry>
      </row>
 
+     <row>
+      <entry><structname>pg_stat_vacuum_progress</><indexterm><primary>pg_stat_vacuum_progress</primary></indexterm></entry>
+      <entry>One row for each backend (including autovacuum worker processes) running
+      <command>VACUUM</>, showing current progress in terms of heap pages it
+      has finished processing. Backends running <command>VACUUM FULL</> are
+      not included.</entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -1822,6 +1829,88 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
    controls exactly which functions are tracked.
   </para>
 
+  <table id="pg-stat-vacuum-progress" xreflabel="pg_stat_vacuum_progress">
+   <title><structname>pg_stat_vacuum_progress</structname> View</title>
+   <tgroup cols="3">
+    <thead>
+    <row>
+      <entry>Column</entry>
+      <entry>Type</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+   <tbody>
+    <row>
+     <entry><structfield>pid</></entry>
+     <entry><type>integer</></entry>
+     <entry>Process ID of backend</entry>
+    </row>
+    <row>
+     <entry><structfield>relid</></entry>
+     <entry><type>oid</></entry>
+     <entry>OID of a table</entry>
+    </row>
+    <row>
+     <entry><structfield>phase</></entry>
+     <entry><type>integer</></entry>
+     <entry>Current phase of vacuum.
+       Possible values are:
+       <itemizedlist>
+        <listitem>
+         <para>
+          <literal>Scanning Heap</>: Scanning heap blocks.
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>Vacuuming Index and Heap</>: Vacuuming index and heap blocks.
+         </para>
+        </listitem>
+       </itemizedlist>
+     </entry>
+    </row>
+    <row>
+     <entry><structfield>total_heap_blks</></entry>
+     <entry><type>integer</></entry>
+     <entry>Total number of heap blocks in this table</entry>
+    </row>
+    <row>
+     <entry><structfield>current_heap_blkno</></entry>
+     <entry><type>integer</></entry>
+     <entry>Current heap block number</entry>
+    </row>
+    <row>
+     <entry><structfield>total_index_pages</></entry>
+     <entry><type>integer</></entry>
+     <entry>Total number of index pages in this table</entry>
+    </row>
+    <row>
+     <entry><structfield>scanned_index_pages</></entry>
+     <entry><type>integer</></entry>
+     <entry>Number of index pages processed</entry>
+    </row>
+    <row>
+     <entry><structfield>index_scan_count</></entry>
+     <entry><type>integer</></entry>
+     <entry>Number of times index scan has been performed so far</entry>
+    </row>
+    <row>
+     <entry><structfield>percent_complete</></entry>
+     <entry><type>double precision</></entry>
+     <entry>Amount of work done in percent</entry>
+    </row>
+   </tbody>
+   </tgroup>
+  </table>
+
+  <para>
+   The <structname>pg_stat_vacuum_progress</structname> view will contain
+   one row for each backend (including autovacuum worker processes), showing
+   progress of <command>VACUUM</> running in it. Note that the backends
+   running <command>VACUUM FULL</> are not shown.
+  </para>
+
  </sect2>
 
  <sect2 id="monitoring-stats-functions">
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index abf9a70..330b638 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -645,6 +645,23 @@ CREATE VIEW pg_stat_activity AS
     WHERE S.datid = D.oid AND
             S.usesysid = U.oid;
 
+CREATE VIEW pg_stat_vacuum_progress AS
+	SELECT
+			S.pid,
+			S.relid,
+			CASE S.phase
+				WHEN 1 THEN 'Scanning Heap'
+				WHEN 2 THEN 'Vacuuming Index and Heap'
+				ELSE 'Unknown phase'
+			END AS phase,
+			S.total_heap_blks,
+			S.current_heap_blkno,
+			S.total_index_pages,
+			S.scanned_index_pages,
+			S.index_scan_count,
+			S.percent_complete
+	FROM pg_stat_get_progress_info(1) AS S;
+
 CREATE VIEW pg_stat_replication AS
     SELECT
             S.pid,
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 4cb4acf..4401922 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -287,12 +287,17 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
 		/*
 		 * Loop to process each selected relation.
 		 */
+		pgstat_reset_local_progress();
+
 		foreach(cur, relations)
 		{
 			Oid			relid = lfirst_oid(cur);
 
 			if (options & VACOPT_VACUUM)
 			{
+				if (!(options & VACOPT_FULL))
+					pgstat_report_progress_set_command(COMMAND_LAZY_VACUUM);
+
 				if (!vacuum_rel(relid, relation, options, params))
 					continue;
 			}
@@ -325,6 +330,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
 	{
 		in_vacuum = false;
 		VacuumCostActive = false;
+		pgstat_reset_local_progress();
 		PG_RE_THROW();
 	}
 	PG_END_TRY();
@@ -355,6 +361,7 @@ vacuum(int options, RangeVar *relation, Oid relid, VacuumParams *params,
 		vac_update_datfrozenxid();
 	}
 
+	pgstat_reset_local_progress();
 	/*
 	 * Clean up working storage --- note we must do this after
 	 * StartTransactionCommand, else we might be trying to delete the active
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index 4f6f6e7..9c8a248 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -433,7 +433,10 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			   Relation *Irel, int nindexes, bool scan_all)
 {
 	BlockNumber nblocks,
-				blkno;
+				blkno,
+				total_heap_blks,
+				total_index_pages = 0,
+				scanned_index_pages = 0;
 	HeapTupleData tuple;
 	char	   *relname;
 	BlockNumber empty_pages,
@@ -450,14 +453,18 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	bool		skipping_all_visible_blocks;
 	xl_heap_freeze_tuple *frozen;
 	StringInfoData buf;
+	Oid	relid;
 
 	pg_rusage_init(&ru0);
 
 	relname = RelationGetRelationName(onerel);
+	relid = RelationGetRelid(onerel);
 	ereport(elevel,
 			(errmsg("vacuuming \"%s.%s\"",
 					get_namespace_name(RelationGetNamespace(onerel)),
 					relname)));
+	/* Report relid of the relation*/
+	pgstat_report_progress_set_command_target(relid);
 
 	empty_pages = vacuumed_pages = 0;
 	num_tuples = tups_vacuumed = nkeep = nunused = 0;
@@ -465,7 +472,11 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	indstats = (IndexBulkDeleteResult **)
 		palloc0(nindexes * sizeof(IndexBulkDeleteResult *));
 
-	nblocks = RelationGetNumberOfBlocks(onerel);
+	total_heap_blks = nblocks = RelationGetNumberOfBlocks(onerel);
+
+	for (i = 0; i < nindexes; i++)
+		total_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+
 	vacrelstats->rel_pages = nblocks;
 	vacrelstats->scanned_pages = 0;
 	vacrelstats->nonempty_pages = 0;
@@ -474,6 +485,10 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	lazy_space_alloc(vacrelstats, nblocks);
 	frozen = palloc(sizeof(xl_heap_freeze_tuple) * MaxHeapTuplesPerPage);
 
+	/* Report count of total heap blocks and total index pages of a relation*/
+	pgstat_report_progress_update_counter(1, total_heap_blks);
+	pgstat_report_progress_update_counter(3, total_index_pages);
+
 	/*
 	 * We want to skip pages that don't require vacuuming according to the
 	 * visibility map, but only when we can skip at least SKIP_PAGES_THRESHOLD
@@ -527,6 +542,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	else
 		skipping_all_visible_blocks = false;
 
+	pgstat_report_progress_update_counter(0, VACUUM_PHASE_SCAN_HEAP);
 	for (blkno = 0; blkno < nblocks; blkno++)
 	{
 		Buffer		buf;
@@ -543,6 +559,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		bool		has_dead_tuples;
 		TransactionId visibility_cutoff_xid = InvalidTransactionId;
 
+		/* Update current block number of the relation */
+		pgstat_report_progress_update_counter(2, blkno + 1);
+
 		/* see note above about forcing scanning of last page */
 #define FORCE_CHECK_PAGE() \
 		(blkno == nblocks - 1 && should_attempt_truncation(vacrelstats))
@@ -603,11 +622,18 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			/* Log cleanup info before we touch indexes */
 			vacuum_log_cleanup_info(onerel, vacrelstats);
 
+			pgstat_report_progress_update_counter(0, VACUUM_PHASE_VACUUM_INDEX_HEAP);
 			/* Remove index entries */
 			for (i = 0; i < nindexes; i++)
+			{
 				lazy_vacuum_index(Irel[i],
 								  &indstats[i],
 								  vacrelstats);
+				scanned_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+				/* Update scanned index pages of a relation*/
+				pgstat_report_progress_update_counter(4, scanned_index_pages);
+			}
+			pgstat_report_progress_update_counter(5, vacrelstats->num_index_scans);
 			/* Remove tuples from heap */
 			lazy_vacuum_heap(onerel, vacrelstats);
 
@@ -617,8 +643,10 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			 * valid.
 			 */
 			vacrelstats->num_dead_tuples = 0;
+			scanned_index_pages = 0;
 			vacrelstats->num_index_scans++;
 		}
+		pgstat_report_progress_update_counter(0, VACUUM_PHASE_SCAN_HEAP);
 
 		/*
 		 * Pin the visibility map page in case we need to mark the page
@@ -1089,8 +1117,11 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		 */
 		if (vacrelstats->num_dead_tuples == prev_dead_count)
 			RecordPageWithFreeSpace(onerel, blkno, freespace);
-	}
 
+		if (blkno == nblocks - 1 && vacrelstats->num_dead_tuples == 0 && nindexes != 0
+			&& vacrelstats->num_index_scans == 0)
+			total_index_pages = 0;
+	}
 	pfree(frozen);
 
 	/* save stats for use later */
@@ -1120,14 +1151,22 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		/* Log cleanup info before we touch indexes */
 		vacuum_log_cleanup_info(onerel, vacrelstats);
 
+		pgstat_report_progress_update_counter(0, VACUUM_PHASE_VACUUM_INDEX_HEAP);
 		/* Remove index entries */
 		for (i = 0; i < nindexes; i++)
+		{
 			lazy_vacuum_index(Irel[i],
 							  &indstats[i],
 							  vacrelstats);
+			scanned_index_pages += RelationGetNumberOfBlocks(Irel[i]);
+			/* Update the scanned index pages and number of index scan */
+			pgstat_report_progress_update_counter(4, scanned_index_pages);
+		}
+		pgstat_report_progress_update_counter(5, vacrelstats->num_index_scans + 1);
 		/* Remove tuples from heap */
 		lazy_vacuum_heap(onerel, vacrelstats);
 		vacrelstats->num_index_scans++;
+		scanned_index_pages = 0;
 	}
 
 	/* Do post-vacuum cleanup and statistics update for each index */
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index da768c6..7c4c1c5 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -2851,6 +2851,82 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
 	pgstat_increment_changecount_after(beentry);
 }
 
+/*-----------
+ * pgstat_report_progress_update_counter()-
+ *
+ * Called to update different values of command progress
+ *-----------
+ */
+void
+pgstat_report_progress_update_counter(int index, uint32 counter)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	if(!beentry)
+		return;
+
+	if (!pgstat_track_activities)
+		return;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_progress_param[index] = counter;
+	pgstat_increment_changecount_after(beentry);
+}
+
+/* ----------
+ *	pgstat_report_progress_set_command_target() -
+ *
+ *	Called to update command target relation oid.
+ * ----------
+ */
+void
+pgstat_report_progress_set_command_target(Oid relid)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	if (!beentry)
+		return;
+
+	if (!pgstat_track_activities)
+		return;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_relid = relid;
+	pgstat_increment_changecount_after(beentry);
+}
+
+/*-----------
+ * pgstat_report_progress_set_command()-
+ *
+ * Called to update command the backend is about to start running.
+ *-----------
+ */
+void
+pgstat_report_progress_set_command(int32 commandId)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_command = commandId;
+	pgstat_increment_changecount_after(beentry);
+}
+
+/*--------
+ * pgstat_reset_local_progress()-
+ *
+ * Reset local backend's progress parameters. Resetting st_command will do.
+ *--------
+ */
+void
+pgstat_reset_local_progress()
+{
+	PgBackendStatus *beentry = MyBEEntry;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_command = 0;
+	pgstat_increment_changecount_after(beentry);
+}
+
 /* ----------
  * pgstat_report_appname() -
  *
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 1b22fcc..bb6cc7a 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -53,6 +53,7 @@ extern Datum pg_stat_get_function_self_time(PG_FUNCTION_ARGS);
 
 extern Datum pg_stat_get_backend_idset(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_activity(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_progress_info(PG_FUNCTION_ARGS);
 extern Datum pg_backend_pid(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_pid(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_dbid(PG_FUNCTION_ARGS);
@@ -523,7 +524,106 @@ pg_stat_get_backend_idset(PG_FUNCTION_ARGS)
 		SRF_RETURN_DONE(funcctx);
 	}
 }
+/*
+ * Returns progress values of commands stored by each backend
+ * executing command.
+ */
+Datum
+pg_stat_get_progress_info(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_PROGRESS_COLS	30
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			cmdtype = PG_GETARG_INT32(0);
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+
+	/* 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);
 
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		Datum		values[PG_STAT_GET_PROGRESS_COLS];
+		bool		nulls[PG_STAT_GET_PROGRESS_COLS];
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+
+		MemSet(values, 0, sizeof(values));
+		MemSet(nulls, 0, sizeof(nulls));
+
+		local_beentry = pgstat_fetch_stat_local_beentry(curr_backend);
+		if (!local_beentry)
+			continue;
+		beentry = &local_beentry->backendStatus;
+
+		/* Report values for only those backends which are running command */
+		if(!beentry || beentry->st_command != cmdtype)
+			continue;
+
+		/* Values available to all callers */
+		values[0] = Int32GetDatum(beentry->st_procpid);
+		values[1] = ObjectIdGetDatum(beentry->st_relid);
+
+		/*  Report values for only those backends which are running VACUUM command */
+		if (cmdtype == COMMAND_LAZY_VACUUM)
+		{
+			/*Progress can only be viewed by role member.*/
+			if (has_privs_of_role(GetUserId(), beentry->st_userid))
+			{
+				values[2] = UInt32GetDatum(beentry->st_progress_param[0]);
+				values[3] = UInt32GetDatum(beentry->st_progress_param[1]);
+				values[4] = UInt32GetDatum(beentry->st_progress_param[2]);
+				values[5] = UInt32GetDatum(beentry->st_progress_param[3]);
+				values[6] = UInt32GetDatum(beentry->st_progress_param[4]);
+				values[7] = UInt32GetDatum(beentry->st_progress_param[5]);
+				if (beentry->st_progress_param[1] != 0)
+					values[8] = Float8GetDatum(beentry->st_progress_param[2] * 100 / beentry->st_progress_param[1]);
+				else
+					nulls[8] = true;
+			}
+			else
+			{
+				nulls[2] = true;
+				nulls[3] = true;
+				nulls[4] = true;
+				nulls[5] = true;
+				nulls[6] = true;
+				nulls[7] = true;
+				nulls[8] = true;
+			}
+		}
+		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+	}
+
+	/* clean up and return the tuplestore */
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
 /*
  * Returns activity of PG backends.
  */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 62b9125..4e6daa0 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2710,6 +2710,8 @@ DATA(insert OID = 1936 (  pg_stat_get_backend_idset		PGNSP PGUID 12 1 100 0 0 f
 DESCR("statistics: currently active backend IDs");
 DATA(insert OID = 2022 (  pg_stat_get_activity			PGNSP PGUID 12 1 100 0 0 f f f f f t s r 1 0 2249 "23" "{23,26,23,26,25,25,25,16,1184,1184,1184,1184,869,25,23,28,28,16,25,25,23,16,25}" "{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{pid,datid,pid,usesysid,application_name,state,query,waiting,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,ssl,sslversion,sslcipher,sslbits,sslcompression,sslclientdn}" _null_ _null_ pg_stat_get_activity _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active backends");
+DATA(insert OID = 3319 (  pg_stat_get_progress_info           PGNSP PGUID 12 1 100 0 0 f f f f f t s r 1 0 2249 "23" "{23,23,26,23,23,23,23,23,23,701}" "{i,o,o,o,o,o,o,o,o,o}" "{cmdtype,pid,relid,phase,total_heap_blks,current_heap_blkno,total_index_pages,scanned_index_pages,index_scan_count,percent_complete}" _null_ _null_ pg_stat_get_progress_info _null_ _null_ _null_ ));
+DESCR("statistics: information about progress of backends running maintenance command");
 DATA(insert OID = 3099 (  pg_stat_get_wal_senders	PGNSP PGUID 12 1 10 0 0 f f f f f t s r 0 0 2249 "" "{23,25,3220,3220,3220,3220,23,25}" "{o,o,o,o,o,o,o,o}" "{pid,state,sent_location,write_location,flush_location,replay_location,sync_priority,sync_state}" _null_ _null_ pg_stat_get_wal_senders _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active replication");
 DATA(insert OID = 3317 (  pg_stat_get_wal_receiver	PGNSP PGUID 12 1 0 0 0 f f f f f f s r 0 0 2249 "" "{23,25,3220,23,3220,23,1184,1184,3220,1184,25}" "{o,o,o,o,o,o,o,o,o,o,o}" "{pid,status,receive_start_lsn,receive_start_tli,received_lsn,received_tli,last_msg_send_time,last_msg_receipt_time,latest_end_lsn,latest_end_time,slot_name}" _null_ _null_ pg_stat_get_wal_receiver _null_ _null_ _null_ ));
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 65e968e..3ee2c5a 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -205,6 +205,9 @@ typedef struct PgStat_MsgHdr
 #define PGSTAT_MAX_MSG_SIZE 1000
 #define PGSTAT_MSG_PAYLOAD	(PGSTAT_MAX_MSG_SIZE - sizeof(PgStat_MsgHdr))
 
+#define N_PROGRESS_PARAM 10
+#define VACUUM_PHASE_SCAN_HEAP 1
+#define VACUUM_PHASE_VACUUM_INDEX_HEAP 2
 
 /* ----------
  * PgStat_MsgDummy				A dummy message, ignored by the collector
@@ -776,6 +779,19 @@ typedef struct PgBackendStatus
 
 	/* current command string; MUST be null-terminated */
 	char	   *st_activity;
+
+	/*
+	 * Information about the progress of activity/command being run by the backend.
+	 * The progress parameters indicate progress of a command. Different
+	 * commands can report different number of parameters of each type.
+	 *
+	 * st_command reports which activity/command is being run by the backend.
+	 * This is used in the SQL callable functions to display progress values
+	 * for respective commands.
+	 */
+	uint32		st_command;
+	uint32		st_progress_param[N_PROGRESS_PARAM];
+	Oid		st_relid;
 } PgBackendStatus;
 
 /*
@@ -815,6 +831,7 @@ typedef struct PgBackendStatus
 		save_changecount = beentry->st_changecount; \
 	} while (0)
 
+#define COMMAND_LAZY_VACUUM 0x01
 /* ----------
  * LocalPgBackendStatus
  *
@@ -928,6 +945,9 @@ extern void pgstat_initialize(void);
 extern void pgstat_bestart(void);
 
 extern void pgstat_report_activity(BackendState state, const char *cmd_str);
+extern void pgstat_report_progress_set_command(int32 commandId);
+extern void pgstat_reset_local_progress(void);
+extern void pgstat_report_progress_update_counter(int index, uint32 counter);
 extern void pgstat_report_tempfile(size_t filesize);
 extern void pgstat_report_appname(const char *appname);
 extern void pgstat_report_xact_timestamp(TimestampTz tstamp);
@@ -938,6 +958,7 @@ extern const char *pgstat_get_crashed_backend_activity(int pid, char *buffer,
 
 extern PgStat_TableStatus *find_tabstat_entry(Oid rel_id);
 extern PgStat_BackendFunctionEntry *find_funcstat_entry(Oid func_id);
+extern void pgstat_report_progress_set_command_target(Oid relid);
 
 extern void pgstat_initstats(Relation rel);
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 81bc5c9..e667c8c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1851,6 +1851,20 @@ pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
     pg_stat_all_tables.autoanalyze_count
    FROM pg_stat_all_tables
   WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
+pg_stat_vacuum_progress| SELECT s.pid,
+    s.relid,
+        CASE s.phase
+            WHEN 1 THEN 'Scanning Heap'::text
+            WHEN 2 THEN 'Vacuuming Index and Heap'::text
+            ELSE 'Unknown phase'::text
+        END AS phase,
+    s.total_heap_blks,
+    s.current_heap_blkno,
+    s.total_index_pages,
+    s.scanned_index_pages,
+    s.index_scan_count,
+    s.percent_complete
+   FROM pg_stat_get_progress_info(1) s(pid, relid, phase, total_heap_blks, current_heap_blkno, total_index_pages, scanned_index_pages, index_scan_count, percent_complete);
 pg_stat_wal_receiver| SELECT s.pid,
     s.status,
     s.receive_start_lsn,
#168Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Noname (#167)
Re: [PROPOSAL] VACUUM Progress Checker.

Hi Vinayak,

Thanks for updating the patch! A quick comment:

On 2016/02/26 17:28, pokurev@pm.nttdata.co.jp wrote:

CREATE VIEW pg_stat_vacuum_progress AS
SELECT S.s[1] as pid,
S.s[2] as relid,
CASE S.s[3]
WHEN 1 THEN 'Scanning Heap'
WHEN 2 THEN 'Vacuuming Index and Heap'
ELSE 'Unknown phase'
END,
....
FROM pg_stat_get_command_progress(PROGRESS_COMMAND_VACUUM) as S;

# The name of the function could be other than *_command_progress.

The name of function is updated as pg_stat_get_progress_info() and also updated the function.
Updated the pg_stat_vacuum_progress view as suggested.

So, pg_stat_get_progress_info() now accepts a parameter to distinguish
different commands. I see the following in its definition:

+		/*  Report values for only those backends which are running VACUUM
command */
+		if (cmdtype == COMMAND_LAZY_VACUUM)
+		{
+			/*Progress can only be viewed by role member.*/
+			if (has_privs_of_role(GetUserId(), beentry->st_userid))
+			{
+				values[2] = UInt32GetDatum(beentry->st_progress_param[0]);
+				values[3] = UInt32GetDatum(beentry->st_progress_param[1]);
+				values[4] = UInt32GetDatum(beentry->st_progress_param[2]);
+				values[5] = UInt32GetDatum(beentry->st_progress_param[3]);
+				values[6] = UInt32GetDatum(beentry->st_progress_param[4]);
+				values[7] = UInt32GetDatum(beentry->st_progress_param[5]);
+				if (beentry->st_progress_param[1] != 0)
+					values[8] = Float8GetDatum(beentry->st_progress_param[2] * 100 /
beentry->st_progress_param[1]);
+				else
+					nulls[8] = true;
+			}
+			else
+			{
+				nulls[2] = true;
+				nulls[3] = true;
+				nulls[4] = true;
+				nulls[5] = true;
+				nulls[6] = true;
+				nulls[7] = true;
+				nulls[8] = true;
+			}
+		}

How about doing this in a separate function which takes the command id as
parameter and returns an array of values and the number of values (per
command id). pg_stat_get_progress_info() then creates values[] and nulls[]
arrays from that and returns that as result set. It will be a cleaner
separation of activities, perhaps.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#169Vinayak Pokale
vinpokale@gmail.com
In reply to: Amit Langote (#168)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

On Fri, Feb 26, 2016 at 6:19 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp

wrote:

Hi Vinayak,

Thanks for updating the patch! A quick comment:

On 2016/02/26 17:28, pokurev@pm.nttdata.co.jp wrote:

CREATE VIEW pg_stat_vacuum_progress AS
SELECT S.s[1] as pid,
S.s[2] as relid,
CASE S.s[3]
WHEN 1 THEN 'Scanning Heap'
WHEN 2 THEN 'Vacuuming Index and Heap'
ELSE 'Unknown phase'
END,
....
FROM pg_stat_get_command_progress(PROGRESS_COMMAND_VACUUM) as S;

# The name of the function could be other than *_command_progress.

The name of function is updated as pg_stat_get_progress_info() and also

updated the function.

Updated the pg_stat_vacuum_progress view as suggested.

So, pg_stat_get_progress_info() now accepts a parameter to distinguish
different commands. I see the following in its definition:

+               /*  Report values for only those backends which are
running VACUUM
command */
+               if (cmdtype == COMMAND_LAZY_VACUUM)
+               {
+                       /*Progress can only be viewed by role member.*/
+                       if (has_privs_of_role(GetUserId(),
beentry->st_userid))
+                       {
+                               values[2] =
UInt32GetDatum(beentry->st_progress_param[0]);
+                               values[3] =
UInt32GetDatum(beentry->st_progress_param[1]);
+                               values[4] =
UInt32GetDatum(beentry->st_progress_param[2]);
+                               values[5] =
UInt32GetDatum(beentry->st_progress_param[3]);
+                               values[6] =
UInt32GetDatum(beentry->st_progress_param[4]);
+                               values[7] =
UInt32GetDatum(beentry->st_progress_param[5]);
+                               if (beentry->st_progress_param[1] != 0)
+                                       values[8] =
Float8GetDatum(beentry->st_progress_param[2] * 100 /
beentry->st_progress_param[1]);
+                               else
+                                       nulls[8] = true;
+                       }
+                       else
+                       {
+                               nulls[2] = true;
+                               nulls[3] = true;
+                               nulls[4] = true;
+                               nulls[5] = true;
+                               nulls[6] = true;
+                               nulls[7] = true;
+                               nulls[8] = true;
+                       }
+               }

How about doing this in a separate function which takes the command id as
parameter and returns an array of values and the number of values (per
command id). pg_stat_get_progress_info() then creates values[] and nulls[]
arrays from that and returns that as result set. It will be a cleaner
separation of activities, perhaps.

+1

Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#170大山真実
oyama.masanori.1987@gmail.com
In reply to: Vinayak Pokale (#169)
Re: [PROPOSAL] VACUUM Progress Checker.

Hi!

I'm interesting this patch and tested it. I found two strange thing.

* Incorrect counting

Reproduce:
1. Client1 execute "VACUUM"
2. Client2 execute "VACUUM"
3. Client3 execute "SELECT * FROM pg_stat_vacuum_progress".
pid | relid | phase | total_heap_blks | current_heap_blkno |
total_index_pages | scanned_index_pages | index_scan_count |
percent_complete
------+-------+---------------+-----------------+--------------------+-------------------+---------------------+------------------+------------------
9267 | 16551 | Scanning Heap | 164151 | 316 |
27422 | 7 | 1 | 0
9764 | 16554 | Scanning Heap | 2 | 2 |
2 | 27422 | 1 | 100
(2 rows)

Client2 is waiting for Clinet1 "VACUUM" but percent_complete of Client2
"VACUUM" is 100.

* Not end VACUUM ANALYZE in spite of "percent_complete=100"

Client_1 execute "VACUUM ANALYZE", then Client_2 execute "SELECT * FROM
pg_stat_vacuum_progress".

pid | relid | phase | total_heap_blks | current_heap_blkno |
total_index_pages | scanned_index_pages | index_scan_count |
percent_complete
------+-------+---------------+-----------------+--------------------+-------------------+---------------------+------------------+------------------
9277 | 16551 | Scanning Heap | 163935 | 163935 |
27422 | 7 | 1 | 100
(1 row

percent_complete is 100 but Client_1 "VACUUM ANALYZE" do not response yet.

Of course, Client_1 is executing analyze after vacuum. But it seem to me
that this confuses users.
If percent_complete becomes 100 that row should be deleted quickly.

Regards,
Masanori Ohyama
NTT Open Source Software Center

2016年2月27日(土) 13:54 Vinayak Pokale <vinpokale@gmail.com>:

Show quoted text

Hello,

On Fri, Feb 26, 2016 at 6:19 PM, Amit Langote <
Langote_Amit_f8@lab.ntt.co.jp> wrote:

Hi Vinayak,

Thanks for updating the patch! A quick comment:

On 2016/02/26 17:28, pokurev@pm.nttdata.co.jp wrote:

CREATE VIEW pg_stat_vacuum_progress AS
SELECT S.s[1] as pid,
S.s[2] as relid,
CASE S.s[3]
WHEN 1 THEN 'Scanning Heap'
WHEN 2 THEN 'Vacuuming Index and Heap'
ELSE 'Unknown phase'
END,
....
FROM pg_stat_get_command_progress(PROGRESS_COMMAND_VACUUM) as S;

# The name of the function could be other than *_command_progress.

The name of function is updated as pg_stat_get_progress_info() and also

updated the function.

Updated the pg_stat_vacuum_progress view as suggested.

So, pg_stat_get_progress_info() now accepts a parameter to distinguish
different commands. I see the following in its definition:

+               /*  Report values for only those backends which are
running VACUUM
command */
+               if (cmdtype == COMMAND_LAZY_VACUUM)
+               {
+                       /*Progress can only be viewed by role member.*/
+                       if (has_privs_of_role(GetUserId(),
beentry->st_userid))
+                       {
+                               values[2] =
UInt32GetDatum(beentry->st_progress_param[0]);
+                               values[3] =
UInt32GetDatum(beentry->st_progress_param[1]);
+                               values[4] =
UInt32GetDatum(beentry->st_progress_param[2]);
+                               values[5] =
UInt32GetDatum(beentry->st_progress_param[3]);
+                               values[6] =
UInt32GetDatum(beentry->st_progress_param[4]);
+                               values[7] =
UInt32GetDatum(beentry->st_progress_param[5]);
+                               if (beentry->st_progress_param[1] != 0)
+                                       values[8] =
Float8GetDatum(beentry->st_progress_param[2] * 100 /
beentry->st_progress_param[1]);
+                               else
+                                       nulls[8] = true;
+                       }
+                       else
+                       {
+                               nulls[2] = true;
+                               nulls[3] = true;
+                               nulls[4] = true;
+                               nulls[5] = true;
+                               nulls[6] = true;
+                               nulls[7] = true;
+                               nulls[8] = true;
+                       }
+               }

How about doing this in a separate function which takes the command id as
parameter and returns an array of values and the number of values (per
command id). pg_stat_get_progress_info() then creates values[] and nulls[]
arrays from that and returns that as result set. It will be a cleaner
separation of activities, perhaps.

+1

Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#171Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: 大山真実 (#170)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello, thank for testing this.

At Sat, 27 Feb 2016 17:19:05 +0000, 大山真実 <oyama.masanori.1987@gmail.com> wrote in <CAJ_V8TmJG0Z8RPpN9DhTLEnfDxWEUoBQXQRQvQ7mbE47y3X9+Q@mail.gmail.com>

Hi!

I'm interesting this patch and tested it. I found two strange thing.

* Incorrect counting

Reproduce:
1. Client1 execute "VACUUM"
2. Client2 execute "VACUUM"
3. Client3 execute "SELECT * FROM pg_stat_vacuum_progress".
pid | relid | phase | total_heap_blks | current_heap_blkno |
total_index_pages | scanned_index_pages | index_scan_count |
percent_complete
------+-------+---------------+-----------------+--------------------+-------------------+---------------------+------------------+------------------
9267 | 16551 | Scanning Heap | 164151 | 316 |
27422 | 7 | 1 | 0
9764 | 16554 | Scanning Heap | 2 | 2 |
2 | 27422 | 1 | 100
(2 rows)

Client2 is waiting for Clinet1 "VACUUM" but percent_complete of Client2
"VACUUM" is 100.
* Not end VACUUM ANALYZE in spite of "percent_complete=100"

The inidividual record is telling about *one* relation now under
vacuuming (or just after the processing), not about all relations
to be vacuumed as a whole. It is the specification of this patch
for now. However it cannot tell how long the invoker should wait
for the vauum to end, it seems to be way difficult to calculate
statistics against the all relations to be processed.

Anyway other status messages such as "Waiting for XXXX" would be
necessary.

Client_1 execute "VACUUM ANALYZE", then Client_2 execute "SELECT * FROM
pg_stat_vacuum_progress".

pid | relid | phase | total_heap_blks | current_heap_blkno |
total_index_pages | scanned_index_pages | index_scan_count |
percent_complete
------+-------+---------------+-----------------+--------------------+-------------------+---------------------+------------------+------------------
9277 | 16551 | Scanning Heap | 163935 | 163935 |
27422 | 7 | 1 | 100
(1 row

percent_complete is 100 but Client_1 "VACUUM ANALYZE" do not response yet.

Of course, Client_1 is executing analyze after vacuum. But it seem to me
that this confuses users.
If percent_complete becomes 100 that row should be deleted quickly.

Maybe some works other than vacuuming pages is performing or
waiting a lock to be acquired. If it is a matter of progress, it
should be counted in the progress, but not for something like
waiting for a lock. It is a matter of status messages.

Regards,
Masanori Ohyama
NTT Open Source Software Center

2016年2月27日(土) 13:54 Vinayak Pokale <vinpokale@gmail.com>:

Hello,

On Fri, Feb 26, 2016 at 6:19 PM, Amit Langote <
Langote_Amit_f8@lab.ntt.co.jp> wrote:

Hi Vinayak,

Thanks for updating the patch! A quick comment:

On 2016/02/26 17:28, pokurev@pm.nttdata.co.jp wrote:

CREATE VIEW pg_stat_vacuum_progress AS
SELECT S.s[1] as pid,
S.s[2] as relid,
CASE S.s[3]
WHEN 1 THEN 'Scanning Heap'
WHEN 2 THEN 'Vacuuming Index and Heap'
ELSE 'Unknown phase'
END,
....
FROM pg_stat_get_command_progress(PROGRESS_COMMAND_VACUUM) as S;

# The name of the function could be other than *_command_progress.

The name of function is updated as pg_stat_get_progress_info() and also

updated the function.

Updated the pg_stat_vacuum_progress view as suggested.

So, pg_stat_get_progress_info() now accepts a parameter to distinguish
different commands. I see the following in its definition:

+               /*  Report values for only those backends which are
running VACUUM
command */
+               if (cmdtype == COMMAND_LAZY_VACUUM)
+               {
+                       /*Progress can only be viewed by role member.*/
+                       if (has_privs_of_role(GetUserId(),
beentry->st_userid))
+                       {
+                               values[2] =
UInt32GetDatum(beentry->st_progress_param[0]);
+                               values[3] =
UInt32GetDatum(beentry->st_progress_param[1]);
+                               values[4] =
UInt32GetDatum(beentry->st_progress_param[2]);
+                               values[5] =
UInt32GetDatum(beentry->st_progress_param[3]);
+                               values[6] =
UInt32GetDatum(beentry->st_progress_param[4]);
+                               values[7] =
UInt32GetDatum(beentry->st_progress_param[5]);
+                               if (beentry->st_progress_param[1] != 0)
+                                       values[8] =
Float8GetDatum(beentry->st_progress_param[2] * 100 /
beentry->st_progress_param[1]);
+                               else
+                                       nulls[8] = true;
+                       }
+                       else
+                       {
+                               nulls[2] = true;
+                               nulls[3] = true;
+                               nulls[4] = true;
+                               nulls[5] = true;
+                               nulls[6] = true;
+                               nulls[7] = true;
+                               nulls[8] = true;
+                       }
+               }

How about doing this in a separate function which takes the command id as
parameter and returns an array of values and the number of values (per
command id). pg_stat_get_progress_info() then creates values[] and nulls[]
arrays from that and returns that as result set. It will be a cleaner
separation of activities, perhaps.

+1

Accessing an element out of array safely be NULL and the caller
should know the number of elements, so I prefer one integer (or
bigint?) array to be returned. Or anyway the internal array has
finite number of elements, the function may return an array
exactly reflects the internal.

Last, I found one small bug mentioned above.

+        if (beentry->st_progress_param[1] != 0)
+          values[8] = Float8GetDatum(beentry->st_progress_param[2] * 100 / beentry->st_progress_param[1]);

Float8GetDatum(int/int) cannot have decimal places.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#172Robert Haas
robertmhaas@gmail.com
In reply to: Noname (#167)
Re: [PROPOSAL] VACUUM Progress Checker.

On Fri, Feb 26, 2016 at 3:28 AM, <pokurev@pm.nttdata.co.jp> wrote:

Thank you for your comments.
Please find attached patch addressing following comments.

As I might have written upthread, transferring the whole string
as a progress message is useless at least in this scenario. Since
they are a set of fixed messages, each of them can be represented
by an identifier, an integer number. I don't see a reason for
sending the whole of a string beyond a backend.

Agreed. I used following macros.
#define VACUUM_PHASE_SCAN_HEAP 1
#define VACUUM_PHASE_VACUUM_INDEX_HEAP 2

I guess num_index_scans could better be reported after all the indexes are
done, that is, after the for loop ends.

Agreed. I have corrected it.

CREATE VIEW pg_stat_vacuum_progress AS
SELECT S.s[1] as pid,
S.s[2] as relid,
CASE S.s[3]
WHEN 1 THEN 'Scanning Heap'
WHEN 2 THEN 'Vacuuming Index and Heap'
ELSE 'Unknown phase'
END,
....
FROM pg_stat_get_command_progress(PROGRESS_COMMAND_VACUUM) as S;

# The name of the function could be other than *_command_progress.

The name of function is updated as pg_stat_get_progress_info() and also updated the function.
Updated the pg_stat_vacuum_progress view as suggested.

I'm positive I've said this at least once before while reviewing this
patch, and I think more than once: we should be trying to build a
general progress-reporting facility here with vacuum as the first
user. Therefore, for example, pg_stat_get_progress_info's output
columns should have generic names, not names specific to VACUUM.
pg_stat_vacuum_progress can alias them to a vacuum-specific name. See
for example the relationship between pg_stats and pg_statistic.

I think VACUUM should have three phases, not two. lazy_vacuum_index()
and lazy_vacuum_heap() are lumped together right now, but I think they
shouldn't be.

Please create named constants for the first argument to
pgstat_report_progress_update_counter(), maybe with names like
PROGRESS_VACUUM_WHATEVER.

+               /* Update current block number of the relation */
+               pgstat_report_progress_update_counter(2, blkno + 1);

Why + 1?

I thought we had a plan to update the counter of scanned index pages
after each index page was vacuumed by the AM. Doing it only after
vacuuming the entire index is much less granular and generally less
useful. See /messages/by-id/56500356.4070101@BlueTreble.com

+               if (blkno == nblocks - 1 &&
vacrelstats->num_dead_tuples == 0 && nindexes != 0
+                       && vacrelstats->num_index_scans == 0)
+                       total_index_pages = 0;

I'm not sure what this is trying to do, perhaps because there is no
comment explaining it. Whatever the intent, I suspect that such a
complex test is likely to be fragile. Perhaps there is a better way?

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#173Amit Langote
amitlangote09@gmail.com
In reply to: Robert Haas (#172)
2 attachment(s)
Re: [PROPOSAL] VACUUM Progress Checker.

On Sat, Mar 5, 2016 at 7:11 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Fri, Feb 26, 2016 at 3:28 AM, <pokurev@pm.nttdata.co.jp> wrote:

Thank you for your comments.
Please find attached patch addressing following comments.

I'm positive I've said this at least once before while reviewing this
patch, and I think more than once: we should be trying to build a
general progress-reporting facility here with vacuum as the first
user. Therefore, for example, pg_stat_get_progress_info's output
columns should have generic names, not names specific to VACUUM.
pg_stat_vacuum_progress can alias them to a vacuum-specific name. See
for example the relationship between pg_stats and pg_statistic.

I think VACUUM should have three phases, not two. lazy_vacuum_index()
and lazy_vacuum_heap() are lumped together right now, but I think they
shouldn't be.

Please create named constants for the first argument to
pgstat_report_progress_update_counter(), maybe with names like
PROGRESS_VACUUM_WHATEVER.

+               /* Update current block number of the relation */
+               pgstat_report_progress_update_counter(2, blkno + 1);

Why + 1?

I thought we had a plan to update the counter of scanned index pages
after each index page was vacuumed by the AM. Doing it only after
vacuuming the entire index is much less granular and generally less
useful. See /messages/by-id/56500356.4070101@BlueTreble.com

+               if (blkno == nblocks - 1 &&
vacrelstats->num_dead_tuples == 0 && nindexes != 0
+                       && vacrelstats->num_index_scans == 0)
+                       total_index_pages = 0;

I'm not sure what this is trying to do, perhaps because there is no
comment explaining it. Whatever the intent, I suspect that such a
complex test is likely to be fragile. Perhaps there is a better way?

So, I took the Vinayak's latest patch and rewrote it a little while
maintaining the original idea but modifying code to some degree. Hope
original author(s) are okay with it. Vinayak, do see if the rewritten
patch is alright and improve it anyway you want.

I broke it into two:

0001-Provide-a-way-for-utility-commands-to-report-progres.patch
0002-Implement-progress-reporting-for-VACUUM-command.patch

The code review comments received recently (including mine) have been
incorporated.

However, I didn't implement the report-per-index-page-vacuumed bit but
should be easy to code once the details are finalized (questions like
whether it requires modifying any existing interfaces, etc).

Thanks,
Amit

Attachments:

0001-Provide-a-way-for-utility-commands-to-report-progres.patchapplication/octet-stream; name=0001-Provide-a-way-for-utility-commands-to-report-progres.patchDownload
From aeeae41c19280c33882d8ff7095e0284396817b0 Mon Sep 17 00:00:00 2001
From: Amit <amitlangote09@gmail.com>
Date: Sun, 28 Feb 2016 01:50:07 +0900
Subject: [PATCH 1/2] Provide a way for utility commands to report progress

Commands can update values in shared memory using:

  pgstat_progress_update_counter(counter_index, counter_value)

Up to 10 independent unsigned integer values can be published by commands.
In addition to those, a command should always report its BackendCommandType
and the OID of the object (often a table or index) it processes at the
beginning of the processing using:

  pgstat_progress_set_command(cmdtype)
  pgstat_progress_set_command_target(objid)

A view can be defined in system_views.sql that outputs the values returned
by pg_stat_get_progress_info(cmdtype), where 'cmdtype' is numeric value as
mentioned above.  Each such view has columns corresponding to the counters
published by respective commands.

There is a SQL-callable function pg_stat_reset_local_progress() which
when called, resets the progress information of the backend of the session
in which its called.  It is useful to erase progress info of commands
previously run in the session.
---
 doc/src/sgml/monitoring.sgml        |   8 +++
 src/backend/postmaster/pgstat.c     | 100 ++++++++++++++++++++++++++++++++++++
 src/backend/utils/adt/pgstatfuncs.c |  97 ++++++++++++++++++++++++++++++++++
 src/include/catalog/pg_proc.h       |   4 ++
 src/include/pgstat.h                |  26 ++++++++++
 5 files changed, 235 insertions(+)

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 85459d0..45d9ed7 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -1935,6 +1935,14 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
        zero (requires superuser privileges)
       </entry>
      </row>
+
+     <row>
+      <entry><literal><function>pg_stat_reset_local_progress</function>()</literal><indexterm><primary>pg_stat_reset_local_progress</primary></indexterm></entry>
+      <entry><type>void</type></entry>
+      <entry>
+       Reset command progress parameters of local backend
+      </entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index da768c6..900773b 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -2731,6 +2731,10 @@ pgstat_bestart(void)
 	beentry->st_clienthostname[NAMEDATALEN - 1] = '\0';
 	beentry->st_appname[NAMEDATALEN - 1] = '\0';
 	beentry->st_activity[pgstat_track_activity_query_size - 1] = '\0';
+	beentry->st_command = COMMAND_INVALID;
+	beentry->st_command_objid = InvalidOid;
+	MemSet(&beentry->st_progress_param, 0,
+								sizeof(beentry->st_progress_param));
 
 	pgstat_increment_changecount_after(beentry);
 
@@ -2851,6 +2855,102 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
 	pgstat_increment_changecount_after(beentry);
 }
 
+/*-----------
+ * pgstat_progress_update_param() -
+ *
+ * Update index'th member in st_progress_param[] of own backend entry.
+ *-----------
+ */
+void
+pgstat_progress_update_param(int index, uint32 val)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	if(!beentry)
+		return;
+
+	if (!pgstat_track_activities)
+		return;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_progress_param[index] = val;
+	pgstat_increment_changecount_after(beentry);
+}
+
+/*-----------
+ * pgstat_progress_set_command()-
+ *
+ * Set st_command in own backend entry.
+ *-----------
+ */
+void
+pgstat_progress_set_command(BackendCommandType cmdtype)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_command = cmdtype;
+	pgstat_increment_changecount_after(beentry);
+}
+
+/* ----------
+ *	pgstat_progress_set_command_target() -
+ *
+ *	Set st_command_objid in own backend entry.
+ * ----------
+ */
+void
+pgstat_progress_set_command_target(Oid objid)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	if (!beentry)
+		return;
+
+	if (!pgstat_track_activities)
+		return;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_command_objid = objid;
+	pgstat_increment_changecount_after(beentry);
+}
+
+/*-----------
+ * pgstat_progress_get_num_param()-
+ *
+ * Returns number of progress parameters stored in st_progress_param
+ * by cmdtype.
+ *-----------
+ */
+int
+pgstat_progress_get_num_param(BackendCommandType cmdtype)
+{
+	switch(cmdtype)
+	{
+		default:
+			return 0;
+	}
+
+	return 0;
+}
+
+/*--------
+ * pgstat_reset_local_progress()-
+ *
+ * Reset local backend's progress parameters. Setting st_command to
+ * COMMAND_INVALID will do.
+ *--------
+ */
+void
+pgstat_reset_local_progress()
+{
+	PgBackendStatus *beentry = MyBEEntry;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_command = COMMAND_INVALID;
+	pgstat_increment_changecount_after(beentry);
+}
+
 /* ----------
  * pgstat_report_appname() -
  *
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 1b22fcc..c9313ba 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -64,6 +64,8 @@ extern Datum pg_stat_get_backend_xact_start(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_start(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_client_addr(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_client_port(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_progress_info(PG_FUNCTION_ARGS);
+extern Datum pg_stat_reset_local_progress(PG_FUNCTION_ARGS);
 
 extern Datum pg_stat_get_db_numbackends(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_db_xact_commit(PG_FUNCTION_ARGS);
@@ -523,6 +525,101 @@ pg_stat_get_backend_idset(PG_FUNCTION_ARGS)
 		SRF_RETURN_DONE(funcctx);
 	}
 }
+/*
+ * Returns progress parameter values of backends running given command
+ */
+Datum
+pg_stat_get_progress_info(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_PROGRESS_COLS	N_PROGRESS_PARAM + 2
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int32		cmdtype = PG_GETARG_INT32(0);
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+
+	/* 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);
+
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		Datum		values[PG_STAT_GET_PROGRESS_COLS];
+		bool		nulls[PG_STAT_GET_PROGRESS_COLS];
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		bool		user_can_view;
+		int			i;
+		int			n_param;
+
+		MemSet(values, 0, sizeof(values));
+		MemSet(nulls, 0, sizeof(nulls));
+
+		local_beentry = pgstat_fetch_stat_local_beentry(curr_backend);
+
+		if (!local_beentry)
+			continue;
+
+		beentry = &local_beentry->backendStatus;
+
+		user_can_view = has_privs_of_role(GetUserId(),
+												   beentry->st_userid);
+
+		/* Report values for only those backends which are running command */
+		if (!beentry ||
+			!has_privs_of_role(GetUserId(), beentry->st_userid) ||
+			beentry->st_command != cmdtype)
+			continue;
+
+		/* Values available to all callers */
+		values[0] = Int32GetDatum(beentry->st_procpid);
+		values[1] = ObjectIdGetDatum(beentry->st_command_objid);
+
+		n_param = pgstat_progress_get_num_param(cmdtype);
+
+		for(i = 0; i < n_param; i++)
+			values[i+2] = UInt32GetDatum(beentry->st_progress_param[i]);
+
+		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+	}
+
+	/* clean up and return the tuplestore */
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+ 
+/* Reset local backend's command progress info */
+Datum
+pg_stat_reset_local_progress(PG_FUNCTION_ARGS)
+{
+	pgstat_reset_local_progress();
+
+	PG_RETURN_VOID();
+}
 
 /*
  * Returns activity of PG backends.
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 62b9125..3935302 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2710,6 +2710,8 @@ DATA(insert OID = 1936 (  pg_stat_get_backend_idset		PGNSP PGUID 12 1 100 0 0 f
 DESCR("statistics: currently active backend IDs");
 DATA(insert OID = 2022 (  pg_stat_get_activity			PGNSP PGUID 12 1 100 0 0 f f f f f t s r 1 0 2249 "23" "{23,26,23,26,25,25,25,16,1184,1184,1184,1184,869,25,23,28,28,16,25,25,23,16,25}" "{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{pid,datid,pid,usesysid,application_name,state,query,waiting,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,ssl,sslversion,sslcipher,sslbits,sslcompression,sslclientdn}" _null_ _null_ pg_stat_get_activity _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active backends");
+DATA(insert OID = 3318 (  pg_stat_get_progress_info           PGNSP PGUID 12 1 100 0 0 f f f f f t s r 1 0 2249 "23" "{23,23,26,23,23,23,23,23,23,23,23,23,23}" "{i,o,o,o,o,o,o,o,o,o,o,o,o}" "{cmdtype,pid,relid,param1,param2,param3,param4,param5,param6,param7,param8,param9,param10}" _null_ _null_ pg_stat_get_progress_info _null_ _null_ _null_ ));
+DESCR("statistics: information about progress of backends running maintenance command");
 DATA(insert OID = 3099 (  pg_stat_get_wal_senders	PGNSP PGUID 12 1 10 0 0 f f f f f t s r 0 0 2249 "" "{23,25,3220,3220,3220,3220,23,25}" "{o,o,o,o,o,o,o,o}" "{pid,state,sent_location,write_location,flush_location,replay_location,sync_priority,sync_state}" _null_ _null_ pg_stat_get_wal_senders _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active replication");
 DATA(insert OID = 3317 (  pg_stat_get_wal_receiver	PGNSP PGUID 12 1 0 0 0 f f f f f f s r 0 0 2249 "" "{23,25,3220,23,3220,23,1184,1184,3220,1184,25}" "{o,o,o,o,o,o,o,o,o,o,o}" "{pid,status,receive_start_lsn,receive_start_tli,received_lsn,received_tli,last_msg_send_time,last_msg_receipt_time,latest_end_lsn,latest_end_time,slot_name}" _null_ _null_ pg_stat_get_wal_receiver _null_ _null_ _null_ ));
@@ -2849,6 +2851,8 @@ DATA(insert OID = 3776 (  pg_stat_reset_single_table_counters	PGNSP PGUID 12 1 0
 DESCR("statistics: reset collected statistics for a single table or index in the current database");
 DATA(insert OID = 3777 (  pg_stat_reset_single_function_counters	PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 2278 "26" _null_ _null_ _null_ _null_ _null_	pg_stat_reset_single_function_counters _null_ _null_ _null_ ));
 DESCR("statistics: reset collected statistics for a single function in the current database");
+DATA(insert OID = 3319 (  pg_stat_reset_local_progress			PGNSP PGUID 12 1 0 0 0 f f f f f f v s 0 0 2278 "" _null_ _null_ _null_ _null_ _null_ pg_stat_reset_local_progress _null_ _null_ _null_ ));
+DESCR("statistics: reset progress information of the local backend");
 
 DATA(insert OID = 3163 (  pg_trigger_depth				PGNSP PGUID 12 1 0 0 0 f f f f t f s s 0 0 23 "" _null_ _null_ _null_ _null_ _null_ pg_trigger_depth _null_ _null_ _null_ ));
 DESCR("current trigger depth");
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 65e968e..8dca73a 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -696,6 +696,17 @@ typedef enum BackendState
 } BackendState;
 
 /* ----------
+ * Command type for progress reporting purposes
+ * ----------
+ */
+typedef enum BackendCommandType
+{
+	COMMAND_INVALID = 0,
+} BackendCommandType;
+
+#define N_PROGRESS_PARAM	10
+
+/* ----------
  * Shared-memory data structures
  * ----------
  */
@@ -776,6 +787,16 @@ typedef struct PgBackendStatus
 
 	/* current command string; MUST be null-terminated */
 	char	   *st_activity;
+
+	/*
+	 * Progress parameters of currently running command in the backend.
+	 * Different commands store different number of up to N_PROGRESS_PARAM
+	 * values in st_progress_param.  However, each command sets st_command
+	 * and st_command_objid at the beginning of command processing.
+	 */
+	BackendCommandType	st_command;
+	Oid					st_command_objid;
+	uint32				st_progress_param[N_PROGRESS_PARAM];
 } PgBackendStatus;
 
 /*
@@ -935,6 +956,11 @@ extern void pgstat_report_waiting(bool waiting);
 extern const char *pgstat_get_backend_current_activity(int pid, bool checkUser);
 extern const char *pgstat_get_crashed_backend_activity(int pid, char *buffer,
 									int buflen);
+extern void pgstat_progress_set_command(BackendCommandType cmdtype);
+extern void pgstat_progress_set_command_target(Oid objid);
+extern void pgstat_progress_update_param(int index, uint32 val);
+extern void pgstat_reset_local_progress(void);
+extern int	pgstat_progress_get_num_param(BackendCommandType cmdtype);
 
 extern PgStat_TableStatus *find_tabstat_entry(Oid rel_id);
 extern PgStat_BackendFunctionEntry *find_funcstat_entry(Oid func_id);
-- 
2.3.2 (Apple Git-55)

0002-Implement-progress-reporting-for-VACUUM-command.patchapplication/octet-stream; name=0002-Implement-progress-reporting-for-VACUUM-command.patchDownload
From 01996c6d65fccca2c015cb5130f32f8759c4c8af Mon Sep 17 00:00:00 2001
From: Amit <amitlangote09@gmail.com>
Date: Sat, 5 Mar 2016 15:58:11 +0900
Subject: [PATCH 2/2] Implement progress reporting for VACUUM command.

This basically utilizes the pgstat_report* API to report a handful of
paramters to indicate its progress.  lazy_vacuum_rel() and lazy_scan_heap()
have been altered to report command start, command target table, and the
following parameters: processing phase, number of heap blocks, number of
index blocks (all indexes), current block number in the main scan loop
(whenever changes), index blocks vacuumed (once per finished index vacuum),
and number of index vacuum passes (every time when all indexes are vacuumed).
Following processing phases are identified and reported whenever it changes:
'scanning heap', 'vacuuming indexes', 'vacuuming heap', 'cleanup', 'done'.
The last value is really a misnomer but maybe clearer when someone is staring
at the progress view being polled.

A view named pg_stat_vacuum_progress has been added that shows these values.
---
 doc/src/sgml/monitoring.sgml         | 114 +++++++++++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |  23 +++++++
 src/backend/commands/vacuumlazy.c    |  81 ++++++++++++++++++++++++-
 src/backend/postmaster/pgstat.c      |   2 +
 src/backend/utils/adt/pgstatfuncs.c  |   2 +-
 src/include/pgstat.h                 |   1 +
 src/test/regress/expected/rules.out  |  20 ++++++
 7 files changed, 241 insertions(+), 2 deletions(-)

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 45d9ed7..e4361ad 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -507,6 +507,13 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       yet included in <structname>pg_stat_user_functions</>).</entry>
      </row>
 
+     <row>
+      <entry><structname>pg_stat_vacuum_progress</><indexterm><primary>pg_stat_vacuum_progress</primary></indexterm></entry>
+      <entry>One row for each backend (including autovacuum worker processes) running
+      <command>VACUUM</>, showing current progress in terms of heap pages it
+      has finished processing.  Note that the backends running
+      <command>VACUUM FULL</> are not included.</entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -1822,6 +1829,113 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
    controls exactly which functions are tracked.
   </para>
 
+  <table id="pg-stat-vacuum-progress" xreflabel="pg_stat_vacuum_progress">
+   <title><structname>pg_stat_vacuum_progress</structname> View</title>
+   <tgroup cols="3">
+    <thead>
+    <row>
+      <entry>Column</entry>
+      <entry>Type</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+   <tbody>
+    <row>
+     <entry><structfield>pid</></entry>
+     <entry><type>integer</></entry>
+     <entry>Process ID of backend</entry>
+    </row>
+    <row>
+     <entry><structfield>relid</></entry>
+     <entry><type>oid</></entry>
+     <entry>OID of a table</entry>
+    </row>
+    <row>
+     <entry><structfield>processing_phase</></entry>
+     <entry><type>integer</></entry>
+     <entry>Current phase of vacuum.
+       Possible values are:
+       <itemizedlist>
+        <listitem>
+         <para>
+          <literal>scanning heap</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>vacuuming indexes</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>vacuuming heap</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>cleanup</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>done</>
+         </para>
+        </listitem>
+       </itemizedlist>
+     </entry>
+    </row>
+    <row>
+     <entry><structfield>total_heap_blks</></entry>
+     <entry><type>integer</></entry>
+     <entry>Total number of heap blocks in the table</entry>
+    </row>
+    <row>
+     <entry><structfield>current_heap_blkno</></entry>
+     <entry><type>integer</></entry>
+     <entry>Current heap block being processed</entry>
+    </row>
+    <row>
+     <entry><structfield>total_index_blks</></entry>
+     <entry><type>integer</></entry>
+     <entry>Total number of index blocks to be processed</entry>
+    </row>
+    <row>
+     <entry><structfield>index_blks_done</></entry>
+     <entry><type>integer</></entry>
+     <entry>Number of index blocks processed</entry>
+    </row>
+    <row>
+     <entry><structfield>index_scan_count</></entry>
+     <entry><type>integer</></entry>
+     <entry>Number of times index scans has been performed so far</entry>
+    </row>
+    <row>
+     <entry><structfield>percent_done</></entry>
+     <entry><type>numeric</></entry>
+     <entry>
+      Amount of work finished in percent in terms of table blocks processed
+     </entry>
+    </row>
+   </tbody>
+   </tgroup>
+  </table>
+
+  <para>
+   The <structname>pg_stat_vacuum_progress</structname> view will contain
+   one row for each backend (including autovacuum worker processes), showing
+   progress of <command>VACUUM</> running in it. Note that the backends
+   running <command>VACUUM FULL</> are not shown.
+  </para>
+
+  <para>
+   When interpreting the value of the <structfield>percent_done</> column, also
+   note the value of <structfield>processing_phase</>.  It's possible for the
+   former to be <literal>100.00</literal>, while the <command>VACUUM</> still
+   has not returned.  In that case, wait for the latter to turn to the value
+   <literal>done</literal>.
+  </para>
+
  </sect2>
 
  <sect2 id="monitoring-stats-functions">
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index abf9a70..64eaf80 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -971,3 +971,26 @@ RETURNS jsonb
 LANGUAGE INTERNAL
 STRICT IMMUTABLE
 AS 'jsonb_set';
+
+CREATE VIEW pg_stat_vacuum_progress AS
+    SELECT
+           S.pid AS pid,
+           S.relid AS relid,
+           CASE S.param1
+               WHEN 1 THEN 'Scanning Heap'
+               WHEN 2 THEN 'Vacuuming Index'
+               WHEN 3 THEN 'Vacuuming Heap'
+               WHEN 4 THEN 'Cleanup'
+               WHEN 5 THEN 'Done'
+               ELSE 'Unknown phase'
+           END AS processing_phase,
+           S.param2 AS total_heap_blks,
+           S.param3 AS current_heap_blkno,
+           S.param4 AS total_index_blks,
+           S.param5 AS index_blks_done,
+           S.param6 AS index_scan_count,
+           CASE S.param2
+			  WHEN 0 THEN 0.0
+			  ELSE ((S.param3 + 1)::numeric / S.param2 * 100)::numeric(5, 2)
+		   END AS percent_done
+   FROM pg_stat_get_progress_info(1) AS S;
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index 4f6f6e7..53cf4e0 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -97,6 +97,28 @@
  */
 #define SKIP_PAGES_THRESHOLD	((BlockNumber) 32)
 
+/*
+ * Follwing progress parameters for lazy vacuum are reported to pgstat
+ */
+#define PROG_PAR_VAC_PHASE_ID			0
+#define PROG_PAR_VAC_HEAP_BLKS			1
+#define PROG_PAR_VAC_CUR_HEAP_BLK		2
+#define PROG_PAR_VAC_IDX_BLKS			3
+#define PROG_PAR_VAC_IDX_BLKS_DONE		4
+#define PROG_PAR_VAC_N_IDX_SCAN			5
+
+/*
+ * Following distinct phases of lazy vacuum are identified.  Although #1, #2
+ * and #3 run in a cyclical manner due to possibly limited memory to work
+ * with, wherein #1 is periodically interrupted to run #2 followed by #3
+ * and back, until all the blocks of the relations have been covered by #1.
+ */
+#define LV_PHASE_SCAN_HEAP			1
+#define LV_PHASE_VACUUM_INDEX		2
+#define LV_PHASE_VACUUM_HEAP		3
+#define LV_PHASE_CLEANUP			4
+#define LV_PHASE_DONE				5
+
 typedef struct LVRelStats
 {
 	/* hasindex = true means two-pass strategy; false means one-pass */
@@ -195,6 +217,10 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params,
 
 	Assert(params != NULL);
 
+	/* initialize pgstat progress info */
+	pgstat_progress_set_command(COMMAND_LAZY_VACUUM);
+	pgstat_progress_set_command_target(RelationGetRelid(onerel));
+
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
 	{
@@ -270,6 +296,9 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params,
 	/* Vacuum the Free Space Map */
 	FreeSpaceMapVacuum(onerel);
 
+	/* We're done doing any heavy handling, so report */
+	pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_DONE);
+
 	/*
 	 * Update statistics in pg_class.
 	 *
@@ -433,7 +462,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			   Relation *Irel, int nindexes, bool scan_all)
 {
 	BlockNumber nblocks,
-				blkno;
+				blkno,
+				total_index_blks,
+			   *current_index_blks;
 	HeapTupleData tuple;
 	char	   *relname;
 	BlockNumber empty_pages,
@@ -474,6 +505,24 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	lazy_space_alloc(vacrelstats, nblocks);
 	frozen = palloc(sizeof(xl_heap_freeze_tuple) * MaxHeapTuplesPerPage);
 
+	/* about to begin heap scan */
+	pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_SCAN_HEAP);
+
+	/* total_heap_blks */
+	pgstat_progress_update_param(PROG_PAR_VAC_HEAP_BLKS, nblocks);
+
+	/* total_index_blks */
+	current_index_blks = (BlockNumber *) palloc(nindexes * sizeof(BlockNumber));
+	total_index_blks = 0;
+	for (i = 0; i < nindexes; i++)
+	{
+		BlockNumber		nblocks = RelationGetNumberOfBlocks(Irel[i]);
+
+		current_index_blks[i] = nblocks;
+		total_index_blks += nblocks;
+	}
+	pgstat_progress_update_param(PROG_PAR_VAC_IDX_BLKS, total_index_blks);
+
 	/*
 	 * We want to skip pages that don't require vacuuming according to the
 	 * visibility map, but only when we can skip at least SKIP_PAGES_THRESHOLD
@@ -581,6 +630,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 
 		vacuum_delay_point();
 
+		/* current_heap_blkno: 0..nblocks-1 */
+		pgstat_progress_update_param(PROG_PAR_VAC_CUR_HEAP_BLK, blkno);
+
 		/*
 		 * If we are close to overrunning the available space for dead-tuple
 		 * TIDs, pause and do a cycle of vacuuming before we tackle this page.
@@ -604,11 +656,22 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			vacuum_log_cleanup_info(onerel, vacrelstats);
 
 			/* Remove index entries */
+			pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_VACUUM_INDEX);
 			for (i = 0; i < nindexes; i++)
+			{
 				lazy_vacuum_index(Irel[i],
 								  &indstats[i],
 								  vacrelstats);
+
+				pgstat_progress_update_param(PROG_PAR_VAC_IDX_BLKS_DONE,
+											 current_index_blks[i]);
+			}
+
+			pgstat_progress_update_param(PROG_PAR_VAC_N_IDX_SCAN,
+										 vacrelstats->num_index_scans+1);
+
 			/* Remove tuples from heap */
+			pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_VACUUM_HEAP);
 			lazy_vacuum_heap(onerel, vacrelstats);
 
 			/*
@@ -618,6 +681,10 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			 */
 			vacrelstats->num_dead_tuples = 0;
 			vacrelstats->num_index_scans++;
+
+			/* go back to scanning the heap */
+			pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID,
+										 LV_PHASE_SCAN_HEAP);
 		}
 
 		/*
@@ -1120,17 +1187,29 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		/* Log cleanup info before we touch indexes */
 		vacuum_log_cleanup_info(onerel, vacrelstats);
 
+		pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_VACUUM_INDEX);
+
 		/* Remove index entries */
 		for (i = 0; i < nindexes; i++)
+		{
 			lazy_vacuum_index(Irel[i],
 							  &indstats[i],
 							  vacrelstats);
+
+			pgstat_progress_update_param(PROG_PAR_VAC_IDX_BLKS_DONE,
+										 current_index_blks[i]);
+		}
+		pgstat_progress_update_param(PROG_PAR_VAC_N_IDX_SCAN,
+									 vacrelstats->num_index_scans + 1);
+
 		/* Remove tuples from heap */
+		pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_VACUUM_HEAP);
 		lazy_vacuum_heap(onerel, vacrelstats);
 		vacrelstats->num_index_scans++;
 	}
 
 	/* Do post-vacuum cleanup and statistics update for each index */
+	pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_CLEANUP);
 	for (i = 0; i < nindexes; i++)
 		lazy_cleanup_index(Irel[i], indstats[i], vacrelstats);
 
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 900773b..d9109d8 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -2927,6 +2927,8 @@ pgstat_progress_get_num_param(BackendCommandType cmdtype)
 {
 	switch(cmdtype)
 	{
+		case COMMAND_LAZY_VACUUM:
+			return 6;
 		default:
 			return 0;
 	}
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c9313ba..1676713 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -611,7 +611,7 @@ pg_stat_get_progress_info(PG_FUNCTION_ARGS)
 
 	return (Datum) 0;
 }
- 
+
 /* Reset local backend's command progress info */
 Datum
 pg_stat_reset_local_progress(PG_FUNCTION_ARGS)
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 8dca73a..87d02fc 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -702,6 +702,7 @@ typedef enum BackendState
 typedef enum BackendCommandType
 {
 	COMMAND_INVALID = 0,
+	COMMAND_LAZY_VACUUM
 } BackendCommandType;
 
 #define N_PROGRESS_PARAM	10
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 81bc5c9..6a3183a 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1851,6 +1851,26 @@ pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
     pg_stat_all_tables.autoanalyze_count
    FROM pg_stat_all_tables
   WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
+pg_stat_vacuum_progress| SELECT s.pid,
+    s.relid,
+        CASE s.param1
+            WHEN 1 THEN 'Scanning Heap'::text
+            WHEN 2 THEN 'Vacuuming Index'::text
+            WHEN 3 THEN 'Vacuuming Heap'::text
+            WHEN 4 THEN 'Cleanup'::text
+            WHEN 5 THEN 'Done'::text
+            ELSE 'Unknown phase'::text
+        END AS processing_phase,
+    s.param2 AS total_heap_blks,
+    s.param3 AS current_heap_blkno,
+    s.param4 AS total_index_blks,
+    s.param5 AS index_blks_done,
+    s.param6 AS index_scan_count,
+        CASE s.param2
+            WHEN 0 THEN 0.0
+            ELSE (((((s.param3 + 1))::numeric / (s.param2)::numeric) * (100)::numeric))::numeric(5,2)
+        END AS percent_done
+   FROM pg_stat_get_progress_info(1) s(pid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10);
 pg_stat_wal_receiver| SELECT s.pid,
     s.status,
     s.receive_start_lsn,
-- 
2.3.2 (Apple Git-55)

#174Amit Langote
amitlangote09@gmail.com
In reply to: Amit Langote (#173)
2 attachment(s)
Re: [PROPOSAL] VACUUM Progress Checker.

On Sat, Mar 5, 2016 at 4:24 PM, Amit Langote <amitlangote09@gmail.com> wrote:

So, I took the Vinayak's latest patch and rewrote it a little

...

I broke it into two:

0001-Provide-a-way-for-utility-commands-to-report-progres.patch
0002-Implement-progress-reporting-for-VACUUM-command.patch

Oops, unamended commit messages in those patches are misleading. So,
please find attached corrected versions.

Thanks,
Amit

Attachments:

0001-Provide-a-way-for-utility-commands-to-report-progres-v2.patchapplication/octet-stream; name=0001-Provide-a-way-for-utility-commands-to-report-progres-v2.patchDownload
From a0488789e77a73ed5203fbe78bf47540d964c4fb Mon Sep 17 00:00:00 2001
From: Amit <amitlangote09@gmail.com>
Date: Sun, 28 Feb 2016 01:50:07 +0900
Subject: [PATCH 1/2] Provide a way for utility commands to report progress

Commands can update values in shared memory using:

  pgstat_progress_update_param(param_index, param_value)

Up to 10 independent unsigned integer values can be published by commands.
In addition to those, a command should always report its BackendCommandType
and the OID of the object (often a table or index) it processes at the
beginning of the processing using:

  pgstat_progress_set_command(cmdtype)
  pgstat_progress_set_command_target(objid)

A view can be defined in system_views.sql that outputs the values returned
by pg_stat_get_progress_info(cmdtype), where 'cmdtype' is numeric value as
mentioned above.  Each such view has columns corresponding to the counters
published by respective commands.

There is a SQL-callable function pg_stat_reset_local_progress() which
when called, resets the progress information of the backend of the session
in which its called.  It is useful to erase progress info of commands
previously run in the session.
---
 doc/src/sgml/monitoring.sgml        |   8 +++
 src/backend/postmaster/pgstat.c     | 100 ++++++++++++++++++++++++++++++++++++
 src/backend/utils/adt/pgstatfuncs.c |  97 ++++++++++++++++++++++++++++++++++
 src/include/catalog/pg_proc.h       |   4 ++
 src/include/pgstat.h                |  26 ++++++++++
 5 files changed, 235 insertions(+)

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 85459d0..45d9ed7 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -1935,6 +1935,14 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
        zero (requires superuser privileges)
       </entry>
      </row>
+
+     <row>
+      <entry><literal><function>pg_stat_reset_local_progress</function>()</literal><indexterm><primary>pg_stat_reset_local_progress</primary></indexterm></entry>
+      <entry><type>void</type></entry>
+      <entry>
+       Reset command progress parameters of local backend
+      </entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index da768c6..900773b 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -2731,6 +2731,10 @@ pgstat_bestart(void)
 	beentry->st_clienthostname[NAMEDATALEN - 1] = '\0';
 	beentry->st_appname[NAMEDATALEN - 1] = '\0';
 	beentry->st_activity[pgstat_track_activity_query_size - 1] = '\0';
+	beentry->st_command = COMMAND_INVALID;
+	beentry->st_command_objid = InvalidOid;
+	MemSet(&beentry->st_progress_param, 0,
+								sizeof(beentry->st_progress_param));
 
 	pgstat_increment_changecount_after(beentry);
 
@@ -2851,6 +2855,102 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
 	pgstat_increment_changecount_after(beentry);
 }
 
+/*-----------
+ * pgstat_progress_update_param() -
+ *
+ * Update index'th member in st_progress_param[] of own backend entry.
+ *-----------
+ */
+void
+pgstat_progress_update_param(int index, uint32 val)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	if(!beentry)
+		return;
+
+	if (!pgstat_track_activities)
+		return;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_progress_param[index] = val;
+	pgstat_increment_changecount_after(beentry);
+}
+
+/*-----------
+ * pgstat_progress_set_command()-
+ *
+ * Set st_command in own backend entry.
+ *-----------
+ */
+void
+pgstat_progress_set_command(BackendCommandType cmdtype)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_command = cmdtype;
+	pgstat_increment_changecount_after(beentry);
+}
+
+/* ----------
+ *	pgstat_progress_set_command_target() -
+ *
+ *	Set st_command_objid in own backend entry.
+ * ----------
+ */
+void
+pgstat_progress_set_command_target(Oid objid)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	if (!beentry)
+		return;
+
+	if (!pgstat_track_activities)
+		return;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_command_objid = objid;
+	pgstat_increment_changecount_after(beentry);
+}
+
+/*-----------
+ * pgstat_progress_get_num_param()-
+ *
+ * Returns number of progress parameters stored in st_progress_param
+ * by cmdtype.
+ *-----------
+ */
+int
+pgstat_progress_get_num_param(BackendCommandType cmdtype)
+{
+	switch(cmdtype)
+	{
+		default:
+			return 0;
+	}
+
+	return 0;
+}
+
+/*--------
+ * pgstat_reset_local_progress()-
+ *
+ * Reset local backend's progress parameters. Setting st_command to
+ * COMMAND_INVALID will do.
+ *--------
+ */
+void
+pgstat_reset_local_progress()
+{
+	PgBackendStatus *beentry = MyBEEntry;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_command = COMMAND_INVALID;
+	pgstat_increment_changecount_after(beentry);
+}
+
 /* ----------
  * pgstat_report_appname() -
  *
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 1b22fcc..c9313ba 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -64,6 +64,8 @@ extern Datum pg_stat_get_backend_xact_start(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_start(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_client_addr(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_client_port(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_progress_info(PG_FUNCTION_ARGS);
+extern Datum pg_stat_reset_local_progress(PG_FUNCTION_ARGS);
 
 extern Datum pg_stat_get_db_numbackends(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_db_xact_commit(PG_FUNCTION_ARGS);
@@ -523,6 +525,101 @@ pg_stat_get_backend_idset(PG_FUNCTION_ARGS)
 		SRF_RETURN_DONE(funcctx);
 	}
 }
+/*
+ * Returns progress parameter values of backends running given command
+ */
+Datum
+pg_stat_get_progress_info(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_PROGRESS_COLS	N_PROGRESS_PARAM + 2
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int32		cmdtype = PG_GETARG_INT32(0);
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+
+	/* 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);
+
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		Datum		values[PG_STAT_GET_PROGRESS_COLS];
+		bool		nulls[PG_STAT_GET_PROGRESS_COLS];
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+		bool		user_can_view;
+		int			i;
+		int			n_param;
+
+		MemSet(values, 0, sizeof(values));
+		MemSet(nulls, 0, sizeof(nulls));
+
+		local_beentry = pgstat_fetch_stat_local_beentry(curr_backend);
+
+		if (!local_beentry)
+			continue;
+
+		beentry = &local_beentry->backendStatus;
+
+		user_can_view = has_privs_of_role(GetUserId(),
+												   beentry->st_userid);
+
+		/* Report values for only those backends which are running command */
+		if (!beentry ||
+			!has_privs_of_role(GetUserId(), beentry->st_userid) ||
+			beentry->st_command != cmdtype)
+			continue;
+
+		/* Values available to all callers */
+		values[0] = Int32GetDatum(beentry->st_procpid);
+		values[1] = ObjectIdGetDatum(beentry->st_command_objid);
+
+		n_param = pgstat_progress_get_num_param(cmdtype);
+
+		for(i = 0; i < n_param; i++)
+			values[i+2] = UInt32GetDatum(beentry->st_progress_param[i]);
+
+		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+	}
+
+	/* clean up and return the tuplestore */
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+ 
+/* Reset local backend's command progress info */
+Datum
+pg_stat_reset_local_progress(PG_FUNCTION_ARGS)
+{
+	pgstat_reset_local_progress();
+
+	PG_RETURN_VOID();
+}
 
 /*
  * Returns activity of PG backends.
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 62b9125..3935302 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2710,6 +2710,8 @@ DATA(insert OID = 1936 (  pg_stat_get_backend_idset		PGNSP PGUID 12 1 100 0 0 f
 DESCR("statistics: currently active backend IDs");
 DATA(insert OID = 2022 (  pg_stat_get_activity			PGNSP PGUID 12 1 100 0 0 f f f f f t s r 1 0 2249 "23" "{23,26,23,26,25,25,25,16,1184,1184,1184,1184,869,25,23,28,28,16,25,25,23,16,25}" "{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{pid,datid,pid,usesysid,application_name,state,query,waiting,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,ssl,sslversion,sslcipher,sslbits,sslcompression,sslclientdn}" _null_ _null_ pg_stat_get_activity _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active backends");
+DATA(insert OID = 3318 (  pg_stat_get_progress_info           PGNSP PGUID 12 1 100 0 0 f f f f f t s r 1 0 2249 "23" "{23,23,26,23,23,23,23,23,23,23,23,23,23}" "{i,o,o,o,o,o,o,o,o,o,o,o,o}" "{cmdtype,pid,relid,param1,param2,param3,param4,param5,param6,param7,param8,param9,param10}" _null_ _null_ pg_stat_get_progress_info _null_ _null_ _null_ ));
+DESCR("statistics: information about progress of backends running maintenance command");
 DATA(insert OID = 3099 (  pg_stat_get_wal_senders	PGNSP PGUID 12 1 10 0 0 f f f f f t s r 0 0 2249 "" "{23,25,3220,3220,3220,3220,23,25}" "{o,o,o,o,o,o,o,o}" "{pid,state,sent_location,write_location,flush_location,replay_location,sync_priority,sync_state}" _null_ _null_ pg_stat_get_wal_senders _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active replication");
 DATA(insert OID = 3317 (  pg_stat_get_wal_receiver	PGNSP PGUID 12 1 0 0 0 f f f f f f s r 0 0 2249 "" "{23,25,3220,23,3220,23,1184,1184,3220,1184,25}" "{o,o,o,o,o,o,o,o,o,o,o}" "{pid,status,receive_start_lsn,receive_start_tli,received_lsn,received_tli,last_msg_send_time,last_msg_receipt_time,latest_end_lsn,latest_end_time,slot_name}" _null_ _null_ pg_stat_get_wal_receiver _null_ _null_ _null_ ));
@@ -2849,6 +2851,8 @@ DATA(insert OID = 3776 (  pg_stat_reset_single_table_counters	PGNSP PGUID 12 1 0
 DESCR("statistics: reset collected statistics for a single table or index in the current database");
 DATA(insert OID = 3777 (  pg_stat_reset_single_function_counters	PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 2278 "26" _null_ _null_ _null_ _null_ _null_	pg_stat_reset_single_function_counters _null_ _null_ _null_ ));
 DESCR("statistics: reset collected statistics for a single function in the current database");
+DATA(insert OID = 3319 (  pg_stat_reset_local_progress			PGNSP PGUID 12 1 0 0 0 f f f f f f v s 0 0 2278 "" _null_ _null_ _null_ _null_ _null_ pg_stat_reset_local_progress _null_ _null_ _null_ ));
+DESCR("statistics: reset progress information of the local backend");
 
 DATA(insert OID = 3163 (  pg_trigger_depth				PGNSP PGUID 12 1 0 0 0 f f f f t f s s 0 0 23 "" _null_ _null_ _null_ _null_ _null_ pg_trigger_depth _null_ _null_ _null_ ));
 DESCR("current trigger depth");
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 65e968e..8dca73a 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -696,6 +696,17 @@ typedef enum BackendState
 } BackendState;
 
 /* ----------
+ * Command type for progress reporting purposes
+ * ----------
+ */
+typedef enum BackendCommandType
+{
+	COMMAND_INVALID = 0,
+} BackendCommandType;
+
+#define N_PROGRESS_PARAM	10
+
+/* ----------
  * Shared-memory data structures
  * ----------
  */
@@ -776,6 +787,16 @@ typedef struct PgBackendStatus
 
 	/* current command string; MUST be null-terminated */
 	char	   *st_activity;
+
+	/*
+	 * Progress parameters of currently running command in the backend.
+	 * Different commands store different number of up to N_PROGRESS_PARAM
+	 * values in st_progress_param.  However, each command sets st_command
+	 * and st_command_objid at the beginning of command processing.
+	 */
+	BackendCommandType	st_command;
+	Oid					st_command_objid;
+	uint32				st_progress_param[N_PROGRESS_PARAM];
 } PgBackendStatus;
 
 /*
@@ -935,6 +956,11 @@ extern void pgstat_report_waiting(bool waiting);
 extern const char *pgstat_get_backend_current_activity(int pid, bool checkUser);
 extern const char *pgstat_get_crashed_backend_activity(int pid, char *buffer,
 									int buflen);
+extern void pgstat_progress_set_command(BackendCommandType cmdtype);
+extern void pgstat_progress_set_command_target(Oid objid);
+extern void pgstat_progress_update_param(int index, uint32 val);
+extern void pgstat_reset_local_progress(void);
+extern int	pgstat_progress_get_num_param(BackendCommandType cmdtype);
 
 extern PgStat_TableStatus *find_tabstat_entry(Oid rel_id);
 extern PgStat_BackendFunctionEntry *find_funcstat_entry(Oid func_id);
-- 
2.3.2 (Apple Git-55)

0002-Implement-progress-reporting-for-VACUUM-command-v2.patchapplication/octet-stream; name=0002-Implement-progress-reporting-for-VACUUM-command-v2.patchDownload
From 115b448d8a9e04a9248e44adeae58c18fccb7cf0 Mon Sep 17 00:00:00 2001
From: Amit <amitlangote09@gmail.com>
Date: Sat, 5 Mar 2016 15:58:11 +0900
Subject: [PATCH 2/2] Implement progress reporting for VACUUM command.

This basically utilizes the pgstat_progress* API to report a handful of
paramters to indicate its progress.  lazy_vacuum_rel() and lazy_scan_heap()
have been altered to report command start, command target table, and the
following parameters: processing phase, number of heap blocks, number of
index blocks (all indexes), current block number in the main scan loop
(whenever changes), index blocks vacuumed (once per finished index vacuum),
and number of index vacuum passes (every time when all indexes are vacuumed).
Following processing phases are identified and reported whenever it changes:
'scanning heap', 'vacuuming indexes', 'vacuuming heap', 'cleanup', 'done'.
The last value is really a misnomer but maybe clearer when someone is staring
at the progress view being polled.

A view named pg_stat_vacuum_progress has been added that shows these values.
---
 doc/src/sgml/monitoring.sgml         | 114 +++++++++++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |  23 +++++++
 src/backend/commands/vacuumlazy.c    |  81 ++++++++++++++++++++++++-
 src/backend/postmaster/pgstat.c      |   2 +
 src/backend/utils/adt/pgstatfuncs.c  |   2 +-
 src/include/pgstat.h                 |   1 +
 src/test/regress/expected/rules.out  |  20 ++++++
 7 files changed, 241 insertions(+), 2 deletions(-)

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 45d9ed7..e4361ad 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -507,6 +507,13 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       yet included in <structname>pg_stat_user_functions</>).</entry>
      </row>
 
+     <row>
+      <entry><structname>pg_stat_vacuum_progress</><indexterm><primary>pg_stat_vacuum_progress</primary></indexterm></entry>
+      <entry>One row for each backend (including autovacuum worker processes) running
+      <command>VACUUM</>, showing current progress in terms of heap pages it
+      has finished processing.  Note that the backends running
+      <command>VACUUM FULL</> are not included.</entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -1822,6 +1829,113 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
    controls exactly which functions are tracked.
   </para>
 
+  <table id="pg-stat-vacuum-progress" xreflabel="pg_stat_vacuum_progress">
+   <title><structname>pg_stat_vacuum_progress</structname> View</title>
+   <tgroup cols="3">
+    <thead>
+    <row>
+      <entry>Column</entry>
+      <entry>Type</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+   <tbody>
+    <row>
+     <entry><structfield>pid</></entry>
+     <entry><type>integer</></entry>
+     <entry>Process ID of backend</entry>
+    </row>
+    <row>
+     <entry><structfield>relid</></entry>
+     <entry><type>oid</></entry>
+     <entry>OID of a table</entry>
+    </row>
+    <row>
+     <entry><structfield>processing_phase</></entry>
+     <entry><type>integer</></entry>
+     <entry>Current phase of vacuum.
+       Possible values are:
+       <itemizedlist>
+        <listitem>
+         <para>
+          <literal>scanning heap</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>vacuuming indexes</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>vacuuming heap</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>cleanup</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>done</>
+         </para>
+        </listitem>
+       </itemizedlist>
+     </entry>
+    </row>
+    <row>
+     <entry><structfield>total_heap_blks</></entry>
+     <entry><type>integer</></entry>
+     <entry>Total number of heap blocks in the table</entry>
+    </row>
+    <row>
+     <entry><structfield>current_heap_blkno</></entry>
+     <entry><type>integer</></entry>
+     <entry>Current heap block being processed</entry>
+    </row>
+    <row>
+     <entry><structfield>total_index_blks</></entry>
+     <entry><type>integer</></entry>
+     <entry>Total number of index blocks to be processed</entry>
+    </row>
+    <row>
+     <entry><structfield>index_blks_done</></entry>
+     <entry><type>integer</></entry>
+     <entry>Number of index blocks processed</entry>
+    </row>
+    <row>
+     <entry><structfield>index_scan_count</></entry>
+     <entry><type>integer</></entry>
+     <entry>Number of times index scans has been performed so far</entry>
+    </row>
+    <row>
+     <entry><structfield>percent_done</></entry>
+     <entry><type>numeric</></entry>
+     <entry>
+      Amount of work finished in percent in terms of table blocks processed
+     </entry>
+    </row>
+   </tbody>
+   </tgroup>
+  </table>
+
+  <para>
+   The <structname>pg_stat_vacuum_progress</structname> view will contain
+   one row for each backend (including autovacuum worker processes), showing
+   progress of <command>VACUUM</> running in it. Note that the backends
+   running <command>VACUUM FULL</> are not shown.
+  </para>
+
+  <para>
+   When interpreting the value of the <structfield>percent_done</> column, also
+   note the value of <structfield>processing_phase</>.  It's possible for the
+   former to be <literal>100.00</literal>, while the <command>VACUUM</> still
+   has not returned.  In that case, wait for the latter to turn to the value
+   <literal>done</literal>.
+  </para>
+
  </sect2>
 
  <sect2 id="monitoring-stats-functions">
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index abf9a70..64eaf80 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -971,3 +971,26 @@ RETURNS jsonb
 LANGUAGE INTERNAL
 STRICT IMMUTABLE
 AS 'jsonb_set';
+
+CREATE VIEW pg_stat_vacuum_progress AS
+    SELECT
+           S.pid AS pid,
+           S.relid AS relid,
+           CASE S.param1
+               WHEN 1 THEN 'Scanning Heap'
+               WHEN 2 THEN 'Vacuuming Index'
+               WHEN 3 THEN 'Vacuuming Heap'
+               WHEN 4 THEN 'Cleanup'
+               WHEN 5 THEN 'Done'
+               ELSE 'Unknown phase'
+           END AS processing_phase,
+           S.param2 AS total_heap_blks,
+           S.param3 AS current_heap_blkno,
+           S.param4 AS total_index_blks,
+           S.param5 AS index_blks_done,
+           S.param6 AS index_scan_count,
+           CASE S.param2
+			  WHEN 0 THEN 0.0
+			  ELSE ((S.param3 + 1)::numeric / S.param2 * 100)::numeric(5, 2)
+		   END AS percent_done
+   FROM pg_stat_get_progress_info(1) AS S;
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index 4f6f6e7..53cf4e0 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -97,6 +97,28 @@
  */
 #define SKIP_PAGES_THRESHOLD	((BlockNumber) 32)
 
+/*
+ * Follwing progress parameters for lazy vacuum are reported to pgstat
+ */
+#define PROG_PAR_VAC_PHASE_ID			0
+#define PROG_PAR_VAC_HEAP_BLKS			1
+#define PROG_PAR_VAC_CUR_HEAP_BLK		2
+#define PROG_PAR_VAC_IDX_BLKS			3
+#define PROG_PAR_VAC_IDX_BLKS_DONE		4
+#define PROG_PAR_VAC_N_IDX_SCAN			5
+
+/*
+ * Following distinct phases of lazy vacuum are identified.  Although #1, #2
+ * and #3 run in a cyclical manner due to possibly limited memory to work
+ * with, wherein #1 is periodically interrupted to run #2 followed by #3
+ * and back, until all the blocks of the relations have been covered by #1.
+ */
+#define LV_PHASE_SCAN_HEAP			1
+#define LV_PHASE_VACUUM_INDEX		2
+#define LV_PHASE_VACUUM_HEAP		3
+#define LV_PHASE_CLEANUP			4
+#define LV_PHASE_DONE				5
+
 typedef struct LVRelStats
 {
 	/* hasindex = true means two-pass strategy; false means one-pass */
@@ -195,6 +217,10 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params,
 
 	Assert(params != NULL);
 
+	/* initialize pgstat progress info */
+	pgstat_progress_set_command(COMMAND_LAZY_VACUUM);
+	pgstat_progress_set_command_target(RelationGetRelid(onerel));
+
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
 	{
@@ -270,6 +296,9 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params,
 	/* Vacuum the Free Space Map */
 	FreeSpaceMapVacuum(onerel);
 
+	/* We're done doing any heavy handling, so report */
+	pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_DONE);
+
 	/*
 	 * Update statistics in pg_class.
 	 *
@@ -433,7 +462,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			   Relation *Irel, int nindexes, bool scan_all)
 {
 	BlockNumber nblocks,
-				blkno;
+				blkno,
+				total_index_blks,
+			   *current_index_blks;
 	HeapTupleData tuple;
 	char	   *relname;
 	BlockNumber empty_pages,
@@ -474,6 +505,24 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	lazy_space_alloc(vacrelstats, nblocks);
 	frozen = palloc(sizeof(xl_heap_freeze_tuple) * MaxHeapTuplesPerPage);
 
+	/* about to begin heap scan */
+	pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_SCAN_HEAP);
+
+	/* total_heap_blks */
+	pgstat_progress_update_param(PROG_PAR_VAC_HEAP_BLKS, nblocks);
+
+	/* total_index_blks */
+	current_index_blks = (BlockNumber *) palloc(nindexes * sizeof(BlockNumber));
+	total_index_blks = 0;
+	for (i = 0; i < nindexes; i++)
+	{
+		BlockNumber		nblocks = RelationGetNumberOfBlocks(Irel[i]);
+
+		current_index_blks[i] = nblocks;
+		total_index_blks += nblocks;
+	}
+	pgstat_progress_update_param(PROG_PAR_VAC_IDX_BLKS, total_index_blks);
+
 	/*
 	 * We want to skip pages that don't require vacuuming according to the
 	 * visibility map, but only when we can skip at least SKIP_PAGES_THRESHOLD
@@ -581,6 +630,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 
 		vacuum_delay_point();
 
+		/* current_heap_blkno: 0..nblocks-1 */
+		pgstat_progress_update_param(PROG_PAR_VAC_CUR_HEAP_BLK, blkno);
+
 		/*
 		 * If we are close to overrunning the available space for dead-tuple
 		 * TIDs, pause and do a cycle of vacuuming before we tackle this page.
@@ -604,11 +656,22 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			vacuum_log_cleanup_info(onerel, vacrelstats);
 
 			/* Remove index entries */
+			pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_VACUUM_INDEX);
 			for (i = 0; i < nindexes; i++)
+			{
 				lazy_vacuum_index(Irel[i],
 								  &indstats[i],
 								  vacrelstats);
+
+				pgstat_progress_update_param(PROG_PAR_VAC_IDX_BLKS_DONE,
+											 current_index_blks[i]);
+			}
+
+			pgstat_progress_update_param(PROG_PAR_VAC_N_IDX_SCAN,
+										 vacrelstats->num_index_scans+1);
+
 			/* Remove tuples from heap */
+			pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_VACUUM_HEAP);
 			lazy_vacuum_heap(onerel, vacrelstats);
 
 			/*
@@ -618,6 +681,10 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			 */
 			vacrelstats->num_dead_tuples = 0;
 			vacrelstats->num_index_scans++;
+
+			/* go back to scanning the heap */
+			pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID,
+										 LV_PHASE_SCAN_HEAP);
 		}
 
 		/*
@@ -1120,17 +1187,29 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		/* Log cleanup info before we touch indexes */
 		vacuum_log_cleanup_info(onerel, vacrelstats);
 
+		pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_VACUUM_INDEX);
+
 		/* Remove index entries */
 		for (i = 0; i < nindexes; i++)
+		{
 			lazy_vacuum_index(Irel[i],
 							  &indstats[i],
 							  vacrelstats);
+
+			pgstat_progress_update_param(PROG_PAR_VAC_IDX_BLKS_DONE,
+										 current_index_blks[i]);
+		}
+		pgstat_progress_update_param(PROG_PAR_VAC_N_IDX_SCAN,
+									 vacrelstats->num_index_scans + 1);
+
 		/* Remove tuples from heap */
+		pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_VACUUM_HEAP);
 		lazy_vacuum_heap(onerel, vacrelstats);
 		vacrelstats->num_index_scans++;
 	}
 
 	/* Do post-vacuum cleanup and statistics update for each index */
+	pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_CLEANUP);
 	for (i = 0; i < nindexes; i++)
 		lazy_cleanup_index(Irel[i], indstats[i], vacrelstats);
 
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 900773b..d9109d8 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -2927,6 +2927,8 @@ pgstat_progress_get_num_param(BackendCommandType cmdtype)
 {
 	switch(cmdtype)
 	{
+		case COMMAND_LAZY_VACUUM:
+			return 6;
 		default:
 			return 0;
 	}
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c9313ba..1676713 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -611,7 +611,7 @@ pg_stat_get_progress_info(PG_FUNCTION_ARGS)
 
 	return (Datum) 0;
 }
- 
+
 /* Reset local backend's command progress info */
 Datum
 pg_stat_reset_local_progress(PG_FUNCTION_ARGS)
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 8dca73a..87d02fc 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -702,6 +702,7 @@ typedef enum BackendState
 typedef enum BackendCommandType
 {
 	COMMAND_INVALID = 0,
+	COMMAND_LAZY_VACUUM
 } BackendCommandType;
 
 #define N_PROGRESS_PARAM	10
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 81bc5c9..6a3183a 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1851,6 +1851,26 @@ pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
     pg_stat_all_tables.autoanalyze_count
    FROM pg_stat_all_tables
   WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
+pg_stat_vacuum_progress| SELECT s.pid,
+    s.relid,
+        CASE s.param1
+            WHEN 1 THEN 'Scanning Heap'::text
+            WHEN 2 THEN 'Vacuuming Index'::text
+            WHEN 3 THEN 'Vacuuming Heap'::text
+            WHEN 4 THEN 'Cleanup'::text
+            WHEN 5 THEN 'Done'::text
+            ELSE 'Unknown phase'::text
+        END AS processing_phase,
+    s.param2 AS total_heap_blks,
+    s.param3 AS current_heap_blkno,
+    s.param4 AS total_index_blks,
+    s.param5 AS index_blks_done,
+    s.param6 AS index_scan_count,
+        CASE s.param2
+            WHEN 0 THEN 0.0
+            ELSE (((((s.param3 + 1))::numeric / (s.param2)::numeric) * (100)::numeric))::numeric(5,2)
+        END AS percent_done
+   FROM pg_stat_get_progress_info(1) s(pid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10);
 pg_stat_wal_receiver| SELECT s.pid,
     s.status,
     s.receive_start_lsn,
-- 
2.3.2 (Apple Git-55)

#175Noname
pokurev@pm.nttdata.co.jp
In reply to: Amit Langote (#174)
Re: [PROPOSAL] VACUUM Progress Checker.

Hi Amit,

Thank you for updating the patch. I am testing it and I will try to improve it.

Regards,
Vinayak

-----Original Message-----
From: Amit Langote [mailto:amitlangote09@gmail.com]
Sent: Saturday, March 05, 2016 4:41 PM
To: Robert Haas <robertmhaas@gmail.com>
Cc: SPS ポクレ ヴィナヤック(三技術) <pokurev@pm.nttdata.co.jp>;
Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Kyotaro HORIGUCHI
<horiguchi.kyotaro@lab.ntt.co.jp>; pgsql-hackers@postgresql.org; SPS 坂野
昌平(三技術) <bannos@nttdata.co.jp>
Subject: Re: [HACKERS] [PROPOSAL] VACUUM Progress Checker.

On Sat, Mar 5, 2016 at 4:24 PM, Amit Langote <amitlangote09@gmail.com>
wrote:

So, I took the Vinayak's latest patch and rewrote it a little

...

I broke it into two:

0001-Provide-a-way-for-utility-commands-to-report-progres.patch
0002-Implement-progress-reporting-for-VACUUM-command.patch

Oops, unamended commit messages in those patches are misleading. So,
please find attached corrected versions.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#176Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Amit Langote (#174)
Re: [PROPOSAL] VACUUM Progress Checker.

Hi, Thank you for the patch.

At Sat, 5 Mar 2016 16:41:29 +0900, Amit Langote <amitlangote09@gmail.com> wrote in <CA+HiwqHTeuqWMc+ktneGqFdJMRXD=syncgU0914TVXaahOF56g@mail.gmail.com>

On Sat, Mar 5, 2016 at 4:24 PM, Amit Langote <amitlangote09@gmail.com> wrote:

So, I took the Vinayak's latest patch and rewrote it a little

...

I broke it into two:

0001-Provide-a-way-for-utility-commands-to-report-progres.patch
0002-Implement-progress-reporting-for-VACUUM-command.patch

Oops, unamended commit messages in those patches are misleading. So,
please find attached corrected versions.

The 0001-P.. adds the following interface functions.

+extern void pgstat_progress_set_command(BackendCommandType cmdtype);
+extern void pgstat_progress_set_command_target(Oid objid);
+extern void pgstat_progress_update_param(int index, uint32 val);
+extern void pgstat_reset_local_progress(void);
+extern int	pgstat_progress_get_num_param(BackendCommandType cmdtype);

I don't like to treat the target object id differently from other
parameters. It could not be needed at all, or could be needed two
or more in contrast. Although oids are not guaranteed to fit
uint32, we have already stored BlockNumber there.

# I think that integer arrays might be needed to be passed as a
# parameter, but it would be the another issue.

pg_stat_get_progress_info returns a tuple with 10 integer columns
(plus an object id). The reason why I suggested use of an integer
array is that it allows the API to serve arbitrary number of
parmeters without a modification of API, and array indexes are
coloreless than any concrete names. Howerver I don't stick to
that if we agree that it is ok to have fixed number of paremters.

pgstat_progress_get_num_param looks not good in the aspect of
genericity. I'd like to define it as an integer array by idexed
by the command type if it is needed. However it seems to me to be
enough that pg_stat_get_progress_info always returns 10 integers
regardless of what the numbers are for. The user sql function,
pg_stat_vacuum_progress as the first user, knows how many numbers
should be read for its work. It reads zeroes safely even if it
reads more than what the producer side offered (unless it tries
to divide something with it).

What do you think about this?

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#177Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Kyotaro HORIGUCHI (#176)
2 attachment(s)
Re: [PROPOSAL] VACUUM Progress Checker.

Horiguchi-san,

Thanks a lot for taking a look!

On 2016/03/07 13:02, Kyotaro HORIGUCHI wrote:

At Sat, 5 Mar 2016 16:41:29 +0900, Amit Langote wrote:

On Sat, Mar 5, 2016 at 4:24 PM, Amit Langote <amitlangote09@gmail.com> wrote:

So, I took the Vinayak's latest patch and rewrote it a little

...

I broke it into two:

0001-Provide-a-way-for-utility-commands-to-report-progres.patch
0002-Implement-progress-reporting-for-VACUUM-command.patch

Oops, unamended commit messages in those patches are misleading. So,
please find attached corrected versions.

The 0001-P.. adds the following interface functions.

+extern void pgstat_progress_set_command(BackendCommandType cmdtype);
+extern void pgstat_progress_set_command_target(Oid objid);
+extern void pgstat_progress_update_param(int index, uint32 val);
+extern void pgstat_reset_local_progress(void);
+extern int	pgstat_progress_get_num_param(BackendCommandType cmdtype);

I don't like to treat the target object id differently from other
parameters. It could not be needed at all, or could be needed two
or more in contrast. Although oids are not guaranteed to fit
uint32, we have already stored BlockNumber there.

I thought giving cmdtype and objid each its own slot would make things a
little bit clearer than stuffing them into st_progress_param[0] and
st_progress_param[1], respectively. Is that what you are suggesting?
Although as I've don, a separate field st_command_objid may be a bit too much.

If they are not special fields, I think we don't need special interface
functions *set_command() and *set_command_target(). But I am still
inclined toward keeping the former.

# I think that integer arrays might be needed to be passed as a
# parameter, but it would be the another issue.

Didn't really think about it. Maybe we should consider a scenario that
would require it.

pg_stat_get_progress_info returns a tuple with 10 integer columns
(plus an object id). The reason why I suggested use of an integer
array is that it allows the API to serve arbitrary number of
parmeters without a modification of API, and array indexes are
coloreless than any concrete names. Howerver I don't stick to
that if we agree that it is ok to have fixed number of paremters.

I think the fixed number of parameters in the form of a fixed-size array
is because st_progress_param[] is part of a shared memory structure as
discussed before. Although such interface has been roughly modeled on how
pg_statistic catalog and pg_stats view or get_attstatsslot() function
work, shared memory structures take the place of the catalog, so there are
some restrictions (fixed size array being one).

Regarding index into st_progress_param[], pgstat.c/pgstatfuncs.c should
not bother what it is. As exemplified in patch 0002, individual index
numbers can be defined as macros by individual command modules (suggested
by Robert recently) with certain convention for readability such as the
following in lazyvacuum.c:

#define PROG_PAR_VAC_RELID 0
#define PROG_PAR_VAC_PHASE_ID 1
#define PROG_PAR_VAC_HEAP_BLKS 2
#define PROG_PAR_VAC_CUR_HEAP_BLK 3
... so on.

Then, to report a changed parameter:

pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_SCAN_HEAP);
...
pgstat_progress_update_param(PROG_PAR_VAC_CUR_HEAP_BLK, blkno);

by the way, following is proargnames[] for pg_stat_get_progress_info():

cmdtype integer,
OUT pid integer,
OUT param1 integer,
OUT param2 integer,
...
OUT param10 integer

So, it is a responsibility of a command specific progress view definition
that it interprets values of param1..param10 appropriately. In fact, the
implementer of the progress reporting for a command determines what goes
into which slot of st_progress_param[], to begin with.

pgstat_progress_get_num_param looks not good in the aspect of
genericity. I'd like to define it as an integer array by idexed
by the command type if it is needed. However it seems to me to be
enough that pg_stat_get_progress_info always returns 10 integers
regardless of what the numbers are for. The user sql function,
pg_stat_vacuum_progress as the first user, knows how many numbers
should be read for its work. It reads zeroes safely even if it
reads more than what the producer side offered (unless it tries
to divide something with it).

Thinking a bit, perhaps we don't need num_param(cmdtpye) function or array
at all as you seem to suggest. It serves no useful purpose now that I see
it. pg_stat_get_progress_info() should simply copy
st_progress_param[0...PG_STAT_GET_PROGRESS_COLS-1] to the result and view
definer knows what's what.

Attached updated patches which incorporate above mentioned changes. If
Vinayak has something else in mind about anything, he can weigh in.

Thanks,
Amit

Attachments:

0001-Provide-a-way-for-utility-commands-to-report-progres-v3.patchtext/x-diff; name=0001-Provide-a-way-for-utility-commands-to-report-progres-v3.patchDownload
From 52a398f5104cd50f8bfcc4fd1fbb5bb102eddbf5 Mon Sep 17 00:00:00 2001
From: Amit <amitlangote09@gmail.com>
Date: Sun, 28 Feb 2016 01:50:07 +0900
Subject: [PATCH 1/2] Provide a way for utility commands to report progress

Commands can update values in shared memory using:

  pgstat_progress_update_param(param_index, param_value)

Up to 10 independent unsigned integer values can be published by commands.
In addition to those, a command should always report its BackendCommandType
at the beginning of the processing using:

  pgstat_progress_set_command(cmdtype)

A view can be defined in system_views.sql that outputs the values returned
by pg_stat_get_progress_info(cmdtype), where 'cmdtype' is numeric value as
mentioned above.  Each such view has columns corresponding to the counters
published by respective commands.

There is a SQL-callable function pg_stat_reset_local_progress() which
when called, resets the progress information of the backend of the session
in which its called.  It is useful to erase progress info of commands
previously run in the session.
---
 doc/src/sgml/monitoring.sgml        |    8 +++
 src/backend/postmaster/pgstat.c     |   60 ++++++++++++++++++++++
 src/backend/utils/adt/pgstatfuncs.c |   93 +++++++++++++++++++++++++++++++++++
 src/include/catalog/pg_proc.h       |    4 ++
 src/include/pgstat.h                |   23 +++++++++
 5 files changed, 188 insertions(+), 0 deletions(-)

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 85459d0..45d9ed7 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -1935,6 +1935,14 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
        zero (requires superuser privileges)
       </entry>
      </row>
+
+     <row>
+      <entry><literal><function>pg_stat_reset_local_progress</function>()</literal><indexterm><primary>pg_stat_reset_local_progress</primary></indexterm></entry>
+      <entry><type>void</type></entry>
+      <entry>
+       Reset command progress parameters of local backend
+      </entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index da768c6..5e238cc 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -2731,6 +2731,8 @@ pgstat_bestart(void)
 	beentry->st_clienthostname[NAMEDATALEN - 1] = '\0';
 	beentry->st_appname[NAMEDATALEN - 1] = '\0';
 	beentry->st_activity[pgstat_track_activity_query_size - 1] = '\0';
+	beentry->st_command = COMMAND_INVALID;
+	MemSet(&beentry->st_progress_param, 0, sizeof(beentry->st_progress_param));
 
 	pgstat_increment_changecount_after(beentry);
 
@@ -2851,6 +2853,64 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
 	pgstat_increment_changecount_after(beentry);
 }
 
+/*-----------
+ * pgstat_progress_update_param() -
+ *
+ * Update index'th member in st_progress_param[] of own backend entry.
+ *-----------
+ */
+void
+pgstat_progress_update_param(int index, uint32 val)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	if(!beentry)
+		return;
+
+	if (!pgstat_track_activities)
+		return;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_progress_param[index] = val;
+	pgstat_increment_changecount_after(beentry);
+}
+
+/*-----------
+ * pgstat_progress_start_command()-
+ *
+ * Set st_command in own backend entry.  Also, zero-initialize
+ * st_progress_param array.
+ *-----------
+ */
+void
+pgstat_progress_start_command(BackendCommandType cmdtype)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_command = cmdtype;
+	MemSet(&beentry->st_progress_param, 0,
+		   sizeof(beentry->st_progress_param));
+	pgstat_increment_changecount_after(beentry);
+}
+
+/*--------
+ * pgstat_reset_local_progress()-
+ *
+ * Reset local backend's progress parameters. Setting st_command to
+ * COMMAND_INVALID will do.
+ *--------
+ */
+void
+pgstat_reset_local_progress()
+{
+	PgBackendStatus *beentry = MyBEEntry;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_command = COMMAND_INVALID;
+	pgstat_increment_changecount_after(beentry);
+}
+
 /* ----------
  * pgstat_report_appname() -
  *
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 1b22fcc..a73ae8d 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -64,6 +64,8 @@ extern Datum pg_stat_get_backend_xact_start(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_start(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_client_addr(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_client_port(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_progress_info(PG_FUNCTION_ARGS);
+extern Datum pg_stat_reset_local_progress(PG_FUNCTION_ARGS);
 
 extern Datum pg_stat_get_db_numbackends(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_db_xact_commit(PG_FUNCTION_ARGS);
@@ -524,6 +526,97 @@ pg_stat_get_backend_idset(PG_FUNCTION_ARGS)
 	}
 }
 
+#define PG_STAT_GET_PROGRESS_COLS	N_PROGRESS_PARAM + 1
+
+/*
+ * Returns progress parameter values of backends running a given command
+ */
+Datum
+pg_stat_get_progress_info(PG_FUNCTION_ARGS)
+{
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int32		cmdtype = PG_GETARG_INT32(0);
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+
+	/* 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);
+
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		LocalPgBackendStatus   *local_beentry;
+		PgBackendStatus		   *beentry;
+		Datum		values[PG_STAT_GET_PROGRESS_COLS];
+		bool		nulls[PG_STAT_GET_PROGRESS_COLS];
+		int			i;
+
+		MemSet(values, 0, sizeof(values));
+		MemSet(nulls, 0, sizeof(nulls));
+
+		local_beentry = pgstat_fetch_stat_local_beentry(curr_backend);
+
+		if (!local_beentry)
+			continue;
+
+		beentry = &local_beentry->backendStatus;
+
+		/*
+		 * Report values for only those backends which are running the given
+		 * command.  XXX - privilege check is maybe dubious.
+		 */
+		if (!beentry ||
+			beentry->st_command != cmdtype ||
+			!has_privs_of_role(GetUserId(), beentry->st_userid))
+			continue;
+
+		/* Values available to all callers */
+		values[0] = Int32GetDatum(beentry->st_procpid);
+		for(i = 0; i < N_PROGRESS_PARAM; i++)
+			values[i+1] = UInt32GetDatum(beentry->st_progress_param[i]);
+
+		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+	}
+
+	/* clean up and return the tuplestore */
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+/* Reset local backend's command progress info */
+Datum
+pg_stat_reset_local_progress(PG_FUNCTION_ARGS)
+{
+	pgstat_reset_local_progress();
+
+	PG_RETURN_VOID();
+}
+
 /*
  * Returns activity of PG backends.
  */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index cbbb883..67dc31d 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2710,6 +2710,8 @@ DATA(insert OID = 1936 (  pg_stat_get_backend_idset		PGNSP PGUID 12 1 100 0 0 f
 DESCR("statistics: currently active backend IDs");
 DATA(insert OID = 2022 (  pg_stat_get_activity			PGNSP PGUID 12 1 100 0 0 f f f f f t s r 1 0 2249 "23" "{23,26,23,26,25,25,25,16,1184,1184,1184,1184,869,25,23,28,28,16,25,25,23,16,25}" "{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{pid,datid,pid,usesysid,application_name,state,query,waiting,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,ssl,sslversion,sslcipher,sslbits,sslcompression,sslclientdn}" _null_ _null_ pg_stat_get_activity _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active backends");
+DATA(insert OID = 3318 (  pg_stat_get_progress_info           PGNSP PGUID 12 1 100 0 0 f f f f f t s r 1 0 2249 "23" "{23,23,23,23,23,23,23,23,23,23,23,23}" "{i,o,o,o,o,o,o,o,o,o,o,o}" "{cmdtype,pid,param1,param2,param3,param4,param5,param6,param7,param8,param9,param10}" _null_ _null_ pg_stat_get_progress_info _null_ _null_ _null_ ));
+DESCR("statistics: information about progress of backends running maintenance command");
 DATA(insert OID = 3099 (  pg_stat_get_wal_senders	PGNSP PGUID 12 1 10 0 0 f f f f f t s r 0 0 2249 "" "{23,25,3220,3220,3220,3220,23,25}" "{o,o,o,o,o,o,o,o}" "{pid,state,sent_location,write_location,flush_location,replay_location,sync_priority,sync_state}" _null_ _null_ pg_stat_get_wal_senders _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active replication");
 DATA(insert OID = 3317 (  pg_stat_get_wal_receiver	PGNSP PGUID 12 1 0 0 0 f f f f f f s r 0 0 2249 "" "{23,25,3220,23,3220,23,1184,1184,3220,1184,25}" "{o,o,o,o,o,o,o,o,o,o,o}" "{pid,status,receive_start_lsn,receive_start_tli,received_lsn,received_tli,last_msg_send_time,last_msg_receipt_time,latest_end_lsn,latest_end_time,slot_name}" _null_ _null_ pg_stat_get_wal_receiver _null_ _null_ _null_ ));
@@ -2849,6 +2851,8 @@ DATA(insert OID = 3776 (  pg_stat_reset_single_table_counters	PGNSP PGUID 12 1 0
 DESCR("statistics: reset collected statistics for a single table or index in the current database");
 DATA(insert OID = 3777 (  pg_stat_reset_single_function_counters	PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 2278 "26" _null_ _null_ _null_ _null_ _null_	pg_stat_reset_single_function_counters _null_ _null_ _null_ ));
 DESCR("statistics: reset collected statistics for a single function in the current database");
+DATA(insert OID = 3319 (  pg_stat_reset_local_progress			PGNSP PGUID 12 1 0 0 0 f f f f f f v s 0 0 2278 "" _null_ _null_ _null_ _null_ _null_ pg_stat_reset_local_progress _null_ _null_ _null_ ));
+DESCR("statistics: reset progress information of the local backend");
 
 DATA(insert OID = 3163 (  pg_trigger_depth				PGNSP PGUID 12 1 0 0 0 f f f f t f s s 0 0 23 "" _null_ _null_ _null_ _null_ _null_ pg_trigger_depth _null_ _null_ _null_ ));
 DESCR("current trigger depth");
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 65e968e..17fae7d 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -696,6 +696,17 @@ typedef enum BackendState
 } BackendState;
 
 /* ----------
+ * Command type for progress reporting purposes
+ * ----------
+ */
+typedef enum BackendCommandType
+{
+	COMMAND_INVALID = 0,
+} BackendCommandType;
+
+#define N_PROGRESS_PARAM	10
+
+/* ----------
  * Shared-memory data structures
  * ----------
  */
@@ -776,6 +787,15 @@ typedef struct PgBackendStatus
 
 	/* current command string; MUST be null-terminated */
 	char	   *st_activity;
+
+	/*
+	 * Progress parameters of currently running utility command in the
+	 * backend.  Different commands store different number of up to
+	 * N_PROGRESS_PARAM values in st_progress_param.  However, each command
+	 * must set st_command at the beginning of command processing.
+	 */
+	BackendCommandType	st_command;
+	uint32				st_progress_param[N_PROGRESS_PARAM];
 } PgBackendStatus;
 
 /*
@@ -935,6 +955,9 @@ extern void pgstat_report_waiting(bool waiting);
 extern const char *pgstat_get_backend_current_activity(int pid, bool checkUser);
 extern const char *pgstat_get_crashed_backend_activity(int pid, char *buffer,
 									int buflen);
+extern void pgstat_progress_start_command(BackendCommandType cmdtype);
+extern void pgstat_progress_update_param(int index, uint32 val);
+extern void pgstat_reset_local_progress(void);
 
 extern PgStat_TableStatus *find_tabstat_entry(Oid rel_id);
 extern PgStat_BackendFunctionEntry *find_funcstat_entry(Oid func_id);
-- 
1.7.1

0002-Implement-progress-reporting-for-VACUUM-command-v3.patchtext/x-diff; name=0002-Implement-progress-reporting-for-VACUUM-command-v3.patchDownload
From a3d80139d89019820455aea196ade7c814317ece Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 7 Mar 2016 14:38:34 +0900
Subject: [PATCH 2/2] Implement progress reporting for VACUUM command.

This basically utilizes the pgstat_progress* API to report a handful of
paramters to indicate its progress.  lazy_vacuum_rel() and lazy_scan_heap()
have been altered to report command start, command target table, and the
following parameters: processing phase, number of heap blocks, number of
index blocks (all indexes), current block number in the main scan loop
(whenever changes), index blocks vacuumed (once per finished index vacuum),
and number of index vacuum passes (every time when all indexes are vacuumed).
Following processing phases are identified and reported whenever it changes:
'scanning heap', 'vacuuming indexes', 'vacuuming heap', 'cleanup', 'done'.
The last value is really a misnomer but maybe clearer when someone is staring
at the progress view being polled.

A view named pg_stat_vacuum_progress has been added that shows these values.
---
 doc/src/sgml/monitoring.sgml         |  114 ++++++++++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   23 +++++++
 src/backend/commands/vacuumlazy.c    |   82 ++++++++++++++++++++++++-
 src/include/pgstat.h                 |    1 +
 src/test/regress/expected/rules.out  |   20 ++++++
 5 files changed, 239 insertions(+), 1 deletions(-)

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 45d9ed7..e4361ad 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -507,6 +507,13 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       yet included in <structname>pg_stat_user_functions</>).</entry>
      </row>
 
+     <row>
+      <entry><structname>pg_stat_vacuum_progress</><indexterm><primary>pg_stat_vacuum_progress</primary></indexterm></entry>
+      <entry>One row for each backend (including autovacuum worker processes) running
+      <command>VACUUM</>, showing current progress in terms of heap pages it
+      has finished processing.  Note that the backends running
+      <command>VACUUM FULL</> are not included.</entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -1822,6 +1829,113 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
    controls exactly which functions are tracked.
   </para>
 
+  <table id="pg-stat-vacuum-progress" xreflabel="pg_stat_vacuum_progress">
+   <title><structname>pg_stat_vacuum_progress</structname> View</title>
+   <tgroup cols="3">
+    <thead>
+    <row>
+      <entry>Column</entry>
+      <entry>Type</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+   <tbody>
+    <row>
+     <entry><structfield>pid</></entry>
+     <entry><type>integer</></entry>
+     <entry>Process ID of backend</entry>
+    </row>
+    <row>
+     <entry><structfield>relid</></entry>
+     <entry><type>oid</></entry>
+     <entry>OID of a table</entry>
+    </row>
+    <row>
+     <entry><structfield>processing_phase</></entry>
+     <entry><type>integer</></entry>
+     <entry>Current phase of vacuum.
+       Possible values are:
+       <itemizedlist>
+        <listitem>
+         <para>
+          <literal>scanning heap</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>vacuuming indexes</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>vacuuming heap</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>cleanup</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>done</>
+         </para>
+        </listitem>
+       </itemizedlist>
+     </entry>
+    </row>
+    <row>
+     <entry><structfield>total_heap_blks</></entry>
+     <entry><type>integer</></entry>
+     <entry>Total number of heap blocks in the table</entry>
+    </row>
+    <row>
+     <entry><structfield>current_heap_blkno</></entry>
+     <entry><type>integer</></entry>
+     <entry>Current heap block being processed</entry>
+    </row>
+    <row>
+     <entry><structfield>total_index_blks</></entry>
+     <entry><type>integer</></entry>
+     <entry>Total number of index blocks to be processed</entry>
+    </row>
+    <row>
+     <entry><structfield>index_blks_done</></entry>
+     <entry><type>integer</></entry>
+     <entry>Number of index blocks processed</entry>
+    </row>
+    <row>
+     <entry><structfield>index_scan_count</></entry>
+     <entry><type>integer</></entry>
+     <entry>Number of times index scans has been performed so far</entry>
+    </row>
+    <row>
+     <entry><structfield>percent_done</></entry>
+     <entry><type>numeric</></entry>
+     <entry>
+      Amount of work finished in percent in terms of table blocks processed
+     </entry>
+    </row>
+   </tbody>
+   </tgroup>
+  </table>
+
+  <para>
+   The <structname>pg_stat_vacuum_progress</structname> view will contain
+   one row for each backend (including autovacuum worker processes), showing
+   progress of <command>VACUUM</> running in it. Note that the backends
+   running <command>VACUUM FULL</> are not shown.
+  </para>
+
+  <para>
+   When interpreting the value of the <structfield>percent_done</> column, also
+   note the value of <structfield>processing_phase</>.  It's possible for the
+   former to be <literal>100.00</literal>, while the <command>VACUUM</> still
+   has not returned.  In that case, wait for the latter to turn to the value
+   <literal>done</literal>.
+  </para>
+
  </sect2>
 
  <sect2 id="monitoring-stats-functions">
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index abf9a70..156d379 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -971,3 +971,26 @@ RETURNS jsonb
 LANGUAGE INTERNAL
 STRICT IMMUTABLE
 AS 'jsonb_set';
+
+CREATE VIEW pg_stat_vacuum_progress AS
+    SELECT
+           S.pid AS pid,
+           S.param1 AS relid,
+           CASE S.param2
+               WHEN 1 THEN 'Scanning Heap'
+               WHEN 2 THEN 'Vacuuming Index'
+               WHEN 3 THEN 'Vacuuming Heap'
+               WHEN 4 THEN 'Cleanup'
+               WHEN 5 THEN 'Done'
+               ELSE 'Unknown'
+           END AS processing_phase,
+           S.param3 AS total_heap_blks,
+           S.param4 AS current_heap_blkno,
+           S.param5 AS total_index_blks,
+           S.param6 AS index_blks_done,
+           S.param7 AS index_scan_count,
+           CASE S.param3
+			  WHEN 0 THEN 100::numeric(5, 2)
+			  ELSE ((S.param4 + 1)::numeric / S.param3 * 100)::numeric(5, 2)
+		   END AS percent_done
+   FROM pg_stat_get_progress_info(1) AS S;
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index 8f7b248..73ccd53 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -97,6 +97,29 @@
  */
 #define SKIP_PAGES_THRESHOLD	((BlockNumber) 32)
 
+/*
+ * Follwing progress parameters for lazy vacuum are reported to pgstat
+ */
+#define PROG_PAR_VAC_RELID				0
+#define PROG_PAR_VAC_PHASE_ID			1
+#define PROG_PAR_VAC_HEAP_BLKS			2
+#define PROG_PAR_VAC_CUR_HEAP_BLK		3
+#define PROG_PAR_VAC_IDX_BLKS			4
+#define PROG_PAR_VAC_IDX_BLKS_DONE		5
+#define PROG_PAR_VAC_N_IDX_SCAN			6
+
+/*
+ * Following distinct phases of lazy vacuum are identified.  Although #1, #2
+ * and #3 run in a cyclical manner due to possibly limited memory to work
+ * with, wherein #1 is periodically interrupted to run #2 followed by #3
+ * and back, until all the blocks of the relations have been covered by #1.
+ */
+#define LV_PHASE_SCAN_HEAP			1
+#define LV_PHASE_VACUUM_INDEX		2
+#define LV_PHASE_VACUUM_HEAP		3
+#define LV_PHASE_CLEANUP			4
+#define LV_PHASE_DONE				5
+
 typedef struct LVRelStats
 {
 	/* hasindex = true means two-pass strategy; false means one-pass */
@@ -195,6 +218,10 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params,
 
 	Assert(params != NULL);
 
+	/* initialize pgstat progress info */
+	pgstat_progress_start_command(COMMAND_LAZY_VACUUM);
+	pgstat_progress_update_param(PROG_PAR_VAC_RELID, RelationGetRelid(onerel));
+
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
 	{
@@ -270,6 +297,9 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params,
 	/* Vacuum the Free Space Map */
 	FreeSpaceMapVacuum(onerel);
 
+	/* We're done doing any heavy handling, so report */
+	pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_DONE);
+
 	/*
 	 * Update statistics in pg_class.
 	 *
@@ -433,7 +463,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			   Relation *Irel, int nindexes, bool scan_all)
 {
 	BlockNumber nblocks,
-				blkno;
+				blkno,
+				total_index_blks,
+			   *current_index_blks;
 	HeapTupleData tuple;
 	char	   *relname;
 	BlockNumber empty_pages,
@@ -474,6 +506,24 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	lazy_space_alloc(vacrelstats, nblocks);
 	frozen = palloc(sizeof(xl_heap_freeze_tuple) * MaxHeapTuplesPerPage);
 
+	/* about to begin heap scan */
+	pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_SCAN_HEAP);
+
+	/* total_heap_blks */
+	pgstat_progress_update_param(PROG_PAR_VAC_HEAP_BLKS, nblocks);
+
+	/* total_index_blks */
+	current_index_blks = (BlockNumber *) palloc(nindexes * sizeof(BlockNumber));
+	total_index_blks = 0;
+	for (i = 0; i < nindexes; i++)
+	{
+		BlockNumber		nblocks = RelationGetNumberOfBlocks(Irel[i]);
+
+		current_index_blks[i] = nblocks;
+		total_index_blks += nblocks;
+	}
+	pgstat_progress_update_param(PROG_PAR_VAC_IDX_BLKS, total_index_blks);
+
 	/*
 	 * We want to skip pages that don't require vacuuming according to the
 	 * visibility map, but only when we can skip at least SKIP_PAGES_THRESHOLD
@@ -581,6 +631,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 
 		vacuum_delay_point();
 
+		/* current_heap_blkno: 0..nblocks-1 */
+		pgstat_progress_update_param(PROG_PAR_VAC_CUR_HEAP_BLK, blkno);
+
 		/*
 		 * If we are close to overrunning the available space for dead-tuple
 		 * TIDs, pause and do a cycle of vacuuming before we tackle this page.
@@ -604,11 +657,22 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			vacuum_log_cleanup_info(onerel, vacrelstats);
 
 			/* Remove index entries */
+			pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_VACUUM_INDEX);
 			for (i = 0; i < nindexes; i++)
+			{
 				lazy_vacuum_index(Irel[i],
 								  &indstats[i],
 								  vacrelstats);
+
+				pgstat_progress_update_param(PROG_PAR_VAC_IDX_BLKS_DONE,
+											 current_index_blks[i]);
+			}
+
+			pgstat_progress_update_param(PROG_PAR_VAC_N_IDX_SCAN,
+										 vacrelstats->num_index_scans+1);
+
 			/* Remove tuples from heap */
+			pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_VACUUM_HEAP);
 			lazy_vacuum_heap(onerel, vacrelstats);
 
 			/*
@@ -618,6 +682,10 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			 */
 			vacrelstats->num_dead_tuples = 0;
 			vacrelstats->num_index_scans++;
+
+			/* go back to scanning the heap */
+			pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID,
+										 LV_PHASE_SCAN_HEAP);
 		}
 
 		/*
@@ -1153,17 +1221,29 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		/* Log cleanup info before we touch indexes */
 		vacuum_log_cleanup_info(onerel, vacrelstats);
 
+		pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_VACUUM_INDEX);
+
 		/* Remove index entries */
 		for (i = 0; i < nindexes; i++)
+		{
 			lazy_vacuum_index(Irel[i],
 							  &indstats[i],
 							  vacrelstats);
+
+			pgstat_progress_update_param(PROG_PAR_VAC_IDX_BLKS_DONE,
+										 current_index_blks[i]);
+		}
+		pgstat_progress_update_param(PROG_PAR_VAC_N_IDX_SCAN,
+									 vacrelstats->num_index_scans + 1);
+
 		/* Remove tuples from heap */
+		pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_VACUUM_HEAP);
 		lazy_vacuum_heap(onerel, vacrelstats);
 		vacrelstats->num_index_scans++;
 	}
 
 	/* Do post-vacuum cleanup and statistics update for each index */
+	pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_CLEANUP);
 	for (i = 0; i < nindexes; i++)
 		lazy_cleanup_index(Irel[i], indstats[i], vacrelstats);
 
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 17fae7d..eca451c 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -702,6 +702,7 @@ typedef enum BackendState
 typedef enum BackendCommandType
 {
 	COMMAND_INVALID = 0,
+	COMMAND_LAZY_VACUUM
 } BackendCommandType;
 
 #define N_PROGRESS_PARAM	10
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 81bc5c9..1d14272 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1851,6 +1851,26 @@ pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
     pg_stat_all_tables.autoanalyze_count
    FROM pg_stat_all_tables
   WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
+pg_stat_vacuum_progress| SELECT s.pid,
+    s.param1 AS relid,
+        CASE s.param2
+            WHEN 1 THEN 'Scanning Heap'::text
+            WHEN 2 THEN 'Vacuuming Index'::text
+            WHEN 3 THEN 'Vacuuming Heap'::text
+            WHEN 4 THEN 'Cleanup'::text
+            WHEN 5 THEN 'Done'::text
+            ELSE 'Unknown'::text
+        END AS processing_phase,
+    s.param3 AS total_heap_blks,
+    s.param4 AS current_heap_blkno,
+    s.param5 AS total_index_blks,
+    s.param6 AS index_blks_done,
+    s.param7 AS index_scan_count,
+        CASE s.param2
+            WHEN 0 THEN 0.0
+            ELSE (((((s.param3 + 1))::numeric / (s.param2)::numeric) * (100)::numeric))::numeric(5,2)
+        END AS percent_done
+   FROM pg_stat_get_progress_info(1) s(pid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10);
 pg_stat_wal_receiver| SELECT s.pid,
     s.status,
     s.receive_start_lsn,
-- 
1.7.1

#178Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Amit Langote (#177)
Re: [PROPOSAL] VACUUM Progress Checker.

Hi, Amit.

At Mon, 7 Mar 2016 16:16:30 +0900, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote in <56DD2ACE.5050208@lab.ntt.co.jp>

Horiguchi-san,

Thanks a lot for taking a look!

On 2016/03/07 13:02, Kyotaro HORIGUCHI wrote:

At Sat, 5 Mar 2016 16:41:29 +0900, Amit Langote wrote:

On Sat, Mar 5, 2016 at 4:24 PM, Amit Langote <amitlangote09@gmail.com> wrote:

So, I took the Vinayak's latest patch and rewrote it a little

...

I broke it into two:

0001-Provide-a-way-for-utility-commands-to-report-progres.patch
0002-Implement-progress-reporting-for-VACUUM-command.patch

Oops, unamended commit messages in those patches are misleading. So,
please find attached corrected versions.

The 0001-P.. adds the following interface functions.

+extern void pgstat_progress_set_command(BackendCommandType cmdtype);
+extern void pgstat_progress_set_command_target(Oid objid);
+extern void pgstat_progress_update_param(int index, uint32 val);
+extern void pgstat_reset_local_progress(void);
+extern int	pgstat_progress_get_num_param(BackendCommandType cmdtype);

I don't like to treat the target object id differently from other
parameters. It could not be needed at all, or could be needed two
or more in contrast. Although oids are not guaranteed to fit
uint32, we have already stored BlockNumber there.

I thought giving cmdtype and objid each its own slot would make things a
little bit clearer than stuffing them into st_progress_param[0] and
st_progress_param[1], respectively. Is that what you are suggesting?
Although as I've don, a separate field st_command_objid may be a bit too much.

I mentioned only of object id as you seem to take me. The command
type is essential unlike the target object ids. It is needed by
all statistics views of this kind to filter required backends.

If they are not special fields, I think we don't need special interface
functions *set_command() and *set_command_target(). But I am still
inclined toward keeping the former.

# I think that integer arrays might be needed to be passed as a
# parameter, but it would be the another issue.

Didn't really think about it. Maybe we should consider a scenario that
would require it.

Imagine to provide a statictics of a vacuum commnad as a
whole. It will vacuum several relations at once so the view could
be like the following.

select * from pg_stat_vacuum_command;
- [ Record 1 ]
worker_pid : 3243
command : vacuum full
rels_scheduled : {16387, 16390, 16393}
rels_finished : {16384}
status : Processing 16384, awiting for a lock.
..

This needs arrays if we want this but it would be another issue
as I said.

pg_stat_get_progress_info returns a tuple with 10 integer columns
(plus an object id). The reason why I suggested use of an integer
array is that it allows the API to serve arbitrary number of
parmeters without a modification of API, and array indexes are
coloreless than any concrete names. Howerver I don't stick to
that if we agree that it is ok to have fixed number of paremters.

I think the fixed number of parameters in the form of a fixed-size array
is because st_progress_param[] is part of a shared memory structure as
discussed before. Although such interface has been roughly modeled on how
pg_statistic catalog and pg_stats view or get_attstatsslot() function
work, shared memory structures take the place of the catalog, so there are
some restrictions (fixed size array being one).

It depends on how easy we take it to widen the parameter slots in
shared memory:p Anyway I don't stick that since it doesn't make
a siginificant difference.

Regarding index into st_progress_param[], pgstat.c/pgstatfuncs.c should
not bother what it is. As exemplified in patch 0002, individual index
numbers can be defined as macros by individual command modules (suggested
by Robert recently) with certain convention for readability such as the
following in lazyvacuum.c:

#define PROG_PAR_VAC_RELID 0
#define PROG_PAR_VAC_PHASE_ID 1
#define PROG_PAR_VAC_HEAP_BLKS 2
#define PROG_PAR_VAC_CUR_HEAP_BLK 3
... so on.

Then, to report a changed parameter:

pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_SCAN_HEAP);
...
pgstat_progress_update_param(PROG_PAR_VAC_CUR_HEAP_BLK, blkno);

Yeah, it seems fine for me.

by the way, following is proargnames[] for pg_stat_get_progress_info():

cmdtype integer,
OUT pid integer,
OUT param1 integer,
OUT param2 integer,
...
OUT param10 integer

So, it is a responsibility of a command specific progress view definition
that it interprets values of param1..param10 appropriately. In fact, the
implementer of the progress reporting for a command determines what goes
into which slot of st_progress_param[], to begin with.

It seems quite fine, too.

pgstat_progress_get_num_param looks not good in the aspect of
genericity. I'd like to define it as an integer array by idexed
by the command type if it is needed. However it seems to me to be
enough that pg_stat_get_progress_info always returns 10 integers
regardless of what the numbers are for. The user sql function,
pg_stat_vacuum_progress as the first user, knows how many numbers
should be read for its work. It reads zeroes safely even if it
reads more than what the producer side offered (unless it tries
to divide something with it).

Thinking a bit, perhaps we don't need num_param(cmdtpye) function or array
at all as you seem to suggest. It serves no useful purpose now that I see

^^; Sorry for the cryptic description..

it. pg_stat_get_progress_info() should simply copy
st_progress_param[0...PG_STAT_GET_PROGRESS_COLS-1] to the result and view
definer knows what's what.

Attached updated patches which incorporate above mentioned changes. If
Vinayak has something else in mind about anything, he can weigh in.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#179Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Kyotaro HORIGUCHI (#178)
Re: [PROPOSAL] VACUUM Progress Checker.

Horiguchi-san,

Thanks for a quick reply, :)

On 2016/03/07 18:18, Kyotaro HORIGUCHI wrote:

At Mon, 7 Mar 2016 16:16:30 +0900, Amit Langote wrote:

On 2016/03/07 13:02, Kyotaro HORIGUCHI wrote:

The 0001-P.. adds the following interface functions.

I don't like to treat the target object id differently from other
parameters. It could not be needed at all, or could be needed two
or more in contrast. Although oids are not guaranteed to fit
uint32, we have already stored BlockNumber there.

I thought giving cmdtype and objid each its own slot would make things a
little bit clearer than stuffing them into st_progress_param[0] and
st_progress_param[1], respectively. Is that what you are suggesting?
Although as I've don, a separate field st_command_objid may be a bit too much.

I mentioned only of object id as you seem to take me. The command
type is essential unlike the target object ids. It is needed by
all statistics views of this kind to filter required backends.

Yep.

# I think that integer arrays might be needed to be passed as a
# parameter, but it would be the another issue.

Didn't really think about it. Maybe we should consider a scenario that
would require it.

Imagine to provide a statictics of a vacuum commnad as a
whole. It will vacuum several relations at once so the view could
be like the following.

select * from pg_stat_vacuum_command;
- [ Record 1 ]
worker_pid : 3243
command : vacuum full
rels_scheduled : {16387, 16390, 16393}
rels_finished : {16384}
status : Processing 16384, awiting for a lock.
..

This needs arrays if we want this but it would be another issue
as I said.

Oh, I see. This does call for at least some consideration of how to
support variable size parameter values.

By the way, looking at the "status" message in your example, it doesn't
seem like a candidate for evaluation in a CASE..WHEN expression? Maybe,
we should re-introduce[1] a fixed-size char st_progress_message[] field.
Since, ISTM, such a command's internal code is in better position to
compute that kind of message string. IIRC, a reason that was given to not
have such a field was, among other things, the copy overhead of message
strings. But commands like the one in your example, could afford that
much overhead since the frequency of message change would be less and less
compared with the time elapsed between the changes anyway.

I think the fixed number of parameters in the form of a fixed-size array
is because st_progress_param[] is part of a shared memory structure as
discussed before. Although such interface has been roughly modeled on how
pg_statistic catalog and pg_stats view or get_attstatsslot() function
work, shared memory structures take the place of the catalog, so there are
some restrictions (fixed size array being one).

It depends on how easy we take it to widen the parameter slots in
shared memory:p Anyway I don't stick that since it doesn't make
a siginificant difference.

Your above example makes me wonder how we can provide for it.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#180Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Amit Langote (#179)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2016/03/07 19:11, Amit Langote wrote:

we should re-introduce[1] a fixed-size char st_progress_message[] field.

Sorry, that [1] does not refer to anything, just a leftover from my draft.
I thought I had a link handy for an email where some sort of
justification was given as to why st_progress_message field was removed
from the patch. I couldn't find it.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#181Robert Haas
robertmhaas@gmail.com
In reply to: Kyotaro HORIGUCHI (#176)
Re: [PROPOSAL] VACUUM Progress Checker.

On Sun, Mar 6, 2016 at 11:02 PM, Kyotaro HORIGUCHI
<horiguchi.kyotaro@lab.ntt.co.jp> wrote:

At Sat, 5 Mar 2016 16:41:29 +0900, Amit Langote <amitlangote09@gmail.com> wrote in <CA+HiwqHTeuqWMc+ktneGqFdJMRXD=syncgU0914TVXaahOF56g@mail.gmail.com>

On Sat, Mar 5, 2016 at 4:24 PM, Amit Langote <amitlangote09@gmail.com> wrote:

So, I took the Vinayak's latest patch and rewrote it a little

...

I broke it into two:

0001-Provide-a-way-for-utility-commands-to-report-progres.patch
0002-Implement-progress-reporting-for-VACUUM-command.patch

Oops, unamended commit messages in those patches are misleading. So,
please find attached corrected versions.

The 0001-P.. adds the following interface functions.

+extern void pgstat_progress_set_command(BackendCommandType cmdtype);
+extern void pgstat_progress_set_command_target(Oid objid);
+extern void pgstat_progress_update_param(int index, uint32 val);
+extern void pgstat_reset_local_progress(void);
+extern int     pgstat_progress_get_num_param(BackendCommandType cmdtype);

I don't like to treat the target object id differently from other
parameters. It could not be needed at all, or could be needed two
or more in contrast. Although oids are not guaranteed to fit
uint32, we have already stored BlockNumber there.

Well...

There's not much point in deciding that the parameters are uint32,
because we don't have that type at the SQL level.
pgstat_progress_update_param() really ought to take either int32 or
int64 as an argument, because that's what we can actually handle from
SQL, and it seems pretty clear that int64 is better since otherwise we
can't fit, among other things, a block number.

Given that, I tend to think that treating the command target specially
and passing that as an OID is reasonable. We're not going to be able
to pass variable-sized arrays through this mechanism, ever, because
our shared memory segment doesn't work like that. And it seems to me
that nearly every command somebody might want to report progress on
will touch, basically, one relation a a time. So I don't see the harm
in hardcoding that idea into the facility.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#182Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#181)
3 attachment(s)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2016/03/07 23:48, Robert Haas wrote:

On Sun, Mar 6, 2016 at 11:02 PM, Kyotaro HORIGUCHI
<horiguchi.kyotaro@lab.ntt.co.jp> wrote:

The 0001-P.. adds the following interface functions.

+extern void pgstat_progress_set_command(BackendCommandType cmdtype);
+extern void pgstat_progress_set_command_target(Oid objid);
+extern void pgstat_progress_update_param(int index, uint32 val);
+extern void pgstat_reset_local_progress(void);
+extern int     pgstat_progress_get_num_param(BackendCommandType cmdtype);

I don't like to treat the target object id differently from other
parameters. It could not be needed at all, or could be needed two
or more in contrast. Although oids are not guaranteed to fit
uint32, we have already stored BlockNumber there.

Well...

There's not much point in deciding that the parameters are uint32,
because we don't have that type at the SQL level.
pgstat_progress_update_param() really ought to take either int32 or
int64 as an argument, because that's what we can actually handle from
SQL, and it seems pretty clear that int64 is better since otherwise we
can't fit, among other things, a block number.

Given that, I tend to think that treating the command target specially
and passing that as an OID is reasonable. We're not going to be able
to pass variable-sized arrays through this mechanism, ever, because
our shared memory segment doesn't work like that. And it seems to me
that nearly every command somebody might want to report progress on
will touch, basically, one relation a a time. So I don't see the harm
in hardcoding that idea into the facility.

Updated versions attached.

* changed st_progress_param to int64 and so did the argument of
pgstat_progress_update_param(). Likewise changed param1..param10 of
pg_stat_get_progress_info()'s output columns to bigint.

* Added back the Oid field st_command_target and corresponding function
pgstat_progress_set_command_target(Oid).

* I attempted to implement a method to report index blocks done from
lazy_tid_reaped() albeit with some limitations. Patch 0003 is that
attempt. In summary, I modified the index bulk delete callback interface
to receive a BlockNumber argument index_blkno:

 /* Typedef for callback function to determine if a tuple is bulk-deletable */
-typedef bool (*IndexBulkDeleteCallback) (ItemPointer itemptr, void *state);
+typedef bool (*IndexBulkDeleteCallback) (ItemPointer itemptr,
+                                         BlockNumber index_blkno,
+                                         void *state);

Then added 2 more fields to LVRelStats:

@@ -143,6 +143,8 @@ typedef struct LVRelStats
     int         num_index_scans;
     TransactionId latestRemovedXid;
     bool        lock_waiter_detected;
+    BlockNumber last_index_blkno;
+    BlockNumber index_blks_vacuumed;

Then in lazy_tid_reaped(), if the index block number received in the
index_blkno argument has changed from the previous call, increment the
count of index blocks processed and
pgstat_report_update_param(index_blks_done). I wonder if we should reset
the the saved block number and the count for every index vacuumed by
lazy_vacuum_index(). Right now, total_index_blks counts all indexes and
counting blocks using the rough method mentioned above is sensible only
for one index at time. Actually, the method has different kinds of
problems to deal with anyway. For example, with a btree index, one can
expect that the final count does not match total_index_blks obtained using
RelationGetNumberOfBlocks(). Moreover, each AM has its own idiosyncratic
way of traversing the index pages. I dared only touch the btree case to
make it pass current block number to the callback. It finishes with
index_blks_done << total_index_blks since I guess the callback is called
only on the leaf pages. Any ideas?

* I am also tempted to add num_dead_tuples and dead_tuples_vacuumed to add
granularity to 'vacuuming heap' phase but didn't in this version. Should we?

Thanks,
Amit

Attachments:

0001-Provide-a-way-for-utility-commands-to-report-progres-v4.patchtext/x-diff; name=0001-Provide-a-way-for-utility-commands-to-report-progres-v4.patchDownload
From 1608f3bd8153045da9cb3db269597b92042a4d9c Mon Sep 17 00:00:00 2001
From: Amit <amitlangote09@gmail.com>
Date: Sun, 28 Feb 2016 01:50:07 +0900
Subject: [PATCH 1/3] Provide a way for utility commands to report progress

Commands can update a set of parameters in shared memory using:

  pgstat_progress_update_param(param_index, param_value)

Up to 10 independent 64-bit integer values can be published by commands.
In addition to those, a command should always report its BackendCommandType
and the OID of the relation it is about to begin processing at the beginning
of the processing using:

  pgstat_progress_start_command(cmdtype)
  pgstat_progress_set_command_target(relid)

A view can be defined in system_views.sql that outputs the values returned
by pg_stat_get_progress_info(cmdtype), where 'cmdtype' is numeric value as
mentioned above.  Each such view has columns corresponding to the counters
published by respective commands.

There is a SQL-callable function pg_stat_reset_local_progress() which
when called, resets the progress information of the backend of the session
in which its called.  It is useful to erase progress info of commands
previously run in the session.
---
 doc/src/sgml/monitoring.sgml        |    8 +++
 src/backend/postmaster/pgstat.c     |   76 ++++++++++++++++++++++++++++
 src/backend/utils/adt/pgstatfuncs.c |   93 +++++++++++++++++++++++++++++++++++
 src/include/catalog/pg_proc.h       |    4 ++
 src/include/pgstat.h                |   26 ++++++++++
 5 files changed, 207 insertions(+), 0 deletions(-)

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 85459d0..45d9ed7 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -1935,6 +1935,14 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
        zero (requires superuser privileges)
       </entry>
      </row>
+
+     <row>
+      <entry><literal><function>pg_stat_reset_local_progress</function>()</literal><indexterm><primary>pg_stat_reset_local_progress</primary></indexterm></entry>
+      <entry><type>void</type></entry>
+      <entry>
+       Reset command progress parameters of local backend
+      </entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index da768c6..af5be5f 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -2731,6 +2731,8 @@ pgstat_bestart(void)
 	beentry->st_clienthostname[NAMEDATALEN - 1] = '\0';
 	beentry->st_appname[NAMEDATALEN - 1] = '\0';
 	beentry->st_activity[pgstat_track_activity_query_size - 1] = '\0';
+	beentry->st_command = COMMAND_INVALID;
+	MemSet(&beentry->st_progress_param, 0, sizeof(beentry->st_progress_param));
 
 	pgstat_increment_changecount_after(beentry);
 
@@ -2851,6 +2853,80 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
 	pgstat_increment_changecount_after(beentry);
 }
 
+/*-----------
+ * pgstat_progress_update_param() -
+ *
+ * Update index'th member in st_progress_param[] of own backend entry.
+ *-----------
+ */
+void
+pgstat_progress_update_param(int index, int64 val)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	if(!beentry)
+		return;
+
+	if (!pgstat_track_activities)
+		return;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_progress_param[index] = val;
+	pgstat_increment_changecount_after(beentry);
+}
+
+/*-----------
+ * pgstat_progress_start_command()-
+ *
+ * Set st_command in own backend entry.  Also, zero-initialize
+ * st_progress_param array.
+ *-----------
+ */
+void
+pgstat_progress_start_command(BackendCommandType cmdtype)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_command = cmdtype;
+	MemSet(&beentry->st_progress_param, 0,
+		   sizeof(beentry->st_progress_param));
+	pgstat_increment_changecount_after(beentry);
+}
+
+/*-----------
+ * pgstat_progress_set_command_target()-
+ *
+ * Set st_command_target in own backend entry.
+ *-----------
+ */
+void
+pgstat_progress_set_command_target(Oid relid)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_command_target = relid;
+	pgstat_increment_changecount_after(beentry);
+}
+
+/*--------
+ * pgstat_reset_local_progress()-
+ *
+ * Reset local backend's progress parameters. Setting st_command to
+ * COMMAND_INVALID will do.
+ *--------
+ */
+void
+pgstat_reset_local_progress()
+{
+	PgBackendStatus *beentry = MyBEEntry;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_command = COMMAND_INVALID;
+	pgstat_increment_changecount_after(beentry);
+}
+
 /* ----------
  * pgstat_report_appname() -
  *
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 1b22fcc..ac72def 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -64,6 +64,8 @@ extern Datum pg_stat_get_backend_xact_start(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_start(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_client_addr(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_client_port(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_progress_info(PG_FUNCTION_ARGS);
+extern Datum pg_stat_reset_local_progress(PG_FUNCTION_ARGS);
 
 extern Datum pg_stat_get_db_numbackends(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_db_xact_commit(PG_FUNCTION_ARGS);
@@ -525,6 +527,97 @@ pg_stat_get_backend_idset(PG_FUNCTION_ARGS)
 }
 
 /*
+ * Returns progress parameter values of backends running a given command
+ */
+Datum
+pg_stat_get_progress_info(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_PROGRESS_COLS	N_PROGRESS_PARAM + 2
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int32		cmdtype = PG_GETARG_INT32(0);
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+
+	/* 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);
+
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		LocalPgBackendStatus   *local_beentry;
+		PgBackendStatus		   *beentry;
+		Datum		values[PG_STAT_GET_PROGRESS_COLS];
+		bool		nulls[PG_STAT_GET_PROGRESS_COLS];
+		int			i;
+
+		MemSet(values, 0, sizeof(values));
+		MemSet(nulls, 0, sizeof(nulls));
+
+		local_beentry = pgstat_fetch_stat_local_beentry(curr_backend);
+
+		if (!local_beentry)
+			continue;
+
+		beentry = &local_beentry->backendStatus;
+
+		/*
+		 * Report values for only those backends which are running the given
+		 * command.  XXX - privilege check is maybe dubious.
+		 */
+		if (!beentry ||
+			beentry->st_command != cmdtype ||
+			!has_privs_of_role(GetUserId(), beentry->st_userid))
+			continue;
+
+		/* Values available to all callers */
+		values[0] = Int32GetDatum(beentry->st_procpid);
+		values[1] = ObjectIdGetDatum(beentry->st_command_target);
+		for(i = 0; i < N_PROGRESS_PARAM; i++)
+			values[i+2] = UInt32GetDatum(beentry->st_progress_param[i]);
+
+		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+	}
+
+	/* clean up and return the tuplestore */
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+/* Reset local backend's command progress info */
+Datum
+pg_stat_reset_local_progress(PG_FUNCTION_ARGS)
+{
+	pgstat_reset_local_progress();
+
+	PG_RETURN_VOID();
+}
+
+/*
  * Returns activity of PG backends.
  */
 Datum
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index cbbb883..daaa233 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2710,6 +2710,8 @@ DATA(insert OID = 1936 (  pg_stat_get_backend_idset		PGNSP PGUID 12 1 100 0 0 f
 DESCR("statistics: currently active backend IDs");
 DATA(insert OID = 2022 (  pg_stat_get_activity			PGNSP PGUID 12 1 100 0 0 f f f f f t s r 1 0 2249 "23" "{23,26,23,26,25,25,25,16,1184,1184,1184,1184,869,25,23,28,28,16,25,25,23,16,25}" "{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{pid,datid,pid,usesysid,application_name,state,query,waiting,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,ssl,sslversion,sslcipher,sslbits,sslcompression,sslclientdn}" _null_ _null_ pg_stat_get_activity _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active backends");
+DATA(insert OID = 3318 (  pg_stat_get_progress_info           PGNSP PGUID 12 1 100 0 0 f f f f f t s r 1 0 2249 "23" "{23,23,26,20,20,20,20,20,20,20,20,20,20}" "{i,o,o,o,o,o,o,o,o,o,o,o,o}" "{cmdtype,pid,relid,param1,param2,param3,param4,param5,param6,param7,param8,param9,param10}" _null_ _null_ pg_stat_get_progress_info _null_ _null_ _null_ ));
+DESCR("statistics: information about progress of backends running maintenance command");
 DATA(insert OID = 3099 (  pg_stat_get_wal_senders	PGNSP PGUID 12 1 10 0 0 f f f f f t s r 0 0 2249 "" "{23,25,3220,3220,3220,3220,23,25}" "{o,o,o,o,o,o,o,o}" "{pid,state,sent_location,write_location,flush_location,replay_location,sync_priority,sync_state}" _null_ _null_ pg_stat_get_wal_senders _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active replication");
 DATA(insert OID = 3317 (  pg_stat_get_wal_receiver	PGNSP PGUID 12 1 0 0 0 f f f f f f s r 0 0 2249 "" "{23,25,3220,23,3220,23,1184,1184,3220,1184,25}" "{o,o,o,o,o,o,o,o,o,o,o}" "{pid,status,receive_start_lsn,receive_start_tli,received_lsn,received_tli,last_msg_send_time,last_msg_receipt_time,latest_end_lsn,latest_end_time,slot_name}" _null_ _null_ pg_stat_get_wal_receiver _null_ _null_ _null_ ));
@@ -2849,6 +2851,8 @@ DATA(insert OID = 3776 (  pg_stat_reset_single_table_counters	PGNSP PGUID 12 1 0
 DESCR("statistics: reset collected statistics for a single table or index in the current database");
 DATA(insert OID = 3777 (  pg_stat_reset_single_function_counters	PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 2278 "26" _null_ _null_ _null_ _null_ _null_	pg_stat_reset_single_function_counters _null_ _null_ _null_ ));
 DESCR("statistics: reset collected statistics for a single function in the current database");
+DATA(insert OID = 3319 (  pg_stat_reset_local_progress			PGNSP PGUID 12 1 0 0 0 f f f f f f v s 0 0 2278 "" _null_ _null_ _null_ _null_ _null_ pg_stat_reset_local_progress _null_ _null_ _null_ ));
+DESCR("statistics: reset progress information of the local backend");
 
 DATA(insert OID = 3163 (  pg_trigger_depth				PGNSP PGUID 12 1 0 0 0 f f f f t f s s 0 0 23 "" _null_ _null_ _null_ _null_ _null_ pg_trigger_depth _null_ _null_ _null_ ));
 DESCR("current trigger depth");
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 65e968e..dfa6b31 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -696,6 +696,17 @@ typedef enum BackendState
 } BackendState;
 
 /* ----------
+ * Command type for progress reporting purposes
+ * ----------
+ */
+typedef enum BackendCommandType
+{
+	COMMAND_INVALID = 0,
+} BackendCommandType;
+
+#define N_PROGRESS_PARAM	10
+
+/* ----------
  * Shared-memory data structures
  * ----------
  */
@@ -776,6 +787,17 @@ typedef struct PgBackendStatus
 
 	/* current command string; MUST be null-terminated */
 	char	   *st_activity;
+
+	/*
+	 * Progress parameters of currently running utility command in the
+	 * backend.  Different commands store different number of up to
+	 * N_PROGRESS_PARAM values in st_progress_param.  However, each command
+	 * must set st_command and st_command_target (OID of the relation it is
+	 * about to begin to process) at the beginning of command processing.
+	 */
+	BackendCommandType	st_command;
+	Oid					st_command_target;
+	int64				st_progress_param[N_PROGRESS_PARAM];
 } PgBackendStatus;
 
 /*
@@ -935,6 +957,10 @@ extern void pgstat_report_waiting(bool waiting);
 extern const char *pgstat_get_backend_current_activity(int pid, bool checkUser);
 extern const char *pgstat_get_crashed_backend_activity(int pid, char *buffer,
 									int buflen);
+extern void pgstat_progress_start_command(BackendCommandType cmdtype);
+extern void pgstat_progress_set_command_target(Oid relid);
+extern void pgstat_progress_update_param(int index, int64 val);
+extern void pgstat_reset_local_progress(void);
 
 extern PgStat_TableStatus *find_tabstat_entry(Oid rel_id);
 extern PgStat_BackendFunctionEntry *find_funcstat_entry(Oid func_id);
-- 
1.7.1

0002-Implement-progress-reporting-for-VACUUM-command-v4.patchtext/x-diff; name=0002-Implement-progress-reporting-for-VACUUM-command-v4.patchDownload
From cdeb9655479bf983d19b39ef9c5674a3eaca12e1 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 7 Mar 2016 14:38:34 +0900
Subject: [PATCH 2/3] Implement progress reporting for VACUUM command.

This basically utilizes the pgstat_progress* API to report a handful of
paramters to indicate its progress.  lazy_vacuum_rel() and lazy_scan_heap()
have been altered to report command start, command target table, and the
following parameters: processing phase, number of heap blocks, number of
index blocks (all indexes), current heap block number in the main scan loop
(whenever changes), index blocks vacuumed (once per finished index vacuum),
and number of index vacuum passes (every time when all indexes are vacuumed).
Following processing phases are identified and reported whenever one changes
to another: 'scanning heap', 'vacuuming indexes', 'vacuuming heap', 'cleanup',
'done'. The last value is really a misnomer but maybe clearer when someone is
staring at the progress view being polled.

TODO: find a way to report index pages vacuumed in a more granular manner than
the current report per index vacuumed.

A view named pg_stat_vacuum_progress has been added that shows these values.
---
 doc/src/sgml/monitoring.sgml         |  114 ++++++++++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   23 +++++++
 src/backend/commands/vacuumlazy.c    |   80 +++++++++++++++++++++++-
 src/include/pgstat.h                 |    1 +
 src/test/regress/expected/rules.out  |   20 ++++++
 5 files changed, 237 insertions(+), 1 deletions(-)

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 45d9ed7..e4361ad 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -507,6 +507,13 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       yet included in <structname>pg_stat_user_functions</>).</entry>
      </row>
 
+     <row>
+      <entry><structname>pg_stat_vacuum_progress</><indexterm><primary>pg_stat_vacuum_progress</primary></indexterm></entry>
+      <entry>One row for each backend (including autovacuum worker processes) running
+      <command>VACUUM</>, showing current progress in terms of heap pages it
+      has finished processing.  Note that the backends running
+      <command>VACUUM FULL</> are not included.</entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -1822,6 +1829,113 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
    controls exactly which functions are tracked.
   </para>
 
+  <table id="pg-stat-vacuum-progress" xreflabel="pg_stat_vacuum_progress">
+   <title><structname>pg_stat_vacuum_progress</structname> View</title>
+   <tgroup cols="3">
+    <thead>
+    <row>
+      <entry>Column</entry>
+      <entry>Type</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+   <tbody>
+    <row>
+     <entry><structfield>pid</></entry>
+     <entry><type>integer</></entry>
+     <entry>Process ID of backend</entry>
+    </row>
+    <row>
+     <entry><structfield>relid</></entry>
+     <entry><type>oid</></entry>
+     <entry>OID of a table</entry>
+    </row>
+    <row>
+     <entry><structfield>processing_phase</></entry>
+     <entry><type>integer</></entry>
+     <entry>Current phase of vacuum.
+       Possible values are:
+       <itemizedlist>
+        <listitem>
+         <para>
+          <literal>scanning heap</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>vacuuming indexes</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>vacuuming heap</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>cleanup</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>done</>
+         </para>
+        </listitem>
+       </itemizedlist>
+     </entry>
+    </row>
+    <row>
+     <entry><structfield>total_heap_blks</></entry>
+     <entry><type>integer</></entry>
+     <entry>Total number of heap blocks in the table</entry>
+    </row>
+    <row>
+     <entry><structfield>current_heap_blkno</></entry>
+     <entry><type>integer</></entry>
+     <entry>Current heap block being processed</entry>
+    </row>
+    <row>
+     <entry><structfield>total_index_blks</></entry>
+     <entry><type>integer</></entry>
+     <entry>Total number of index blocks to be processed</entry>
+    </row>
+    <row>
+     <entry><structfield>index_blks_done</></entry>
+     <entry><type>integer</></entry>
+     <entry>Number of index blocks processed</entry>
+    </row>
+    <row>
+     <entry><structfield>index_scan_count</></entry>
+     <entry><type>integer</></entry>
+     <entry>Number of times index scans has been performed so far</entry>
+    </row>
+    <row>
+     <entry><structfield>percent_done</></entry>
+     <entry><type>numeric</></entry>
+     <entry>
+      Amount of work finished in percent in terms of table blocks processed
+     </entry>
+    </row>
+   </tbody>
+   </tgroup>
+  </table>
+
+  <para>
+   The <structname>pg_stat_vacuum_progress</structname> view will contain
+   one row for each backend (including autovacuum worker processes), showing
+   progress of <command>VACUUM</> running in it. Note that the backends
+   running <command>VACUUM FULL</> are not shown.
+  </para>
+
+  <para>
+   When interpreting the value of the <structfield>percent_done</> column, also
+   note the value of <structfield>processing_phase</>.  It's possible for the
+   former to be <literal>100.00</literal>, while the <command>VACUUM</> still
+   has not returned.  In that case, wait for the latter to turn to the value
+   <literal>done</literal>.
+  </para>
+
  </sect2>
 
  <sect2 id="monitoring-stats-functions">
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index abf9a70..f622a4a 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -971,3 +971,26 @@ RETURNS jsonb
 LANGUAGE INTERNAL
 STRICT IMMUTABLE
 AS 'jsonb_set';
+
+CREATE VIEW pg_stat_vacuum_progress AS
+    SELECT
+           S.pid AS pid,
+           S.relid AS relid,
+           CASE S.param1
+               WHEN 1 THEN 'scanning heap'
+               WHEN 2 THEN 'vacuuming indexes'
+               WHEN 3 THEN 'vacuuming heap'
+               WHEN 4 THEN 'cleanup'
+               WHEN 5 THEN 'done'
+               ELSE 'unknown'
+           END AS processing_phase,
+           S.param2 AS total_heap_blks,
+           S.param3 AS current_heap_blkno,
+           S.param4 AS total_index_blks,
+           S.param5 AS index_blks_done,
+           S.param6 AS index_scan_count,
+           CASE S.param2
+			  WHEN 0 THEN 100::numeric(5, 2)
+			  ELSE ((S.param3 + 1)::numeric / S.param2 * 100)::numeric(5, 2)
+		   END AS percent_done
+   FROM pg_stat_get_progress_info(1) AS S;
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index 8f7b248..60c3b3b 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -97,6 +97,28 @@
  */
 #define SKIP_PAGES_THRESHOLD	((BlockNumber) 32)
 
+/*
+ * Follwing progress parameters for lazy vacuum are reported to pgstat
+ */
+#define PROG_PAR_VAC_PHASE_ID			0
+#define PROG_PAR_VAC_HEAP_BLKS			1
+#define PROG_PAR_VAC_CUR_HEAP_BLK		2
+#define PROG_PAR_VAC_IDX_BLKS			3
+#define PROG_PAR_VAC_IDX_BLKS_DONE		4
+#define PROG_PAR_VAC_N_IDX_SCAN			5
+
+/*
+ * Following distinct phases of lazy vacuum are identified.  Although #1, #2
+ * and #3 run in a cyclical manner due to possibly limited memory to work
+ * with, wherein #1 is periodically interrupted to run #2 followed by #3
+ * and back, until all the blocks of the relations have been covered by #1.
+ */
+#define LV_PHASE_SCAN_HEAP			1
+#define LV_PHASE_VACUUM_INDEX		2
+#define LV_PHASE_VACUUM_HEAP		3
+#define LV_PHASE_CLEANUP			4
+#define LV_PHASE_DONE				5
+
 typedef struct LVRelStats
 {
 	/* hasindex = true means two-pass strategy; false means one-pass */
@@ -195,6 +217,10 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params,
 
 	Assert(params != NULL);
 
+	/* initialize pgstat progress info */
+	pgstat_progress_start_command(COMMAND_LAZY_VACUUM);
+	pgstat_progress_set_command_target(RelationGetRelid(onerel));
+
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
 	{
@@ -270,6 +296,9 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params,
 	/* Vacuum the Free Space Map */
 	FreeSpaceMapVacuum(onerel);
 
+	/* We're done doing any heavy handling, so report */
+	pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_DONE);
+
 	/*
 	 * Update statistics in pg_class.
 	 *
@@ -433,7 +462,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			   Relation *Irel, int nindexes, bool scan_all)
 {
 	BlockNumber nblocks,
-				blkno;
+				blkno,
+				total_index_blks,
+			   *current_index_blks;
 	HeapTupleData tuple;
 	char	   *relname;
 	BlockNumber empty_pages,
@@ -474,6 +505,24 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	lazy_space_alloc(vacrelstats, nblocks);
 	frozen = palloc(sizeof(xl_heap_freeze_tuple) * MaxHeapTuplesPerPage);
 
+	/* about to begin heap scan */
+	pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_SCAN_HEAP);
+
+	/* total_heap_blks */
+	pgstat_progress_update_param(PROG_PAR_VAC_HEAP_BLKS, nblocks);
+
+	/* total_index_blks */
+	current_index_blks = (BlockNumber *) palloc(nindexes * sizeof(BlockNumber));
+	total_index_blks = 0;
+	for (i = 0; i < nindexes; i++)
+	{
+		BlockNumber		nblocks = RelationGetNumberOfBlocks(Irel[i]);
+
+		current_index_blks[i] = nblocks;
+		total_index_blks += nblocks;
+	}
+	pgstat_progress_update_param(PROG_PAR_VAC_IDX_BLKS, total_index_blks);
+
 	/*
 	 * We want to skip pages that don't require vacuuming according to the
 	 * visibility map, but only when we can skip at least SKIP_PAGES_THRESHOLD
@@ -581,6 +630,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 
 		vacuum_delay_point();
 
+		/* current_heap_blkno: 0..nblocks-1 */
+		pgstat_progress_update_param(PROG_PAR_VAC_CUR_HEAP_BLK, blkno);
+
 		/*
 		 * If we are close to overrunning the available space for dead-tuple
 		 * TIDs, pause and do a cycle of vacuuming before we tackle this page.
@@ -604,11 +656,22 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			vacuum_log_cleanup_info(onerel, vacrelstats);
 
 			/* Remove index entries */
+			pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_VACUUM_INDEX);
 			for (i = 0; i < nindexes; i++)
+			{
 				lazy_vacuum_index(Irel[i],
 								  &indstats[i],
 								  vacrelstats);
+
+				pgstat_progress_update_param(PROG_PAR_VAC_IDX_BLKS_DONE,
+											 current_index_blks[i]);
+			}
+
+			pgstat_progress_update_param(PROG_PAR_VAC_N_IDX_SCAN,
+										 vacrelstats->num_index_scans+1);
+
 			/* Remove tuples from heap */
+			pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_VACUUM_HEAP);
 			lazy_vacuum_heap(onerel, vacrelstats);
 
 			/*
@@ -618,6 +681,10 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			 */
 			vacrelstats->num_dead_tuples = 0;
 			vacrelstats->num_index_scans++;
+
+			/* go back to scanning the heap */
+			pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID,
+										 LV_PHASE_SCAN_HEAP);
 		}
 
 		/*
@@ -1154,16 +1221,27 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		vacuum_log_cleanup_info(onerel, vacrelstats);
 
 		/* Remove index entries */
+		pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_VACUUM_INDEX);
 		for (i = 0; i < nindexes; i++)
+		{
 			lazy_vacuum_index(Irel[i],
 							  &indstats[i],
 							  vacrelstats);
+
+			pgstat_progress_update_param(PROG_PAR_VAC_IDX_BLKS_DONE,
+										 current_index_blks[i]);
+		}
+		pgstat_progress_update_param(PROG_PAR_VAC_N_IDX_SCAN,
+									 vacrelstats->num_index_scans + 1);
+
 		/* Remove tuples from heap */
+		pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_VACUUM_HEAP);
 		lazy_vacuum_heap(onerel, vacrelstats);
 		vacrelstats->num_index_scans++;
 	}
 
 	/* Do post-vacuum cleanup and statistics update for each index */
+	pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_CLEANUP);
 	for (i = 0; i < nindexes; i++)
 		lazy_cleanup_index(Irel[i], indstats[i], vacrelstats);
 
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index dfa6b31..948783e 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -702,6 +702,7 @@ typedef enum BackendState
 typedef enum BackendCommandType
 {
 	COMMAND_INVALID = 0,
+	COMMAND_LAZY_VACUUM
 } BackendCommandType;
 
 #define N_PROGRESS_PARAM	10
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 81bc5c9..0ef6c76 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1851,6 +1851,26 @@ pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
     pg_stat_all_tables.autoanalyze_count
    FROM pg_stat_all_tables
   WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
+pg_stat_vacuum_progress| SELECT s.pid,
+    s.relid,
+        CASE s.param1
+            WHEN 1 THEN 'scanning heap'::text
+            WHEN 2 THEN 'vacuuming indexes'::text
+            WHEN 3 THEN 'vacuuming heap'::text
+            WHEN 4 THEN 'cleanup'::text
+            WHEN 5 THEN 'done'::text
+            ELSE 'unknown'::text
+        END AS processing_phase,
+    s.param2 AS total_heap_blks,
+    s.param3 AS current_heap_blkno,
+    s.param4 AS total_index_blks,
+    s.param5 AS index_blks_done,
+    s.param6 AS index_scan_count,
+        CASE s.param2
+            WHEN 0 THEN (100)::numeric(5,2)
+            ELSE (((((s.param3 + 1))::numeric / (s.param2)::numeric) * (100)::numeric))::numeric(5,2)
+        END AS percent_done
+   FROM pg_stat_get_progress_info(1) s(pid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10);
 pg_stat_wal_receiver| SELECT s.pid,
     s.status,
     s.receive_start_lsn,
-- 
1.7.1

0003-Add-a-block-number-argument-to-index-bulk-delete-cal-v4.patchtext/x-diff; name=0003-Add-a-block-number-argument-to-index-bulk-delete-cal-v4.patchDownload
From 5972d8516786615bed3a587b0d2fb78dba2e74ed Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 8 Mar 2016 15:23:38 +0900
Subject: [PATCH 3/3] Add a block number argument to index bulk delete callback.

Currently, index AMs can only pass a ItemPointer (heap tid) and a generic
pointer to caller specified struct.  Add a BlockNumber so that the callback
can use it for something.  For example, lazy_tid_reaped() now uses it to
track the progress of AM's bulk delete scan of an index by comparing it with
the last call's value that it stores in its private state on every change.
The private state being the LVRelStats struct that now has the corresponding
field.  On every change, the count of index blocks vacuumed is incremented
which is also a new field in LVRelStats. The count is reset for every index
vacuum round.

Then a pgstat_progress_update_param call has been added to lazy_tid_reaped,
that pushes the count on every increment.

Currently, only btree AM is changed to pass the block number, others pass
InvalidBlockNumber.
---
 src/backend/access/gin/ginvacuum.c    |    2 +-
 src/backend/access/gist/gistvacuum.c  |    2 +-
 src/backend/access/hash/hash.c        |    2 +-
 src/backend/access/nbtree/nbtree.c    |    2 +-
 src/backend/access/spgist/spgvacuum.c |    6 +++---
 src/backend/catalog/index.c           |    5 +++--
 src/backend/commands/vacuumlazy.c     |   25 ++++++++++++++-----------
 src/include/access/genam.h            |    4 +++-
 8 files changed, 27 insertions(+), 21 deletions(-)

diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c
index 6a4b98a..64e69d6 100644
--- a/src/backend/access/gin/ginvacuum.c
+++ b/src/backend/access/gin/ginvacuum.c
@@ -55,7 +55,7 @@ ginVacuumItemPointers(GinVacuumState *gvs, ItemPointerData *items,
 	 */
 	for (i = 0; i < nitem; i++)
 	{
-		if (gvs->callback(items + i, gvs->callback_state))
+		if (gvs->callback(items + i, InvalidBlockNumber, gvs->callback_state))
 		{
 			gvs->result->tuples_removed += 1;
 			if (!tmpitems)
diff --git a/src/backend/access/gist/gistvacuum.c b/src/backend/access/gist/gistvacuum.c
index 7947ff9..96e89c3 100644
--- a/src/backend/access/gist/gistvacuum.c
+++ b/src/backend/access/gist/gistvacuum.c
@@ -202,7 +202,7 @@ gistbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 				iid = PageGetItemId(page, i);
 				idxtuple = (IndexTuple) PageGetItem(page, iid);
 
-				if (callback(&(idxtuple->t_tid), callback_state))
+				if (callback(&(idxtuple->t_tid), InvalidBlockNumber, callback_state))
 					todelete[ntodelete++] = i;
 				else
 					stats->num_index_tuples += 1;
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 3d48c4f..1530f0b 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -575,7 +575,7 @@ loop_top:
 				itup = (IndexTuple) PageGetItem(page,
 												PageGetItemId(page, offno));
 				htup = &(itup->t_tid);
-				if (callback(htup, callback_state))
+				if (callback(htup, InvalidBlockNumber, callback_state))
 				{
 					/* mark the item for deletion */
 					deletable[ndeletable++] = offno;
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index f2905cb..8c6be30 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -1034,7 +1034,7 @@ restart:
 				 * applies to *any* type of index that marks index tuples as
 				 * killed.
 				 */
-				if (callback(htup, callback_state))
+				if (callback(htup, blkno, callback_state))
 					deletable[ndeletable++] = offnum;
 			}
 		}
diff --git a/src/backend/access/spgist/spgvacuum.c b/src/backend/access/spgist/spgvacuum.c
index 15b867f..6c7265b 100644
--- a/src/backend/access/spgist/spgvacuum.c
+++ b/src/backend/access/spgist/spgvacuum.c
@@ -154,7 +154,7 @@ vacuumLeafPage(spgBulkDeleteState *bds, Relation index, Buffer buffer,
 		{
 			Assert(ItemPointerIsValid(&lt->heapPtr));
 
-			if (bds->callback(&lt->heapPtr, bds->callback_state))
+			if (bds->callback(&lt->heapPtr, InvalidBlockNumber, bds->callback_state))
 			{
 				bds->stats->tuples_removed += 1;
 				deletable[i] = true;
@@ -424,7 +424,7 @@ vacuumLeafRoot(spgBulkDeleteState *bds, Relation index, Buffer buffer)
 		{
 			Assert(ItemPointerIsValid(&lt->heapPtr));
 
-			if (bds->callback(&lt->heapPtr, bds->callback_state))
+			if (bds->callback(&lt->heapPtr, InvalidBlockNumber, bds->callback_state))
 			{
 				bds->stats->tuples_removed += 1;
 				toDelete[xlrec.nDelete] = i;
@@ -902,7 +902,7 @@ spgbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 
 /* Dummy callback to delete no tuples during spgvacuumcleanup */
 static bool
-dummy_callback(ItemPointer itemptr, void *state)
+dummy_callback(ItemPointer itemptr, BlockNumber index_blkno, void *state)
 {
 	return false;
 }
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 31a1438..d792fdb 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -114,7 +114,8 @@ static void IndexCheckExclusion(Relation heapRelation,
 					IndexInfo *indexInfo);
 static inline int64 itemptr_encode(ItemPointer itemptr);
 static inline void itemptr_decode(ItemPointer itemptr, int64 encoded);
-static bool validate_index_callback(ItemPointer itemptr, void *opaque);
+static bool validate_index_callback(ItemPointer itemptr, BlockNumber index_blkno,
+						void *opaque);
 static void validate_index_heapscan(Relation heapRelation,
 						Relation indexRelation,
 						IndexInfo *indexInfo,
@@ -2913,7 +2914,7 @@ itemptr_decode(ItemPointer itemptr, int64 encoded)
  * validate_index_callback - bulkdelete callback to collect the index TIDs
  */
 static bool
-validate_index_callback(ItemPointer itemptr, void *opaque)
+validate_index_callback(ItemPointer itemptr, BlockNumber index_blkno, void *opaque)
 {
 	v_i_state  *state = (v_i_state *) opaque;
 	int64		encoded = itemptr_encode(itemptr);
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index 60c3b3b..2e28f4b 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -143,6 +143,8 @@ typedef struct LVRelStats
 	int			num_index_scans;
 	TransactionId latestRemovedXid;
 	bool		lock_waiter_detected;
+	BlockNumber	last_index_blkno;
+	BlockNumber	index_blks_vacuumed;
 } LVRelStats;
 
 
@@ -176,7 +178,7 @@ static BlockNumber count_nondeletable_pages(Relation onerel,
 static void lazy_space_alloc(LVRelStats *vacrelstats, BlockNumber relblocks);
 static void lazy_record_dead_tuple(LVRelStats *vacrelstats,
 					   ItemPointer itemptr);
-static bool lazy_tid_reaped(ItemPointer itemptr, void *state);
+static bool lazy_tid_reaped(ItemPointer itemptr, BlockNumber index_blkno, void *state);
 static int	vac_cmp_itemptr(const void *left, const void *right);
 static bool heap_page_is_all_visible(Relation rel, Buffer buf,
 						 TransactionId *visibility_cutoff_xid, bool *all_frozen);
@@ -657,16 +659,12 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 
 			/* Remove index entries */
 			pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_VACUUM_INDEX);
+			vacrelstats->index_blks_vacuumed = 0;	/* reset for this round */
 			for (i = 0; i < nindexes; i++)
-			{
 				lazy_vacuum_index(Irel[i],
 								  &indstats[i],
 								  vacrelstats);
 
-				pgstat_progress_update_param(PROG_PAR_VAC_IDX_BLKS_DONE,
-											 current_index_blks[i]);
-			}
-
 			pgstat_progress_update_param(PROG_PAR_VAC_N_IDX_SCAN,
 										 vacrelstats->num_index_scans+1);
 
@@ -1222,15 +1220,12 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 
 		/* Remove index entries */
 		pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_VACUUM_INDEX);
+		vacrelstats->index_blks_vacuumed = 0;	/* reset for this round */
 		for (i = 0; i < nindexes; i++)
-		{
 			lazy_vacuum_index(Irel[i],
 							  &indstats[i],
 							  vacrelstats);
 
-			pgstat_progress_update_param(PROG_PAR_VAC_IDX_BLKS_DONE,
-										 current_index_blks[i]);
-		}
 		pgstat_progress_update_param(PROG_PAR_VAC_N_IDX_SCAN,
 									 vacrelstats->num_index_scans + 1);
 
@@ -1922,11 +1917,19 @@ lazy_record_dead_tuple(LVRelStats *vacrelstats,
  *		Assumes dead_tuples array is in sorted order.
  */
 static bool
-lazy_tid_reaped(ItemPointer itemptr, void *state)
+lazy_tid_reaped(ItemPointer itemptr, BlockNumber index_blkno, void *state)
 {
 	LVRelStats *vacrelstats = (LVRelStats *) state;
 	ItemPointer res;
 
+	if (index_blkno != vacrelstats->last_index_blkno)
+	{
+		++vacrelstats->index_blks_vacuumed;
+		pgstat_progress_update_param(PROG_PAR_VAC_IDX_BLKS_DONE,
+									 vacrelstats->index_blks_vacuumed);
+		vacrelstats->last_index_blkno = index_blkno;
+	}
+
 	res = (ItemPointer) bsearch((void *) itemptr,
 								(void *) vacrelstats->dead_tuples,
 								vacrelstats->num_dead_tuples,
diff --git a/src/include/access/genam.h b/src/include/access/genam.h
index 81907d5..8d67d1c 100644
--- a/src/include/access/genam.h
+++ b/src/include/access/genam.h
@@ -77,7 +77,9 @@ typedef struct IndexBulkDeleteResult
 } IndexBulkDeleteResult;
 
 /* Typedef for callback function to determine if a tuple is bulk-deletable */
-typedef bool (*IndexBulkDeleteCallback) (ItemPointer itemptr, void *state);
+typedef bool (*IndexBulkDeleteCallback) (ItemPointer itemptr,
+										 BlockNumber index_blkno,
+										 void *state);
 
 /* struct definitions appear in relscan.h */
 typedef struct IndexScanDescData *IndexScanDesc;
-- 
1.7.1

#183Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Amit Langote (#182)
Re: [PROPOSAL] VACUUM Progress Checker.

You're so quick.

At Tue, 8 Mar 2016 17:02:24 +0900, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote in <56DE8710.4070202@lab.ntt.co.jp>

On 2016/03/07 23:48, Robert Haas wrote:

I don't like to treat the target object id differently from other
parameters. It could not be needed at all, or could be needed two
or more in contrast. Although oids are not guaranteed to fit
uint32, we have already stored BlockNumber there.

Well...

There's not much point in deciding that the parameters are uint32,
because we don't have that type at the SQL level.
pgstat_progress_update_param() really ought to take either int32 or
int64 as an argument, because that's what we can actually handle from
SQL, and it seems pretty clear that int64 is better since otherwise we
can't fit, among other things, a block number.

Given that, I tend to think that treating the command target specially
and passing that as an OID is reasonable. We're not going to be able
to pass variable-sized arrays through this mechanism, ever, because
our shared memory segment doesn't work like that. And it seems to me
that nearly every command somebody might want to report progress on
will touch, basically, one relation a a time. So I don't see the harm
in hardcoding that idea into the facility.

We'd concatenate two int32s into int64s but widening each
parameters to int64 would be preferable. Additional 4 bytes by
the defalut number of maxbackends 100 by 10 parameters = 4kb, 4MB
for 1000 backends is not so big for modern machines?

Updated versions attached.

* changed st_progress_param to int64 and so did the argument of
pgstat_progress_update_param(). Likewise changed param1..param10 of
pg_stat_get_progress_info()'s output columns to bigint.

* Added back the Oid field st_command_target and corresponding function
pgstat_progress_set_command_target(Oid).

+	beentry->st_command = COMMAND_INVALID;
+	MemSet(&beentry->st_progress_param, 0, sizeof(beentry->st_progress_param));

The MemSet seems useless since it gets the same initialization on
setting st_command.

+		/*
+		 * Report values for only those backends which are running the given
+		 * command.  XXX - privilege check is maybe dubious.
+		 */
+		if (!beentry ||
+			beentry->st_command != cmdtype ||
+			!has_privs_of_role(GetUserId(), beentry->st_userid))
+			continue;

We can simplly ignore unpriviledged backends, or all zeroz or
nulls to signal that the caller has no priviledge.

0002

+ FROM pg_stat_get_progress_info(1) AS S;

Ah... This magick number seems quite bad.. The function should
take the command type in maybe string type.

+ FROM pg_stat_get_progress_info('lazy vacuum') AS S;

Using an array of the names would be acceptable, maybe.

| char *progress_command_names[] = {'lazy vacuum', NULL};

However the numbers for the phases ('scanning heap' and so..) is
acceptable for me for reasons uncertain to me, it also could be
represented in names but is might be rahter bothersome..

+			  WHEN 0 THEN 100::numeric(5, 2)
+			  ELSE ((S.param3 + 1)::numeric / S.param2 * 100)::numeric(5, 2)

This usage of numeric seems overkill to me.

* I attempted to implement a method to report index blocks done from
lazy_tid_reaped() albeit with some limitations. Patch 0003 is that
attempt. In summary, I modified the index bulk delete callback interface
to receive a BlockNumber argument index_blkno:

/* Typedef for callback function to determine if a tuple is bulk-deletable */
-typedef bool (*IndexBulkDeleteCallback) (ItemPointer itemptr, void *state);
+typedef bool (*IndexBulkDeleteCallback) (ItemPointer itemptr,
+                                         BlockNumber index_blkno,
+                                         void *state);

Then added 2 more fields to LVRelStats:

@@ -143,6 +143,8 @@ typedef struct LVRelStats
int         num_index_scans;
TransactionId latestRemovedXid;
bool        lock_waiter_detected;
+    BlockNumber last_index_blkno;
+    BlockNumber index_blks_vacuumed;

Then in lazy_tid_reaped(), if the index block number received in the
index_blkno argument has changed from the previous call, increment the
count of index blocks processed and
pgstat_report_update_param(index_blks_done). I wonder if we should reset
the the saved block number and the count for every index vacuumed by
lazy_vacuum_index(). Right now, total_index_blks counts all indexes and
counting blocks using the rough method mentioned above is sensible only
for one index at time. Actually, the method has different kinds of
problems to deal with anyway. For example, with a btree index, one can
expect that the final count does not match total_index_blks obtained using
RelationGetNumberOfBlocks(). Moreover, each AM has its own idiosyncratic
way of traversing the index pages. I dared only touch the btree case to
make it pass current block number to the callback. It finishes with
index_blks_done << total_index_blks since I guess the callback is called
only on the leaf pages. Any ideas?

To the contrary, I suppose it counts one index page more than
once for the cases of uncorrelated heaps. index_blks_vacuumd can
exceed RelationGetNumberOfBlocks() in extreme cases. If I'm not
missing something, it stands on a quite fragile graound.

* I am also tempted to add num_dead_tuples and dead_tuples_vacuumed to add
granularity to 'vacuuming heap' phase but didn't in this version. Should we?

How do you think they are to be used?

reagards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#184Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#182)
Re: [PROPOSAL] VACUUM Progress Checker.

On Tue, Mar 8, 2016 at 3:02 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Updated versions attached.

* changed st_progress_param to int64 and so did the argument of
pgstat_progress_update_param(). Likewise changed param1..param10 of
pg_stat_get_progress_info()'s output columns to bigint.

* Added back the Oid field st_command_target and corresponding function
pgstat_progress_set_command_target(Oid).

What the heck do we have an SQL-visible pg_stat_reset_local_progress()
for? Surely if we ever need that, it's a bug.

I think pgstat_progress_update_param() should Assert(index >= 0 &&
index < N_PROGRESS_PARAM). But I'd rename N_PROGRESS_PARAM to
PGSTAT_NUM_PROGRESS_PARAM.

Regarding "XXX - privilege check is maybe dubious" - I think the
privilege check here should match pg_stat_activity. If it does,
there's nothing dubious about that IMHO.

This patch has been worked on by so many people and reviewed by so
many people that I can't keep track of who should be credited when it
gets committed. Could someone provide a list of author(s) and
reviewer(s)?

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#185Amit Langote
amitlangote09@gmail.com
In reply to: Robert Haas (#184)
Re: [PROPOSAL] VACUUM Progress Checker.

On Wed, Mar 9, 2016 at 12:24 AM, Robert Haas <robertmhaas@gmail.com> wrote:

This patch has been worked on by so many people and reviewed by so
many people that I can't keep track of who should be credited when it
gets committed. Could someone provide a list of author(s) and
reviewer(s)?

Original authors are Rahila Syed and Vinayak Pokale.

I have been reviewing this for last few CFs. I sent in last few
revisions as well.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#186Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Amit Langote (#185)
Re: [PROPOSAL] VACUUM Progress Checker.

At Wed, 9 Mar 2016 01:16:26 +0900, Amit Langote <amitlangote09@gmail.com> wrote in <CA+HiwqGP3MzhvhVQf5EEzMPUk13BjxFaDGd1AXxZdQDaan30Ow@mail.gmail.com>

On Wed, Mar 9, 2016 at 12:24 AM, Robert Haas <robertmhaas@gmail.com> wrote:

This patch has been worked on by so many people and reviewed by so
many people that I can't keep track of who should be credited when it
gets committed. Could someone provide a list of author(s) and
reviewer(s)?

Original authors are Rahila Syed and Vinayak Pokale.

I have been reviewing this for last few CFs. I sent in last few
revisions as well.

The owner of this is Vinayak and, ah, I forgot to add myself as a
reviewer. I have also reviewed this for last few CFs.

So, as looking into CF app, it seems not so inconsistent with the
persons who appears in this thread for thses three CFs.

Authors: Vinayak Pokale, Rahila Syed, Amit Langote
Reviewers: Amit Langote, Kyotaro Horiguchi

Is there anyone who shold be added in this list?

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#187Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#184)
3 attachment(s)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2016/03/09 0:24, Robert Haas wrote:

On Tue, Mar 8, 2016 at 3:02 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Updated versions attached.

* changed st_progress_param to int64 and so did the argument of
pgstat_progress_update_param(). Likewise changed param1..param10 of
pg_stat_get_progress_info()'s output columns to bigint.

* Added back the Oid field st_command_target and corresponding function
pgstat_progress_set_command_target(Oid).

What the heck do we have an SQL-visible pg_stat_reset_local_progress()
for? Surely if we ever need that, it's a bug.

OK, now I am not sure what I was thinking adding that function. Removed.

I think pgstat_progress_update_param() should Assert(index >= 0 &&
index < N_PROGRESS_PARAM). But I'd rename N_PROGRESS_PARAM to
PGSTAT_NUM_PROGRESS_PARAM.

Agreed, done.

Regarding "XXX - privilege check is maybe dubious" - I think the
privilege check here should match pg_stat_activity. If it does,
there's nothing dubious about that IMHO.

OK, done. So, it shows pid column to all, while rest of the values -
relid, param1..param10 are only shown to role members. Unlike
pg_stat_activity, there is no text column to stash a "<insufficient
privilege>" message into, so all that's done is to output null values.

The attached revision addresses above and one of Horiguchi-san's comments
in his email yesterday.

Thanks a lot for the review.

Thanks,
Amit

Attachments:

0001-Provide-a-way-for-utility-commands-to-report-progres-v5.patchtext/x-diff; name=0001-Provide-a-way-for-utility-commands-to-report-progres-v5.patchDownload
From 9473230af72e0a0e3b60a8ddf1922698f7f17145 Mon Sep 17 00:00:00 2001
From: Amit <amitlangote09@gmail.com>
Date: Sun, 28 Feb 2016 01:50:07 +0900
Subject: [PATCH 1/3] Provide a way for utility commands to report progress

Commands can update a set of parameters in shared memory using:

  pgstat_progress_update_param(param_index, param_value)

Up to 10 independent 64-bit integer values can be published by commands.
In addition to those, a command should always report its BackendCommandType
and the OID of the relation it is about to begin processing at the beginning
of the processing using:

  pgstat_progress_start_command(cmdtype)
  pgstat_progress_set_command_target(relid)

A view can be defined in system_views.sql that outputs the values returned
by pg_stat_get_progress_info(cmdtype), where 'cmdtype' is numeric value as
mentioned above.  Each such view has columns corresponding to the counters
published by respective commands.
---
 src/backend/postmaster/pgstat.c     |   60 +++++++++++++++++++++++
 src/backend/utils/adt/pgstatfuncs.c |   91 +++++++++++++++++++++++++++++++++++
 src/include/catalog/pg_proc.h       |    2 +
 src/include/pgstat.h                |   25 ++++++++++
 4 files changed, 178 insertions(+), 0 deletions(-)

diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index da768c6..a7d0133 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -2731,6 +2731,7 @@ pgstat_bestart(void)
 	beentry->st_clienthostname[NAMEDATALEN - 1] = '\0';
 	beentry->st_appname[NAMEDATALEN - 1] = '\0';
 	beentry->st_activity[pgstat_track_activity_query_size - 1] = '\0';
+	beentry->st_command = COMMAND_INVALID;
 
 	pgstat_increment_changecount_after(beentry);
 
@@ -2851,6 +2852,65 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
 	pgstat_increment_changecount_after(beentry);
 }
 
+/*-----------
+ * pgstat_progress_update_param() -
+ *
+ * Update index'th member in st_progress_param[] of own backend entry.
+ *-----------
+ */
+void
+pgstat_progress_update_param(int index, int64 val)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	if(!beentry)
+		return;
+
+	if (!pgstat_track_activities)
+		return;
+
+	Assert(index >= 0 && index < PGSTAT_NUM_PROGRESS_PARAM);
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_progress_param[index] = val;
+	pgstat_increment_changecount_after(beentry);
+}
+
+/*-----------
+ * pgstat_progress_start_command()-
+ *
+ * Set st_command in own backend entry.  Also, zero-initialize
+ * st_progress_param array.
+ *-----------
+ */
+void
+pgstat_progress_start_command(BackendCommandType cmdtype)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_command = cmdtype;
+	MemSet(&beentry->st_progress_param, 0,
+		   sizeof(beentry->st_progress_param));
+	pgstat_increment_changecount_after(beentry);
+}
+
+/*-----------
+ * pgstat_progress_set_command_target()-
+ *
+ * Set st_command_target in own backend entry.
+ *-----------
+ */
+void
+pgstat_progress_set_command_target(Oid relid)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_command_target = relid;
+	pgstat_increment_changecount_after(beentry);
+}
+
 /* ----------
  * pgstat_report_appname() -
  *
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 1b22fcc..a12310d 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -64,6 +64,7 @@ extern Datum pg_stat_get_backend_xact_start(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_start(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_client_addr(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_client_port(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_progress_info(PG_FUNCTION_ARGS);
 
 extern Datum pg_stat_get_db_numbackends(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_db_xact_commit(PG_FUNCTION_ARGS);
@@ -525,6 +526,96 @@ pg_stat_get_backend_idset(PG_FUNCTION_ARGS)
 }
 
 /*
+ * Returns progress parameter values of backends running a given command
+ */
+Datum
+pg_stat_get_progress_info(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_PROGRESS_COLS	PGSTAT_NUM_PROGRESS_PARAM + 2
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int32		cmdtype = PG_GETARG_INT32(0);
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+
+	/* 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);
+
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		LocalPgBackendStatus   *local_beentry;
+		PgBackendStatus		   *beentry;
+		Datum		values[PG_STAT_GET_PROGRESS_COLS];
+		bool		nulls[PG_STAT_GET_PROGRESS_COLS];
+		int			i;
+
+		MemSet(values, 0, sizeof(values));
+		MemSet(nulls, 0, sizeof(nulls));
+
+		local_beentry = pgstat_fetch_stat_local_beentry(curr_backend);
+
+		if (!local_beentry)
+			continue;
+
+		beentry = &local_beentry->backendStatus;
+
+		/*
+		 * Report values for only those backends which are running the given
+		 * command.
+		 */
+		if (!beentry || beentry->st_command != cmdtype)
+			continue;
+
+		/* Value available to all callers */
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		/* show rest of the values including relid only to role members */
+		if (has_privs_of_role(GetUserId(), beentry->st_userid))
+		{
+			values[1] = ObjectIdGetDatum(beentry->st_command_target);
+			for(i = 0; i < PGSTAT_NUM_PROGRESS_PARAM; i++)
+				values[i+2] = UInt32GetDatum(beentry->st_progress_param[i]);
+		}
+		else
+		{
+			for (i = 1; i < PGSTAT_NUM_PROGRESS_PARAM + 1; i++)
+				nulls[i] = true;
+		}
+
+		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+	}
+
+	/* clean up and return the tuplestore */
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+/*
  * Returns activity of PG backends.
  */
 Datum
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index cbbb883..61a310a 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2710,6 +2710,8 @@ DATA(insert OID = 1936 (  pg_stat_get_backend_idset		PGNSP PGUID 12 1 100 0 0 f
 DESCR("statistics: currently active backend IDs");
 DATA(insert OID = 2022 (  pg_stat_get_activity			PGNSP PGUID 12 1 100 0 0 f f f f f t s r 1 0 2249 "23" "{23,26,23,26,25,25,25,16,1184,1184,1184,1184,869,25,23,28,28,16,25,25,23,16,25}" "{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{pid,datid,pid,usesysid,application_name,state,query,waiting,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,ssl,sslversion,sslcipher,sslbits,sslcompression,sslclientdn}" _null_ _null_ pg_stat_get_activity _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active backends");
+DATA(insert OID = 3318 (  pg_stat_get_progress_info           PGNSP PGUID 12 1 100 0 0 f f f f f t s r 1 0 2249 "23" "{23,23,26,20,20,20,20,20,20,20,20,20,20}" "{i,o,o,o,o,o,o,o,o,o,o,o,o}" "{cmdtype,pid,relid,param1,param2,param3,param4,param5,param6,param7,param8,param9,param10}" _null_ _null_ pg_stat_get_progress_info _null_ _null_ _null_ ));
+DESCR("statistics: information about progress of backends running maintenance command");
 DATA(insert OID = 3099 (  pg_stat_get_wal_senders	PGNSP PGUID 12 1 10 0 0 f f f f f t s r 0 0 2249 "" "{23,25,3220,3220,3220,3220,23,25}" "{o,o,o,o,o,o,o,o}" "{pid,state,sent_location,write_location,flush_location,replay_location,sync_priority,sync_state}" _null_ _null_ pg_stat_get_wal_senders _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active replication");
 DATA(insert OID = 3317 (  pg_stat_get_wal_receiver	PGNSP PGUID 12 1 0 0 0 f f f f f f s r 0 0 2249 "" "{23,25,3220,23,3220,23,1184,1184,3220,1184,25}" "{o,o,o,o,o,o,o,o,o,o,o}" "{pid,status,receive_start_lsn,receive_start_tli,received_lsn,received_tli,last_msg_send_time,last_msg_receipt_time,latest_end_lsn,latest_end_time,slot_name}" _null_ _null_ pg_stat_get_wal_receiver _null_ _null_ _null_ ));
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 65e968e..c529e66 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -696,6 +696,17 @@ typedef enum BackendState
 } BackendState;
 
 /* ----------
+ * Command type for progress reporting purposes
+ * ----------
+ */
+typedef enum BackendCommandType
+{
+	COMMAND_INVALID = 0,
+} BackendCommandType;
+
+#define PGSTAT_NUM_PROGRESS_PARAM	10
+
+/* ----------
  * Shared-memory data structures
  * ----------
  */
@@ -776,6 +787,17 @@ typedef struct PgBackendStatus
 
 	/* current command string; MUST be null-terminated */
 	char	   *st_activity;
+
+	/*
+	 * Progress parameters of currently running utility command in the backend
+	 * Different commands store different number of up to
+	 * PGSTAT_NUM_PROGRESS_PARAM values in st_progress_param.  However, each
+	 * command must set st_command and st_command_target (OID of the relation
+	 * it is about to begin to process) at the beginning of processing.
+	 */
+	BackendCommandType	st_command;
+	Oid					st_command_target;
+	int64				st_progress_param[PGSTAT_NUM_PROGRESS_PARAM];
 } PgBackendStatus;
 
 /*
@@ -935,6 +957,9 @@ extern void pgstat_report_waiting(bool waiting);
 extern const char *pgstat_get_backend_current_activity(int pid, bool checkUser);
 extern const char *pgstat_get_crashed_backend_activity(int pid, char *buffer,
 									int buflen);
+extern void pgstat_progress_start_command(BackendCommandType cmdtype);
+extern void pgstat_progress_set_command_target(Oid relid);
+extern void pgstat_progress_update_param(int index, int64 val);
 
 extern PgStat_TableStatus *find_tabstat_entry(Oid rel_id);
 extern PgStat_BackendFunctionEntry *find_funcstat_entry(Oid func_id);
-- 
1.7.1

0002-WIP-Implement-progress-reporting-for-VACUUM-command-v5.patchtext/x-diff; name=0002-WIP-Implement-progress-reporting-for-VACUUM-command-v5.patchDownload
From 6016d554a5aa269db6916eec7130910eabc85237 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 7 Mar 2016 14:38:34 +0900
Subject: [PATCH 2/3] WIP: Implement progress reporting for VACUUM command.

This basically utilizes the pgstat_progress* API to report a handful of
paramters to indicate its progress.  lazy_vacuum_rel() and lazy_scan_heap()
have been altered to report command start, command target table, and the
following parameters: processing phase, number of heap blocks, number of
index blocks (all indexes), current heap block number in the main scan loop
(whenever changes), index blocks vacuumed (once per finished index vacuum),
and number of index vacuum passes (every time when all indexes are vacuumed).
Following processing phases are identified and reported whenever one changes
to another: 'scanning heap', 'vacuuming indexes', 'vacuuming heap', 'cleanup',
'done'. The last value is really a misnomer but maybe clearer when someone is
staring at the progress view being polled.

TODO: find a way to report index pages vacuumed in a more granular manner than
the current report per index vacuumed.

A view named pg_stat_vacuum_progress has been added that shows these values.
---
 doc/src/sgml/monitoring.sgml         |  114 ++++++++++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   23 +++++++
 src/backend/commands/vacuumlazy.c    |   80 +++++++++++++++++++++++-
 src/include/pgstat.h                 |    1 +
 src/test/regress/expected/rules.out  |   20 ++++++
 5 files changed, 237 insertions(+), 1 deletions(-)

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 85459d0..d60efbe 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -507,6 +507,13 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       yet included in <structname>pg_stat_user_functions</>).</entry>
      </row>
 
+     <row>
+      <entry><structname>pg_stat_vacuum_progress</><indexterm><primary>pg_stat_vacuum_progress</primary></indexterm></entry>
+      <entry>One row for each backend (including autovacuum worker processes) running
+      <command>VACUUM</>, showing current progress in terms of heap pages it
+      has finished processing.  Note that the backends running
+      <command>VACUUM FULL</> are not included.</entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -1822,6 +1829,113 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
    controls exactly which functions are tracked.
   </para>
 
+  <table id="pg-stat-vacuum-progress" xreflabel="pg_stat_vacuum_progress">
+   <title><structname>pg_stat_vacuum_progress</structname> View</title>
+   <tgroup cols="3">
+    <thead>
+    <row>
+      <entry>Column</entry>
+      <entry>Type</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+   <tbody>
+    <row>
+     <entry><structfield>pid</></entry>
+     <entry><type>integer</></entry>
+     <entry>Process ID of backend</entry>
+    </row>
+    <row>
+     <entry><structfield>relid</></entry>
+     <entry><type>oid</></entry>
+     <entry>OID of a table</entry>
+    </row>
+    <row>
+     <entry><structfield>processing_phase</></entry>
+     <entry><type>integer</></entry>
+     <entry>Current phase of vacuum.
+       Possible values are:
+       <itemizedlist>
+        <listitem>
+         <para>
+          <literal>scanning heap</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>vacuuming indexes</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>vacuuming heap</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>cleanup</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>done</>
+         </para>
+        </listitem>
+       </itemizedlist>
+     </entry>
+    </row>
+    <row>
+     <entry><structfield>total_heap_blks</></entry>
+     <entry><type>integer</></entry>
+     <entry>Total number of heap blocks in the table</entry>
+    </row>
+    <row>
+     <entry><structfield>current_heap_blkno</></entry>
+     <entry><type>integer</></entry>
+     <entry>Current heap block being processed</entry>
+    </row>
+    <row>
+     <entry><structfield>total_index_blks</></entry>
+     <entry><type>integer</></entry>
+     <entry>Total number of index blocks to be processed</entry>
+    </row>
+    <row>
+     <entry><structfield>index_blks_done</></entry>
+     <entry><type>integer</></entry>
+     <entry>Number of index blocks processed</entry>
+    </row>
+    <row>
+     <entry><structfield>index_scan_count</></entry>
+     <entry><type>integer</></entry>
+     <entry>Number of times index scans has been performed so far</entry>
+    </row>
+    <row>
+     <entry><structfield>percent_done</></entry>
+     <entry><type>numeric</></entry>
+     <entry>
+      Amount of work finished in percent in terms of table blocks processed
+     </entry>
+    </row>
+   </tbody>
+   </tgroup>
+  </table>
+
+  <para>
+   The <structname>pg_stat_vacuum_progress</structname> view will contain
+   one row for each backend (including autovacuum worker processes), showing
+   progress of <command>VACUUM</> running in it. Note that the backends
+   running <command>VACUUM FULL</> are not shown.
+  </para>
+
+  <para>
+   When interpreting the value of the <structfield>percent_done</> column, also
+   note the value of <structfield>processing_phase</>.  It's possible for the
+   former to be <literal>100.00</literal>, while the <command>VACUUM</> still
+   has not returned.  In that case, wait for the latter to turn to the value
+   <literal>done</literal>.
+  </para>
+
  </sect2>
 
  <sect2 id="monitoring-stats-functions">
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index abf9a70..f622a4a 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -971,3 +971,26 @@ RETURNS jsonb
 LANGUAGE INTERNAL
 STRICT IMMUTABLE
 AS 'jsonb_set';
+
+CREATE VIEW pg_stat_vacuum_progress AS
+    SELECT
+           S.pid AS pid,
+           S.relid AS relid,
+           CASE S.param1
+               WHEN 1 THEN 'scanning heap'
+               WHEN 2 THEN 'vacuuming indexes'
+               WHEN 3 THEN 'vacuuming heap'
+               WHEN 4 THEN 'cleanup'
+               WHEN 5 THEN 'done'
+               ELSE 'unknown'
+           END AS processing_phase,
+           S.param2 AS total_heap_blks,
+           S.param3 AS current_heap_blkno,
+           S.param4 AS total_index_blks,
+           S.param5 AS index_blks_done,
+           S.param6 AS index_scan_count,
+           CASE S.param2
+			  WHEN 0 THEN 100::numeric(5, 2)
+			  ELSE ((S.param3 + 1)::numeric / S.param2 * 100)::numeric(5, 2)
+		   END AS percent_done
+   FROM pg_stat_get_progress_info(1) AS S;
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index 8f7b248..60c3b3b 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -97,6 +97,28 @@
  */
 #define SKIP_PAGES_THRESHOLD	((BlockNumber) 32)
 
+/*
+ * Follwing progress parameters for lazy vacuum are reported to pgstat
+ */
+#define PROG_PAR_VAC_PHASE_ID			0
+#define PROG_PAR_VAC_HEAP_BLKS			1
+#define PROG_PAR_VAC_CUR_HEAP_BLK		2
+#define PROG_PAR_VAC_IDX_BLKS			3
+#define PROG_PAR_VAC_IDX_BLKS_DONE		4
+#define PROG_PAR_VAC_N_IDX_SCAN			5
+
+/*
+ * Following distinct phases of lazy vacuum are identified.  Although #1, #2
+ * and #3 run in a cyclical manner due to possibly limited memory to work
+ * with, wherein #1 is periodically interrupted to run #2 followed by #3
+ * and back, until all the blocks of the relations have been covered by #1.
+ */
+#define LV_PHASE_SCAN_HEAP			1
+#define LV_PHASE_VACUUM_INDEX		2
+#define LV_PHASE_VACUUM_HEAP		3
+#define LV_PHASE_CLEANUP			4
+#define LV_PHASE_DONE				5
+
 typedef struct LVRelStats
 {
 	/* hasindex = true means two-pass strategy; false means one-pass */
@@ -195,6 +217,10 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params,
 
 	Assert(params != NULL);
 
+	/* initialize pgstat progress info */
+	pgstat_progress_start_command(COMMAND_LAZY_VACUUM);
+	pgstat_progress_set_command_target(RelationGetRelid(onerel));
+
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
 	{
@@ -270,6 +296,9 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params,
 	/* Vacuum the Free Space Map */
 	FreeSpaceMapVacuum(onerel);
 
+	/* We're done doing any heavy handling, so report */
+	pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_DONE);
+
 	/*
 	 * Update statistics in pg_class.
 	 *
@@ -433,7 +462,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			   Relation *Irel, int nindexes, bool scan_all)
 {
 	BlockNumber nblocks,
-				blkno;
+				blkno,
+				total_index_blks,
+			   *current_index_blks;
 	HeapTupleData tuple;
 	char	   *relname;
 	BlockNumber empty_pages,
@@ -474,6 +505,24 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	lazy_space_alloc(vacrelstats, nblocks);
 	frozen = palloc(sizeof(xl_heap_freeze_tuple) * MaxHeapTuplesPerPage);
 
+	/* about to begin heap scan */
+	pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_SCAN_HEAP);
+
+	/* total_heap_blks */
+	pgstat_progress_update_param(PROG_PAR_VAC_HEAP_BLKS, nblocks);
+
+	/* total_index_blks */
+	current_index_blks = (BlockNumber *) palloc(nindexes * sizeof(BlockNumber));
+	total_index_blks = 0;
+	for (i = 0; i < nindexes; i++)
+	{
+		BlockNumber		nblocks = RelationGetNumberOfBlocks(Irel[i]);
+
+		current_index_blks[i] = nblocks;
+		total_index_blks += nblocks;
+	}
+	pgstat_progress_update_param(PROG_PAR_VAC_IDX_BLKS, total_index_blks);
+
 	/*
 	 * We want to skip pages that don't require vacuuming according to the
 	 * visibility map, but only when we can skip at least SKIP_PAGES_THRESHOLD
@@ -581,6 +630,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 
 		vacuum_delay_point();
 
+		/* current_heap_blkno: 0..nblocks-1 */
+		pgstat_progress_update_param(PROG_PAR_VAC_CUR_HEAP_BLK, blkno);
+
 		/*
 		 * If we are close to overrunning the available space for dead-tuple
 		 * TIDs, pause and do a cycle of vacuuming before we tackle this page.
@@ -604,11 +656,22 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			vacuum_log_cleanup_info(onerel, vacrelstats);
 
 			/* Remove index entries */
+			pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_VACUUM_INDEX);
 			for (i = 0; i < nindexes; i++)
+			{
 				lazy_vacuum_index(Irel[i],
 								  &indstats[i],
 								  vacrelstats);
+
+				pgstat_progress_update_param(PROG_PAR_VAC_IDX_BLKS_DONE,
+											 current_index_blks[i]);
+			}
+
+			pgstat_progress_update_param(PROG_PAR_VAC_N_IDX_SCAN,
+										 vacrelstats->num_index_scans+1);
+
 			/* Remove tuples from heap */
+			pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_VACUUM_HEAP);
 			lazy_vacuum_heap(onerel, vacrelstats);
 
 			/*
@@ -618,6 +681,10 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			 */
 			vacrelstats->num_dead_tuples = 0;
 			vacrelstats->num_index_scans++;
+
+			/* go back to scanning the heap */
+			pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID,
+										 LV_PHASE_SCAN_HEAP);
 		}
 
 		/*
@@ -1154,16 +1221,27 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		vacuum_log_cleanup_info(onerel, vacrelstats);
 
 		/* Remove index entries */
+		pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_VACUUM_INDEX);
 		for (i = 0; i < nindexes; i++)
+		{
 			lazy_vacuum_index(Irel[i],
 							  &indstats[i],
 							  vacrelstats);
+
+			pgstat_progress_update_param(PROG_PAR_VAC_IDX_BLKS_DONE,
+										 current_index_blks[i]);
+		}
+		pgstat_progress_update_param(PROG_PAR_VAC_N_IDX_SCAN,
+									 vacrelstats->num_index_scans + 1);
+
 		/* Remove tuples from heap */
+		pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_VACUUM_HEAP);
 		lazy_vacuum_heap(onerel, vacrelstats);
 		vacrelstats->num_index_scans++;
 	}
 
 	/* Do post-vacuum cleanup and statistics update for each index */
+	pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_CLEANUP);
 	for (i = 0; i < nindexes; i++)
 		lazy_cleanup_index(Irel[i], indstats[i], vacrelstats);
 
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index c529e66..fecac83 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -702,6 +702,7 @@ typedef enum BackendState
 typedef enum BackendCommandType
 {
 	COMMAND_INVALID = 0,
+	COMMAND_LAZY_VACUUM
 } BackendCommandType;
 
 #define PGSTAT_NUM_PROGRESS_PARAM	10
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 81bc5c9..0ef6c76 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1851,6 +1851,26 @@ pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
     pg_stat_all_tables.autoanalyze_count
    FROM pg_stat_all_tables
   WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
+pg_stat_vacuum_progress| SELECT s.pid,
+    s.relid,
+        CASE s.param1
+            WHEN 1 THEN 'scanning heap'::text
+            WHEN 2 THEN 'vacuuming indexes'::text
+            WHEN 3 THEN 'vacuuming heap'::text
+            WHEN 4 THEN 'cleanup'::text
+            WHEN 5 THEN 'done'::text
+            ELSE 'unknown'::text
+        END AS processing_phase,
+    s.param2 AS total_heap_blks,
+    s.param3 AS current_heap_blkno,
+    s.param4 AS total_index_blks,
+    s.param5 AS index_blks_done,
+    s.param6 AS index_scan_count,
+        CASE s.param2
+            WHEN 0 THEN (100)::numeric(5,2)
+            ELSE (((((s.param3 + 1))::numeric / (s.param2)::numeric) * (100)::numeric))::numeric(5,2)
+        END AS percent_done
+   FROM pg_stat_get_progress_info(1) s(pid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10);
 pg_stat_wal_receiver| SELECT s.pid,
     s.status,
     s.receive_start_lsn,
-- 
1.7.1

0003-WIP-Add-a-block-number-argument-to-index-bulk-delete-v5.patchtext/x-diff; name=0003-WIP-Add-a-block-number-argument-to-index-bulk-delete-v5.patchDownload
From a2b820f79f206489f3d1a9f2a97466480699f0a6 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 8 Mar 2016 15:23:38 +0900
Subject: [PATCH 3/3] WIP: Add a block number argument to index bulk delete callback.

Currently, index AMs can only pass a ItemPointer (heap tid) and a generic
pointer to caller specified struct.  Add a BlockNumber so that the callback
can use it for something.  For example, lazy_tid_reaped() now uses it to
track the progress of AM's bulk delete scan of an index by comparing it with
the last call's value that it stores in its private state on every change.
The private state being the LVRelStats struct that now has the corresponding
field.  On every change, the count of index blocks vacuumed is incremented
which is also a new field in LVRelStats. The count is reset for every index
vacuum round.

Then a pgstat_progress_update_param call has been added to lazy_tid_reaped,
that pushes the count on every increment.

Currently, only btree AM is changed to pass the block number, others pass
InvalidBlockNumber.
---
 src/backend/access/gin/ginvacuum.c    |    2 +-
 src/backend/access/gist/gistvacuum.c  |    2 +-
 src/backend/access/hash/hash.c        |    2 +-
 src/backend/access/nbtree/nbtree.c    |    2 +-
 src/backend/access/spgist/spgvacuum.c |    6 ++--
 src/backend/catalog/index.c           |    5 ++-
 src/backend/commands/vacuumlazy.c     |   36 ++++++++++++++------------------
 src/include/access/genam.h            |    4 ++-
 8 files changed, 29 insertions(+), 30 deletions(-)

diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c
index 6a4b98a..64e69d6 100644
--- a/src/backend/access/gin/ginvacuum.c
+++ b/src/backend/access/gin/ginvacuum.c
@@ -55,7 +55,7 @@ ginVacuumItemPointers(GinVacuumState *gvs, ItemPointerData *items,
 	 */
 	for (i = 0; i < nitem; i++)
 	{
-		if (gvs->callback(items + i, gvs->callback_state))
+		if (gvs->callback(items + i, InvalidBlockNumber, gvs->callback_state))
 		{
 			gvs->result->tuples_removed += 1;
 			if (!tmpitems)
diff --git a/src/backend/access/gist/gistvacuum.c b/src/backend/access/gist/gistvacuum.c
index 7947ff9..96e89c3 100644
--- a/src/backend/access/gist/gistvacuum.c
+++ b/src/backend/access/gist/gistvacuum.c
@@ -202,7 +202,7 @@ gistbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 				iid = PageGetItemId(page, i);
 				idxtuple = (IndexTuple) PageGetItem(page, iid);
 
-				if (callback(&(idxtuple->t_tid), callback_state))
+				if (callback(&(idxtuple->t_tid), InvalidBlockNumber, callback_state))
 					todelete[ntodelete++] = i;
 				else
 					stats->num_index_tuples += 1;
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 3d48c4f..1530f0b 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -575,7 +575,7 @@ loop_top:
 				itup = (IndexTuple) PageGetItem(page,
 												PageGetItemId(page, offno));
 				htup = &(itup->t_tid);
-				if (callback(htup, callback_state))
+				if (callback(htup, InvalidBlockNumber, callback_state))
 				{
 					/* mark the item for deletion */
 					deletable[ndeletable++] = offno;
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index f2905cb..8c6be30 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -1034,7 +1034,7 @@ restart:
 				 * applies to *any* type of index that marks index tuples as
 				 * killed.
 				 */
-				if (callback(htup, callback_state))
+				if (callback(htup, blkno, callback_state))
 					deletable[ndeletable++] = offnum;
 			}
 		}
diff --git a/src/backend/access/spgist/spgvacuum.c b/src/backend/access/spgist/spgvacuum.c
index 15b867f..6c7265b 100644
--- a/src/backend/access/spgist/spgvacuum.c
+++ b/src/backend/access/spgist/spgvacuum.c
@@ -154,7 +154,7 @@ vacuumLeafPage(spgBulkDeleteState *bds, Relation index, Buffer buffer,
 		{
 			Assert(ItemPointerIsValid(&lt->heapPtr));
 
-			if (bds->callback(&lt->heapPtr, bds->callback_state))
+			if (bds->callback(&lt->heapPtr, InvalidBlockNumber, bds->callback_state))
 			{
 				bds->stats->tuples_removed += 1;
 				deletable[i] = true;
@@ -424,7 +424,7 @@ vacuumLeafRoot(spgBulkDeleteState *bds, Relation index, Buffer buffer)
 		{
 			Assert(ItemPointerIsValid(&lt->heapPtr));
 
-			if (bds->callback(&lt->heapPtr, bds->callback_state))
+			if (bds->callback(&lt->heapPtr, InvalidBlockNumber, bds->callback_state))
 			{
 				bds->stats->tuples_removed += 1;
 				toDelete[xlrec.nDelete] = i;
@@ -902,7 +902,7 @@ spgbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 
 /* Dummy callback to delete no tuples during spgvacuumcleanup */
 static bool
-dummy_callback(ItemPointer itemptr, void *state)
+dummy_callback(ItemPointer itemptr, BlockNumber index_blkno, void *state)
 {
 	return false;
 }
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 31a1438..d792fdb 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -114,7 +114,8 @@ static void IndexCheckExclusion(Relation heapRelation,
 					IndexInfo *indexInfo);
 static inline int64 itemptr_encode(ItemPointer itemptr);
 static inline void itemptr_decode(ItemPointer itemptr, int64 encoded);
-static bool validate_index_callback(ItemPointer itemptr, void *opaque);
+static bool validate_index_callback(ItemPointer itemptr, BlockNumber index_blkno,
+						void *opaque);
 static void validate_index_heapscan(Relation heapRelation,
 						Relation indexRelation,
 						IndexInfo *indexInfo,
@@ -2913,7 +2914,7 @@ itemptr_decode(ItemPointer itemptr, int64 encoded)
  * validate_index_callback - bulkdelete callback to collect the index TIDs
  */
 static bool
-validate_index_callback(ItemPointer itemptr, void *opaque)
+validate_index_callback(ItemPointer itemptr, BlockNumber index_blkno, void *opaque)
 {
 	v_i_state  *state = (v_i_state *) opaque;
 	int64		encoded = itemptr_encode(itemptr);
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index 60c3b3b..d628822 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -143,6 +143,8 @@ typedef struct LVRelStats
 	int			num_index_scans;
 	TransactionId latestRemovedXid;
 	bool		lock_waiter_detected;
+	BlockNumber	last_index_blkno;
+	BlockNumber	index_blks_vacuumed;
 } LVRelStats;
 
 
@@ -176,7 +178,7 @@ static BlockNumber count_nondeletable_pages(Relation onerel,
 static void lazy_space_alloc(LVRelStats *vacrelstats, BlockNumber relblocks);
 static void lazy_record_dead_tuple(LVRelStats *vacrelstats,
 					   ItemPointer itemptr);
-static bool lazy_tid_reaped(ItemPointer itemptr, void *state);
+static bool lazy_tid_reaped(ItemPointer itemptr, BlockNumber index_blkno, void *state);
 static int	vac_cmp_itemptr(const void *left, const void *right);
 static bool heap_page_is_all_visible(Relation rel, Buffer buf,
 						 TransactionId *visibility_cutoff_xid, bool *all_frozen);
@@ -463,8 +465,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 {
 	BlockNumber nblocks,
 				blkno,
-				total_index_blks,
-			   *current_index_blks;
+				total_index_blks;
 	HeapTupleData tuple;
 	char	   *relname;
 	BlockNumber empty_pages,
@@ -512,15 +513,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	pgstat_progress_update_param(PROG_PAR_VAC_HEAP_BLKS, nblocks);
 
 	/* total_index_blks */
-	current_index_blks = (BlockNumber *) palloc(nindexes * sizeof(BlockNumber));
 	total_index_blks = 0;
 	for (i = 0; i < nindexes; i++)
-	{
-		BlockNumber		nblocks = RelationGetNumberOfBlocks(Irel[i]);
-
-		current_index_blks[i] = nblocks;
-		total_index_blks += nblocks;
-	}
+		total_index_blks += RelationGetNumberOfBlocks(Irel[i]);
 	pgstat_progress_update_param(PROG_PAR_VAC_IDX_BLKS, total_index_blks);
 
 	/*
@@ -657,16 +652,12 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 
 			/* Remove index entries */
 			pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_VACUUM_INDEX);
+			vacrelstats->index_blks_vacuumed = 0;	/* reset for this round */
 			for (i = 0; i < nindexes; i++)
-			{
 				lazy_vacuum_index(Irel[i],
 								  &indstats[i],
 								  vacrelstats);
 
-				pgstat_progress_update_param(PROG_PAR_VAC_IDX_BLKS_DONE,
-											 current_index_blks[i]);
-			}
-
 			pgstat_progress_update_param(PROG_PAR_VAC_N_IDX_SCAN,
 										 vacrelstats->num_index_scans+1);
 
@@ -1222,15 +1213,12 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 
 		/* Remove index entries */
 		pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_VACUUM_INDEX);
+		vacrelstats->index_blks_vacuumed = 0;	/* reset for this round */
 		for (i = 0; i < nindexes; i++)
-		{
 			lazy_vacuum_index(Irel[i],
 							  &indstats[i],
 							  vacrelstats);
 
-			pgstat_progress_update_param(PROG_PAR_VAC_IDX_BLKS_DONE,
-										 current_index_blks[i]);
-		}
 		pgstat_progress_update_param(PROG_PAR_VAC_N_IDX_SCAN,
 									 vacrelstats->num_index_scans + 1);
 
@@ -1922,11 +1910,19 @@ lazy_record_dead_tuple(LVRelStats *vacrelstats,
  *		Assumes dead_tuples array is in sorted order.
  */
 static bool
-lazy_tid_reaped(ItemPointer itemptr, void *state)
+lazy_tid_reaped(ItemPointer itemptr, BlockNumber index_blkno, void *state)
 {
 	LVRelStats *vacrelstats = (LVRelStats *) state;
 	ItemPointer res;
 
+	if (index_blkno != vacrelstats->last_index_blkno)
+	{
+		++vacrelstats->index_blks_vacuumed;
+		pgstat_progress_update_param(PROG_PAR_VAC_IDX_BLKS_DONE,
+									 vacrelstats->index_blks_vacuumed);
+		vacrelstats->last_index_blkno = index_blkno;
+	}
+
 	res = (ItemPointer) bsearch((void *) itemptr,
 								(void *) vacrelstats->dead_tuples,
 								vacrelstats->num_dead_tuples,
diff --git a/src/include/access/genam.h b/src/include/access/genam.h
index 81907d5..8d67d1c 100644
--- a/src/include/access/genam.h
+++ b/src/include/access/genam.h
@@ -77,7 +77,9 @@ typedef struct IndexBulkDeleteResult
 } IndexBulkDeleteResult;
 
 /* Typedef for callback function to determine if a tuple is bulk-deletable */
-typedef bool (*IndexBulkDeleteCallback) (ItemPointer itemptr, void *state);
+typedef bool (*IndexBulkDeleteCallback) (ItemPointer itemptr,
+										 BlockNumber index_blkno,
+										 void *state);
 
 /* struct definitions appear in relscan.h */
 typedef struct IndexScanDescData *IndexScanDesc;
-- 
1.7.1

#188Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Kyotaro HORIGUCHI (#186)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2016/03/09 9:22, Kyotaro HORIGUCHI wrote:

On Wed, Mar 9, 2016 at 12:24 AM, Robert Haas <robertmhaas@gmail.com> wrote:

This patch has been worked on by so many people and reviewed by so
many people that I can't keep track of who should be credited when it
gets committed. Could someone provide a list of author(s) and
reviewer(s)?

Original authors are Rahila Syed and Vinayak Pokale.

I have been reviewing this for last few CFs. I sent in last few
revisions as well.

The owner of this is Vinayak and, ah, I forgot to add myself as a
reviewer. I have also reviewed this for last few CFs.

So, as looking into CF app, it seems not so inconsistent with the
persons who appears in this thread for thses three CFs.

Authors: Vinayak Pokale, Rahila Syed, Amit Langote
Reviewers: Amit Langote, Kyotaro Horiguchi

Is there anyone who shold be added in this list?

Jim Nasby, Thom Brown, Masahiko Sawada, Fujii Masao, Masanori Oyama and of
course, Robert himself.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#189Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Kyotaro HORIGUCHI (#183)
Re: [PROPOSAL] VACUUM Progress Checker.

Horiguchi-san,

Thanks for the review!

On 2016/03/08 18:19, Kyotaro HORIGUCHI wrote:

Updated versions attached.

* changed st_progress_param to int64 and so did the argument of
pgstat_progress_update_param(). Likewise changed param1..param10 of
pg_stat_get_progress_info()'s output columns to bigint.

* Added back the Oid field st_command_target and corresponding function
pgstat_progress_set_command_target(Oid).

+	beentry->st_command = COMMAND_INVALID;
+	MemSet(&beentry->st_progress_param, 0, sizeof(beentry->st_progress_param));

The MemSet seems useless since it gets the same initialization on
setting st_command.

Right, every backend start should not have to pay that price. Fixed in
the latest version.

+		/*
+		 * Report values for only those backends which are running the given
+		 * command.  XXX - privilege check is maybe dubious.
+		 */
+		if (!beentry ||
+			beentry->st_command != cmdtype ||
+			!has_privs_of_role(GetUserId(), beentry->st_userid))
+			continue;

We can simplly ignore unpriviledged backends, or all zeroz or
nulls to signal that the caller has no priviledge.

As suggested by Robert, used pg_stat_get_activity() style. In this case,
show 'pid' to all but the rest only to role members.

0002

+ FROM pg_stat_get_progress_info(1) AS S;

Ah... This magick number seems quite bad.. The function should
take the command type in maybe string type.

+ FROM pg_stat_get_progress_info('lazy vacuum') AS S;

Using an array of the names would be acceptable, maybe.

| char *progress_command_names[] = {'lazy vacuum', NULL};

Hm, I think the way it is *may* be OK the way it is, but...

As done in the patch, the way we identify commands is with the enum
BackendCommandType:

+typedef enum BackendCommandType
+{
+    COMMAND_INVALID = 0,
+    COMMAND_LAZY_VACUUM
+} BackendCommandType;

Perhaps we could create a struct:

typedef struct PgStatProgressCommand
{
char *cmd_name;
BackendCommandType cmd_type;
} PgStatProgressCommand;

static const struct PgStatProgressCommand commands[] = {
{"vacuum", COMMAND_LAZY_VACUUM},
{NULL, COMMAND_INVALID}
};

However the numbers for the phases ('scanning heap' and so..) is
acceptable for me for reasons uncertain to me, it also could be
represented in names but is might be rahter bothersome..

In initial versions of the patch, it used to be char * that was passed for
identifying phases. But, then we got rid of char * progress parameters
altogether. So, there are no longer any text columns in
pg_stat_get_progress_info()'s result. It may not work out well in long
run to forever not have those (your recent example comes to mind).

+			  WHEN 0 THEN 100::numeric(5, 2)
+			  ELSE ((S.param3 + 1)::numeric / S.param2 * 100)::numeric(5, 2)

This usage of numeric seems overkill to me.

Hmm, how could this rather be written?

* I attempted to implement a method to report index blocks done from
lazy_tid_reaped() albeit with some limitations. Patch 0003 is that
attempt. In summary, I modified the index bulk delete callback interface
to receive a BlockNumber argument index_blkno:

[ snip ]

way of traversing the index pages. I dared only touch the btree case to
make it pass current block number to the callback. It finishes with
index_blks_done << total_index_blks since I guess the callback is called
only on the leaf pages. Any ideas?

To the contrary, I suppose it counts one index page more than
once for the cases of uncorrelated heaps. index_blks_vacuumd can
exceed RelationGetNumberOfBlocks() in extreme cases. If I'm not
missing something, it stands on a quite fragile graound.

Yeah, the method is not entirely foolproof yet.

* I am also tempted to add num_dead_tuples and dead_tuples_vacuumed to add
granularity to 'vacuuming heap' phase but didn't in this version. Should we?

How do you think they are to be used?

I just realized there are objections to some columns be counters for pages
and others counting tuples. So, I guess I withdraw. I am just worried
that 'vacuuming heap' phase may take arbitrarily long if dead tuples array
is big. Since we were thinking of adding more granularity to 'vacuuming
indexes' phase, I thought we should do for the former too.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#190Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Amit Langote (#189)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2016/03/08 18:19, Kyotaro HORIGUCHI wrote:

+			  WHEN 0 THEN 100::numeric(5, 2)
+			  ELSE ((S.param3 + 1)::numeric / S.param2 * 100)::numeric(5, 2)

This usage of numeric seems overkill to me.

Hmm, how could this rather be written?

OK, agreed about the overkill. Following might be better:

+ WHEN 0 THEN round(100.0, 2)
+ ELSE round((S.param3 + 1) * 100.0 / S.param2, 2)

Will update that patch.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#191Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Amit Langote (#187)
3 attachment(s)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2016/03/09 10:11, Amit Langote wrote:

The attached revision addresses above and one of Horiguchi-san's comments
in his email yesterday.

I fixed one more issue in 0002 per Horiguchi-san's comment. Sorry about
so many versions.

Thanks,
Amit

Attachments:

0001-Provide-a-way-for-utility-commands-to-report-progres-v6.patchtext/x-diff; name=0001-Provide-a-way-for-utility-commands-to-report-progres-v6.patchDownload
From 9473230af72e0a0e3b60a8ddf1922698f7f17145 Mon Sep 17 00:00:00 2001
From: Amit <amitlangote09@gmail.com>
Date: Sun, 28 Feb 2016 01:50:07 +0900
Subject: [PATCH 1/3] Provide a way for utility commands to report progress

Commands can update a set of parameters in shared memory using:

  pgstat_progress_update_param(param_index, param_value)

Up to 10 independent 64-bit integer values can be published by commands.
In addition to those, a command should always report its BackendCommandType
and the OID of the relation it is about to begin processing at the beginning
of the processing using:

  pgstat_progress_start_command(cmdtype)
  pgstat_progress_set_command_target(relid)

A view can be defined in system_views.sql that outputs the values returned
by pg_stat_get_progress_info(cmdtype), where 'cmdtype' is numeric value as
mentioned above.  Each such view has columns corresponding to the counters
published by respective commands.
---
 src/backend/postmaster/pgstat.c     |   60 +++++++++++++++++++++++
 src/backend/utils/adt/pgstatfuncs.c |   91 +++++++++++++++++++++++++++++++++++
 src/include/catalog/pg_proc.h       |    2 +
 src/include/pgstat.h                |   25 ++++++++++
 4 files changed, 178 insertions(+), 0 deletions(-)

diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index da768c6..a7d0133 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -2731,6 +2731,7 @@ pgstat_bestart(void)
 	beentry->st_clienthostname[NAMEDATALEN - 1] = '\0';
 	beentry->st_appname[NAMEDATALEN - 1] = '\0';
 	beentry->st_activity[pgstat_track_activity_query_size - 1] = '\0';
+	beentry->st_command = COMMAND_INVALID;
 
 	pgstat_increment_changecount_after(beentry);
 
@@ -2851,6 +2852,65 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
 	pgstat_increment_changecount_after(beentry);
 }
 
+/*-----------
+ * pgstat_progress_update_param() -
+ *
+ * Update index'th member in st_progress_param[] of own backend entry.
+ *-----------
+ */
+void
+pgstat_progress_update_param(int index, int64 val)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	if(!beentry)
+		return;
+
+	if (!pgstat_track_activities)
+		return;
+
+	Assert(index >= 0 && index < PGSTAT_NUM_PROGRESS_PARAM);
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_progress_param[index] = val;
+	pgstat_increment_changecount_after(beentry);
+}
+
+/*-----------
+ * pgstat_progress_start_command()-
+ *
+ * Set st_command in own backend entry.  Also, zero-initialize
+ * st_progress_param array.
+ *-----------
+ */
+void
+pgstat_progress_start_command(BackendCommandType cmdtype)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_command = cmdtype;
+	MemSet(&beentry->st_progress_param, 0,
+		   sizeof(beentry->st_progress_param));
+	pgstat_increment_changecount_after(beentry);
+}
+
+/*-----------
+ * pgstat_progress_set_command_target()-
+ *
+ * Set st_command_target in own backend entry.
+ *-----------
+ */
+void
+pgstat_progress_set_command_target(Oid relid)
+{
+	volatile PgBackendStatus *beentry = MyBEEntry;
+
+	pgstat_increment_changecount_before(beentry);
+	beentry->st_command_target = relid;
+	pgstat_increment_changecount_after(beentry);
+}
+
 /* ----------
  * pgstat_report_appname() -
  *
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 1b22fcc..a12310d 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -64,6 +64,7 @@ extern Datum pg_stat_get_backend_xact_start(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_start(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_client_addr(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_backend_client_port(PG_FUNCTION_ARGS);
+extern Datum pg_stat_get_progress_info(PG_FUNCTION_ARGS);
 
 extern Datum pg_stat_get_db_numbackends(PG_FUNCTION_ARGS);
 extern Datum pg_stat_get_db_xact_commit(PG_FUNCTION_ARGS);
@@ -525,6 +526,96 @@ pg_stat_get_backend_idset(PG_FUNCTION_ARGS)
 }
 
 /*
+ * Returns progress parameter values of backends running a given command
+ */
+Datum
+pg_stat_get_progress_info(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_PROGRESS_COLS	PGSTAT_NUM_PROGRESS_PARAM + 2
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int32		cmdtype = PG_GETARG_INT32(0);
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+
+	/* 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);
+
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		LocalPgBackendStatus   *local_beentry;
+		PgBackendStatus		   *beentry;
+		Datum		values[PG_STAT_GET_PROGRESS_COLS];
+		bool		nulls[PG_STAT_GET_PROGRESS_COLS];
+		int			i;
+
+		MemSet(values, 0, sizeof(values));
+		MemSet(nulls, 0, sizeof(nulls));
+
+		local_beentry = pgstat_fetch_stat_local_beentry(curr_backend);
+
+		if (!local_beentry)
+			continue;
+
+		beentry = &local_beentry->backendStatus;
+
+		/*
+		 * Report values for only those backends which are running the given
+		 * command.
+		 */
+		if (!beentry || beentry->st_command != cmdtype)
+			continue;
+
+		/* Value available to all callers */
+		values[0] = Int32GetDatum(beentry->st_procpid);
+
+		/* show rest of the values including relid only to role members */
+		if (has_privs_of_role(GetUserId(), beentry->st_userid))
+		{
+			values[1] = ObjectIdGetDatum(beentry->st_command_target);
+			for(i = 0; i < PGSTAT_NUM_PROGRESS_PARAM; i++)
+				values[i+2] = UInt32GetDatum(beentry->st_progress_param[i]);
+		}
+		else
+		{
+			for (i = 1; i < PGSTAT_NUM_PROGRESS_PARAM + 1; i++)
+				nulls[i] = true;
+		}
+
+		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+	}
+
+	/* clean up and return the tuplestore */
+	tuplestore_donestoring(tupstore);
+
+	return (Datum) 0;
+}
+
+/*
  * Returns activity of PG backends.
  */
 Datum
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index cbbb883..61a310a 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2710,6 +2710,8 @@ DATA(insert OID = 1936 (  pg_stat_get_backend_idset		PGNSP PGUID 12 1 100 0 0 f
 DESCR("statistics: currently active backend IDs");
 DATA(insert OID = 2022 (  pg_stat_get_activity			PGNSP PGUID 12 1 100 0 0 f f f f f t s r 1 0 2249 "23" "{23,26,23,26,25,25,25,16,1184,1184,1184,1184,869,25,23,28,28,16,25,25,23,16,25}" "{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{pid,datid,pid,usesysid,application_name,state,query,waiting,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,ssl,sslversion,sslcipher,sslbits,sslcompression,sslclientdn}" _null_ _null_ pg_stat_get_activity _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active backends");
+DATA(insert OID = 3318 (  pg_stat_get_progress_info           PGNSP PGUID 12 1 100 0 0 f f f f f t s r 1 0 2249 "23" "{23,23,26,20,20,20,20,20,20,20,20,20,20}" "{i,o,o,o,o,o,o,o,o,o,o,o,o}" "{cmdtype,pid,relid,param1,param2,param3,param4,param5,param6,param7,param8,param9,param10}" _null_ _null_ pg_stat_get_progress_info _null_ _null_ _null_ ));
+DESCR("statistics: information about progress of backends running maintenance command");
 DATA(insert OID = 3099 (  pg_stat_get_wal_senders	PGNSP PGUID 12 1 10 0 0 f f f f f t s r 0 0 2249 "" "{23,25,3220,3220,3220,3220,23,25}" "{o,o,o,o,o,o,o,o}" "{pid,state,sent_location,write_location,flush_location,replay_location,sync_priority,sync_state}" _null_ _null_ pg_stat_get_wal_senders _null_ _null_ _null_ ));
 DESCR("statistics: information about currently active replication");
 DATA(insert OID = 3317 (  pg_stat_get_wal_receiver	PGNSP PGUID 12 1 0 0 0 f f f f f f s r 0 0 2249 "" "{23,25,3220,23,3220,23,1184,1184,3220,1184,25}" "{o,o,o,o,o,o,o,o,o,o,o}" "{pid,status,receive_start_lsn,receive_start_tli,received_lsn,received_tli,last_msg_send_time,last_msg_receipt_time,latest_end_lsn,latest_end_time,slot_name}" _null_ _null_ pg_stat_get_wal_receiver _null_ _null_ _null_ ));
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 65e968e..c529e66 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -696,6 +696,17 @@ typedef enum BackendState
 } BackendState;
 
 /* ----------
+ * Command type for progress reporting purposes
+ * ----------
+ */
+typedef enum BackendCommandType
+{
+	COMMAND_INVALID = 0,
+} BackendCommandType;
+
+#define PGSTAT_NUM_PROGRESS_PARAM	10
+
+/* ----------
  * Shared-memory data structures
  * ----------
  */
@@ -776,6 +787,17 @@ typedef struct PgBackendStatus
 
 	/* current command string; MUST be null-terminated */
 	char	   *st_activity;
+
+	/*
+	 * Progress parameters of currently running utility command in the backend
+	 * Different commands store different number of up to
+	 * PGSTAT_NUM_PROGRESS_PARAM values in st_progress_param.  However, each
+	 * command must set st_command and st_command_target (OID of the relation
+	 * it is about to begin to process) at the beginning of processing.
+	 */
+	BackendCommandType	st_command;
+	Oid					st_command_target;
+	int64				st_progress_param[PGSTAT_NUM_PROGRESS_PARAM];
 } PgBackendStatus;
 
 /*
@@ -935,6 +957,9 @@ extern void pgstat_report_waiting(bool waiting);
 extern const char *pgstat_get_backend_current_activity(int pid, bool checkUser);
 extern const char *pgstat_get_crashed_backend_activity(int pid, char *buffer,
 									int buflen);
+extern void pgstat_progress_start_command(BackendCommandType cmdtype);
+extern void pgstat_progress_set_command_target(Oid relid);
+extern void pgstat_progress_update_param(int index, int64 val);
 
 extern PgStat_TableStatus *find_tabstat_entry(Oid rel_id);
 extern PgStat_BackendFunctionEntry *find_funcstat_entry(Oid func_id);
-- 
1.7.1

0002-WIP-Implement-progress-reporting-for-VACUUM-command-v6.patchtext/x-diff; name=0002-WIP-Implement-progress-reporting-for-VACUUM-command-v6.patchDownload
From 555e7f3917029cee3f6fdd771a0f0ee81e20a5e5 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 7 Mar 2016 14:38:34 +0900
Subject: [PATCH 2/3] WIP: Implement progress reporting for VACUUM command.

This basically utilizes the pgstat_progress* API to report a handful of
paramters to indicate its progress.  lazy_vacuum_rel() and lazy_scan_heap()
have been altered to report command start, command target table, and the
following parameters: processing phase, number of heap blocks, number of
index blocks (all indexes), current heap block number in the main scan loop
(whenever changes), index blocks vacuumed (once per finished index vacuum),
and number of index vacuum passes (every time when all indexes are vacuumed).
Following processing phases are identified and reported whenever one changes
to another: 'scanning heap', 'vacuuming indexes', 'vacuuming heap', 'cleanup',
'done'. The last value is really a misnomer but maybe clearer when someone is
staring at the progress view being polled.

TODO: find a way to report index pages vacuumed in a more granular manner than
the current report per index vacuumed.

A view named pg_stat_vacuum_progress has been added that shows these values.
---
 doc/src/sgml/monitoring.sgml         |  114 ++++++++++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   23 +++++++
 src/backend/commands/vacuumlazy.c    |   80 +++++++++++++++++++++++-
 src/include/pgstat.h                 |    1 +
 src/test/regress/expected/rules.out  |   20 ++++++
 5 files changed, 237 insertions(+), 1 deletions(-)

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 85459d0..d60efbe 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -507,6 +507,13 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       yet included in <structname>pg_stat_user_functions</>).</entry>
      </row>
 
+     <row>
+      <entry><structname>pg_stat_vacuum_progress</><indexterm><primary>pg_stat_vacuum_progress</primary></indexterm></entry>
+      <entry>One row for each backend (including autovacuum worker processes) running
+      <command>VACUUM</>, showing current progress in terms of heap pages it
+      has finished processing.  Note that the backends running
+      <command>VACUUM FULL</> are not included.</entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -1822,6 +1829,113 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
    controls exactly which functions are tracked.
   </para>
 
+  <table id="pg-stat-vacuum-progress" xreflabel="pg_stat_vacuum_progress">
+   <title><structname>pg_stat_vacuum_progress</structname> View</title>
+   <tgroup cols="3">
+    <thead>
+    <row>
+      <entry>Column</entry>
+      <entry>Type</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+   <tbody>
+    <row>
+     <entry><structfield>pid</></entry>
+     <entry><type>integer</></entry>
+     <entry>Process ID of backend</entry>
+    </row>
+    <row>
+     <entry><structfield>relid</></entry>
+     <entry><type>oid</></entry>
+     <entry>OID of a table</entry>
+    </row>
+    <row>
+     <entry><structfield>processing_phase</></entry>
+     <entry><type>integer</></entry>
+     <entry>Current phase of vacuum.
+       Possible values are:
+       <itemizedlist>
+        <listitem>
+         <para>
+          <literal>scanning heap</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>vacuuming indexes</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>vacuuming heap</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>cleanup</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>done</>
+         </para>
+        </listitem>
+       </itemizedlist>
+     </entry>
+    </row>
+    <row>
+     <entry><structfield>total_heap_blks</></entry>
+     <entry><type>integer</></entry>
+     <entry>Total number of heap blocks in the table</entry>
+    </row>
+    <row>
+     <entry><structfield>current_heap_blkno</></entry>
+     <entry><type>integer</></entry>
+     <entry>Current heap block being processed</entry>
+    </row>
+    <row>
+     <entry><structfield>total_index_blks</></entry>
+     <entry><type>integer</></entry>
+     <entry>Total number of index blocks to be processed</entry>
+    </row>
+    <row>
+     <entry><structfield>index_blks_done</></entry>
+     <entry><type>integer</></entry>
+     <entry>Number of index blocks processed</entry>
+    </row>
+    <row>
+     <entry><structfield>index_scan_count</></entry>
+     <entry><type>integer</></entry>
+     <entry>Number of times index scans has been performed so far</entry>
+    </row>
+    <row>
+     <entry><structfield>percent_done</></entry>
+     <entry><type>numeric</></entry>
+     <entry>
+      Amount of work finished in percent in terms of table blocks processed
+     </entry>
+    </row>
+   </tbody>
+   </tgroup>
+  </table>
+
+  <para>
+   The <structname>pg_stat_vacuum_progress</structname> view will contain
+   one row for each backend (including autovacuum worker processes), showing
+   progress of <command>VACUUM</> running in it. Note that the backends
+   running <command>VACUUM FULL</> are not shown.
+  </para>
+
+  <para>
+   When interpreting the value of the <structfield>percent_done</> column, also
+   note the value of <structfield>processing_phase</>.  It's possible for the
+   former to be <literal>100.00</literal>, while the <command>VACUUM</> still
+   has not returned.  In that case, wait for the latter to turn to the value
+   <literal>done</literal>.
+  </para>
+
  </sect2>
 
  <sect2 id="monitoring-stats-functions">
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index abf9a70..0da6447 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -971,3 +971,26 @@ RETURNS jsonb
 LANGUAGE INTERNAL
 STRICT IMMUTABLE
 AS 'jsonb_set';
+
+CREATE VIEW pg_stat_vacuum_progress AS
+    SELECT
+           S.pid AS pid,
+           S.relid AS relid,
+           CASE S.param1
+               WHEN 1 THEN 'scanning heap'
+               WHEN 2 THEN 'vacuuming indexes'
+               WHEN 3 THEN 'vacuuming heap'
+               WHEN 4 THEN 'cleanup'
+               WHEN 5 THEN 'done'
+               ELSE 'unknown'
+           END AS processing_phase,
+           S.param2 AS total_heap_blks,
+           S.param3 AS current_heap_blkno,
+           S.param4 AS total_index_blks,
+           S.param5 AS index_blks_done,
+           S.param6 AS index_scan_count,
+           CASE S.param2
+			  WHEN 0 THEN round(100.0, 2)
+			  ELSE round((S.param3 + 1) * 100.0 / S.param2, 2)
+		   END AS percent_done
+   FROM pg_stat_get_progress_info(1) AS S;
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index 8f7b248..60c3b3b 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -97,6 +97,28 @@
  */
 #define SKIP_PAGES_THRESHOLD	((BlockNumber) 32)
 
+/*
+ * Follwing progress parameters for lazy vacuum are reported to pgstat
+ */
+#define PROG_PAR_VAC_PHASE_ID			0
+#define PROG_PAR_VAC_HEAP_BLKS			1
+#define PROG_PAR_VAC_CUR_HEAP_BLK		2
+#define PROG_PAR_VAC_IDX_BLKS			3
+#define PROG_PAR_VAC_IDX_BLKS_DONE		4
+#define PROG_PAR_VAC_N_IDX_SCAN			5
+
+/*
+ * Following distinct phases of lazy vacuum are identified.  Although #1, #2
+ * and #3 run in a cyclical manner due to possibly limited memory to work
+ * with, wherein #1 is periodically interrupted to run #2 followed by #3
+ * and back, until all the blocks of the relations have been covered by #1.
+ */
+#define LV_PHASE_SCAN_HEAP			1
+#define LV_PHASE_VACUUM_INDEX		2
+#define LV_PHASE_VACUUM_HEAP		3
+#define LV_PHASE_CLEANUP			4
+#define LV_PHASE_DONE				5
+
 typedef struct LVRelStats
 {
 	/* hasindex = true means two-pass strategy; false means one-pass */
@@ -195,6 +217,10 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params,
 
 	Assert(params != NULL);
 
+	/* initialize pgstat progress info */
+	pgstat_progress_start_command(COMMAND_LAZY_VACUUM);
+	pgstat_progress_set_command_target(RelationGetRelid(onerel));
+
 	/* measure elapsed time iff autovacuum logging requires it */
 	if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)
 	{
@@ -270,6 +296,9 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params,
 	/* Vacuum the Free Space Map */
 	FreeSpaceMapVacuum(onerel);
 
+	/* We're done doing any heavy handling, so report */
+	pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_DONE);
+
 	/*
 	 * Update statistics in pg_class.
 	 *
@@ -433,7 +462,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			   Relation *Irel, int nindexes, bool scan_all)
 {
 	BlockNumber nblocks,
-				blkno;
+				blkno,
+				total_index_blks,
+			   *current_index_blks;
 	HeapTupleData tuple;
 	char	   *relname;
 	BlockNumber empty_pages,
@@ -474,6 +505,24 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	lazy_space_alloc(vacrelstats, nblocks);
 	frozen = palloc(sizeof(xl_heap_freeze_tuple) * MaxHeapTuplesPerPage);
 
+	/* about to begin heap scan */
+	pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_SCAN_HEAP);
+
+	/* total_heap_blks */
+	pgstat_progress_update_param(PROG_PAR_VAC_HEAP_BLKS, nblocks);
+
+	/* total_index_blks */
+	current_index_blks = (BlockNumber *) palloc(nindexes * sizeof(BlockNumber));
+	total_index_blks = 0;
+	for (i = 0; i < nindexes; i++)
+	{
+		BlockNumber		nblocks = RelationGetNumberOfBlocks(Irel[i]);
+
+		current_index_blks[i] = nblocks;
+		total_index_blks += nblocks;
+	}
+	pgstat_progress_update_param(PROG_PAR_VAC_IDX_BLKS, total_index_blks);
+
 	/*
 	 * We want to skip pages that don't require vacuuming according to the
 	 * visibility map, but only when we can skip at least SKIP_PAGES_THRESHOLD
@@ -581,6 +630,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 
 		vacuum_delay_point();
 
+		/* current_heap_blkno: 0..nblocks-1 */
+		pgstat_progress_update_param(PROG_PAR_VAC_CUR_HEAP_BLK, blkno);
+
 		/*
 		 * If we are close to overrunning the available space for dead-tuple
 		 * TIDs, pause and do a cycle of vacuuming before we tackle this page.
@@ -604,11 +656,22 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			vacuum_log_cleanup_info(onerel, vacrelstats);
 
 			/* Remove index entries */
+			pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_VACUUM_INDEX);
 			for (i = 0; i < nindexes; i++)
+			{
 				lazy_vacuum_index(Irel[i],
 								  &indstats[i],
 								  vacrelstats);
+
+				pgstat_progress_update_param(PROG_PAR_VAC_IDX_BLKS_DONE,
+											 current_index_blks[i]);
+			}
+
+			pgstat_progress_update_param(PROG_PAR_VAC_N_IDX_SCAN,
+										 vacrelstats->num_index_scans+1);
+
 			/* Remove tuples from heap */
+			pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_VACUUM_HEAP);
 			lazy_vacuum_heap(onerel, vacrelstats);
 
 			/*
@@ -618,6 +681,10 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			 */
 			vacrelstats->num_dead_tuples = 0;
 			vacrelstats->num_index_scans++;
+
+			/* go back to scanning the heap */
+			pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID,
+										 LV_PHASE_SCAN_HEAP);
 		}
 
 		/*
@@ -1154,16 +1221,27 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		vacuum_log_cleanup_info(onerel, vacrelstats);
 
 		/* Remove index entries */
+		pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_VACUUM_INDEX);
 		for (i = 0; i < nindexes; i++)
+		{
 			lazy_vacuum_index(Irel[i],
 							  &indstats[i],
 							  vacrelstats);
+
+			pgstat_progress_update_param(PROG_PAR_VAC_IDX_BLKS_DONE,
+										 current_index_blks[i]);
+		}
+		pgstat_progress_update_param(PROG_PAR_VAC_N_IDX_SCAN,
+									 vacrelstats->num_index_scans + 1);
+
 		/* Remove tuples from heap */
+		pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_VACUUM_HEAP);
 		lazy_vacuum_heap(onerel, vacrelstats);
 		vacrelstats->num_index_scans++;
 	}
 
 	/* Do post-vacuum cleanup and statistics update for each index */
+	pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_CLEANUP);
 	for (i = 0; i < nindexes; i++)
 		lazy_cleanup_index(Irel[i], indstats[i], vacrelstats);
 
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index c529e66..fecac83 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -702,6 +702,7 @@ typedef enum BackendState
 typedef enum BackendCommandType
 {
 	COMMAND_INVALID = 0,
+	COMMAND_LAZY_VACUUM
 } BackendCommandType;
 
 #define PGSTAT_NUM_PROGRESS_PARAM	10
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 81bc5c9..ec9a1ef 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1851,6 +1851,26 @@ pg_stat_user_tables| SELECT pg_stat_all_tables.relid,
     pg_stat_all_tables.autoanalyze_count
    FROM pg_stat_all_tables
   WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text));
+pg_stat_vacuum_progress| SELECT s.pid,
+    s.relid,
+        CASE s.param1
+            WHEN 1 THEN 'scanning heap'::text
+            WHEN 2 THEN 'vacuuming indexes'::text
+            WHEN 3 THEN 'vacuuming heap'::text
+            WHEN 4 THEN 'cleanup'::text
+            WHEN 5 THEN 'done'::text
+            ELSE 'unknown'::text
+        END AS processing_phase,
+    s.param2 AS total_heap_blks,
+    s.param3 AS current_heap_blkno,
+    s.param4 AS total_index_blks,
+    s.param5 AS index_blks_done,
+    s.param6 AS index_scan_count,
+        CASE s.param2
+            WHEN 0 THEN round(100.0, 2)
+            ELSE round(((((s.param3 + 1))::numeric * 100.0) / (s.param2)::numeric), 2)
+        END AS percent_done
+   FROM pg_stat_get_progress_info(1) s(pid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10);
 pg_stat_wal_receiver| SELECT s.pid,
     s.status,
     s.receive_start_lsn,
-- 
1.7.1

0003-WIP-Add-a-block-number-argument-to-index-bulk-delete-v6.patchtext/x-diff; name=0003-WIP-Add-a-block-number-argument-to-index-bulk-delete-v6.patchDownload
From b7c3d6854cdfabd232eda99ee2302e2ebbd765db Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 8 Mar 2016 15:23:38 +0900
Subject: [PATCH 3/3] WIP: Add a block number argument to index bulk delete callback.

Currently, index AMs can only pass a ItemPointer (heap tid) and a generic
pointer to caller specified struct.  Add a BlockNumber so that the callback
can use it for something.  For example, lazy_tid_reaped() now uses it to
track the progress of AM's bulk delete scan of an index by comparing it with
the last call's value that it stores in its private state on every change.
The private state being the LVRelStats struct that now has the corresponding
field.  On every change, the count of index blocks vacuumed is incremented
which is also a new field in LVRelStats. The count is reset for every index
vacuum round.

Then a pgstat_progress_update_param call has been added to lazy_tid_reaped,
that pushes the count on every increment.

Currently, only btree AM is changed to pass the block number, others pass
InvalidBlockNumber.
---
 src/backend/access/gin/ginvacuum.c    |    2 +-
 src/backend/access/gist/gistvacuum.c  |    2 +-
 src/backend/access/hash/hash.c        |    2 +-
 src/backend/access/nbtree/nbtree.c    |    2 +-
 src/backend/access/spgist/spgvacuum.c |    6 ++--
 src/backend/catalog/index.c           |    5 ++-
 src/backend/commands/vacuumlazy.c     |   36 ++++++++++++++------------------
 src/include/access/genam.h            |    4 ++-
 8 files changed, 29 insertions(+), 30 deletions(-)

diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c
index 6a4b98a..64e69d6 100644
--- a/src/backend/access/gin/ginvacuum.c
+++ b/src/backend/access/gin/ginvacuum.c
@@ -55,7 +55,7 @@ ginVacuumItemPointers(GinVacuumState *gvs, ItemPointerData *items,
 	 */
 	for (i = 0; i < nitem; i++)
 	{
-		if (gvs->callback(items + i, gvs->callback_state))
+		if (gvs->callback(items + i, InvalidBlockNumber, gvs->callback_state))
 		{
 			gvs->result->tuples_removed += 1;
 			if (!tmpitems)
diff --git a/src/backend/access/gist/gistvacuum.c b/src/backend/access/gist/gistvacuum.c
index 7947ff9..96e89c3 100644
--- a/src/backend/access/gist/gistvacuum.c
+++ b/src/backend/access/gist/gistvacuum.c
@@ -202,7 +202,7 @@ gistbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 				iid = PageGetItemId(page, i);
 				idxtuple = (IndexTuple) PageGetItem(page, iid);
 
-				if (callback(&(idxtuple->t_tid), callback_state))
+				if (callback(&(idxtuple->t_tid), InvalidBlockNumber, callback_state))
 					todelete[ntodelete++] = i;
 				else
 					stats->num_index_tuples += 1;
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 3d48c4f..1530f0b 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -575,7 +575,7 @@ loop_top:
 				itup = (IndexTuple) PageGetItem(page,
 												PageGetItemId(page, offno));
 				htup = &(itup->t_tid);
-				if (callback(htup, callback_state))
+				if (callback(htup, InvalidBlockNumber, callback_state))
 				{
 					/* mark the item for deletion */
 					deletable[ndeletable++] = offno;
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index f2905cb..8c6be30 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -1034,7 +1034,7 @@ restart:
 				 * applies to *any* type of index that marks index tuples as
 				 * killed.
 				 */
-				if (callback(htup, callback_state))
+				if (callback(htup, blkno, callback_state))
 					deletable[ndeletable++] = offnum;
 			}
 		}
diff --git a/src/backend/access/spgist/spgvacuum.c b/src/backend/access/spgist/spgvacuum.c
index 15b867f..6c7265b 100644
--- a/src/backend/access/spgist/spgvacuum.c
+++ b/src/backend/access/spgist/spgvacuum.c
@@ -154,7 +154,7 @@ vacuumLeafPage(spgBulkDeleteState *bds, Relation index, Buffer buffer,
 		{
 			Assert(ItemPointerIsValid(&lt->heapPtr));
 
-			if (bds->callback(&lt->heapPtr, bds->callback_state))
+			if (bds->callback(&lt->heapPtr, InvalidBlockNumber, bds->callback_state))
 			{
 				bds->stats->tuples_removed += 1;
 				deletable[i] = true;
@@ -424,7 +424,7 @@ vacuumLeafRoot(spgBulkDeleteState *bds, Relation index, Buffer buffer)
 		{
 			Assert(ItemPointerIsValid(&lt->heapPtr));
 
-			if (bds->callback(&lt->heapPtr, bds->callback_state))
+			if (bds->callback(&lt->heapPtr, InvalidBlockNumber, bds->callback_state))
 			{
 				bds->stats->tuples_removed += 1;
 				toDelete[xlrec.nDelete] = i;
@@ -902,7 +902,7 @@ spgbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 
 /* Dummy callback to delete no tuples during spgvacuumcleanup */
 static bool
-dummy_callback(ItemPointer itemptr, void *state)
+dummy_callback(ItemPointer itemptr, BlockNumber index_blkno, void *state)
 {
 	return false;
 }
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 31a1438..d792fdb 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -114,7 +114,8 @@ static void IndexCheckExclusion(Relation heapRelation,
 					IndexInfo *indexInfo);
 static inline int64 itemptr_encode(ItemPointer itemptr);
 static inline void itemptr_decode(ItemPointer itemptr, int64 encoded);
-static bool validate_index_callback(ItemPointer itemptr, void *opaque);
+static bool validate_index_callback(ItemPointer itemptr, BlockNumber index_blkno,
+						void *opaque);
 static void validate_index_heapscan(Relation heapRelation,
 						Relation indexRelation,
 						IndexInfo *indexInfo,
@@ -2913,7 +2914,7 @@ itemptr_decode(ItemPointer itemptr, int64 encoded)
  * validate_index_callback - bulkdelete callback to collect the index TIDs
  */
 static bool
-validate_index_callback(ItemPointer itemptr, void *opaque)
+validate_index_callback(ItemPointer itemptr, BlockNumber index_blkno, void *opaque)
 {
 	v_i_state  *state = (v_i_state *) opaque;
 	int64		encoded = itemptr_encode(itemptr);
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index 60c3b3b..d628822 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -143,6 +143,8 @@ typedef struct LVRelStats
 	int			num_index_scans;
 	TransactionId latestRemovedXid;
 	bool		lock_waiter_detected;
+	BlockNumber	last_index_blkno;
+	BlockNumber	index_blks_vacuumed;
 } LVRelStats;
 
 
@@ -176,7 +178,7 @@ static BlockNumber count_nondeletable_pages(Relation onerel,
 static void lazy_space_alloc(LVRelStats *vacrelstats, BlockNumber relblocks);
 static void lazy_record_dead_tuple(LVRelStats *vacrelstats,
 					   ItemPointer itemptr);
-static bool lazy_tid_reaped(ItemPointer itemptr, void *state);
+static bool lazy_tid_reaped(ItemPointer itemptr, BlockNumber index_blkno, void *state);
 static int	vac_cmp_itemptr(const void *left, const void *right);
 static bool heap_page_is_all_visible(Relation rel, Buffer buf,
 						 TransactionId *visibility_cutoff_xid, bool *all_frozen);
@@ -463,8 +465,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 {
 	BlockNumber nblocks,
 				blkno,
-				total_index_blks,
-			   *current_index_blks;
+				total_index_blks;
 	HeapTupleData tuple;
 	char	   *relname;
 	BlockNumber empty_pages,
@@ -512,15 +513,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	pgstat_progress_update_param(PROG_PAR_VAC_HEAP_BLKS, nblocks);
 
 	/* total_index_blks */
-	current_index_blks = (BlockNumber *) palloc(nindexes * sizeof(BlockNumber));
 	total_index_blks = 0;
 	for (i = 0; i < nindexes; i++)
-	{
-		BlockNumber		nblocks = RelationGetNumberOfBlocks(Irel[i]);
-
-		current_index_blks[i] = nblocks;
-		total_index_blks += nblocks;
-	}
+		total_index_blks += RelationGetNumberOfBlocks(Irel[i]);
 	pgstat_progress_update_param(PROG_PAR_VAC_IDX_BLKS, total_index_blks);
 
 	/*
@@ -657,16 +652,12 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 
 			/* Remove index entries */
 			pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_VACUUM_INDEX);
+			vacrelstats->index_blks_vacuumed = 0;	/* reset for this round */
 			for (i = 0; i < nindexes; i++)
-			{
 				lazy_vacuum_index(Irel[i],
 								  &indstats[i],
 								  vacrelstats);
 
-				pgstat_progress_update_param(PROG_PAR_VAC_IDX_BLKS_DONE,
-											 current_index_blks[i]);
-			}
-
 			pgstat_progress_update_param(PROG_PAR_VAC_N_IDX_SCAN,
 										 vacrelstats->num_index_scans+1);
 
@@ -1222,15 +1213,12 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 
 		/* Remove index entries */
 		pgstat_progress_update_param(PROG_PAR_VAC_PHASE_ID, LV_PHASE_VACUUM_INDEX);
+		vacrelstats->index_blks_vacuumed = 0;	/* reset for this round */
 		for (i = 0; i < nindexes; i++)
-		{
 			lazy_vacuum_index(Irel[i],
 							  &indstats[i],
 							  vacrelstats);
 
-			pgstat_progress_update_param(PROG_PAR_VAC_IDX_BLKS_DONE,
-										 current_index_blks[i]);
-		}
 		pgstat_progress_update_param(PROG_PAR_VAC_N_IDX_SCAN,
 									 vacrelstats->num_index_scans + 1);
 
@@ -1922,11 +1910,19 @@ lazy_record_dead_tuple(LVRelStats *vacrelstats,
  *		Assumes dead_tuples array is in sorted order.
  */
 static bool
-lazy_tid_reaped(ItemPointer itemptr, void *state)
+lazy_tid_reaped(ItemPointer itemptr, BlockNumber index_blkno, void *state)
 {
 	LVRelStats *vacrelstats = (LVRelStats *) state;
 	ItemPointer res;
 
+	if (index_blkno != vacrelstats->last_index_blkno)
+	{
+		++vacrelstats->index_blks_vacuumed;
+		pgstat_progress_update_param(PROG_PAR_VAC_IDX_BLKS_DONE,
+									 vacrelstats->index_blks_vacuumed);
+		vacrelstats->last_index_blkno = index_blkno;
+	}
+
 	res = (ItemPointer) bsearch((void *) itemptr,
 								(void *) vacrelstats->dead_tuples,
 								vacrelstats->num_dead_tuples,
diff --git a/src/include/access/genam.h b/src/include/access/genam.h
index 81907d5..8d67d1c 100644
--- a/src/include/access/genam.h
+++ b/src/include/access/genam.h
@@ -77,7 +77,9 @@ typedef struct IndexBulkDeleteResult
 } IndexBulkDeleteResult;
 
 /* Typedef for callback function to determine if a tuple is bulk-deletable */
-typedef bool (*IndexBulkDeleteCallback) (ItemPointer itemptr, void *state);
+typedef bool (*IndexBulkDeleteCallback) (ItemPointer itemptr,
+										 BlockNumber index_blkno,
+										 void *state);
 
 /* struct definitions appear in relscan.h */
 typedef struct IndexScanDescData *IndexScanDesc;
-- 
1.7.1

#192Noname
pokurev@pm.nttdata.co.jp
In reply to: Amit Langote (#190)
Re: [PROPOSAL] VACUUM Progress Checker.

Hi Amit,

-----Original Message-----
From: Amit Langote [mailto:Langote_Amit_f8@lab.ntt.co.jp]
Sent: Wednesday, March 09, 2016 4:29 PM
To: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
Cc: robertmhaas@gmail.com; amitlangote09@gmail.com; SPS ポクレ ヴィ
ナヤック(三技術) <pokurev@pm.nttdata.co.jp>; pgsql-
hackers@postgresql.org; SPS 坂野 昌平(三技術) <bannos@nttdata.co.jp>
Subject: Re: [HACKERS] [PROPOSAL] VACUUM Progress Checker.

On 2016/03/08 18:19, Kyotaro HORIGUCHI wrote:

+			  WHEN 0 THEN 100::numeric(5, 2)
+			  ELSE ((S.param3 + 1)::numeric / S.param2 *

100)::numeric(5, 2)

This usage of numeric seems overkill to me.

Hmm, how could this rather be written?

OK, agreed about the overkill. Following might be better:

+ WHEN 0 THEN round(100.0, 2)
+ ELSE round((S.param3 + 1) * 100.0 / S.param2, 2)

+1

Will update that patch.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#193Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#191)
Re: [PROPOSAL] VACUUM Progress Checker.

On Wed, Mar 9, 2016 at 2:37 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/03/09 10:11, Amit Langote wrote:

The attached revision addresses above and one of Horiguchi-san's comments
in his email yesterday.

I fixed one more issue in 0002 per Horiguchi-san's comment. Sorry about
so many versions.

I've committed 0001 with heavy revisions. Just because we don't need
an SQL-visible function to clear the command progress doesn't mean we
don't need to clear it at all; rather, it has to happen automatically.
I also did a bunch of identifier renaming, added datid to the view
output, adjusted the comments, and so on. Please rebase the remainder
of the series. Thanks.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#194Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Robert Haas (#193)
Re: [PROPOSAL] VACUUM Progress Checker.

Hi,

On Wed, 2016-03-09 at 12:16 -0500, Robert Haas wrote:

On Wed, Mar 9, 2016 at 2:37 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/03/09 10:11, Amit Langote wrote:

The attached revision addresses above and one of Horiguchi-san's comments
in his email yesterday.

I fixed one more issue in 0002 per Horiguchi-san's comment. Sorry about
so many versions.

I've committed 0001 with heavy revisions. Just because we don't need
an SQL-visible function to clear the command progress doesn't mean we
don't need to clear it at all; rather, it has to happen automatically.
I also did a bunch of identifier renaming, added datid to the view
output, adjusted the comments, and so on. Please rebase the remainder
of the series. Thanks.

I'm pretty sure this piece of code ends up accessing subscripts above
array bounds (and gcc 4.6.4 complains about that):

#define PG_STAT_GET_PROGRESS_COLS PGSTAT_NUM_PROGRESS_PARAM + 3

...

bool nulls[PG_STAT_GET_PROGRESS_COLS];

...

nulls[2] = true;
for (i = 1; i < PGSTAT_NUM_PROGRESS_PARAM + 1; i++)
nulls[i+3] = true;

Now let's say PARAM=10, which means COLS=13. The last index accessed by
the loop will be i=10, which means we'll do this:

nulls[13] = true;

which is above bounds.

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#195Noname
pokurev@pm.nttdata.co.jp
In reply to: Robert Haas (#193)
Re: [PROPOSAL] VACUUM Progress Checker.

Hi,
Thank you very much for committing this feature.

-----Original Message-----
From: Robert Haas [mailto:robertmhaas@gmail.com]
Sent: Thursday, March 10, 2016 2:17 AM
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
Cc: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Amit Langote
<amitlangote09@gmail.com>; SPS ポクレ ヴィナヤック(三技術)
<pokurev@pm.nttdata.co.jp>; pgsql-hackers@postgresql.org; SPS 坂野 昌
平(三技術) <bannos@nttdata.co.jp>
Subject: Re: [HACKERS] [PROPOSAL] VACUUM Progress Checker.

On Wed, Mar 9, 2016 at 2:37 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/03/09 10:11, Amit Langote wrote:

The attached revision addresses above and one of Horiguchi-san's
comments in his email yesterday.

I fixed one more issue in 0002 per Horiguchi-san's comment. Sorry
about so many versions.

I've committed 0001 with heavy revisions. Just because we don't need an
SQL-visible function to clear the command progress doesn't mean we don't
need to clear it at all; rather, it has to happen automatically.
I also did a bunch of identifier renaming, added datid to the view output,
adjusted the comments, and so on. Please rebase the remainder of the
series. Thanks.

Some minor typos need to fix.
+/*-----------
 + * pgstat_progress_start_command() -
 + *
 + * Set st_command in own backend entry.  Also, zero-initialize
 + * st_progress_param array.
 + *-----------
 + */
In the description we need to use st_progress_command instead of st_command.
+/*-----------
 + * pgstat_progress_end_command() -
 + *
 + * Update index'th member in st_progress_param[] of own backend entry.
 + *-----------
 + */
Here also need to change the description.

Regards,
Vinayak Pokale

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#196Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#193)
3 attachment(s)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2016/03/10 2:16, Robert Haas wrote:

On Wed, Mar 9, 2016 at 2:37 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/03/09 10:11, Amit Langote wrote:

The attached revision addresses above and one of Horiguchi-san's comments
in his email yesterday.

I fixed one more issue in 0002 per Horiguchi-san's comment. Sorry about
so many versions.

I've committed 0001 with heavy revisions. Just because we don't need
an SQL-visible function to clear the command progress doesn't mean we
don't need to clear it at all; rather, it has to happen automatically.
I also did a bunch of identifier renaming, added datid to the view
output, adjusted the comments, and so on. Please rebase the remainder
of the series. Thanks.

Great, thanks a lot for the review and committing in much better shape!

I rebased remainder patches (attached).

0001 is a small patch to fix issues reported by Tomas and Vinayak. 0002
and 0003 are WIP patches to implement progress reporting for vacuum.

Thanks,
Amit

Attachments:

0001-Some-minor-fixes-in-commit-b6fb6471.patchtext/x-diff; name=0001-Some-minor-fixes-in-commit-b6fb6471.patchDownload
From 7cb702c7fae9fceef3048a82522390844c5a67cc Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 10 Mar 2016 11:25:20 +0900
Subject: [PATCH 1/3] Some minor fixes in commit b6fb6471.

---
 src/backend/postmaster/pgstat.c     |    7 ++++---
 src/backend/utils/adt/pgstatfuncs.c |    2 +-
 2 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index ce5da3e..4424cb8 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -2861,8 +2861,8 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
 /*-----------
  * pgstat_progress_start_command() -
  *
- * Set st_command in own backend entry.  Also, zero-initialize
- * st_progress_param array.
+ * Set st_progress_command (and st_progress_command_target) in own backend
+ * entry.  Also, zero-initialize st_progress_param array.
  *-----------
  */
 void
@@ -2904,7 +2904,8 @@ pgstat_progress_update_param(int index, int64 val)
 /*-----------
  * pgstat_progress_end_command() -
  *
- * Update index'th member in st_progress_param[] of own backend entry.
+ * Reset st_progress_command (and st_progress_command_target) in own backend
+ * entry.  This signals the end of the command.
  *-----------
  */
 void
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 0c790ff..2fb51fa 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -614,7 +614,7 @@ pg_stat_get_progress_info(PG_FUNCTION_ARGS)
 		else
 		{
 			nulls[2] = true;
-			for (i = 1; i < PGSTAT_NUM_PROGRESS_PARAM + 1; i++)
+			for (i = 0; i < PGSTAT_NUM_PROGRESS_PARAM; i++)
 				nulls[i+3] = true;
 		}
 
-- 
1.7.1

0002-WIP-Implement-progress-reporting-for-VACUUM-command-v7.patchtext/x-diff; name=0002-WIP-Implement-progress-reporting-for-VACUUM-command-v7.patchDownload
From 4567da933b25a9e23fe1a72a6994d3a9b7bc1ea4 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 7 Mar 2016 14:38:34 +0900
Subject: [PATCH 2/3] WIP: Implement progress reporting for VACUUM command.

This basically utilizes the pgstat_progress* API to report a handful of
paramters to indicate its progress.  lazy_vacuum_rel() and lazy_scan_heap()
have been altered to report command start, command target table, and the
following parameters: processing phase, number of heap blocks, number of
index blocks (all indexes), current heap block number in the main scan loop
(whenever changes), index blocks vacuumed (once per finished index vacuum),
and number of index vacuum passes (every time when all indexes are vacuumed).
Following processing phases are identified and reported whenever one changes
to another: 'scanning heap', 'vacuuming indexes', 'vacuuming heap', and
'cleanup'.

TODO: find a way to report index pages vacuumed in a more granular manner than
the current report per index vacuumed.

A view named pg_stat_vacuum_progress has been added that shows these values.
---
 doc/src/sgml/monitoring.sgml         |  106 ++++++++++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   24 ++++++++
 src/backend/commands/vacuumlazy.c    |   73 +++++++++++++++++++++++-
 src/test/regress/expected/rules.out  |   24 ++++++++
 4 files changed, 226 insertions(+), 1 deletions(-)

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 85459d0..544f959 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -507,6 +507,12 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       yet included in <structname>pg_stat_user_functions</>).</entry>
      </row>
 
+     <row>
+      <entry><structname>pg_stat_progress_vacuum</><indexterm><primary>pg_stat_progress_vacuum</primary></indexterm></entry>
+      <entry>One row for each backend (including autovacuum worker processes) running
+      <command>VACUUM</>, showing current progress in terms of heap pages it
+      has finished processing.</entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -1822,6 +1828,106 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
    controls exactly which functions are tracked.
   </para>
 
+  <table id="pg-stat-progress-vacuum" xreflabel="pg_stat_progress_vacuum">
+   <title><structname>pg_stat_progress_vacuum</structname> View</title>
+   <tgroup cols="3">
+    <thead>
+    <row>
+      <entry>Column</entry>
+      <entry>Type</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+   <tbody>
+    <row>
+     <entry><structfield>pid</></entry>
+     <entry><type>integer</></entry>
+     <entry>Process ID of backend</entry>
+    </row>
+    <row>
+     <entry><structfield>database</></entry>
+     <entry><type>name</></entry>
+     <entry>Name of the database this backend is connected to</entry>
+    </row>
+    <row>
+     <entry><structfield>tablename</></entry>
+     <entry><type>name</></entry>
+     <entry>Schema-qualified name of the table being vacuumed</entry>
+    </row>
+    <row>
+     <entry><structfield>processing_phase</></entry>
+     <entry><type>text</></entry>
+     <entry>Current processing phase of vacuum.
+       Possible values are:
+       <itemizedlist>
+        <listitem>
+         <para>
+          <literal>scanning heap</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>vacuuming indexes</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>vacuuming heap</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>cleanup</>
+         </para>
+        </listitem>
+       </itemizedlist>
+     </entry>
+    </row>
+    <row>
+     <entry><structfield>total_heap_blocks</></entry>
+     <entry><type>integer</></entry>
+     <entry>Total number of heap blocks in the table</entry>
+    </row>
+    <row>
+     <entry><structfield>current_heap_block</></entry>
+     <entry><type>integer</></entry>
+     <entry>Current heap block being processed</entry>
+    </row>
+    <row>
+     <entry><structfield>total_index_blocks</></entry>
+     <entry><type>integer</></entry>
+     <entry>Total number of index blocks to be processed</entry>
+    </row>
+    <row>
+     <entry><structfield>index_blocks_done</></entry>
+     <entry><type>integer</></entry>
+     <entry>Number of index blocks processed in current vacuum round</entry>
+    </row>
+    <row>
+     <entry><structfield>index_vacuum_count</></entry>
+     <entry><type>integer</></entry>
+     <entry>Number of times index vacuum round has been performed so far</entry>
+    </row>
+    <row>
+     <entry><structfield>percent_done</></entry>
+     <entry><type>numeric</></entry>
+     <entry>
+      Amount of work finished in percent in terms of table blocks processed
+     </entry>
+    </row>
+   </tbody>
+   </tgroup>
+  </table>
+
+  <para>
+   The <structname>pg_stat_progress_vacuum</structname> view will contain
+   one row for each backend (including autovacuum worker processes), showing
+   parameters that can help determine the progress of <command>VACUUM</command>
+   command running in it. Note that the backends running
+   <command>VACUUM FULL</command> are not shown.
+  </para>
+
  </sect2>
 
  <sect2 id="monitoring-stats-functions">
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index abf9a70..2ce6e00 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -971,3 +971,27 @@ RETURNS jsonb
 LANGUAGE INTERNAL
 STRICT IMMUTABLE
 AS 'jsonb_set';
+
+CREATE VIEW pg_stat_progress_vacuum AS
+    SELECT
+            S.pid AS pid,
+            D.datname AS database,
+            quote_ident(N.nspname) || '.' || quote_ident(C.relname) AS tablename,
+            CASE S.param1
+                WHEN 1 THEN 'scanning heap'
+                WHEN 2 THEN 'vacuuming indexes'
+                WHEN 3 THEN 'vacuuming heap'
+                WHEN 4 THEN 'cleanup'
+                ELSE 'unknown phase'
+            END AS processing_phase,
+            S.param2 AS total_heap_blocks,
+            S.param3 AS current_heap_block,
+            S.param4 AS total_index_blocks,
+            S.param5 AS index_blocks_done,
+            S.param6 AS index_vacuum_count,
+            CASE S.param2
+                WHEN 0 THEN round(100.0, 2)
+			    ELSE round((S.param3 + 1) * 100.0 / S.param2, 2)
+            END AS percent_done
+    FROM pg_database D, pg_class C, pg_namespace N, pg_stat_get_progress_info('VACUUM') AS S
+    WHERE S.datid = D.oid AND S.relid = C.oid AND C.relnamespace = N.oid;
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index 61d2edd..0771b91 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -97,6 +97,29 @@
  */
 #define SKIP_PAGES_THRESHOLD	((BlockNumber) 32)
 
+/*
+ * Progress parameters of (lazy) vacuum reported to pgstat progress tracking
+ * facility
+ */
+#define PROG_PARAM_VAC_PHASE			0
+#define PROG_PARAM_VAC_HEAP_BLKS		1
+#define PROG_PARAM_VAC_CURR_HEAP_BLK	2
+#define PROG_PARAM_VAC_IDX_BLKS			3
+#define PROG_PARAM_VAC_IDX_BLKS_DONE	4
+#define PROG_PARAM_VAC_IDX_VAC_COUNT	5
+
+/*
+ * Following distinct phases of lazy vacuum are identified.  #1, #2 and #3
+ * run in a cyclical manner due to possibly limited memory to work with,
+ * whereby #1 is periodically interrupted to run #2, followed by #3, and
+ * back to #1.  Cycle repeats until all blocks of the relation have been
+ * covered by #1.
+ */
+#define LV_PHASE_SCAN_HEAP			1
+#define LV_PHASE_VACUUM_INDEX		2
+#define LV_PHASE_VACUUM_HEAP		3
+#define LV_PHASE_CLEANUP			4
+
 typedef struct LVRelStats
 {
 	/* hasindex = true means two-pass strategy; false means one-pass */
@@ -437,7 +460,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			   Relation *Irel, int nindexes, bool scan_all)
 {
 	BlockNumber nblocks,
-				blkno;
+				blkno,
+				total_index_blks,
+			   *current_index_blks;
 	HeapTupleData tuple;
 	char	   *relname;
 	BlockNumber empty_pages,
@@ -478,6 +503,24 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	lazy_space_alloc(vacrelstats, nblocks);
 	frozen = palloc(sizeof(xl_heap_freeze_tuple) * MaxHeapTuplesPerPage);
 
+	/* We're about to begin heap scan. */
+	pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_SCAN_HEAP);
+
+	/* total_heap_blks */
+	pgstat_progress_update_param(PROG_PARAM_VAC_HEAP_BLKS, nblocks);
+
+	/* total_index_blks */
+	current_index_blks = (BlockNumber *) palloc(nindexes * sizeof(BlockNumber));
+	total_index_blks = 0;
+	for (i = 0; i < nindexes; i++)
+	{
+		BlockNumber		nblocks = RelationGetNumberOfBlocks(Irel[i]);
+
+		current_index_blks[i] = nblocks;
+		total_index_blks += nblocks;
+	}
+	pgstat_progress_update_param(PROG_PARAM_VAC_IDX_BLKS, total_index_blks);
+
 	/*
 	 * We want to skip pages that don't require vacuuming according to the
 	 * visibility map, but only when we can skip at least SKIP_PAGES_THRESHOLD
@@ -585,6 +628,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 
 		vacuum_delay_point();
 
+		/* current_heap_blkno: 0..nblocks-1 */
+		pgstat_progress_update_param(PROG_PARAM_VAC_CURR_HEAP_BLK, blkno);
+
 		/*
 		 * If we are close to overrunning the available space for dead-tuple
 		 * TIDs, pause and do a cycle of vacuuming before we tackle this page.
@@ -608,11 +654,22 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			vacuum_log_cleanup_info(onerel, vacrelstats);
 
 			/* Remove index entries */
+			pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_VACUUM_INDEX);
 			for (i = 0; i < nindexes; i++)
+			{
 				lazy_vacuum_index(Irel[i],
 								  &indstats[i],
 								  vacrelstats);
+
+				pgstat_progress_update_param(PROG_PARAM_VAC_IDX_BLKS_DONE,
+											 current_index_blks[i]);
+			}
+
+			pgstat_progress_update_param(PROG_PARAM_VAC_IDX_VAC_COUNT,
+										 vacrelstats->num_index_scans+1);
+
 			/* Remove tuples from heap */
+			pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_VACUUM_HEAP);
 			lazy_vacuum_heap(onerel, vacrelstats);
 
 			/*
@@ -622,6 +679,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			 */
 			vacrelstats->num_dead_tuples = 0;
 			vacrelstats->num_index_scans++;
+
+			/* Going back to scanning the heap */
+			pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_SCAN_HEAP);
 		}
 
 		/*
@@ -1151,16 +1211,27 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		vacuum_log_cleanup_info(onerel, vacrelstats);
 
 		/* Remove index entries */
+		pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_VACUUM_INDEX);
 		for (i = 0; i < nindexes; i++)
+		{
 			lazy_vacuum_index(Irel[i],
 							  &indstats[i],
 							  vacrelstats);
+
+			pgstat_progress_update_param(PROG_PARAM_VAC_IDX_BLKS_DONE,
+										 current_index_blks[i]);
+		}
+		pgstat_progress_update_param(PROG_PARAM_VAC_IDX_VAC_COUNT,
+									 vacrelstats->num_index_scans + 1);
+
 		/* Remove tuples from heap */
+		pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_VACUUM_HEAP);
 		lazy_vacuum_heap(onerel, vacrelstats);
 		vacrelstats->num_index_scans++;
 	}
 
 	/* Do post-vacuum cleanup and statistics update for each index */
+	pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_CLEANUP);
 	for (i = 0; i < nindexes; i++)
 		lazy_cleanup_index(Irel[i], indstats[i], vacrelstats);
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 81bc5c9..6cd496b 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1746,6 +1746,30 @@ pg_stat_database_conflicts| SELECT d.oid AS datid,
     pg_stat_get_db_conflict_bufferpin(d.oid) AS confl_bufferpin,
     pg_stat_get_db_conflict_startup_deadlock(d.oid) AS confl_deadlock
    FROM pg_database d;
+pg_stat_progress_vacuum| SELECT s.pid,
+    d.datname AS database,
+    ((quote_ident((n.nspname)::text) || '.'::text) || quote_ident((c.relname)::text)) AS tablename,
+        CASE s.param1
+            WHEN 1 THEN 'scanning heap'::text
+            WHEN 2 THEN 'vacuuming indexes'::text
+            WHEN 3 THEN 'vacuuming heap'::text
+            WHEN 4 THEN 'cleanup'::text
+            ELSE 'unknown phase'::text
+        END AS processing_phase,
+    s.param2 AS total_heap_blocks,
+    s.param3 AS current_heap_block,
+    s.param4 AS total_index_blocks,
+    s.param5 AS index_blocks_done,
+    s.param6 AS index_vacuum_count,
+        CASE s.param2
+            WHEN 0 THEN round(100.0, 2)
+            ELSE round(((((s.param3 + 1))::numeric * 100.0) / (s.param2)::numeric), 2)
+        END AS percent_done
+   FROM pg_database d,
+    pg_class c,
+    pg_namespace n,
+    pg_stat_get_progress_info('VACUUM'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10)
+  WHERE ((s.datid = d.oid) AND (s.relid = c.oid) AND (c.relnamespace = n.oid));
 pg_stat_replication| SELECT s.pid,
     s.usesysid,
     u.rolname AS usename,
-- 
1.7.1

0003-WIP-Add-a-block-number-argument-to-index-bulk-delete-v7.patchtext/x-diff; name=0003-WIP-Add-a-block-number-argument-to-index-bulk-delete-v7.patchDownload
From 2df9d3af0e4e07e2949cdbc465ec28191c514199 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 8 Mar 2016 15:23:38 +0900
Subject: [PATCH 3/3] WIP: Add a block number argument to index bulk delete callback.

Currently, index AMs can only pass a ItemPointer (heap tid) and a generic
pointer to caller specified struct.  Add a BlockNumber so that the callback
can use it for something.  For example, lazy_tid_reaped() now uses it to
track the progress of AM's bulk delete scan of an index by comparing it with
the last call's value that it stores in its private state on every change.
The private state being the LVRelStats struct that now has the corresponding
field.  On every change, the count of index blocks vacuumed is incremented
which is also a new field in LVRelStats. The count is reset for every index
vacuum round.

Then a pgstat_progress_update_param call has been added to lazy_tid_reaped,
that pushes the count on every increment.

Currently, only btree AM is changed to pass the block number, others pass
InvalidBlockNumber.
---
 src/backend/access/gin/ginvacuum.c    |    2 +-
 src/backend/access/gist/gistvacuum.c  |    2 +-
 src/backend/access/hash/hash.c        |    2 +-
 src/backend/access/nbtree/nbtree.c    |    2 +-
 src/backend/access/spgist/spgvacuum.c |    6 ++--
 src/backend/catalog/index.c           |    5 ++-
 src/backend/commands/vacuumlazy.c     |   36 ++++++++++++++------------------
 src/include/access/genam.h            |    4 ++-
 8 files changed, 29 insertions(+), 30 deletions(-)

diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c
index 6a4b98a..64e69d6 100644
--- a/src/backend/access/gin/ginvacuum.c
+++ b/src/backend/access/gin/ginvacuum.c
@@ -55,7 +55,7 @@ ginVacuumItemPointers(GinVacuumState *gvs, ItemPointerData *items,
 	 */
 	for (i = 0; i < nitem; i++)
 	{
-		if (gvs->callback(items + i, gvs->callback_state))
+		if (gvs->callback(items + i, InvalidBlockNumber, gvs->callback_state))
 		{
 			gvs->result->tuples_removed += 1;
 			if (!tmpitems)
diff --git a/src/backend/access/gist/gistvacuum.c b/src/backend/access/gist/gistvacuum.c
index 7947ff9..96e89c3 100644
--- a/src/backend/access/gist/gistvacuum.c
+++ b/src/backend/access/gist/gistvacuum.c
@@ -202,7 +202,7 @@ gistbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 				iid = PageGetItemId(page, i);
 				idxtuple = (IndexTuple) PageGetItem(page, iid);
 
-				if (callback(&(idxtuple->t_tid), callback_state))
+				if (callback(&(idxtuple->t_tid), InvalidBlockNumber, callback_state))
 					todelete[ntodelete++] = i;
 				else
 					stats->num_index_tuples += 1;
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 3d48c4f..1530f0b 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -575,7 +575,7 @@ loop_top:
 				itup = (IndexTuple) PageGetItem(page,
 												PageGetItemId(page, offno));
 				htup = &(itup->t_tid);
-				if (callback(htup, callback_state))
+				if (callback(htup, InvalidBlockNumber, callback_state))
 				{
 					/* mark the item for deletion */
 					deletable[ndeletable++] = offno;
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index f2905cb..8c6be30 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -1034,7 +1034,7 @@ restart:
 				 * applies to *any* type of index that marks index tuples as
 				 * killed.
 				 */
-				if (callback(htup, callback_state))
+				if (callback(htup, blkno, callback_state))
 					deletable[ndeletable++] = offnum;
 			}
 		}
diff --git a/src/backend/access/spgist/spgvacuum.c b/src/backend/access/spgist/spgvacuum.c
index 15b867f..6c7265b 100644
--- a/src/backend/access/spgist/spgvacuum.c
+++ b/src/backend/access/spgist/spgvacuum.c
@@ -154,7 +154,7 @@ vacuumLeafPage(spgBulkDeleteState *bds, Relation index, Buffer buffer,
 		{
 			Assert(ItemPointerIsValid(&lt->heapPtr));
 
-			if (bds->callback(&lt->heapPtr, bds->callback_state))
+			if (bds->callback(&lt->heapPtr, InvalidBlockNumber, bds->callback_state))
 			{
 				bds->stats->tuples_removed += 1;
 				deletable[i] = true;
@@ -424,7 +424,7 @@ vacuumLeafRoot(spgBulkDeleteState *bds, Relation index, Buffer buffer)
 		{
 			Assert(ItemPointerIsValid(&lt->heapPtr));
 
-			if (bds->callback(&lt->heapPtr, bds->callback_state))
+			if (bds->callback(&lt->heapPtr, InvalidBlockNumber, bds->callback_state))
 			{
 				bds->stats->tuples_removed += 1;
 				toDelete[xlrec.nDelete] = i;
@@ -902,7 +902,7 @@ spgbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 
 /* Dummy callback to delete no tuples during spgvacuumcleanup */
 static bool
-dummy_callback(ItemPointer itemptr, void *state)
+dummy_callback(ItemPointer itemptr, BlockNumber index_blkno, void *state)
 {
 	return false;
 }
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 31a1438..d792fdb 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -114,7 +114,8 @@ static void IndexCheckExclusion(Relation heapRelation,
 					IndexInfo *indexInfo);
 static inline int64 itemptr_encode(ItemPointer itemptr);
 static inline void itemptr_decode(ItemPointer itemptr, int64 encoded);
-static bool validate_index_callback(ItemPointer itemptr, void *opaque);
+static bool validate_index_callback(ItemPointer itemptr, BlockNumber index_blkno,
+						void *opaque);
 static void validate_index_heapscan(Relation heapRelation,
 						Relation indexRelation,
 						IndexInfo *indexInfo,
@@ -2913,7 +2914,7 @@ itemptr_decode(ItemPointer itemptr, int64 encoded)
  * validate_index_callback - bulkdelete callback to collect the index TIDs
  */
 static bool
-validate_index_callback(ItemPointer itemptr, void *opaque)
+validate_index_callback(ItemPointer itemptr, BlockNumber index_blkno, void *opaque)
 {
 	v_i_state  *state = (v_i_state *) opaque;
 	int64		encoded = itemptr_encode(itemptr);
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index 0771b91..a2442dd 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -144,6 +144,8 @@ typedef struct LVRelStats
 	int			num_index_scans;
 	TransactionId latestRemovedXid;
 	bool		lock_waiter_detected;
+	BlockNumber	last_index_blkno;
+	BlockNumber	index_blks_vacuumed;
 } LVRelStats;
 
 
@@ -177,7 +179,7 @@ static BlockNumber count_nondeletable_pages(Relation onerel,
 static void lazy_space_alloc(LVRelStats *vacrelstats, BlockNumber relblocks);
 static void lazy_record_dead_tuple(LVRelStats *vacrelstats,
 					   ItemPointer itemptr);
-static bool lazy_tid_reaped(ItemPointer itemptr, void *state);
+static bool lazy_tid_reaped(ItemPointer itemptr, BlockNumber index_blkno, void *state);
 static int	vac_cmp_itemptr(const void *left, const void *right);
 static bool heap_page_is_all_visible(Relation rel, Buffer buf,
 					 TransactionId *visibility_cutoff_xid, bool *all_frozen);
@@ -461,8 +463,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 {
 	BlockNumber nblocks,
 				blkno,
-				total_index_blks,
-			   *current_index_blks;
+				total_index_blks;
 	HeapTupleData tuple;
 	char	   *relname;
 	BlockNumber empty_pages,
@@ -510,15 +511,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	pgstat_progress_update_param(PROG_PARAM_VAC_HEAP_BLKS, nblocks);
 
 	/* total_index_blks */
-	current_index_blks = (BlockNumber *) palloc(nindexes * sizeof(BlockNumber));
 	total_index_blks = 0;
 	for (i = 0; i < nindexes; i++)
-	{
-		BlockNumber		nblocks = RelationGetNumberOfBlocks(Irel[i]);
-
-		current_index_blks[i] = nblocks;
-		total_index_blks += nblocks;
-	}
+		total_index_blks += RelationGetNumberOfBlocks(Irel[i]);
 	pgstat_progress_update_param(PROG_PARAM_VAC_IDX_BLKS, total_index_blks);
 
 	/*
@@ -655,16 +650,12 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 
 			/* Remove index entries */
 			pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_VACUUM_INDEX);
+			vacrelstats->index_blks_vacuumed = 0;	/* reset for this round */
 			for (i = 0; i < nindexes; i++)
-			{
 				lazy_vacuum_index(Irel[i],
 								  &indstats[i],
 								  vacrelstats);
 
-				pgstat_progress_update_param(PROG_PARAM_VAC_IDX_BLKS_DONE,
-											 current_index_blks[i]);
-			}
-
 			pgstat_progress_update_param(PROG_PARAM_VAC_IDX_VAC_COUNT,
 										 vacrelstats->num_index_scans+1);
 
@@ -1212,15 +1203,12 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 
 		/* Remove index entries */
 		pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_VACUUM_INDEX);
+		vacrelstats->index_blks_vacuumed = 0;	/* reset for this round */
 		for (i = 0; i < nindexes; i++)
-		{
 			lazy_vacuum_index(Irel[i],
 							  &indstats[i],
 							  vacrelstats);
 
-			pgstat_progress_update_param(PROG_PARAM_VAC_IDX_BLKS_DONE,
-										 current_index_blks[i]);
-		}
 		pgstat_progress_update_param(PROG_PARAM_VAC_IDX_VAC_COUNT,
 									 vacrelstats->num_index_scans + 1);
 
@@ -1908,11 +1896,19 @@ lazy_record_dead_tuple(LVRelStats *vacrelstats,
  *		Assumes dead_tuples array is in sorted order.
  */
 static bool
-lazy_tid_reaped(ItemPointer itemptr, void *state)
+lazy_tid_reaped(ItemPointer itemptr, BlockNumber index_blkno, void *state)
 {
 	LVRelStats *vacrelstats = (LVRelStats *) state;
 	ItemPointer res;
 
+	if (index_blkno != vacrelstats->last_index_blkno)
+	{
+		++vacrelstats->index_blks_vacuumed;
+		pgstat_progress_update_param(PROG_PARAM_VAC_IDX_BLKS_DONE,
+									 vacrelstats->index_blks_vacuumed);
+		vacrelstats->last_index_blkno = index_blkno;
+	}
+
 	res = (ItemPointer) bsearch((void *) itemptr,
 								(void *) vacrelstats->dead_tuples,
 								vacrelstats->num_dead_tuples,
diff --git a/src/include/access/genam.h b/src/include/access/genam.h
index 81907d5..8d67d1c 100644
--- a/src/include/access/genam.h
+++ b/src/include/access/genam.h
@@ -77,7 +77,9 @@ typedef struct IndexBulkDeleteResult
 } IndexBulkDeleteResult;
 
 /* Typedef for callback function to determine if a tuple is bulk-deletable */
-typedef bool (*IndexBulkDeleteCallback) (ItemPointer itemptr, void *state);
+typedef bool (*IndexBulkDeleteCallback) (ItemPointer itemptr,
+										 BlockNumber index_blkno,
+										 void *state);
 
 /* struct definitions appear in relscan.h */
 typedef struct IndexScanDescData *IndexScanDesc;
-- 
1.7.1

#197Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Amit Langote (#196)
3 attachment(s)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2016/03/10 14:29, Amit Langote wrote:

I rebased remainder patches (attached).

0001 is a small patch to fix issues reported by Tomas and Vinayak. 0002
and 0003 are WIP patches to implement progress reporting for vacuum.

Oops, in 0002, I wrongly joined with pg_class in the definition of
pg_stat_progress_vacuum to output the schema-qualified name of the table
being vacuumed. That means we need to connect to the correct database,
which is undesirable. Updated version fixes that (shows database name and
relid). You may also have noticed that I said pg_stat_progress_vacuum,
not pg_stat_vacuum_progress (IMHO, the former is a better name).

Updated patches attached.

Thanks,
Amit

Attachments:

0001-Some-minor-fixes-in-commit-b6fb6471.patchtext/x-diff; name=0001-Some-minor-fixes-in-commit-b6fb6471.patchDownload
From 7cb702c7fae9fceef3048a82522390844c5a67cc Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 10 Mar 2016 11:25:20 +0900
Subject: [PATCH 1/3] Some minor fixes in commit b6fb6471.

---
 src/backend/postmaster/pgstat.c     |    7 ++++---
 src/backend/utils/adt/pgstatfuncs.c |    2 +-
 2 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index ce5da3e..4424cb8 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -2861,8 +2861,8 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
 /*-----------
  * pgstat_progress_start_command() -
  *
- * Set st_command in own backend entry.  Also, zero-initialize
- * st_progress_param array.
+ * Set st_progress_command (and st_progress_command_target) in own backend
+ * entry.  Also, zero-initialize st_progress_param array.
  *-----------
  */
 void
@@ -2904,7 +2904,8 @@ pgstat_progress_update_param(int index, int64 val)
 /*-----------
  * pgstat_progress_end_command() -
  *
- * Update index'th member in st_progress_param[] of own backend entry.
+ * Reset st_progress_command (and st_progress_command_target) in own backend
+ * entry.  This signals the end of the command.
  *-----------
  */
 void
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 0c790ff..2fb51fa 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -614,7 +614,7 @@ pg_stat_get_progress_info(PG_FUNCTION_ARGS)
 		else
 		{
 			nulls[2] = true;
-			for (i = 1; i < PGSTAT_NUM_PROGRESS_PARAM + 1; i++)
+			for (i = 0; i < PGSTAT_NUM_PROGRESS_PARAM; i++)
 				nulls[i+3] = true;
 		}
 
-- 
1.7.1

0002-WIP-Implement-progress-reporting-for-VACUUM-command-v8.patchtext/x-diff; name=0002-WIP-Implement-progress-reporting-for-VACUUM-command-v8.patchDownload
From 3ca85c000ec2cd6148d0b3adb35aefa7e29ab23d Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 7 Mar 2016 14:38:34 +0900
Subject: [PATCH 2/3] WIP: Implement progress reporting for VACUUM command.

This basically utilizes the pgstat_progress* API to report a handful of
paramters to indicate its progress.  lazy_vacuum_rel() and lazy_scan_heap()
have been altered to report command start, command target table, and the
following parameters: processing phase, number of heap blocks, number of
index blocks (all indexes), current heap block number in the main scan loop
(whenever changes), index blocks vacuumed (once per finished index vacuum),
and number of index vacuum passes (every time when all indexes are vacuumed).
Following processing phases are identified and reported whenever one changes
to another: 'scanning heap', 'vacuuming indexes', 'vacuuming heap', and
'cleanup'.

TODO: find a way to report index pages vacuumed in a more granular manner than
the current report per index vacuumed.

A view named pg_stat_progress_vacuum has been added that shows these values.
---
 doc/src/sgml/monitoring.sgml         |  106 ++++++++++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   24 ++++++++
 src/backend/commands/vacuumlazy.c    |   73 +++++++++++++++++++++++-
 src/test/regress/expected/rules.out  |   22 +++++++
 4 files changed, 224 insertions(+), 1 deletions(-)

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 85459d0..082f94c 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -507,6 +507,12 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       yet included in <structname>pg_stat_user_functions</>).</entry>
      </row>
 
+     <row>
+      <entry><structname>pg_stat_progress_vacuum</><indexterm><primary>pg_stat_progress_vacuum</primary></indexterm></entry>
+      <entry>One row for each backend (including autovacuum worker processes) running
+      <command>VACUUM</>, showing current progress in terms of heap pages it
+      has finished processing.</entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -1822,6 +1828,106 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
    controls exactly which functions are tracked.
   </para>
 
+  <table id="pg-stat-progress-vacuum" xreflabel="pg_stat_progress_vacuum">
+   <title><structname>pg_stat_progress_vacuum</structname> View</title>
+   <tgroup cols="3">
+    <thead>
+    <row>
+      <entry>Column</entry>
+      <entry>Type</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+   <tbody>
+    <row>
+     <entry><structfield>pid</></entry>
+     <entry><type>integer</></entry>
+     <entry>Process ID of backend</entry>
+    </row>
+    <row>
+     <entry><structfield>database</></entry>
+     <entry><type>name</></entry>
+     <entry>Name of the database this backend is connected to</entry>
+    </row>
+    <row>
+     <entry><structfield>relid</></entry>
+     <entry><type>Oid</></entry>
+     <entry>OID of the table being vacuumed</entry>
+    </row>
+    <row>
+     <entry><structfield>processing_phase</></entry>
+     <entry><type>text</></entry>
+     <entry>Current processing phase of vacuum.
+       Possible values are:
+       <itemizedlist>
+        <listitem>
+         <para>
+          <literal>scanning heap</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>vacuuming indexes</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>vacuuming heap</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>cleanup</>
+         </para>
+        </listitem>
+       </itemizedlist>
+     </entry>
+    </row>
+    <row>
+     <entry><structfield>total_heap_blocks</></entry>
+     <entry><type>integer</></entry>
+     <entry>Total number of heap blocks in the table</entry>
+    </row>
+    <row>
+     <entry><structfield>current_heap_block</></entry>
+     <entry><type>integer</></entry>
+     <entry>Current heap block being processed</entry>
+    </row>
+    <row>
+     <entry><structfield>total_index_blocks</></entry>
+     <entry><type>integer</></entry>
+     <entry>Total number of index blocks to be processed</entry>
+    </row>
+    <row>
+     <entry><structfield>index_blocks_done</></entry>
+     <entry><type>integer</></entry>
+     <entry>Number of index blocks processed in current vacuum round</entry>
+    </row>
+    <row>
+     <entry><structfield>index_vacuum_count</></entry>
+     <entry><type>integer</></entry>
+     <entry>Number of times index vacuum round has been performed so far</entry>
+    </row>
+    <row>
+     <entry><structfield>percent_done</></entry>
+     <entry><type>numeric</></entry>
+     <entry>
+      Amount of work finished in percent in terms of table blocks processed
+     </entry>
+    </row>
+   </tbody>
+   </tgroup>
+  </table>
+
+  <para>
+   The <structname>pg_stat_progress_vacuum</structname> view will contain
+   one row for each backend (including autovacuum worker processes), showing
+   parameters that can help determine the progress of <command>VACUUM</command>
+   command running in it. Note that the backends running
+   <command>VACUUM FULL</command> are not shown.
+  </para>
+
  </sect2>
 
  <sect2 id="monitoring-stats-functions">
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index abf9a70..4efc81c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -971,3 +971,27 @@ RETURNS jsonb
 LANGUAGE INTERNAL
 STRICT IMMUTABLE
 AS 'jsonb_set';
+
+CREATE VIEW pg_stat_progress_vacuum AS
+    SELECT
+            S.pid AS pid,
+            D.datname AS database,
+            S.relid AS relid,
+            CASE S.param1
+                WHEN 1 THEN 'scanning heap'
+                WHEN 2 THEN 'vacuuming indexes'
+                WHEN 3 THEN 'vacuuming heap'
+                WHEN 4 THEN 'cleanup'
+                ELSE 'unknown phase'
+            END AS processing_phase,
+            S.param2 AS total_heap_blocks,
+            S.param3 AS current_heap_block,
+            S.param4 AS total_index_blocks,
+            S.param5 AS index_blocks_done,
+            S.param6 AS index_vacuum_count,
+            CASE S.param2
+                WHEN 0 THEN round(100.0, 2)
+			    ELSE round((S.param3 + 1) * 100.0 / S.param2, 2)
+            END AS percent_done
+    FROM pg_database D, pg_stat_get_progress_info('VACUUM') AS S
+    WHERE S.datid = D.oid;
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index 61d2edd..0771b91 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -97,6 +97,29 @@
  */
 #define SKIP_PAGES_THRESHOLD	((BlockNumber) 32)
 
+/*
+ * Progress parameters of (lazy) vacuum reported to pgstat progress tracking
+ * facility
+ */
+#define PROG_PARAM_VAC_PHASE			0
+#define PROG_PARAM_VAC_HEAP_BLKS		1
+#define PROG_PARAM_VAC_CURR_HEAP_BLK	2
+#define PROG_PARAM_VAC_IDX_BLKS			3
+#define PROG_PARAM_VAC_IDX_BLKS_DONE	4
+#define PROG_PARAM_VAC_IDX_VAC_COUNT	5
+
+/*
+ * Following distinct phases of lazy vacuum are identified.  #1, #2 and #3
+ * run in a cyclical manner due to possibly limited memory to work with,
+ * whereby #1 is periodically interrupted to run #2, followed by #3, and
+ * back to #1.  Cycle repeats until all blocks of the relation have been
+ * covered by #1.
+ */
+#define LV_PHASE_SCAN_HEAP			1
+#define LV_PHASE_VACUUM_INDEX		2
+#define LV_PHASE_VACUUM_HEAP		3
+#define LV_PHASE_CLEANUP			4
+
 typedef struct LVRelStats
 {
 	/* hasindex = true means two-pass strategy; false means one-pass */
@@ -437,7 +460,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			   Relation *Irel, int nindexes, bool scan_all)
 {
 	BlockNumber nblocks,
-				blkno;
+				blkno,
+				total_index_blks,
+			   *current_index_blks;
 	HeapTupleData tuple;
 	char	   *relname;
 	BlockNumber empty_pages,
@@ -478,6 +503,24 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	lazy_space_alloc(vacrelstats, nblocks);
 	frozen = palloc(sizeof(xl_heap_freeze_tuple) * MaxHeapTuplesPerPage);
 
+	/* We're about to begin heap scan. */
+	pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_SCAN_HEAP);
+
+	/* total_heap_blks */
+	pgstat_progress_update_param(PROG_PARAM_VAC_HEAP_BLKS, nblocks);
+
+	/* total_index_blks */
+	current_index_blks = (BlockNumber *) palloc(nindexes * sizeof(BlockNumber));
+	total_index_blks = 0;
+	for (i = 0; i < nindexes; i++)
+	{
+		BlockNumber		nblocks = RelationGetNumberOfBlocks(Irel[i]);
+
+		current_index_blks[i] = nblocks;
+		total_index_blks += nblocks;
+	}
+	pgstat_progress_update_param(PROG_PARAM_VAC_IDX_BLKS, total_index_blks);
+
 	/*
 	 * We want to skip pages that don't require vacuuming according to the
 	 * visibility map, but only when we can skip at least SKIP_PAGES_THRESHOLD
@@ -585,6 +628,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 
 		vacuum_delay_point();
 
+		/* current_heap_blkno: 0..nblocks-1 */
+		pgstat_progress_update_param(PROG_PARAM_VAC_CURR_HEAP_BLK, blkno);
+
 		/*
 		 * If we are close to overrunning the available space for dead-tuple
 		 * TIDs, pause and do a cycle of vacuuming before we tackle this page.
@@ -608,11 +654,22 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			vacuum_log_cleanup_info(onerel, vacrelstats);
 
 			/* Remove index entries */
+			pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_VACUUM_INDEX);
 			for (i = 0; i < nindexes; i++)
+			{
 				lazy_vacuum_index(Irel[i],
 								  &indstats[i],
 								  vacrelstats);
+
+				pgstat_progress_update_param(PROG_PARAM_VAC_IDX_BLKS_DONE,
+											 current_index_blks[i]);
+			}
+
+			pgstat_progress_update_param(PROG_PARAM_VAC_IDX_VAC_COUNT,
+										 vacrelstats->num_index_scans+1);
+
 			/* Remove tuples from heap */
+			pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_VACUUM_HEAP);
 			lazy_vacuum_heap(onerel, vacrelstats);
 
 			/*
@@ -622,6 +679,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			 */
 			vacrelstats->num_dead_tuples = 0;
 			vacrelstats->num_index_scans++;
+
+			/* Going back to scanning the heap */
+			pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_SCAN_HEAP);
 		}
 
 		/*
@@ -1151,16 +1211,27 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		vacuum_log_cleanup_info(onerel, vacrelstats);
 
 		/* Remove index entries */
+		pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_VACUUM_INDEX);
 		for (i = 0; i < nindexes; i++)
+		{
 			lazy_vacuum_index(Irel[i],
 							  &indstats[i],
 							  vacrelstats);
+
+			pgstat_progress_update_param(PROG_PARAM_VAC_IDX_BLKS_DONE,
+										 current_index_blks[i]);
+		}
+		pgstat_progress_update_param(PROG_PARAM_VAC_IDX_VAC_COUNT,
+									 vacrelstats->num_index_scans + 1);
+
 		/* Remove tuples from heap */
+		pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_VACUUM_HEAP);
 		lazy_vacuum_heap(onerel, vacrelstats);
 		vacrelstats->num_index_scans++;
 	}
 
 	/* Do post-vacuum cleanup and statistics update for each index */
+	pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_CLEANUP);
 	for (i = 0; i < nindexes; i++)
 		lazy_cleanup_index(Irel[i], indstats[i], vacrelstats);
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 81bc5c9..c406939 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1746,6 +1746,28 @@ pg_stat_database_conflicts| SELECT d.oid AS datid,
     pg_stat_get_db_conflict_bufferpin(d.oid) AS confl_bufferpin,
     pg_stat_get_db_conflict_startup_deadlock(d.oid) AS confl_deadlock
    FROM pg_database d;
+pg_stat_progress_vacuum| SELECT s.pid,
+    d.datname AS database,
+    s.relid,
+        CASE s.param1
+            WHEN 1 THEN 'scanning heap'::text
+            WHEN 2 THEN 'vacuuming indexes'::text
+            WHEN 3 THEN 'vacuuming heap'::text
+            WHEN 4 THEN 'cleanup'::text
+            ELSE 'unknown phase'::text
+        END AS processing_phase,
+    s.param2 AS total_heap_blocks,
+    s.param3 AS current_heap_block,
+    s.param4 AS total_index_blocks,
+    s.param5 AS index_blocks_done,
+    s.param6 AS index_vacuum_count,
+        CASE s.param2
+            WHEN 0 THEN round(100.0, 2)
+            ELSE round(((((s.param3 + 1))::numeric * 100.0) / (s.param2)::numeric), 2)
+        END AS percent_done
+   FROM pg_database d,
+    pg_stat_get_progress_info('VACUUM'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10)
+  WHERE (s.datid = d.oid);
 pg_stat_replication| SELECT s.pid,
     s.usesysid,
     u.rolname AS usename,
-- 
1.7.1

0003-WIP-Add-a-block-number-argument-to-index-bulk-delete-v8.patchtext/x-diff; name=0003-WIP-Add-a-block-number-argument-to-index-bulk-delete-v8.patchDownload
From 457da970b9c667526e78dde6dfab1bb12f9ad520 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 8 Mar 2016 15:23:38 +0900
Subject: [PATCH 3/3] WIP: Add a block number argument to index bulk delete callback.

Currently, index AMs can only pass a ItemPointer (heap tid) and a generic
pointer to caller specified struct.  Add a BlockNumber so that the callback
can use it for something.  For example, lazy_tid_reaped() now uses it to
track the progress of AM's bulk delete scan of an index by comparing it with
the last call's value that it stores in its private state on every change.
The private state being the LVRelStats struct that now has the corresponding
field.  On every change, the count of index blocks vacuumed is incremented
which is also a new field in LVRelStats. The count is reset for every index
vacuum round.

Then a pgstat_progress_update_param call has been added to lazy_tid_reaped,
that pushes the count on every increment.

Currently, only btree AM is changed to pass the block number, others pass
InvalidBlockNumber.
---
 src/backend/access/gin/ginvacuum.c    |    2 +-
 src/backend/access/gist/gistvacuum.c  |    2 +-
 src/backend/access/hash/hash.c        |    2 +-
 src/backend/access/nbtree/nbtree.c    |    2 +-
 src/backend/access/spgist/spgvacuum.c |    6 ++--
 src/backend/catalog/index.c           |    5 ++-
 src/backend/commands/vacuumlazy.c     |   36 ++++++++++++++------------------
 src/include/access/genam.h            |    4 ++-
 8 files changed, 29 insertions(+), 30 deletions(-)

diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c
index 6a4b98a..64e69d6 100644
--- a/src/backend/access/gin/ginvacuum.c
+++ b/src/backend/access/gin/ginvacuum.c
@@ -55,7 +55,7 @@ ginVacuumItemPointers(GinVacuumState *gvs, ItemPointerData *items,
 	 */
 	for (i = 0; i < nitem; i++)
 	{
-		if (gvs->callback(items + i, gvs->callback_state))
+		if (gvs->callback(items + i, InvalidBlockNumber, gvs->callback_state))
 		{
 			gvs->result->tuples_removed += 1;
 			if (!tmpitems)
diff --git a/src/backend/access/gist/gistvacuum.c b/src/backend/access/gist/gistvacuum.c
index 7947ff9..96e89c3 100644
--- a/src/backend/access/gist/gistvacuum.c
+++ b/src/backend/access/gist/gistvacuum.c
@@ -202,7 +202,7 @@ gistbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 				iid = PageGetItemId(page, i);
 				idxtuple = (IndexTuple) PageGetItem(page, iid);
 
-				if (callback(&(idxtuple->t_tid), callback_state))
+				if (callback(&(idxtuple->t_tid), InvalidBlockNumber, callback_state))
 					todelete[ntodelete++] = i;
 				else
 					stats->num_index_tuples += 1;
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 3d48c4f..1530f0b 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -575,7 +575,7 @@ loop_top:
 				itup = (IndexTuple) PageGetItem(page,
 												PageGetItemId(page, offno));
 				htup = &(itup->t_tid);
-				if (callback(htup, callback_state))
+				if (callback(htup, InvalidBlockNumber, callback_state))
 				{
 					/* mark the item for deletion */
 					deletable[ndeletable++] = offno;
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index f2905cb..8c6be30 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -1034,7 +1034,7 @@ restart:
 				 * applies to *any* type of index that marks index tuples as
 				 * killed.
 				 */
-				if (callback(htup, callback_state))
+				if (callback(htup, blkno, callback_state))
 					deletable[ndeletable++] = offnum;
 			}
 		}
diff --git a/src/backend/access/spgist/spgvacuum.c b/src/backend/access/spgist/spgvacuum.c
index 15b867f..6c7265b 100644
--- a/src/backend/access/spgist/spgvacuum.c
+++ b/src/backend/access/spgist/spgvacuum.c
@@ -154,7 +154,7 @@ vacuumLeafPage(spgBulkDeleteState *bds, Relation index, Buffer buffer,
 		{
 			Assert(ItemPointerIsValid(&lt->heapPtr));
 
-			if (bds->callback(&lt->heapPtr, bds->callback_state))
+			if (bds->callback(&lt->heapPtr, InvalidBlockNumber, bds->callback_state))
 			{
 				bds->stats->tuples_removed += 1;
 				deletable[i] = true;
@@ -424,7 +424,7 @@ vacuumLeafRoot(spgBulkDeleteState *bds, Relation index, Buffer buffer)
 		{
 			Assert(ItemPointerIsValid(&lt->heapPtr));
 
-			if (bds->callback(&lt->heapPtr, bds->callback_state))
+			if (bds->callback(&lt->heapPtr, InvalidBlockNumber, bds->callback_state))
 			{
 				bds->stats->tuples_removed += 1;
 				toDelete[xlrec.nDelete] = i;
@@ -902,7 +902,7 @@ spgbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 
 /* Dummy callback to delete no tuples during spgvacuumcleanup */
 static bool
-dummy_callback(ItemPointer itemptr, void *state)
+dummy_callback(ItemPointer itemptr, BlockNumber index_blkno, void *state)
 {
 	return false;
 }
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 31a1438..d792fdb 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -114,7 +114,8 @@ static void IndexCheckExclusion(Relation heapRelation,
 					IndexInfo *indexInfo);
 static inline int64 itemptr_encode(ItemPointer itemptr);
 static inline void itemptr_decode(ItemPointer itemptr, int64 encoded);
-static bool validate_index_callback(ItemPointer itemptr, void *opaque);
+static bool validate_index_callback(ItemPointer itemptr, BlockNumber index_blkno,
+						void *opaque);
 static void validate_index_heapscan(Relation heapRelation,
 						Relation indexRelation,
 						IndexInfo *indexInfo,
@@ -2913,7 +2914,7 @@ itemptr_decode(ItemPointer itemptr, int64 encoded)
  * validate_index_callback - bulkdelete callback to collect the index TIDs
  */
 static bool
-validate_index_callback(ItemPointer itemptr, void *opaque)
+validate_index_callback(ItemPointer itemptr, BlockNumber index_blkno, void *opaque)
 {
 	v_i_state  *state = (v_i_state *) opaque;
 	int64		encoded = itemptr_encode(itemptr);
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index 0771b91..a2442dd 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -144,6 +144,8 @@ typedef struct LVRelStats
 	int			num_index_scans;
 	TransactionId latestRemovedXid;
 	bool		lock_waiter_detected;
+	BlockNumber	last_index_blkno;
+	BlockNumber	index_blks_vacuumed;
 } LVRelStats;
 
 
@@ -177,7 +179,7 @@ static BlockNumber count_nondeletable_pages(Relation onerel,
 static void lazy_space_alloc(LVRelStats *vacrelstats, BlockNumber relblocks);
 static void lazy_record_dead_tuple(LVRelStats *vacrelstats,
 					   ItemPointer itemptr);
-static bool lazy_tid_reaped(ItemPointer itemptr, void *state);
+static bool lazy_tid_reaped(ItemPointer itemptr, BlockNumber index_blkno, void *state);
 static int	vac_cmp_itemptr(const void *left, const void *right);
 static bool heap_page_is_all_visible(Relation rel, Buffer buf,
 					 TransactionId *visibility_cutoff_xid, bool *all_frozen);
@@ -461,8 +463,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 {
 	BlockNumber nblocks,
 				blkno,
-				total_index_blks,
-			   *current_index_blks;
+				total_index_blks;
 	HeapTupleData tuple;
 	char	   *relname;
 	BlockNumber empty_pages,
@@ -510,15 +511,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	pgstat_progress_update_param(PROG_PARAM_VAC_HEAP_BLKS, nblocks);
 
 	/* total_index_blks */
-	current_index_blks = (BlockNumber *) palloc(nindexes * sizeof(BlockNumber));
 	total_index_blks = 0;
 	for (i = 0; i < nindexes; i++)
-	{
-		BlockNumber		nblocks = RelationGetNumberOfBlocks(Irel[i]);
-
-		current_index_blks[i] = nblocks;
-		total_index_blks += nblocks;
-	}
+		total_index_blks += RelationGetNumberOfBlocks(Irel[i]);
 	pgstat_progress_update_param(PROG_PARAM_VAC_IDX_BLKS, total_index_blks);
 
 	/*
@@ -655,16 +650,12 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 
 			/* Remove index entries */
 			pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_VACUUM_INDEX);
+			vacrelstats->index_blks_vacuumed = 0;	/* reset for this round */
 			for (i = 0; i < nindexes; i++)
-			{
 				lazy_vacuum_index(Irel[i],
 								  &indstats[i],
 								  vacrelstats);
 
-				pgstat_progress_update_param(PROG_PARAM_VAC_IDX_BLKS_DONE,
-											 current_index_blks[i]);
-			}
-
 			pgstat_progress_update_param(PROG_PARAM_VAC_IDX_VAC_COUNT,
 										 vacrelstats->num_index_scans+1);
 
@@ -1212,15 +1203,12 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 
 		/* Remove index entries */
 		pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_VACUUM_INDEX);
+		vacrelstats->index_blks_vacuumed = 0;	/* reset for this round */
 		for (i = 0; i < nindexes; i++)
-		{
 			lazy_vacuum_index(Irel[i],
 							  &indstats[i],
 							  vacrelstats);
 
-			pgstat_progress_update_param(PROG_PARAM_VAC_IDX_BLKS_DONE,
-										 current_index_blks[i]);
-		}
 		pgstat_progress_update_param(PROG_PARAM_VAC_IDX_VAC_COUNT,
 									 vacrelstats->num_index_scans + 1);
 
@@ -1908,11 +1896,19 @@ lazy_record_dead_tuple(LVRelStats *vacrelstats,
  *		Assumes dead_tuples array is in sorted order.
  */
 static bool
-lazy_tid_reaped(ItemPointer itemptr, void *state)
+lazy_tid_reaped(ItemPointer itemptr, BlockNumber index_blkno, void *state)
 {
 	LVRelStats *vacrelstats = (LVRelStats *) state;
 	ItemPointer res;
 
+	if (index_blkno != vacrelstats->last_index_blkno)
+	{
+		++vacrelstats->index_blks_vacuumed;
+		pgstat_progress_update_param(PROG_PARAM_VAC_IDX_BLKS_DONE,
+									 vacrelstats->index_blks_vacuumed);
+		vacrelstats->last_index_blkno = index_blkno;
+	}
+
 	res = (ItemPointer) bsearch((void *) itemptr,
 								(void *) vacrelstats->dead_tuples,
 								vacrelstats->num_dead_tuples,
diff --git a/src/include/access/genam.h b/src/include/access/genam.h
index 81907d5..8d67d1c 100644
--- a/src/include/access/genam.h
+++ b/src/include/access/genam.h
@@ -77,7 +77,9 @@ typedef struct IndexBulkDeleteResult
 } IndexBulkDeleteResult;
 
 /* Typedef for callback function to determine if a tuple is bulk-deletable */
-typedef bool (*IndexBulkDeleteCallback) (ItemPointer itemptr, void *state);
+typedef bool (*IndexBulkDeleteCallback) (ItemPointer itemptr,
+										 BlockNumber index_blkno,
+										 void *state);
 
 /* struct definitions appear in relscan.h */
 typedef struct IndexScanDescData *IndexScanDesc;
-- 
1.7.1

#198Noname
pokurev@pm.nttdata.co.jp
In reply to: Amit Langote (#197)
Re: [PROPOSAL] VACUUM Progress Checker.

Hi Amit,

Thank you for updating the patch.

-----Original Message-----
From: Amit Langote [mailto:Langote_Amit_f8@lab.ntt.co.jp]
Sent: Thursday, March 10, 2016 3:36 PM
To: Robert Haas <robertmhaas@gmail.com>
Cc: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Amit Langote
<amitlangote09@gmail.com>; SPS ポクレ ヴィナヤック(三技術)
<pokurev@pm.nttdata.co.jp>; pgsql-hackers@postgresql.org; SPS 坂野 昌
平(三技術) <bannos@nttdata.co.jp>
Subject: Re: [HACKERS] [PROPOSAL] VACUUM Progress Checker.

On 2016/03/10 14:29, Amit Langote wrote:

I rebased remainder patches (attached).

0001 is a small patch to fix issues reported by Tomas and Vinayak.
0002 and 0003 are WIP patches to implement progress reporting for

vacuum.

Oops, in 0002, I wrongly joined with pg_class in the definition of
pg_stat_progress_vacuum to output the schema-qualified name of the table
being vacuumed. That means we need to connect to the correct database,
which is undesirable. Updated version fixes that (shows database name and
relid). You may also have noticed that I said pg_stat_progress_vacuum, not
pg_stat_vacuum_progress (IMHO, the former is a better name).

Updated patches attached.

In 0002-
+CREATE VIEW pg_stat_progress_vacuum AS
+    SELECT
+            S.pid AS pid,
+            D.datname AS database,
+            S.relid AS relid,
.
.
.
.
+    FROM pg_database D, pg_stat_get_progress_info('VACUUM') AS S
+    WHERE S.datid = D.oid;
I think we need to use datid instead of datname.
Robert added datid in pg_stat_get_progress_info() and we are using that function here.
+values[1] = ObjectIdGetDatum(beentry->st_databaseid);

+DATA(insert OID = 3318 ( pg_stat_get_progress_info PGNSP PGUID 12 1 100 0 0 f f f f f t s r 1 0 2249 "25" "{25,23,26,26,20,20,20,20,20,20,20,20,20,20}" "{i,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{cmdtype,pid,datid,relid,param1,param2,param3,param4,param5,param6,param7,param8,param9,param10}" _null_ _null_ pg_stat_get_progress_info _null_ _null_ _null_ ));

So I think it's better to report datid not datname.
The definition of view is simply like:
+CREATE VIEW pg_stat_progress_vacuum AS
+    SELECT
+            S.pid AS pid,
+            S.datid AS datid,
+            S.relid AS relid,
+            CASE S.param1
+                WHEN 1 THEN 'scanning heap'
+                WHEN 2 THEN 'vacuuming indexes'
+                WHEN 3 THEN 'vacuuming heap'
+                WHEN 4 THEN 'cleanup'
+                ELSE 'unknown phase'
+            END AS processing_phase,
+            S.param2 AS total_heap_blocks,
+            S.param3 AS current_heap_block,
+            S.param4 AS total_index_blocks,
+            S.param5 AS index_blocks_done,
+            S.param6 AS index_vacuum_count,
+            CASE S.param2
+                WHEN 0 THEN round(100.0, 2)
+			    ELSE round((S.param3 + 1) * 100.0 / S.param2, 2)
+            END AS percent_done
+    FROM pg_stat_get_progress_info('VACUUM') AS S;

In the pg_stat_activity view, datid and datname are the separate columns. So maybe we can add datname as separate column in pg_stat_progress_vacuum, but I think it's not required only datid is sufficient.
Any comment?

Regards,
Vinayak Pokale

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#199Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Noname (#198)
3 attachment(s)
Re: [PROPOSAL] VACUUM Progress Checker.

Hi Vinayak,

Thanks for the quick review!

On 2016/03/10 16:22, pokurev@pm.nttdata.co.jp wrote:

On 2016/03/10 14:29, Amit Langote wrote:
Updated patches attached.

In 0002-

[ snip ]

I think we need to use datid instead of datname.
Robert added datid in pg_stat_get_progress_info() and we are using that function here.
+values[1] = ObjectIdGetDatum(beentry->st_databaseid);

[ snip ]

So I think it's better to report datid not datname.
The definition of view is simply like:
+CREATE VIEW pg_stat_progress_vacuum AS
+    SELECT
+            S.pid AS pid,
+            S.datid AS datid,
+            S.relid AS relid,
+            CASE S.param1
+                WHEN 1 THEN 'scanning heap'
+                WHEN 2 THEN 'vacuuming indexes'
+                WHEN 3 THEN 'vacuuming heap'
+                WHEN 4 THEN 'cleanup'
+                ELSE 'unknown phase'
+            END AS processing_phase,
+            S.param2 AS total_heap_blocks,
+            S.param3 AS current_heap_block,
+            S.param4 AS total_index_blocks,
+            S.param5 AS index_blocks_done,
+            S.param6 AS index_vacuum_count,
+            CASE S.param2
+                WHEN 0 THEN round(100.0, 2)
+			    ELSE round((S.param3 + 1) * 100.0 / S.param2, 2)
+            END AS percent_done
+    FROM pg_stat_get_progress_info('VACUUM') AS S;

So maybe we can add datname as separate column in pg_stat_progress_vacuum, I think it's not required only datid is sufficient.
Any comment?

Why do you think showing the name may be unacceptable? Wouldn't that be a
little more user-friendly? Though maybe, we can follow the
pg_stat_activity style and have both instead, as you suggest. Attached
updated version does that.

Thanks,
Amit

Attachments:

0001-Some-minor-fixes-in-commit-b6fb6471.patchtext/x-diff; name=0001-Some-minor-fixes-in-commit-b6fb6471.patchDownload
From 7cb702c7fae9fceef3048a82522390844c5a67cc Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 10 Mar 2016 11:25:20 +0900
Subject: [PATCH 1/3] Some minor fixes in commit b6fb6471.

---
 src/backend/postmaster/pgstat.c     |    7 ++++---
 src/backend/utils/adt/pgstatfuncs.c |    2 +-
 2 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index ce5da3e..4424cb8 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -2861,8 +2861,8 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
 /*-----------
  * pgstat_progress_start_command() -
  *
- * Set st_command in own backend entry.  Also, zero-initialize
- * st_progress_param array.
+ * Set st_progress_command (and st_progress_command_target) in own backend
+ * entry.  Also, zero-initialize st_progress_param array.
  *-----------
  */
 void
@@ -2904,7 +2904,8 @@ pgstat_progress_update_param(int index, int64 val)
 /*-----------
  * pgstat_progress_end_command() -
  *
- * Update index'th member in st_progress_param[] of own backend entry.
+ * Reset st_progress_command (and st_progress_command_target) in own backend
+ * entry.  This signals the end of the command.
  *-----------
  */
 void
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 0c790ff..2fb51fa 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -614,7 +614,7 @@ pg_stat_get_progress_info(PG_FUNCTION_ARGS)
 		else
 		{
 			nulls[2] = true;
-			for (i = 1; i < PGSTAT_NUM_PROGRESS_PARAM + 1; i++)
+			for (i = 0; i < PGSTAT_NUM_PROGRESS_PARAM; i++)
 				nulls[i+3] = true;
 		}
 
-- 
1.7.1

0002-WIP-Implement-progress-reporting-for-VACUUM-command-v9.patchtext/x-diff; name=0002-WIP-Implement-progress-reporting-for-VACUUM-command-v9.patchDownload
From 9e3ac989d8583c6ba5e74945602aeaacfaee127f Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 7 Mar 2016 14:38:34 +0900
Subject: [PATCH 2/3] WIP: Implement progress reporting for VACUUM command.

This basically utilizes the pgstat_progress* API to report a handful of
paramters to indicate its progress.  lazy_vacuum_rel() and lazy_scan_heap()
have been altered to report command start, command target table, and the
following parameters: processing phase, number of heap blocks, number of
index blocks (all indexes), current heap block number in the main scan loop
(whenever changes), index blocks vacuumed (once per finished index vacuum),
and number of index vacuum passes (every time when all indexes are vacuumed).
Following processing phases are identified and reported whenever one changes
to another: 'scanning heap', 'vacuuming indexes', 'vacuuming heap', and
'cleanup'.

TODO: find a way to report index pages vacuumed in a more granular manner than
the current report per index vacuumed.

A view named pg_stat_progress_vacuum has been added that shows these values.
---
 doc/src/sgml/monitoring.sgml         |  111 ++++++++++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   25 ++++++++
 src/backend/commands/vacuumlazy.c    |   73 ++++++++++++++++++++++-
 src/test/regress/expected/rules.out  |   23 +++++++
 4 files changed, 231 insertions(+), 1 deletions(-)

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 85459d0..6b26ba9 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -507,6 +507,12 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       yet included in <structname>pg_stat_user_functions</>).</entry>
      </row>
 
+     <row>
+      <entry><structname>pg_stat_progress_vacuum</><indexterm><primary>pg_stat_progress_vacuum</primary></indexterm></entry>
+      <entry>One row for each backend (including autovacuum worker processes) running
+      <command>VACUUM</>, showing current progress in terms of heap pages it
+      has finished processing.</entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -1822,6 +1828,111 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
    controls exactly which functions are tracked.
   </para>
 
+  <table id="pg-stat-progress-vacuum" xreflabel="pg_stat_progress_vacuum">
+   <title><structname>pg_stat_progress_vacuum</structname> View</title>
+   <tgroup cols="3">
+    <thead>
+    <row>
+      <entry>Column</entry>
+      <entry>Type</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+   <tbody>
+    <row>
+     <entry><structfield>pid</></entry>
+     <entry><type>integer</></entry>
+     <entry>Process ID of backend</entry>
+    </row>
+    <row>
+     <entry><structfield>datid</></entry>
+     <entry><type>oid</></entry>
+     <entry>OID of the database this backend is connected to</entry>
+    </row>
+    <row>
+     <entry><structfield>datname</></entry>
+     <entry><type>name</></entry>
+     <entry>Name of the database this backend is connected to</entry>
+    </row>
+    <row>
+     <entry><structfield>relid</></entry>
+     <entry><type>oid</></entry>
+     <entry>OID of the table being vacuumed</entry>
+    </row>
+    <row>
+     <entry><structfield>processing_phase</></entry>
+     <entry><type>text</></entry>
+     <entry>Current processing phase of vacuum.
+       Possible values are:
+       <itemizedlist>
+        <listitem>
+         <para>
+          <literal>scanning heap</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>vacuuming indexes</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>vacuuming heap</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>cleanup</>
+         </para>
+        </listitem>
+       </itemizedlist>
+     </entry>
+    </row>
+    <row>
+     <entry><structfield>total_heap_blocks</></entry>
+     <entry><type>bigint</></entry>
+     <entry>Total number of heap blocks in the table</entry>
+    </row>
+    <row>
+     <entry><structfield>current_heap_block</></entry>
+     <entry><type>bigint</></entry>
+     <entry>Current heap block being processed</entry>
+    </row>
+    <row>
+     <entry><structfield>total_index_blocks</></entry>
+     <entry><type>bigint</></entry>
+     <entry>Total number of index blocks to be processed</entry>
+    </row>
+    <row>
+     <entry><structfield>index_blocks_done</></entry>
+     <entry><type>bigint</></entry>
+     <entry>Number of index blocks processed in current vacuum round</entry>
+    </row>
+    <row>
+     <entry><structfield>index_vacuum_count</></entry>
+     <entry><type>bigint</></entry>
+     <entry>Number of times index vacuum round has been performed so far</entry>
+    </row>
+    <row>
+     <entry><structfield>percent_done</></entry>
+     <entry><type>numeric</></entry>
+     <entry>
+      Amount of work finished in percent in terms of table blocks processed
+     </entry>
+    </row>
+   </tbody>
+   </tgroup>
+  </table>
+
+  <para>
+   The <structname>pg_stat_progress_vacuum</structname> view will contain
+   one row for each backend (including autovacuum worker processes), showing
+   parameters that can help determine the progress of <command>VACUUM</command>
+   command running in it. Note that the backends running
+   <command>VACUUM FULL</command> are not shown.
+  </para>
+
  </sect2>
 
  <sect2 id="monitoring-stats-functions">
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index abf9a70..5d9caf7 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -971,3 +971,28 @@ RETURNS jsonb
 LANGUAGE INTERNAL
 STRICT IMMUTABLE
 AS 'jsonb_set';
+
+CREATE VIEW pg_stat_progress_vacuum AS
+    SELECT
+            S.pid AS pid,
+            S.datid AS datid,
+            D.datname AS datname,
+            S.relid AS relid,
+            CASE S.param1
+                WHEN 1 THEN 'scanning heap'
+                WHEN 2 THEN 'vacuuming indexes'
+                WHEN 3 THEN 'vacuuming heap'
+                WHEN 4 THEN 'cleanup'
+                ELSE 'unknown phase'
+            END AS processing_phase,
+            S.param2 AS total_heap_blocks,
+            S.param3 AS current_heap_block,
+            S.param4 AS total_index_blocks,
+            S.param5 AS index_blocks_done,
+            S.param6 AS index_vacuum_count,
+            CASE S.param2
+                WHEN 0 THEN round(100.0, 2)
+			    ELSE round((S.param3 + 1) * 100.0 / S.param2, 2)
+            END AS percent_done
+    FROM pg_database D, pg_stat_get_progress_info('VACUUM') AS S
+    WHERE S.datid = D.oid;
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index 61d2edd..0771b91 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -97,6 +97,29 @@
  */
 #define SKIP_PAGES_THRESHOLD	((BlockNumber) 32)
 
+/*
+ * Progress parameters of (lazy) vacuum reported to pgstat progress tracking
+ * facility
+ */
+#define PROG_PARAM_VAC_PHASE			0
+#define PROG_PARAM_VAC_HEAP_BLKS		1
+#define PROG_PARAM_VAC_CURR_HEAP_BLK	2
+#define PROG_PARAM_VAC_IDX_BLKS			3
+#define PROG_PARAM_VAC_IDX_BLKS_DONE	4
+#define PROG_PARAM_VAC_IDX_VAC_COUNT	5
+
+/*
+ * Following distinct phases of lazy vacuum are identified.  #1, #2 and #3
+ * run in a cyclical manner due to possibly limited memory to work with,
+ * whereby #1 is periodically interrupted to run #2, followed by #3, and
+ * back to #1.  Cycle repeats until all blocks of the relation have been
+ * covered by #1.
+ */
+#define LV_PHASE_SCAN_HEAP			1
+#define LV_PHASE_VACUUM_INDEX		2
+#define LV_PHASE_VACUUM_HEAP		3
+#define LV_PHASE_CLEANUP			4
+
 typedef struct LVRelStats
 {
 	/* hasindex = true means two-pass strategy; false means one-pass */
@@ -437,7 +460,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			   Relation *Irel, int nindexes, bool scan_all)
 {
 	BlockNumber nblocks,
-				blkno;
+				blkno,
+				total_index_blks,
+			   *current_index_blks;
 	HeapTupleData tuple;
 	char	   *relname;
 	BlockNumber empty_pages,
@@ -478,6 +503,24 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	lazy_space_alloc(vacrelstats, nblocks);
 	frozen = palloc(sizeof(xl_heap_freeze_tuple) * MaxHeapTuplesPerPage);
 
+	/* We're about to begin heap scan. */
+	pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_SCAN_HEAP);
+
+	/* total_heap_blks */
+	pgstat_progress_update_param(PROG_PARAM_VAC_HEAP_BLKS, nblocks);
+
+	/* total_index_blks */
+	current_index_blks = (BlockNumber *) palloc(nindexes * sizeof(BlockNumber));
+	total_index_blks = 0;
+	for (i = 0; i < nindexes; i++)
+	{
+		BlockNumber		nblocks = RelationGetNumberOfBlocks(Irel[i]);
+
+		current_index_blks[i] = nblocks;
+		total_index_blks += nblocks;
+	}
+	pgstat_progress_update_param(PROG_PARAM_VAC_IDX_BLKS, total_index_blks);
+
 	/*
 	 * We want to skip pages that don't require vacuuming according to the
 	 * visibility map, but only when we can skip at least SKIP_PAGES_THRESHOLD
@@ -585,6 +628,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 
 		vacuum_delay_point();
 
+		/* current_heap_blkno: 0..nblocks-1 */
+		pgstat_progress_update_param(PROG_PARAM_VAC_CURR_HEAP_BLK, blkno);
+
 		/*
 		 * If we are close to overrunning the available space for dead-tuple
 		 * TIDs, pause and do a cycle of vacuuming before we tackle this page.
@@ -608,11 +654,22 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			vacuum_log_cleanup_info(onerel, vacrelstats);
 
 			/* Remove index entries */
+			pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_VACUUM_INDEX);
 			for (i = 0; i < nindexes; i++)
+			{
 				lazy_vacuum_index(Irel[i],
 								  &indstats[i],
 								  vacrelstats);
+
+				pgstat_progress_update_param(PROG_PARAM_VAC_IDX_BLKS_DONE,
+											 current_index_blks[i]);
+			}
+
+			pgstat_progress_update_param(PROG_PARAM_VAC_IDX_VAC_COUNT,
+										 vacrelstats->num_index_scans+1);
+
 			/* Remove tuples from heap */
+			pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_VACUUM_HEAP);
 			lazy_vacuum_heap(onerel, vacrelstats);
 
 			/*
@@ -622,6 +679,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			 */
 			vacrelstats->num_dead_tuples = 0;
 			vacrelstats->num_index_scans++;
+
+			/* Going back to scanning the heap */
+			pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_SCAN_HEAP);
 		}
 
 		/*
@@ -1151,16 +1211,27 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		vacuum_log_cleanup_info(onerel, vacrelstats);
 
 		/* Remove index entries */
+		pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_VACUUM_INDEX);
 		for (i = 0; i < nindexes; i++)
+		{
 			lazy_vacuum_index(Irel[i],
 							  &indstats[i],
 							  vacrelstats);
+
+			pgstat_progress_update_param(PROG_PARAM_VAC_IDX_BLKS_DONE,
+										 current_index_blks[i]);
+		}
+		pgstat_progress_update_param(PROG_PARAM_VAC_IDX_VAC_COUNT,
+									 vacrelstats->num_index_scans + 1);
+
 		/* Remove tuples from heap */
+		pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_VACUUM_HEAP);
 		lazy_vacuum_heap(onerel, vacrelstats);
 		vacrelstats->num_index_scans++;
 	}
 
 	/* Do post-vacuum cleanup and statistics update for each index */
+	pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_CLEANUP);
 	for (i = 0; i < nindexes; i++)
 		lazy_cleanup_index(Irel[i], indstats[i], vacrelstats);
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 81bc5c9..97d4e12 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1746,6 +1746,29 @@ pg_stat_database_conflicts| SELECT d.oid AS datid,
     pg_stat_get_db_conflict_bufferpin(d.oid) AS confl_bufferpin,
     pg_stat_get_db_conflict_startup_deadlock(d.oid) AS confl_deadlock
    FROM pg_database d;
+pg_stat_progress_vacuum| SELECT s.pid,
+    s.datid,
+    d.datname,
+    s.relid,
+        CASE s.param1
+            WHEN 1 THEN 'scanning heap'::text
+            WHEN 2 THEN 'vacuuming indexes'::text
+            WHEN 3 THEN 'vacuuming heap'::text
+            WHEN 4 THEN 'cleanup'::text
+            ELSE 'unknown phase'::text
+        END AS processing_phase,
+    s.param2 AS total_heap_blocks,
+    s.param3 AS current_heap_block,
+    s.param4 AS total_index_blocks,
+    s.param5 AS index_blocks_done,
+    s.param6 AS index_vacuum_count,
+        CASE s.param2
+            WHEN 0 THEN round(100.0, 2)
+            ELSE round(((((s.param3 + 1))::numeric * 100.0) / (s.param2)::numeric), 2)
+        END AS percent_done
+   FROM pg_database d,
+    pg_stat_get_progress_info('VACUUM'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10)
+  WHERE (s.datid = d.oid);
 pg_stat_replication| SELECT s.pid,
     s.usesysid,
     u.rolname AS usename,
-- 
1.7.1

0003-WIP-Add-a-block-number-argument-to-index-bulk-delete-v9.patchtext/x-diff; name=0003-WIP-Add-a-block-number-argument-to-index-bulk-delete-v9.patchDownload
From ed31bbaf757b86f8f42ef8be233086829f3c6fbf Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 8 Mar 2016 15:23:38 +0900
Subject: [PATCH 3/3] WIP: Add a block number argument to index bulk delete callback.

Currently, index AMs can only pass a ItemPointer (heap tid) and a generic
pointer to caller specified struct.  Add a BlockNumber so that the callback
can use it for something.  For example, lazy_tid_reaped() now uses it to
track the progress of AM's bulk delete scan of an index by comparing it with
the last call's value that it stores in its private state on every change.
The private state being the LVRelStats struct that now has the corresponding
field.  On every change, the count of index blocks vacuumed is incremented
which is also a new field in LVRelStats. The count is reset for every index
vacuum round.

Then a pgstat_progress_update_param call has been added to lazy_tid_reaped,
that pushes the count on every increment.

Currently, only btree AM is changed to pass the block number, others pass
InvalidBlockNumber.
---
 src/backend/access/gin/ginvacuum.c    |    2 +-
 src/backend/access/gist/gistvacuum.c  |    2 +-
 src/backend/access/hash/hash.c        |    2 +-
 src/backend/access/nbtree/nbtree.c    |    2 +-
 src/backend/access/spgist/spgvacuum.c |    6 ++--
 src/backend/catalog/index.c           |    5 ++-
 src/backend/commands/vacuumlazy.c     |   36 ++++++++++++++------------------
 src/include/access/genam.h            |    4 ++-
 8 files changed, 29 insertions(+), 30 deletions(-)

diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c
index 6a4b98a..64e69d6 100644
--- a/src/backend/access/gin/ginvacuum.c
+++ b/src/backend/access/gin/ginvacuum.c
@@ -55,7 +55,7 @@ ginVacuumItemPointers(GinVacuumState *gvs, ItemPointerData *items,
 	 */
 	for (i = 0; i < nitem; i++)
 	{
-		if (gvs->callback(items + i, gvs->callback_state))
+		if (gvs->callback(items + i, InvalidBlockNumber, gvs->callback_state))
 		{
 			gvs->result->tuples_removed += 1;
 			if (!tmpitems)
diff --git a/src/backend/access/gist/gistvacuum.c b/src/backend/access/gist/gistvacuum.c
index 7947ff9..96e89c3 100644
--- a/src/backend/access/gist/gistvacuum.c
+++ b/src/backend/access/gist/gistvacuum.c
@@ -202,7 +202,7 @@ gistbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 				iid = PageGetItemId(page, i);
 				idxtuple = (IndexTuple) PageGetItem(page, iid);
 
-				if (callback(&(idxtuple->t_tid), callback_state))
+				if (callback(&(idxtuple->t_tid), InvalidBlockNumber, callback_state))
 					todelete[ntodelete++] = i;
 				else
 					stats->num_index_tuples += 1;
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 3d48c4f..1530f0b 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -575,7 +575,7 @@ loop_top:
 				itup = (IndexTuple) PageGetItem(page,
 												PageGetItemId(page, offno));
 				htup = &(itup->t_tid);
-				if (callback(htup, callback_state))
+				if (callback(htup, InvalidBlockNumber, callback_state))
 				{
 					/* mark the item for deletion */
 					deletable[ndeletable++] = offno;
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index f2905cb..8c6be30 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -1034,7 +1034,7 @@ restart:
 				 * applies to *any* type of index that marks index tuples as
 				 * killed.
 				 */
-				if (callback(htup, callback_state))
+				if (callback(htup, blkno, callback_state))
 					deletable[ndeletable++] = offnum;
 			}
 		}
diff --git a/src/backend/access/spgist/spgvacuum.c b/src/backend/access/spgist/spgvacuum.c
index 15b867f..6c7265b 100644
--- a/src/backend/access/spgist/spgvacuum.c
+++ b/src/backend/access/spgist/spgvacuum.c
@@ -154,7 +154,7 @@ vacuumLeafPage(spgBulkDeleteState *bds, Relation index, Buffer buffer,
 		{
 			Assert(ItemPointerIsValid(&lt->heapPtr));
 
-			if (bds->callback(&lt->heapPtr, bds->callback_state))
+			if (bds->callback(&lt->heapPtr, InvalidBlockNumber, bds->callback_state))
 			{
 				bds->stats->tuples_removed += 1;
 				deletable[i] = true;
@@ -424,7 +424,7 @@ vacuumLeafRoot(spgBulkDeleteState *bds, Relation index, Buffer buffer)
 		{
 			Assert(ItemPointerIsValid(&lt->heapPtr));
 
-			if (bds->callback(&lt->heapPtr, bds->callback_state))
+			if (bds->callback(&lt->heapPtr, InvalidBlockNumber, bds->callback_state))
 			{
 				bds->stats->tuples_removed += 1;
 				toDelete[xlrec.nDelete] = i;
@@ -902,7 +902,7 @@ spgbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 
 /* Dummy callback to delete no tuples during spgvacuumcleanup */
 static bool
-dummy_callback(ItemPointer itemptr, void *state)
+dummy_callback(ItemPointer itemptr, BlockNumber index_blkno, void *state)
 {
 	return false;
 }
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 31a1438..d792fdb 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -114,7 +114,8 @@ static void IndexCheckExclusion(Relation heapRelation,
 					IndexInfo *indexInfo);
 static inline int64 itemptr_encode(ItemPointer itemptr);
 static inline void itemptr_decode(ItemPointer itemptr, int64 encoded);
-static bool validate_index_callback(ItemPointer itemptr, void *opaque);
+static bool validate_index_callback(ItemPointer itemptr, BlockNumber index_blkno,
+						void *opaque);
 static void validate_index_heapscan(Relation heapRelation,
 						Relation indexRelation,
 						IndexInfo *indexInfo,
@@ -2913,7 +2914,7 @@ itemptr_decode(ItemPointer itemptr, int64 encoded)
  * validate_index_callback - bulkdelete callback to collect the index TIDs
  */
 static bool
-validate_index_callback(ItemPointer itemptr, void *opaque)
+validate_index_callback(ItemPointer itemptr, BlockNumber index_blkno, void *opaque)
 {
 	v_i_state  *state = (v_i_state *) opaque;
 	int64		encoded = itemptr_encode(itemptr);
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index 0771b91..a2442dd 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -144,6 +144,8 @@ typedef struct LVRelStats
 	int			num_index_scans;
 	TransactionId latestRemovedXid;
 	bool		lock_waiter_detected;
+	BlockNumber	last_index_blkno;
+	BlockNumber	index_blks_vacuumed;
 } LVRelStats;
 
 
@@ -177,7 +179,7 @@ static BlockNumber count_nondeletable_pages(Relation onerel,
 static void lazy_space_alloc(LVRelStats *vacrelstats, BlockNumber relblocks);
 static void lazy_record_dead_tuple(LVRelStats *vacrelstats,
 					   ItemPointer itemptr);
-static bool lazy_tid_reaped(ItemPointer itemptr, void *state);
+static bool lazy_tid_reaped(ItemPointer itemptr, BlockNumber index_blkno, void *state);
 static int	vac_cmp_itemptr(const void *left, const void *right);
 static bool heap_page_is_all_visible(Relation rel, Buffer buf,
 					 TransactionId *visibility_cutoff_xid, bool *all_frozen);
@@ -461,8 +463,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 {
 	BlockNumber nblocks,
 				blkno,
-				total_index_blks,
-			   *current_index_blks;
+				total_index_blks;
 	HeapTupleData tuple;
 	char	   *relname;
 	BlockNumber empty_pages,
@@ -510,15 +511,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	pgstat_progress_update_param(PROG_PARAM_VAC_HEAP_BLKS, nblocks);
 
 	/* total_index_blks */
-	current_index_blks = (BlockNumber *) palloc(nindexes * sizeof(BlockNumber));
 	total_index_blks = 0;
 	for (i = 0; i < nindexes; i++)
-	{
-		BlockNumber		nblocks = RelationGetNumberOfBlocks(Irel[i]);
-
-		current_index_blks[i] = nblocks;
-		total_index_blks += nblocks;
-	}
+		total_index_blks += RelationGetNumberOfBlocks(Irel[i]);
 	pgstat_progress_update_param(PROG_PARAM_VAC_IDX_BLKS, total_index_blks);
 
 	/*
@@ -655,16 +650,12 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 
 			/* Remove index entries */
 			pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_VACUUM_INDEX);
+			vacrelstats->index_blks_vacuumed = 0;	/* reset for this round */
 			for (i = 0; i < nindexes; i++)
-			{
 				lazy_vacuum_index(Irel[i],
 								  &indstats[i],
 								  vacrelstats);
 
-				pgstat_progress_update_param(PROG_PARAM_VAC_IDX_BLKS_DONE,
-											 current_index_blks[i]);
-			}
-
 			pgstat_progress_update_param(PROG_PARAM_VAC_IDX_VAC_COUNT,
 										 vacrelstats->num_index_scans+1);
 
@@ -1212,15 +1203,12 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 
 		/* Remove index entries */
 		pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_VACUUM_INDEX);
+		vacrelstats->index_blks_vacuumed = 0;	/* reset for this round */
 		for (i = 0; i < nindexes; i++)
-		{
 			lazy_vacuum_index(Irel[i],
 							  &indstats[i],
 							  vacrelstats);
 
-			pgstat_progress_update_param(PROG_PARAM_VAC_IDX_BLKS_DONE,
-										 current_index_blks[i]);
-		}
 		pgstat_progress_update_param(PROG_PARAM_VAC_IDX_VAC_COUNT,
 									 vacrelstats->num_index_scans + 1);
 
@@ -1908,11 +1896,19 @@ lazy_record_dead_tuple(LVRelStats *vacrelstats,
  *		Assumes dead_tuples array is in sorted order.
  */
 static bool
-lazy_tid_reaped(ItemPointer itemptr, void *state)
+lazy_tid_reaped(ItemPointer itemptr, BlockNumber index_blkno, void *state)
 {
 	LVRelStats *vacrelstats = (LVRelStats *) state;
 	ItemPointer res;
 
+	if (index_blkno != vacrelstats->last_index_blkno)
+	{
+		++vacrelstats->index_blks_vacuumed;
+		pgstat_progress_update_param(PROG_PARAM_VAC_IDX_BLKS_DONE,
+									 vacrelstats->index_blks_vacuumed);
+		vacrelstats->last_index_blkno = index_blkno;
+	}
+
 	res = (ItemPointer) bsearch((void *) itemptr,
 								(void *) vacrelstats->dead_tuples,
 								vacrelstats->num_dead_tuples,
diff --git a/src/include/access/genam.h b/src/include/access/genam.h
index 81907d5..8d67d1c 100644
--- a/src/include/access/genam.h
+++ b/src/include/access/genam.h
@@ -77,7 +77,9 @@ typedef struct IndexBulkDeleteResult
 } IndexBulkDeleteResult;
 
 /* Typedef for callback function to determine if a tuple is bulk-deletable */
-typedef bool (*IndexBulkDeleteCallback) (ItemPointer itemptr, void *state);
+typedef bool (*IndexBulkDeleteCallback) (ItemPointer itemptr,
+										 BlockNumber index_blkno,
+										 void *state);
 
 /* struct definitions appear in relscan.h */
 typedef struct IndexScanDescData *IndexScanDesc;
-- 
1.7.1

#200Noname
pokurev@pm.nttdata.co.jp
In reply to: Amit Langote (#199)
Re: [PROPOSAL] VACUUM Progress Checker.

Hi Amit,

Thank you for updating the patch.

-----Original Message-----
From: Amit Langote [mailto:Langote_Amit_f8@lab.ntt.co.jp]
Sent: Thursday, March 10, 2016 5:09 PM
To: SPS ポクレ ヴィナヤック(三技術) <pokurev@pm.nttdata.co.jp>;
robertmhaas@gmail.com
Cc: horiguchi.kyotaro@lab.ntt.co.jp; amitlangote09@gmail.com; pgsql-
hackers@postgresql.org; SPS 坂野 昌平(三技術) <bannos@nttdata.co.jp>
Subject: Re: [HACKERS] [PROPOSAL] VACUUM Progress Checker.

Hi Vinayak,

Thanks for the quick review!

On 2016/03/10 16:22, pokurev@pm.nttdata.co.jp wrote:

On 2016/03/10 14:29, Amit Langote wrote:
Updated patches attached.

In 0002-

[ snip ]

I think we need to use datid instead of datname.
Robert added datid in pg_stat_get_progress_info() and we are using that

function here.

+values[1] = ObjectIdGetDatum(beentry->st_databaseid);

[ snip ]

So I think it's better to report datid not datname.
The definition of view is simply like:
+CREATE VIEW pg_stat_progress_vacuum AS
+    SELECT
+            S.pid AS pid,
+            S.datid AS datid,
+            S.relid AS relid,
+            CASE S.param1
+                WHEN 1 THEN 'scanning heap'
+                WHEN 2 THEN 'vacuuming indexes'
+                WHEN 3 THEN 'vacuuming heap'
+                WHEN 4 THEN 'cleanup'
+                ELSE 'unknown phase'
+            END AS processing_phase,
+            S.param2 AS total_heap_blocks,
+            S.param3 AS current_heap_block,
+            S.param4 AS total_index_blocks,
+            S.param5 AS index_blocks_done,
+            S.param6 AS index_vacuum_count,
+            CASE S.param2
+                WHEN 0 THEN round(100.0, 2)
+			    ELSE round((S.param3 + 1) * 100.0 / S.param2, 2)
+            END AS percent_done
+    FROM pg_stat_get_progress_info('VACUUM') AS S;

So maybe we can add datname as separate column in

pg_stat_progress_vacuum, I think it's not required only datid is sufficient.

Any comment?

Why do you think showing the name may be unacceptable? Wouldn't that
be a little more user-friendly? Though maybe, we can follow the
pg_stat_activity style and have both instead, as you suggest. Attached
updated version does that.

+1
I think reporting both (datid and datname) is more user-friendly.
Thank you.

Regards,
Vinayak Pokale

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#201Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Noname (#200)
Re: [PROPOSAL] VACUUM Progress Checker.

Hi,

At Thu, 10 Mar 2016 08:21:36 +0000, <pokurev@pm.nttdata.co.jp> wrote in <8e09c2fe530d4008aa0019e38c1d5453@MP-MSGSS-MBX007.msg.nttdata.co.jp>

So maybe we can add datname as separate column in

pg_stat_progress_vacuum, I think it's not required only datid is sufficient.

Any comment?

Why do you think showing the name may be unacceptable? Wouldn't that
be a little more user-friendly? Though maybe, we can follow the
pg_stat_activity style and have both instead, as you suggest. Attached
updated version does that.

+1
I think reporting both (datid and datname) is more user-friendly.
Thank you.

I don't like showing both oid and name and only "user friendry"
doesn't seem to justify adding redundant columns in-a-sense.

So, I have looked into system_views.sql and picked up what
catalogs/views shows objects in such way, that is, showing both
object id and its name.

Show by name: pg_policies, pg_rules, pg_tablespg_matviews,
pg_indexes, pg_stats, pg_prepared_xacts, pg_seclabels,
pg_stat(io)_*_tables/indexes.schemaname
pg_stat_*_functions.schemaname

Show by oid : pg_locks, pg_user_mappings.umid

Both : pg_stat(io)_*_tables/indexes.relid/relname, indexrelid/indexname;
pg_stat_activity.datid/datname, usesysid/usename
pg_stat_activity.datid/datname, usesysid/usename
pg_replication_slots.datoid/database
pg_stat_database(_conflicts).datid/datname
pg_stat_*_functions.funcid/funcname
pg_user_mappings.srvid/srvname,umuser/usename

It's surprising to see this result for me. The nature of this
view is near to pg_stat* views so it is proper to show *both of
database and relation* in both of oid and name.

Thoughts?

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#202Robert Haas
robertmhaas@gmail.com
In reply to: Kyotaro HORIGUCHI (#201)
Re: [PROPOSAL] VACUUM Progress Checker.

On Thu, Mar 10, 2016 at 6:10 AM, Kyotaro HORIGUCHI
<horiguchi.kyotaro@lab.ntt.co.jp> wrote:

So, I have looked into system_views.sql and picked up what
catalogs/views shows objects in such way, that is, showing both
object id and its name.

Show by name: pg_policies, pg_rules, pg_tablespg_matviews,
pg_indexes, pg_stats, pg_prepared_xacts, pg_seclabels,
pg_stat(io)_*_tables/indexes.schemaname
pg_stat_*_functions.schemaname

Show by oid : pg_locks, pg_user_mappings.umid

Both : pg_stat(io)_*_tables/indexes.relid/relname, indexrelid/indexname;
pg_stat_activity.datid/datname, usesysid/usename
pg_stat_activity.datid/datname, usesysid/usename
pg_replication_slots.datoid/database
pg_stat_database(_conflicts).datid/datname
pg_stat_*_functions.funcid/funcname
pg_user_mappings.srvid/srvname,umuser/usename

It's surprising to see this result for me. The nature of this
view is near to pg_stat* views so it is proper to show *both of
database and relation* in both of oid and name.

Thoughts?

I think the problem is that you can't show the name of a non-global
SQL object (such as a relation) unless the object is in the current
database. Many of the views in the first group are database-local
views, while things like pg_locks span all databases. We can show the
datid/relid always, but if we have a relname column it will have to be
NULL unless the datid is our database.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#203Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#199)
Re: [PROPOSAL] VACUUM Progress Checker.

On Thu, Mar 10, 2016 at 3:08 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Hi Vinayak,

Thanks for the quick review!

Committed 0001 earlier this morning.

On 0002:

+       /* total_index_blks */
+       current_index_blks = (BlockNumber *) palloc(nindexes *
sizeof(BlockNumber));
+       total_index_blks = 0;
+       for (i = 0; i < nindexes; i++)
+       {
+               BlockNumber             nblocks =
RelationGetNumberOfBlocks(Irel[i]);
+
+               current_index_blks[i] = nblocks;
+               total_index_blks += nblocks;
+       }
+       pgstat_progress_update_param(PROG_PARAM_VAC_IDX_BLKS, total_index_blks);

I think this is a bad idea. The value calculated here isn't
necessarily accurate, because the number of index blocks can change
between the time this is calculated and the time the indexes are
actually vacuumed. If a client just wants the length of the indexes
in round figures, that's already SQL-visible, and there's little
reason to make VACUUM do it all the time whether anyone is looking at
the progress information or not. Note that I'm not complaining about
the fact that you exposed the heap block count, because in that case
you are exposing the actual value that VACUUM is using to guide its
work. The client can get the *current* length of the relation, but
the value you are exposing gives you the number of blocks *this
particular VACUUM intends to scan*. That has some incremental value -
but the index information doesn't have the same thing going for it.

On 0003:

I think you should make this work for all AMs, not just btree, and
then consolidate it with 0002.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#204Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#203)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2016/03/10 23:29, Robert Haas wrote:

On Thu, Mar 10, 2016 at 3:08 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Hi Vinayak,

Thanks for the quick review!

Committed 0001 earlier this morning.

Thanks!

On 0002:

+       /* total_index_blks */
+       current_index_blks = (BlockNumber *) palloc(nindexes *
sizeof(BlockNumber));
+       total_index_blks = 0;
+       for (i = 0; i < nindexes; i++)
+       {
+               BlockNumber             nblocks =
RelationGetNumberOfBlocks(Irel[i]);
+
+               current_index_blks[i] = nblocks;
+               total_index_blks += nblocks;
+       }
+       pgstat_progress_update_param(PROG_PARAM_VAC_IDX_BLKS, total_index_blks);

I think this is a bad idea. The value calculated here isn't
necessarily accurate, because the number of index blocks can change
between the time this is calculated and the time the indexes are
actually vacuumed. If a client just wants the length of the indexes
in round figures, that's already SQL-visible, and there's little
reason to make VACUUM do it all the time whether anyone is looking at
the progress information or not. Note that I'm not complaining about
the fact that you exposed the heap block count, because in that case
you are exposing the actual value that VACUUM is using to guide its
work. The client can get the *current* length of the relation, but
the value you are exposing gives you the number of blocks *this
particular VACUUM intends to scan*. That has some incremental value -
but the index information doesn't have the same thing going for it.

So, from what I understand here, we should not put total count of index
pages into st_progress_param; rather, have the client (reading
pg_stat_progress_vacuum) derive it using pg_indexes_size() (?), as and
when necessary. However, only server is able to tell the current position
within an index vacuuming round (or how many pages into a given index
vacuuming round), so report that using some not-yet-existent mechanism.

On 0003:

I think you should make this work for all AMs, not just btree, and
then consolidate it with 0002.

OK.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#205Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#204)
Re: [PROPOSAL] VACUUM Progress Checker.

On Thu, Mar 10, 2016 at 9:04 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

So, from what I understand here, we should not put total count of index
pages into st_progress_param; rather, have the client (reading
pg_stat_progress_vacuum) derive it using pg_indexes_size() (?), as and
when necessary. However, only server is able to tell the current position
within an index vacuuming round (or how many pages into a given index
vacuuming round), so report that using some not-yet-existent mechanism.

Isn't that mechanism what you are trying to create in 0003? But
otherwise, yes, you've accurate summarized what I think we should do.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#206Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#205)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2016/03/11 13:16, Robert Haas wrote:

On Thu, Mar 10, 2016 at 9:04 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

So, from what I understand here, we should not put total count of index
pages into st_progress_param; rather, have the client (reading
pg_stat_progress_vacuum) derive it using pg_indexes_size() (?), as and
when necessary. However, only server is able to tell the current position
within an index vacuuming round (or how many pages into a given index
vacuuming round), so report that using some not-yet-existent mechanism.

Isn't that mechanism what you are trying to create in 0003?

Right, 0003 should hopefully become that mechanism.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#207Amit Langote
amitlangote09@gmail.com
In reply to: Amit Langote (#206)
1 attachment(s)
Re: [PROPOSAL] VACUUM Progress Checker.

On Fri, Mar 11, 2016 at 2:31 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/03/11 13:16, Robert Haas wrote:

On Thu, Mar 10, 2016 at 9:04 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

So, from what I understand here, we should not put total count of index
pages into st_progress_param; rather, have the client (reading
pg_stat_progress_vacuum) derive it using pg_indexes_size() (?), as and
when necessary. However, only server is able to tell the current position
within an index vacuuming round (or how many pages into a given index
vacuuming round), so report that using some not-yet-existent mechanism.

Isn't that mechanism what you are trying to create in 0003?

Right, 0003 should hopefully become that mechanism.

About 0003:

Earlier, it was trying to report vacuumed index block count using
lazy_tid_reaped() callback for which I had added a index_blkno
argument to IndexBulkDeleteCallback. Turns out it's not such a good
place to do what we are trying to do. This callback is called for
every heap pointer in an index. Not all index pages contain heap
pointers, which means the existing callback does not allow to count
all the index blocks that AM would read to finish a given index vacuum
run.

Instead, the attached patch adds a IndexBulkDeleteProgressCallback
which AMs should call for every block that's read (say, right before a
call to ReadBufferExtended) as part of a given vacuum run. The
callback with help of some bookkeeping state can count each block and
report to pgstat_progress API. Now, I am not sure if all AMs read 1..N
blocks for every vacuum or if it's possible that some blocks are read
more than once in single vacuum, etc. IOW, some AM's processing may
be non-linear and counting blocks 1..N (where N is reported total
index blocks) may not be possible. However, this is the best I could
think of as doing what we are trying to do here. Maybe index AM
experts can chime in on that.

Thoughts?

Thanks,
Amit

Attachments:

0001-WIP-Implement-progress-reporting-for-VACUUM-command-v11.patchapplication/octet-stream; name=0001-WIP-Implement-progress-reporting-for-VACUUM-command-v11.patchDownload
From 8af355a19f68a04d24c616734c7f0286705b8030 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 11 Mar 2016 18:19:52 +0900
Subject: [PATCH] WIP: Implement progress reporting for VACUUM command.

This basically utilizes the pgstat_progress* API to report a handful of
paramters to indicate its progress.  lazy_vacuum_rel() and lazy_scan_heap()
have been altered to report command start/end, command target table, and the
following parameters: processing phase and number of heap blocks at the start,
current heap block number in the main scan loop (whenever it changes) and the
number of index vacuum passes (every time a index vacuum round is finished).
Following processing phases are identified and reported whenever one changes
into another: 'scanning heap', 'vacuuming indexes', 'vacuuming heap', and
finally 'cleanup'.

To indicate the progress of a given index vacuuming round (the 'vacuuming
indexes' phase), index_bulk_delete() is passed one more callback function
called lazy_vacuum_index_progress() of type IndexBulkDeleteProgressCallback.
This means the signature of ambulkdelete now has 2 more arguments named
progress_callback and progress_callback_state.  An AM should call it with
the state parameter whenever a new block is read as part of a vacuum scan.
lazy_vacuum_index_progress() simply reports the updated counter which is
maintained in LVRelStats.index_blocks_vacuumed for a given vacuuming round
using the pgstat_progress* API.

A view named pg_stat_progress_vacuum has been added that shows these values.
---
 doc/src/sgml/monitoring.sgml          | 111 ++++++++++++++++++++++++++++++++++
 src/backend/access/brin/brin.c        |   4 +-
 src/backend/access/gin/ginvacuum.c    |  18 +++++-
 src/backend/access/gist/gistvacuum.c  |   6 +-
 src/backend/access/hash/hash.c        |   6 +-
 src/backend/access/index/indexam.c    |   7 ++-
 src/backend/access/nbtree/nbtree.c    |  20 +++++-
 src/backend/access/spgist/spgvacuum.c |  12 +++-
 src/backend/catalog/index.c           |   3 +-
 src/backend/catalog/system_views.sql  |  25 ++++++++
 src/backend/commands/vacuumlazy.c     |  67 +++++++++++++++++++-
 src/include/access/amapi.h            |   4 +-
 src/include/access/brin_internal.h    |   4 +-
 src/include/access/genam.h            |   7 ++-
 src/include/access/gin_private.h      |   4 +-
 src/include/access/gist_private.h     |   4 +-
 src/include/access/hash.h             |   4 +-
 src/include/access/nbtree.h           |   4 +-
 src/include/access/spgist.h           |   4 +-
 src/test/regress/expected/rules.out   |  23 +++++++
 20 files changed, 317 insertions(+), 20 deletions(-)

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index ec5328e..bff5e43 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -507,6 +507,12 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       yet included in <structname>pg_stat_user_functions</>).</entry>
      </row>
 
+     <row>
+      <entry><structname>pg_stat_progress_vacuum</><indexterm><primary>pg_stat_progress_vacuum</primary></indexterm></entry>
+      <entry>One row for each backend (including autovacuum worker processes) running
+      <command>VACUUM</>, showing current progress in terms of heap pages it
+      has finished processing.</entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -2204,6 +2210,111 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i
    controls exactly which functions are tracked.
   </para>
 
+  <table id="pg-stat-progress-vacuum" xreflabel="pg_stat_progress_vacuum">
+   <title><structname>pg_stat_progress_vacuum</structname> View</title>
+   <tgroup cols="3">
+    <thead>
+    <row>
+      <entry>Column</entry>
+      <entry>Type</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+   <tbody>
+    <row>
+     <entry><structfield>pid</></entry>
+     <entry><type>integer</></entry>
+     <entry>Process ID of backend</entry>
+    </row>
+    <row>
+     <entry><structfield>datid</></entry>
+     <entry><type>oid</></entry>
+     <entry>OID of the database this backend is connected to</entry>
+    </row>
+    <row>
+     <entry><structfield>datname</></entry>
+     <entry><type>name</></entry>
+     <entry>Name of the database this backend is connected to</entry>
+    </row>
+    <row>
+     <entry><structfield>relid</></entry>
+     <entry><type>oid</></entry>
+     <entry>OID of the table being vacuumed</entry>
+    </row>
+    <row>
+     <entry><structfield>processing_phase</></entry>
+     <entry><type>text</></entry>
+     <entry>Current processing phase of vacuum.
+       Possible values are:
+       <itemizedlist>
+        <listitem>
+         <para>
+          <literal>scanning heap</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>vacuuming indexes</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>vacuuming heap</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>cleanup</>
+         </para>
+        </listitem>
+       </itemizedlist>
+     </entry>
+    </row>
+    <row>
+     <entry><structfield>total_heap_blocks</></entry>
+     <entry><type>bigint</></entry>
+     <entry>Total number of heap blocks in the table</entry>
+    </row>
+    <row>
+     <entry><structfield>current_heap_block</></entry>
+     <entry><type>bigint</></entry>
+     <entry>Current heap block being processed</entry>
+    </row>
+    <row>
+     <entry><structfield>total_index_blocks</></entry>
+     <entry><type>bigint</></entry>
+     <entry>Total number of index blocks to be processed</entry>
+    </row>
+    <row>
+     <entry><structfield>index_blocks_vacuumed</></entry>
+     <entry><type>bigint</></entry>
+     <entry>Number of index blocks processed in current vacuum round</entry>
+    </row>
+    <row>
+     <entry><structfield>index_vacuum_count</></entry>
+     <entry><type>bigint</></entry>
+     <entry>Number of times index vacuum round has been performed so far</entry>
+    </row>
+    <row>
+     <entry><structfield>percent_done</></entry>
+     <entry><type>numeric</></entry>
+     <entry>
+      Amount of work finished in percent in terms of table blocks processed
+     </entry>
+    </row>
+   </tbody>
+   </tgroup>
+  </table>
+
+  <para>
+   The <structname>pg_stat_progress_vacuum</structname> view will contain
+   one row for each backend (including autovacuum worker processes), showing
+   parameters that can help determine the progress of <command>VACUUM</command>
+   command running in it. Note that the backends running
+   <command>VACUUM FULL</command> are not shown.
+  </para>
+
  </sect2>
 
  <sect2 id="monitoring-stats-functions">
diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index c740952..ed25d79 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -706,7 +706,9 @@ brinbuildempty(Relation index)
  */
 IndexBulkDeleteResult *
 brinbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
-			   IndexBulkDeleteCallback callback, void *callback_state)
+			   IndexBulkDeleteCallback callback, void *callback_state,
+			   IndexBulkDeleteProgressCallback progress_callback,
+			   void *progress_callback_state)
 {
 	/* allocate stats if first time through, else re-use existing struct */
 	if (stats == NULL)
diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c
index 6a4b98a..e1dd5bd 100644
--- a/src/backend/access/gin/ginvacuum.c
+++ b/src/backend/access/gin/ginvacuum.c
@@ -29,6 +29,8 @@ struct GinVacuumState
 	IndexBulkDeleteResult *result;
 	IndexBulkDeleteCallback callback;
 	void	   *callback_state;
+	IndexBulkDeleteProgressCallback progress_callback;
+	void	   *progress_callback_state;
 	GinState	ginstate;
 	BufferAccessStrategy strategy;
 	MemoryContext tmpCxt;
@@ -116,6 +118,8 @@ ginVacuumPostingTreeLeaves(GinVacuumState *gvs, BlockNumber blkno, bool isRoot,
 	bool		hasVoidPage = FALSE;
 	MemoryContext oldCxt;
 
+	if (gvs->progress_callback)
+		gvs->progress_callback(gvs->progress_callback_state);
 	buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
 								RBM_NORMAL, gvs->strategy);
 	page = BufferGetPage(buffer);
@@ -322,6 +326,8 @@ ginScanToDelete(GinVacuumState *gvs, BlockNumber blkno, bool isRoot,
 			me = parent->child;
 	}
 
+	if (gvs->progress_callback)
+		gvs->progress_callback(gvs->progress_callback_state);
 	buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
 								RBM_NORMAL, gvs->strategy);
 	page = BufferGetPage(buffer);
@@ -515,7 +521,9 @@ ginVacuumEntryPage(GinVacuumState *gvs, Buffer buffer, BlockNumber *roots, uint3
 
 IndexBulkDeleteResult *
 ginbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
-			  IndexBulkDeleteCallback callback, void *callback_state)
+			  IndexBulkDeleteCallback callback, void *callback_state,
+			  IndexBulkDeleteProgressCallback progress_callback,
+			  void *progress_callback_state)
 {
 	Relation	index = info->index;
 	BlockNumber blkno = GIN_ROOT_BLKNO;
@@ -532,6 +540,8 @@ ginbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 	gvs.index = index;
 	gvs.callback = callback;
 	gvs.callback_state = callback_state;
+	gvs.progress_callback = progress_callback;
+	gvs.progress_callback_state = progress_callback_state;
 	gvs.strategy = info->strategy;
 	initGinState(&gvs.ginstate, index);
 
@@ -548,6 +558,8 @@ ginbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 	stats->num_index_tuples = 0;
 	gvs.result = stats;
 
+	if (progress_callback)
+		progress_callback(progress_callback_state);
 	buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
 								RBM_NORMAL, info->strategy);
 
@@ -581,6 +593,8 @@ ginbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 		Assert(blkno != InvalidBlockNumber);
 
 		UnlockReleaseBuffer(buffer);
+		if (progress_callback)
+			progress_callback(progress_callback_state);
 		buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
 									RBM_NORMAL, info->strategy);
 	}
@@ -624,6 +638,8 @@ ginbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 		if (blkno == InvalidBlockNumber)		/* rightmost page */
 			break;
 
+		if (progress_callback)
+			progress_callback(progress_callback_state);
 		buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
 									RBM_NORMAL, info->strategy);
 		LockBuffer(buffer, GIN_EXCLUSIVE);
diff --git a/src/backend/access/gist/gistvacuum.c b/src/backend/access/gist/gistvacuum.c
index 7947ff9..e692f30 100644
--- a/src/backend/access/gist/gistvacuum.c
+++ b/src/backend/access/gist/gistvacuum.c
@@ -137,7 +137,9 @@ pushStackIfSplited(Page page, GistBDItem *stack)
  */
 IndexBulkDeleteResult *
 gistbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
-			   IndexBulkDeleteCallback callback, void *callback_state)
+			   IndexBulkDeleteCallback callback, void *callback_state,
+			   IndexBulkDeleteProgressCallback progress_callback,
+			   void *progress_callback_state)
 {
 	Relation	rel = info->index;
 	GistBDItem *stack,
@@ -162,6 +164,8 @@ gistbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 		IndexTuple	idxtuple;
 		ItemId		iid;
 
+		if (progress_callback)
+			progress_callback(progress_callback_state);
 		buffer = ReadBufferExtended(rel, MAIN_FORKNUM, stack->blkno,
 									RBM_NORMAL, info->strategy);
 		LockBuffer(buffer, GIST_SHARE);
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 3d48c4f..02d5ca5 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -490,7 +490,9 @@ hashendscan(IndexScanDesc scan)
  */
 IndexBulkDeleteResult *
 hashbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
-			   IndexBulkDeleteCallback callback, void *callback_state)
+			   IndexBulkDeleteCallback callback, void *callback_state,
+			   IndexBulkDeleteProgressCallback progress_callback,
+			   void *progress_callback_state)
 {
 	Relation	rel = info->index;
 	double		tuples_removed;
@@ -556,6 +558,8 @@ loop_top:
 
 			vacuum_delay_point();
 
+			if (progress_callback)
+				progress_callback(progress_callback_state);
 			buf = _hash_getbuf_with_strategy(rel, blkno, HASH_WRITE,
 										   LH_BUCKET_PAGE | LH_OVERFLOW_PAGE,
 											 info->strategy);
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index 54b71cb..899f67f 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -617,7 +617,9 @@ IndexBulkDeleteResult *
 index_bulk_delete(IndexVacuumInfo *info,
 				  IndexBulkDeleteResult *stats,
 				  IndexBulkDeleteCallback callback,
-				  void *callback_state)
+				  void *callback_state,
+				  IndexBulkDeleteProgressCallback progress_callback,
+				  void *progress_callback_state)
 {
 	Relation	indexRelation = info->index;
 
@@ -625,7 +627,8 @@ index_bulk_delete(IndexVacuumInfo *info,
 	CHECK_REL_PROCEDURE(ambulkdelete);
 
 	return indexRelation->rd_amroutine->ambulkdelete(info, stats,
-												   callback, callback_state);
+												   callback, callback_state,
+											progress_callback, progress_callback_state);
 }
 
 /* ----------------
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index f2905cb..7d279fc 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -55,6 +55,8 @@ typedef struct
 	IndexBulkDeleteResult *stats;
 	IndexBulkDeleteCallback callback;
 	void	   *callback_state;
+	IndexBulkDeleteProgressCallback progress_callback;
+	void	   *progress_callback_state;
 	BTCycleId	cycleid;
 	BlockNumber lastBlockVacuumed;		/* highest blkno actually vacuumed */
 	BlockNumber lastBlockLocked;	/* highest blkno we've cleanup-locked */
@@ -71,6 +73,8 @@ static void btbuildCallback(Relation index,
 				void *state);
 static void btvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 			 IndexBulkDeleteCallback callback, void *callback_state,
+			 IndexBulkDeleteProgressCallback progress_callback,
+			 void *progress_callback_state,
 			 BTCycleId cycleid);
 static void btvacuumpage(BTVacState *vstate, BlockNumber blkno,
 			 BlockNumber orig_blkno);
@@ -669,7 +673,9 @@ btrestrpos(IndexScanDesc scan)
  */
 IndexBulkDeleteResult *
 btbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
-			 IndexBulkDeleteCallback callback, void *callback_state)
+			 IndexBulkDeleteCallback callback, void *callback_state,
+			 IndexBulkDeleteProgressCallback progress_callback,
+			 void *progress_callback_state)
 {
 	Relation	rel = info->index;
 	BTCycleId	cycleid;
@@ -684,7 +690,9 @@ btbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 	{
 		cycleid = _bt_start_vacuum(rel);
 
-		btvacuumscan(info, stats, callback, callback_state, cycleid);
+		btvacuumscan(info, stats, callback, callback_state,
+					 progress_callback, progress_callback_state,
+					 cycleid);
 	}
 	PG_END_ENSURE_ERROR_CLEANUP(_bt_end_vacuum_callback, PointerGetDatum(rel));
 	_bt_end_vacuum(rel);
@@ -716,7 +724,7 @@ btvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
 	if (stats == NULL)
 	{
 		stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
-		btvacuumscan(info, stats, NULL, NULL, 0);
+		btvacuumscan(info, stats, NULL, NULL, NULL, NULL, 0);
 	}
 
 	/* Finally, vacuum the FSM */
@@ -752,6 +760,8 @@ btvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
 static void
 btvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 			 IndexBulkDeleteCallback callback, void *callback_state,
+			 IndexBulkDeleteProgressCallback progress_callback,
+			 void *progress_callback_state,
 			 BTCycleId cycleid)
 {
 	Relation	rel = info->index;
@@ -773,6 +783,8 @@ btvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 	vstate.stats = stats;
 	vstate.callback = callback;
 	vstate.callback_state = callback_state;
+	vstate.progress_callback = progress_callback;
+	vstate.progress_callback_state = progress_callback_state;
 	vstate.cycleid = cycleid;
 	vstate.lastBlockVacuumed = BTREE_METAPAGE;	/* Initialise at first block */
 	vstate.lastBlockLocked = BTREE_METAPAGE;
@@ -910,6 +922,8 @@ restart:
 	 * recycle all-zero pages, not fail.  Also, we want to use a nondefault
 	 * buffer access strategy.
 	 */
+	if (vstate->progress_callback)
+		vstate->progress_callback(vstate->progress_callback_state);
 	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL,
 							 info->strategy);
 	LockBuffer(buf, BT_READ);
diff --git a/src/backend/access/spgist/spgvacuum.c b/src/backend/access/spgist/spgvacuum.c
index 15b867f..6e2303e 100644
--- a/src/backend/access/spgist/spgvacuum.c
+++ b/src/backend/access/spgist/spgvacuum.c
@@ -44,6 +44,8 @@ typedef struct spgBulkDeleteState
 	IndexBulkDeleteResult *stats;
 	IndexBulkDeleteCallback callback;
 	void	   *callback_state;
+	IndexBulkDeleteProgressCallback progress_callback;
+	void	   *progress_callback_state;
 
 	/* Additional working state */
 	SpGistState spgstate;		/* for SPGiST operations that need one */
@@ -612,6 +614,8 @@ spgvacuumpage(spgBulkDeleteState *bds, BlockNumber blkno)
 	/* call vacuum_delay_point while not holding any buffer lock */
 	vacuum_delay_point();
 
+	if (bds->progress_callback)
+		bds->progress_callback(bds->progress_callback_state);
 	buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
 								RBM_NORMAL, bds->info->strategy);
 	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
@@ -883,7 +887,9 @@ spgvacuumscan(spgBulkDeleteState *bds)
  */
 IndexBulkDeleteResult *
 spgbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
-			  IndexBulkDeleteCallback callback, void *callback_state)
+			  IndexBulkDeleteCallback callback, void *callback_state,
+			  IndexBulkDeleteProgressCallback progress_callback,
+			  void *progress_callback_state)
 {
 	spgBulkDeleteState bds;
 
@@ -894,6 +900,8 @@ spgbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 	bds.stats = stats;
 	bds.callback = callback;
 	bds.callback_state = callback_state;
+	bds.progress_callback = progress_callback;
+	bds.progress_callback_state = progress_callback_state;
 
 	spgvacuumscan(&bds);
 
@@ -935,6 +943,8 @@ spgvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
 		bds.stats = stats;
 		bds.callback = dummy_callback;
 		bds.callback_state = NULL;
+		bds.progress_callback = NULL;
+		bds.progress_callback_state = NULL;
 
 		spgvacuumscan(&bds);
 	}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 31a1438..7486c38 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2837,7 +2837,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	state.htups = state.itups = state.tups_inserted = 0;
 
 	(void) index_bulk_delete(&ivinfo, NULL,
-							 validate_index_callback, (void *) &state);
+							 validate_index_callback, (void *) &state,
+							 NULL, NULL);
 
 	/* Execute the sort */
 	tuplesort_performsort(state.tuplesort);
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 84aa061..1d745c9 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -972,3 +972,28 @@ RETURNS jsonb
 LANGUAGE INTERNAL
 STRICT IMMUTABLE
 AS 'jsonb_set';
+
+CREATE VIEW pg_stat_progress_vacuum AS
+    SELECT
+            S.pid AS pid,
+            S.datid AS datid,
+            D.datname AS datname,
+            S.relid AS relid,
+            CASE S.param1
+                WHEN 1 THEN 'scanning heap'
+                WHEN 2 THEN 'vacuuming indexes'
+                WHEN 3 THEN 'vacuuming heap'
+                WHEN 4 THEN 'cleanup'
+                ELSE 'unknown phase'
+            END AS processing_phase,
+            S.param2 AS total_heap_blocks,
+            S.param3 AS current_heap_block,
+            pg_indexes_size(S.relid) / 8192 AS total_index_blocks,
+            S.param4 AS index_blocks_vacuumed,
+            S.param5 AS index_vacuum_count,
+            CASE S.param2
+                WHEN 0 THEN round(100.0, 2)
+			    ELSE round((S.param3 + 1) * 100.0 / S.param2, 2)
+            END AS percent_done
+    FROM pg_database D, pg_stat_get_progress_info('VACUUM') AS S
+    WHERE S.datid = D.oid;
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index fe87243..468dc68 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -97,6 +97,28 @@
  */
 #define SKIP_PAGES_THRESHOLD	((BlockNumber) 32)
 
+/*
+ * Progress parameters of (lazy) vacuum reported to pgstat progress tracking
+ * facility
+ */
+#define PROG_PARAM_VAC_PHASE				0
+#define PROG_PARAM_VAC_HEAP_BLKS			1
+#define PROG_PARAM_VAC_CURR_HEAP_BLK		2
+#define PROG_PARAM_VAC_IDX_BLKS_VACUUMED	3
+#define PROG_PARAM_VAC_IDX_VACUUM_COUNT		4
+
+/*
+ * Following distinct phases of lazy vacuum are identified.  #1, #2 and #3
+ * run in a cyclical manner due to possibly limited memory to work with,
+ * whereby #1 is periodically interrupted to run #2, followed by #3, and
+ * back to #1.  Cycle repeats until all blocks of the relation have been
+ * covered by #1.
+ */
+#define LV_PHASE_SCAN_HEAP			1
+#define LV_PHASE_VACUUM_INDEX		2
+#define LV_PHASE_VACUUM_HEAP		3
+#define LV_PHASE_CLEANUP			4
+
 typedef struct LVRelStats
 {
 	/* hasindex = true means two-pass strategy; false means one-pass */
@@ -122,6 +144,7 @@ typedef struct LVRelStats
 	int			num_index_scans;
 	TransactionId latestRemovedXid;
 	bool		lock_waiter_detected;
+	double		index_blocks_vacuumed;
 } LVRelStats;
 
 
@@ -159,6 +182,7 @@ static bool lazy_tid_reaped(ItemPointer itemptr, void *state);
 static int	vac_cmp_itemptr(const void *left, const void *right);
 static bool heap_page_is_all_visible(Relation rel, Buffer buf,
 					 TransactionId *visibility_cutoff_xid, bool *all_frozen);
+static void lazy_vacuum_index_progress(void *state);
 
 
 /*
@@ -481,6 +505,12 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	lazy_space_alloc(vacrelstats, nblocks);
 	frozen = palloc(sizeof(xl_heap_freeze_tuple) * MaxHeapTuplesPerPage);
 
+	/* We're about to begin the main heap scan.*/
+	pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_SCAN_HEAP);
+
+	/* Report total_heap_blocks that will be processed. */
+	pgstat_progress_update_param(PROG_PARAM_VAC_HEAP_BLKS, nblocks);
+
 	/*
 	 * Except when aggressive is set, we want to skip pages that are
 	 * all-visible according to the visibility map, but only when we can skip
@@ -668,11 +698,19 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			vacuum_log_cleanup_info(onerel, vacrelstats);
 
 			/* Remove index entries */
+			pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_VACUUM_INDEX);
+			vacrelstats->index_blocks_vacuumed = 0;
 			for (i = 0; i < nindexes; i++)
 				lazy_vacuum_index(Irel[i],
 								  &indstats[i],
 								  vacrelstats);
+
+			/* Report incremented count of number of index vacuums so far */
+			pgstat_progress_update_param(PROG_PARAM_VAC_IDX_VACUUM_COUNT,
+										 vacrelstats->num_index_scans + 1);
+
 			/* Remove tuples from heap */
+			pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_VACUUM_HEAP);
 			lazy_vacuum_heap(onerel, vacrelstats);
 
 			/*
@@ -682,6 +720,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			 */
 			vacrelstats->num_dead_tuples = 0;
 			vacrelstats->num_index_scans++;
+
+			/* Report going back to scanning heap */
+			pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_SCAN_HEAP);
 		}
 
 		/*
@@ -695,6 +736,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		 */
 		visibilitymap_pin(onerel, blkno, &vmbuffer);
 
+		/* Report current_heap_block that will be worked on. */
+		pgstat_progress_update_param(PROG_PARAM_VAC_CURR_HEAP_BLK, blkno);
+
 		buf = ReadBufferExtended(onerel, MAIN_FORKNUM, blkno,
 								 RBM_NORMAL, vac_strategy);
 
@@ -1212,16 +1256,25 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		vacuum_log_cleanup_info(onerel, vacrelstats);
 
 		/* Remove index entries */
+		pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_VACUUM_INDEX);
+		vacrelstats->index_blocks_vacuumed = 0;
 		for (i = 0; i < nindexes; i++)
 			lazy_vacuum_index(Irel[i],
 							  &indstats[i],
 							  vacrelstats);
+
+		/* Report incremented count of number of index vacuums so far */
+		pgstat_progress_update_param(PROG_PARAM_VAC_IDX_VACUUM_COUNT,
+									 vacrelstats->num_index_scans + 1);
+
 		/* Remove tuples from heap */
+		pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_VACUUM_HEAP);
 		lazy_vacuum_heap(onerel, vacrelstats);
 		vacrelstats->num_index_scans++;
 	}
 
 	/* Do post-vacuum cleanup and statistics update for each index */
+	pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_CLEANUP);
 	for (i = 0; i < nindexes; i++)
 		lazy_cleanup_index(Irel[i], indstats[i], vacrelstats);
 
@@ -1507,7 +1560,9 @@ lazy_vacuum_index(Relation indrel,
 
 	/* Do bulk deletion */
 	*stats = index_bulk_delete(&ivinfo, *stats,
-							   lazy_tid_reaped, (void *) vacrelstats);
+							   lazy_tid_reaped, (void *) vacrelstats,
+							   lazy_vacuum_index_progress,
+							   (void *) vacrelstats);
 
 	ereport(elevel,
 			(errmsg("scanned index \"%s\" to remove %d row versions",
@@ -1912,6 +1967,16 @@ lazy_tid_reaped(ItemPointer itemptr, void *state)
 	return (res != NULL);
 }
 
+static void
+lazy_vacuum_index_progress(void *state)
+{
+	LVRelStats *vacrelstats = (LVRelStats *) state;
+
+	vacrelstats->index_blocks_vacuumed++;
+	pgstat_progress_update_param(PROG_PARAM_VAC_IDX_BLKS_VACUUMED,
+								 vacrelstats->index_blocks_vacuumed);
+}
+
 /*
  * Comparator routines for use with qsort() and bsearch().
  */
diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h
index 35f1061..6c5825c 100644
--- a/src/include/access/amapi.h
+++ b/src/include/access/amapi.h
@@ -50,7 +50,9 @@ typedef bool (*aminsert_function) (Relation indexRelation,
 typedef IndexBulkDeleteResult *(*ambulkdelete_function) (IndexVacuumInfo *info,
 												IndexBulkDeleteResult *stats,
 											IndexBulkDeleteCallback callback,
-													   void *callback_state);
+														void *callback_state,
+						   IndexBulkDeleteProgressCallback progress_callback,
+											  void *progress_callback_state);
 
 /* post-VACUUM cleanup */
 typedef IndexBulkDeleteResult *(*amvacuumcleanup_function) (IndexVacuumInfo *info,
diff --git a/src/include/access/brin_internal.h b/src/include/access/brin_internal.h
index 47317af..ac81207 100644
--- a/src/include/access/brin_internal.h
+++ b/src/include/access/brin_internal.h
@@ -98,7 +98,9 @@ extern void brinendscan(IndexScanDesc scan);
 extern IndexBulkDeleteResult *brinbulkdelete(IndexVacuumInfo *info,
 			   IndexBulkDeleteResult *stats,
 			   IndexBulkDeleteCallback callback,
-			   void *callback_state);
+			   void *callback_state,
+			   IndexBulkDeleteProgressCallback progress_callback,
+			   void *progress_callback_state);
 extern IndexBulkDeleteResult *brinvacuumcleanup(IndexVacuumInfo *info,
 				  IndexBulkDeleteResult *stats);
 extern bytea *brinoptions(Datum reloptions, bool validate);
diff --git a/src/include/access/genam.h b/src/include/access/genam.h
index 81907d5..cb049b5 100644
--- a/src/include/access/genam.h
+++ b/src/include/access/genam.h
@@ -79,6 +79,9 @@ typedef struct IndexBulkDeleteResult
 /* Typedef for callback function to determine if a tuple is bulk-deletable */
 typedef bool (*IndexBulkDeleteCallback) (ItemPointer itemptr, void *state);
 
+/* Typedef for callback function to be called for every index page read */
+typedef void (*IndexBulkDeleteProgressCallback) (void *state);
+
 /* struct definitions appear in relscan.h */
 typedef struct IndexScanDescData *IndexScanDesc;
 typedef struct SysScanDescData *SysScanDesc;
@@ -153,7 +156,9 @@ extern int64 index_getbitmap(IndexScanDesc scan, TIDBitmap *bitmap);
 extern IndexBulkDeleteResult *index_bulk_delete(IndexVacuumInfo *info,
 				  IndexBulkDeleteResult *stats,
 				  IndexBulkDeleteCallback callback,
-				  void *callback_state);
+				  void *callback_state,
+				  IndexBulkDeleteProgressCallback progress_callback,
+				  void *progress_callback_state);
 extern IndexBulkDeleteResult *index_vacuum_cleanup(IndexVacuumInfo *info,
 					 IndexBulkDeleteResult *stats);
 extern bool index_can_return(Relation indexRelation, int attno);
diff --git a/src/include/access/gin_private.h b/src/include/access/gin_private.h
index d2ea588..07a5c78 100644
--- a/src/include/access/gin_private.h
+++ b/src/include/access/gin_private.h
@@ -891,7 +891,9 @@ extern void ginInitConsistentFunction(GinState *ginstate, GinScanKey key);
 extern IndexBulkDeleteResult *ginbulkdelete(IndexVacuumInfo *info,
 			  IndexBulkDeleteResult *stats,
 			  IndexBulkDeleteCallback callback,
-			  void *callback_state);
+			  void *callback_state,
+			  IndexBulkDeleteProgressCallback progress_callback,
+			  void *progress_callback_state);
 extern IndexBulkDeleteResult *ginvacuumcleanup(IndexVacuumInfo *info,
 				 IndexBulkDeleteResult *stats);
 extern ItemPointer ginVacuumItemPointers(GinVacuumState *gvs,
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index f9732ba..6633293 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -544,7 +544,9 @@ extern XLogRecPtr gistGetFakeLSN(Relation rel);
 extern IndexBulkDeleteResult *gistbulkdelete(IndexVacuumInfo *info,
 			   IndexBulkDeleteResult *stats,
 			   IndexBulkDeleteCallback callback,
-			   void *callback_state);
+			   void *callback_state,
+			   IndexBulkDeleteProgressCallback progress_callback,
+			   void *progress_callback_state);
 extern IndexBulkDeleteResult *gistvacuumcleanup(IndexVacuumInfo *info,
 				  IndexBulkDeleteResult *stats);
 
diff --git a/src/include/access/hash.h b/src/include/access/hash.h
index 3a68390..37edfdf 100644
--- a/src/include/access/hash.h
+++ b/src/include/access/hash.h
@@ -259,7 +259,9 @@ extern void hashendscan(IndexScanDesc scan);
 extern IndexBulkDeleteResult *hashbulkdelete(IndexVacuumInfo *info,
 			   IndexBulkDeleteResult *stats,
 			   IndexBulkDeleteCallback callback,
-			   void *callback_state);
+			   void *callback_state,
+			   IndexBulkDeleteProgressCallback progress_callback,
+			   void *progress_callback_state);
 extern IndexBulkDeleteResult *hashvacuumcleanup(IndexVacuumInfo *info,
 				  IndexBulkDeleteResult *stats);
 extern bytea *hashoptions(Datum reloptions, bool validate);
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 9046b16..3e990ed 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -671,7 +671,9 @@ extern void btrestrpos(IndexScanDesc scan);
 extern IndexBulkDeleteResult *btbulkdelete(IndexVacuumInfo *info,
 			 IndexBulkDeleteResult *stats,
 			 IndexBulkDeleteCallback callback,
-			 void *callback_state);
+			 void *callback_state,
+			 IndexBulkDeleteProgressCallback progress_callback,
+			 void *progress_callback_state);
 extern IndexBulkDeleteResult *btvacuumcleanup(IndexVacuumInfo *info,
 				IndexBulkDeleteResult *stats);
 extern bool btcanreturn(Relation index, int attno);
diff --git a/src/include/access/spgist.h b/src/include/access/spgist.h
index 1994f71..e40236c 100644
--- a/src/include/access/spgist.h
+++ b/src/include/access/spgist.h
@@ -199,7 +199,9 @@ extern bool spgcanreturn(Relation index, int attno);
 extern IndexBulkDeleteResult *spgbulkdelete(IndexVacuumInfo *info,
 			  IndexBulkDeleteResult *stats,
 			  IndexBulkDeleteCallback callback,
-			  void *callback_state);
+			  void *callback_state,
+			  IndexBulkDeleteProgressCallback progress_callback,
+			  void *progress_callback_state);
 extern IndexBulkDeleteResult *spgvacuumcleanup(IndexVacuumInfo *info,
 				 IndexBulkDeleteResult *stats);
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 22ea06c..fca60a4 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1747,6 +1747,29 @@ pg_stat_database_conflicts| SELECT d.oid AS datid,
     pg_stat_get_db_conflict_bufferpin(d.oid) AS confl_bufferpin,
     pg_stat_get_db_conflict_startup_deadlock(d.oid) AS confl_deadlock
    FROM pg_database d;
+pg_stat_progress_vacuum| SELECT s.pid,
+    s.datid,
+    d.datname,
+    s.relid,
+        CASE s.param1
+            WHEN 1 THEN 'scanning heap'::text
+            WHEN 2 THEN 'vacuuming indexes'::text
+            WHEN 3 THEN 'vacuuming heap'::text
+            WHEN 4 THEN 'cleanup'::text
+            ELSE 'unknown phase'::text
+        END AS processing_phase,
+    s.param2 AS total_heap_blocks,
+    s.param3 AS current_heap_block,
+    (pg_indexes_size((s.relid)::regclass) / 8192) AS total_index_blocks,
+    s.param4 AS index_blocks_vacuumed,
+    s.param5 AS index_vacuum_count,
+        CASE s.param2
+            WHEN 0 THEN round(100.0, 2)
+            ELSE round(((((s.param3 + 1))::numeric * 100.0) / (s.param2)::numeric), 2)
+        END AS percent_done
+   FROM pg_database d,
+    pg_stat_get_progress_info('VACUUM'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10)
+  WHERE (s.datid = d.oid);
 pg_stat_replication| SELECT s.pid,
     s.usesysid,
     u.rolname AS usename,
-- 
2.3.2 (Apple Git-55)

#208Jim Nasby
Jim.Nasby@BlueTreble.com
In reply to: Robert Haas (#202)
Re: [PROPOSAL] VACUUM Progress Checker.

On 3/10/16 7:48 AM, Robert Haas wrote:

I think the problem is that you can't show the name of a non-global
SQL object (such as a relation) unless the object is in the current
database. Many of the views in the first group are database-local
views, while things like pg_locks span all databases. We can show the
datid/relid always, but if we have a relname column it will have to be
NULL unless the datid is our database.

I would prefer that if the object is in another database we at least
display the OID. That way, if you're logging this info you can go back
later and figure out what was going on.
--
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Experts in Analytics, Data Architecture and PostgreSQL
Data in Trouble? Get it in Treble! http://BlueTreble.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#209Rahila Syed
rahilasyed90@gmail.com
In reply to: Amit Langote (#207)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

While I am still looking at this WIP patch, I had one suggestion.

Instead of making changes in the index AM API can we have a call to update
the shared state using pgstat_progress* API

directly from specific index level code?

Like pgstat_count_index_scan(rel) call from _bt_first does. Though this
function basically updates local structures and sends the count to stat
collector via messages we can have a function which will instead modify the
shared state using the progress API committed recently.

Thank you,

Rahila Syed

#210Amit Langote
amitlangote09@gmail.com
In reply to: Rahila Syed (#209)
Re: [PROPOSAL] VACUUM Progress Checker.

Hi,

Thanks for taking a look at the patch.

On Mon, Mar 14, 2016 at 6:55 PM, Rahila Syed <rahilasyed90@gmail.com> wrote:

Hello,

While I am still looking at this WIP patch, I had one suggestion.

Instead of making changes in the index AM API can we have a call to update
the shared state using pgstat_progress* API

directly from specific index level code?

Like pgstat_count_index_scan(rel) call from _bt_first does. Though this
function basically updates local structures and sends the count to stat
collector via messages we can have a function which will instead modify the
shared state using the progress API committed recently.

I chose the callback approach because we need to count the index
blocks within the context of a given vacuum run. For example, as
proposed, progress_callback_state (in this case, a pointer to the
LVRelStats struct for a given vacuum run) keeps the block count for a
given index vacuum run. It is reset when next index vacuuming round
starts. Also, remember that the count is across all indexes.

If we call pgstat_progress API directly from within AM, what I just
described above seems difficult to achieve modularly. But maybe, I'm
missing something.

Aside from whether we should use one of the above two methods, I think
we also have to figure out, for each AM, how to count correctly
considering non-linearity (tree traversal, recursion and such) of most
AMs' vacuum scans.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#211Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#207)
Re: [PROPOSAL] VACUUM Progress Checker.

On Sat, Mar 12, 2016 at 7:49 AM, Amit Langote <amitlangote09@gmail.com> wrote:

On Fri, Mar 11, 2016 at 2:31 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/03/11 13:16, Robert Haas wrote:

On Thu, Mar 10, 2016 at 9:04 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

So, from what I understand here, we should not put total count of index
pages into st_progress_param; rather, have the client (reading
pg_stat_progress_vacuum) derive it using pg_indexes_size() (?), as and
when necessary. However, only server is able to tell the current position
within an index vacuuming round (or how many pages into a given index
vacuuming round), so report that using some not-yet-existent mechanism.

Isn't that mechanism what you are trying to create in 0003?

Right, 0003 should hopefully become that mechanism.

About 0003:

Earlier, it was trying to report vacuumed index block count using
lazy_tid_reaped() callback for which I had added a index_blkno
argument to IndexBulkDeleteCallback. Turns out it's not such a good
place to do what we are trying to do. This callback is called for
every heap pointer in an index. Not all index pages contain heap
pointers, which means the existing callback does not allow to count
all the index blocks that AM would read to finish a given index vacuum
run.

Instead, the attached patch adds a IndexBulkDeleteProgressCallback
which AMs should call for every block that's read (say, right before a
call to ReadBufferExtended) as part of a given vacuum run. The
callback with help of some bookkeeping state can count each block and
report to pgstat_progress API. Now, I am not sure if all AMs read 1..N
blocks for every vacuum or if it's possible that some blocks are read
more than once in single vacuum, etc. IOW, some AM's processing may
be non-linear and counting blocks 1..N (where N is reported total
index blocks) may not be possible. However, this is the best I could
think of as doing what we are trying to do here. Maybe index AM
experts can chime in on that.

Thoughts?

Well, I think you need to study the index AMs and figure this out.

But I think for starters you should write a patch that reports the following:

1. phase
2. number of heap blocks scanned
3. number of heap blocks vacuumed
4. number of completed index vac cycles
5. number of dead tuples collected since the last index vac cycle
6. number of dead tuples that we can store before needing to perform
an index vac cycle

All of that should be pretty straightforward, and then we'd have
something we can ship. We can add the detailed index reporting later,
when we get to it, perhaps for 9.7.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#212Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#211)
1 attachment(s)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2016/03/15 3:41, Robert Haas wrote:

On Sat, Mar 12, 2016 at 7:49 AM, Amit Langote <amitlangote09@gmail.com> wrote:

Instead, the attached patch adds a IndexBulkDeleteProgressCallback
which AMs should call for every block that's read (say, right before a
call to ReadBufferExtended) as part of a given vacuum run. The
callback with help of some bookkeeping state can count each block and
report to pgstat_progress API. Now, I am not sure if all AMs read 1..N
blocks for every vacuum or if it's possible that some blocks are read
more than once in single vacuum, etc. IOW, some AM's processing may
be non-linear and counting blocks 1..N (where N is reported total
index blocks) may not be possible. However, this is the best I could
think of as doing what we are trying to do here. Maybe index AM
experts can chime in on that.

Thoughts?

Well, I think you need to study the index AMs and figure this out.

OK. I tried to put calls to the callback in appropriate places, but
couldn't get the resulting progress numbers to look sane. So I ended up
concluding that any attempt to do so is futile unless I analyze each AM's
vacuum code carefully to be able to determine in advance the max bound on
the count of blocks that the callback will report. Anyway, as you
suggest, we can improve it later.

But I think for starters you should write a patch that reports the following:

1. phase
2. number of heap blocks scanned
3. number of heap blocks vacuumed
4. number of completed index vac cycles
5. number of dead tuples collected since the last index vac cycle
6. number of dead tuples that we can store before needing to perform
an index vac cycle

All of that should be pretty straightforward, and then we'd have
something we can ship. We can add the detailed index reporting later,
when we get to it, perhaps for 9.7.

OK, I agree with this plan. Attached updated patch implements this.

Thanks,
Amit

Attachments:

0001-WIP-Implement-progress-reporting-for-VACUUM-command-v12.patchtext/x-diff; name=0001-WIP-Implement-progress-reporting-for-VACUUM-command-v12.patchDownload
From 0f830273cb2afb528b8b9ed2dcbaf136d4c3e64f Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 15 Mar 2016 10:14:33 +0900
Subject: [PATCH] WIP: Implement progress reporting for VACUUM command.

This basically utilizes the pgstat_progress* API to report a handful of
paramters to indicate its progress.  lazy_vacuum_rel() has already been
taught in b6fb6471 to report command start and end so that it's listed
in pg_stat_get_progress_info('VACUUM'). This commit makes lazy_scan_heap
to report following parameters: vacuum phase, total number of heap blocks,
number of heap blocks vacuumed, dead tuples found since last index vacuum
cycle, dead tuple slots left to fill until the next index vacuum run.
Following phases are identified and reported whenever one changes to
another: 'scanning heap', 'vacuuming indexes', 'vacuuming heap', and
finally 'cleanup'.

A view named pg_stat_progress_vacuum has been added with the following
columns: pid (int), datid (oid), datname (name), relid (oid),
vacuum_phase (text), heap_blks_total, heap_blks_vacuumed,
index_vacuum_count, dead_tup_found, dead_tup_slots (all bigint),
percent_done (numeric).
---
 doc/src/sgml/monitoring.sgml         |  114 ++++++++++++++++++++++++++++++++++
 src/backend/catalog/system_views.sql |   25 ++++++++
 src/backend/commands/vacuumlazy.c    |   68 ++++++++++++++++++++
 src/test/regress/expected/rules.out  |   22 +++++++
 4 files changed, 229 insertions(+), 0 deletions(-)

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index ec5328e..e5ffa10 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -507,6 +507,12 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       yet included in <structname>pg_stat_user_functions</>).</entry>
      </row>
 
+     <row>
+      <entry><structname>pg_stat_progress_vacuum</><indexterm><primary>pg_stat_progress_vacuum</primary></indexterm></entry>
+      <entry>One row for each backend (including autovacuum worker processes) running
+      <command>VACUUM</>, showing current progress in terms of heap pages it
+      has finished processing.</entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -2204,6 +2210,114 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i
    controls exactly which functions are tracked.
   </para>
 
+  <table id="pg-stat-progress-vacuum" xreflabel="pg_stat_progress_vacuum">
+   <title><structname>pg_stat_progress_vacuum</structname> View</title>
+   <tgroup cols="3">
+    <thead>
+    <row>
+      <entry>Column</entry>
+      <entry>Type</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+   <tbody>
+    <row>
+     <entry><structfield>pid</></entry>
+     <entry><type>integer</></entry>
+     <entry>Process ID of backend</entry>
+    </row>
+    <row>
+     <entry><structfield>datid</></entry>
+     <entry><type>oid</></entry>
+     <entry>OID of the database this backend is connected to</entry>
+    </row>
+    <row>
+     <entry><structfield>datname</></entry>
+     <entry><type>name</></entry>
+     <entry>Name of the database this backend is connected to</entry>
+    </row>
+    <row>
+     <entry><structfield>relid</></entry>
+     <entry><type>oid</></entry>
+     <entry>OID of the table being vacuumed</entry>
+    </row>
+    <row>
+     <entry><structfield>vacuum_phase</></entry>
+     <entry><type>text</></entry>
+     <entry>Current processing phase of vacuum.
+       Possible values are:
+       <itemizedlist>
+        <listitem>
+         <para>
+          <literal>scanning heap</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>vacuuming indexes</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>vacuuming heap</>
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>cleanup</>
+         </para>
+        </listitem>
+       </itemizedlist>
+     </entry>
+    </row>
+    <row>
+     <entry><structfield>heap_blks_total</></entry>
+     <entry><type>bigint</></entry>
+     <entry>Total number of heap blocks in the table</entry>
+    </row>
+    <row>
+     <entry><structfield>heap_blks_vacuumed</></entry>
+     <entry><type>bigint</></entry>
+     <entry>Number of heap blocks vacuumed</entry>
+    </row>
+    <row>
+     <entry><structfield>index_vacuum_count</></entry>
+     <entry><type>bigint</></entry>
+     <entry>Number of completed index vacuums cycles</entry>
+    </row>
+    <row>
+     <entry><structfield>dead_tup_found</></entry>
+     <entry><type>bigint</></entry>
+     <entry>Number of dead tuples collected since the last index vacuum cycle</entry>
+    </row>
+    <row>
+     <entry><structfield>dead_tup_slots</></entry>
+     <entry><type>bigint</></entry>
+     <entry>
+      Number of dead tuples that we can store before needing to perform
+      an index vacuum cycle
+     </entry>
+    </row>
+    <row>
+     <entry><structfield>percent_done</></entry>
+     <entry><type>numeric</></entry>
+     <entry>
+      Amount of work finished in percent in terms of table blocks vacuumed
+     </entry>
+    </row>
+   </tbody>
+   </tgroup>
+  </table>
+
+  <para>
+   The <structname>pg_stat_progress_vacuum</structname> view will contain
+   one row for each backend (including autovacuum worker processes), showing
+   parameters that can help determine the progress of <command>VACUUM</command>
+   command running in it. Note that the backends running
+   <command>VACUUM FULL</command> are not shown.
+  </para>
+
  </sect2>
 
  <sect2 id="monitoring-stats-functions">
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 84aa061..28ffc37 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -972,3 +972,28 @@ RETURNS jsonb
 LANGUAGE INTERNAL
 STRICT IMMUTABLE
 AS 'jsonb_set';
+
+CREATE VIEW pg_stat_progress_vacuum AS
+    SELECT
+            S.pid AS pid,
+            S.datid AS datid,
+            D.datname AS datname,
+            S.relid AS relid,
+            CASE S.param1
+                WHEN 1 THEN 'scanning heap'
+                WHEN 2 THEN 'vacuuming indexes'
+                WHEN 3 THEN 'vacuuming heap'
+                WHEN 4 THEN 'cleanup'
+                ELSE 'unknown phase'
+            END AS vacuum_phase,
+            S.param2 AS heap_blks_total,
+            S.param3 AS heap_blks_vacuumed,
+            S.param4 AS index_vacuum_count,
+            S.param5 AS dead_tup_found,
+            S.param6 AS dead_tup_slots,
+            CASE S.param2
+                WHEN 0 THEN round(100.0, 2)
+			    ELSE round((S.param3 + 1) * 100.0 / S.param2, 2)
+            END AS percent_done
+    FROM pg_stat_get_progress_info('VACUUM') AS S
+         JOIN pg_database D ON S.datid = D.oid;
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index fe87243..be30861 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -97,6 +97,29 @@
  */
 #define SKIP_PAGES_THRESHOLD	((BlockNumber) 32)
 
+/*
+ * Progress parameters of (lazy) vacuum reported to pgstat progress tracking
+ * facility
+ */
+#define PROG_PARAM_VAC_PHASE					0
+#define PROG_PARAM_VAC_TOTAL_HEAP_BLKS			1
+#define PROG_PARAM_VAC_HEAP_BLKS_VACUUMED		2
+#define PROG_PARAM_VAC_NUM_INDEX_VACUUMS		3
+#define PROG_PARAM_VAC_NUM_DEAD_TUP				4
+#define PROG_PARAM_VAC_NUM_DEAD_TUP_SLOTS		5
+
+/*
+ * Following distinct phases of lazy vacuum are identified.  #1, #2 and #3
+ * run in a cyclical manner due to possibly limited memory to work with,
+ * whereby #1 is periodically interrupted to run #2, followed by #3, and
+ * back to #1.  Cycle repeats until all blocks of the relation have been
+ * covered by #1.
+ */
+#define LV_PHASE_SCAN_HEAP			1
+#define LV_PHASE_VACUUM_INDEX		2
+#define LV_PHASE_VACUUM_HEAP		3
+#define LV_PHASE_CLEANUP			4
+
 typedef struct LVRelStats
 {
 	/* hasindex = true means two-pass strategy; false means one-pass */
@@ -481,6 +504,12 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 	lazy_space_alloc(vacrelstats, nblocks);
 	frozen = palloc(sizeof(xl_heap_freeze_tuple) * MaxHeapTuplesPerPage);
 
+	/* About to begin the main heap scan loop */
+	pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_SCAN_HEAP);
+
+	/* total_heap_blocks */
+	pgstat_progress_update_param(PROG_PARAM_VAC_TOTAL_HEAP_BLKS, nblocks);
+
 	/*
 	 * Except when aggressive is set, we want to skip pages that are
 	 * all-visible according to the visibility map, but only when we can skip
@@ -572,6 +601,9 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 #define FORCE_CHECK_PAGE() \
 		(blkno == nblocks - 1 && should_attempt_truncation(vacrelstats))
 
+		/* heap_blocks_vacuumed */
+		pgstat_progress_update_param(PROG_PARAM_VAC_HEAP_BLKS_VACUUMED, blkno);
+
 		if (blkno == next_unskippable_block)
 		{
 			/* Time to advance next_unskippable_block */
@@ -668,11 +700,20 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			vacuum_log_cleanup_info(onerel, vacrelstats);
 
 			/* Remove index entries */
+			pgstat_progress_update_param(PROG_PARAM_VAC_PHASE,
+										 LV_PHASE_VACUUM_INDEX);
 			for (i = 0; i < nindexes; i++)
 				lazy_vacuum_index(Irel[i],
 								  &indstats[i],
 								  vacrelstats);
+
+			/* index_vacuum_count */
+			pgstat_progress_update_param(PROG_PARAM_VAC_NUM_INDEX_VACUUMS,
+										 vacrelstats->num_index_scans + 1);
+
 			/* Remove tuples from heap */
+			pgstat_progress_update_param(PROG_PARAM_VAC_PHASE,
+										 LV_PHASE_VACUUM_HEAP);
 			lazy_vacuum_heap(onerel, vacrelstats);
 
 			/*
@@ -682,6 +723,10 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 			 */
 			vacrelstats->num_dead_tuples = 0;
 			vacrelstats->num_index_scans++;
+
+			/* Report going back to scanning heap */
+			pgstat_progress_update_param(PROG_PARAM_VAC_PHASE,
+										 LV_PHASE_SCAN_HEAP);
 		}
 
 		/*
@@ -1180,8 +1225,21 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		 */
 		if (vacrelstats->num_dead_tuples == prev_dead_count)
 			RecordPageWithFreeSpace(onerel, blkno, freespace);
+
+		/* dead_tup_found and dead_tup_slots, if changed */
+		if (vacrelstats->num_dead_tuples != prev_dead_count)
+		{
+			pgstat_progress_update_param(PROG_PARAM_VAC_NUM_DEAD_TUP,
+										 vacrelstats->num_dead_tuples);
+			pgstat_progress_update_param(PROG_PARAM_VAC_NUM_DEAD_TUP_SLOTS,
+										 vacrelstats->max_dead_tuples -
+										 vacrelstats->num_dead_tuples);
+		}
 	}
 
+	/* heap_blocks_vacuumed, one last time */
+	pgstat_progress_update_param(PROG_PARAM_VAC_HEAP_BLKS_VACUUMED, blkno);
+
 	pfree(frozen);
 
 	/* save stats for use later */
@@ -1212,16 +1270,26 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 		vacuum_log_cleanup_info(onerel, vacrelstats);
 
 		/* Remove index entries */
+		pgstat_progress_update_param(PROG_PARAM_VAC_PHASE,
+									 LV_PHASE_VACUUM_INDEX);
 		for (i = 0; i < nindexes; i++)
 			lazy_vacuum_index(Irel[i],
 							  &indstats[i],
 							  vacrelstats);
+
+		/* index_vacuum_count */
+		pgstat_progress_update_param(PROG_PARAM_VAC_NUM_INDEX_VACUUMS,
+									 vacrelstats->num_index_scans + 1);
+
 		/* Remove tuples from heap */
+		pgstat_progress_update_param(PROG_PARAM_VAC_PHASE,
+									 LV_PHASE_VACUUM_HEAP);
 		lazy_vacuum_heap(onerel, vacrelstats);
 		vacrelstats->num_index_scans++;
 	}
 
 	/* Do post-vacuum cleanup and statistics update for each index */
+	pgstat_progress_update_param(PROG_PARAM_VAC_PHASE, LV_PHASE_CLEANUP);
 	for (i = 0; i < nindexes; i++)
 		lazy_cleanup_index(Irel[i], indstats[i], vacrelstats);
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 22ea06c..a8c3a33 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1747,6 +1747,28 @@ pg_stat_database_conflicts| SELECT d.oid AS datid,
     pg_stat_get_db_conflict_bufferpin(d.oid) AS confl_bufferpin,
     pg_stat_get_db_conflict_startup_deadlock(d.oid) AS confl_deadlock
    FROM pg_database d;
+pg_stat_progress_vacuum| SELECT s.pid,
+    s.datid,
+    d.datname,
+    s.relid,
+        CASE s.param1
+            WHEN 1 THEN 'scanning heap'::text
+            WHEN 2 THEN 'vacuuming indexes'::text
+            WHEN 3 THEN 'vacuuming heap'::text
+            WHEN 4 THEN 'cleanup'::text
+            ELSE 'unknown phase'::text
+        END AS vacuum_phase,
+    s.param2 AS heap_blks_total,
+    s.param3 AS heap_blks_vacuumed,
+    s.param4 AS index_vacuum_count,
+    s.param5 AS dead_tups_found,
+    s.param6 AS dead_tup_slots,
+        CASE s.param2
+            WHEN 0 THEN round(100.0, 2)
+            ELSE round(((((s.param3 + 1))::numeric * 100.0) / (s.param2)::numeric), 2)
+        END AS percent_done
+   FROM (pg_stat_get_progress_info('VACUUM'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10)
+     JOIN pg_database d ON ((s.datid = d.oid)));
 pg_stat_replication| SELECT s.pid,
     s.usesysid,
     u.rolname AS usename,
-- 
1.7.1

#213Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#212)
Re: [PROPOSAL] VACUUM Progress Checker.

On Tue, Mar 15, 2016 at 1:16 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/03/15 3:41, Robert Haas wrote:

On Sat, Mar 12, 2016 at 7:49 AM, Amit Langote <amitlangote09@gmail.com> wrote:

Instead, the attached patch adds a IndexBulkDeleteProgressCallback
which AMs should call for every block that's read (say, right before a
call to ReadBufferExtended) as part of a given vacuum run. The
callback with help of some bookkeeping state can count each block and
report to pgstat_progress API. Now, I am not sure if all AMs read 1..N
blocks for every vacuum or if it's possible that some blocks are read
more than once in single vacuum, etc. IOW, some AM's processing may
be non-linear and counting blocks 1..N (where N is reported total
index blocks) may not be possible. However, this is the best I could
think of as doing what we are trying to do here. Maybe index AM
experts can chime in on that.

Thoughts?

Well, I think you need to study the index AMs and figure this out.

OK. I tried to put calls to the callback in appropriate places, but
couldn't get the resulting progress numbers to look sane. So I ended up
concluding that any attempt to do so is futile unless I analyze each AM's
vacuum code carefully to be able to determine in advance the max bound on
the count of blocks that the callback will report. Anyway, as you
suggest, we can improve it later.

I don't think there is any way to bound that, because new blocks can
get added to the index concurrently, and we might end up needing to
scan them. Reporting the number of blocks scanned can still be
useful, though - any chance you can just implement that part of it?

But I think for starters you should write a patch that reports the following:

1. phase
2. number of heap blocks scanned
3. number of heap blocks vacuumed
4. number of completed index vac cycles
5. number of dead tuples collected since the last index vac cycle
6. number of dead tuples that we can store before needing to perform
an index vac cycle

All of that should be pretty straightforward, and then we'd have
something we can ship. We can add the detailed index reporting later,
when we get to it, perhaps for 9.7.

OK, I agree with this plan. Attached updated patch implements this.

Sorta. Committed after renaming what you called heap blocks vacuumed
back to heap blocks scanned, adding heap blocks vacuumed, removing the
overall progress meter which I don't believe will be anything close to
accurate, fixing some stylistic stuff, arranging to update multiple
counters automatically where it could otherwise produce confusion,
moving the new view near similar ones in the file, reformatting it to
follow the style of the rest of the file, exposing the counter
#defines via a header file in case extensions want to use them, and
overhauling and substantially expanding the documentation.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#214Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#213)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2016/03/16 2:33, Robert Haas wrote:

On Tue, Mar 15, 2016 at 1:16 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/03/15 3:41, Robert Haas wrote:

Well, I think you need to study the index AMs and figure this out.

OK. I tried to put calls to the callback in appropriate places, but
couldn't get the resulting progress numbers to look sane. So I ended up
concluding that any attempt to do so is futile unless I analyze each AM's
vacuum code carefully to be able to determine in advance the max bound on
the count of blocks that the callback will report. Anyway, as you
suggest, we can improve it later.

I don't think there is any way to bound that, because new blocks can
get added to the index concurrently, and we might end up needing to
scan them. Reporting the number of blocks scanned can still be
useful, though - any chance you can just implement that part of it?

Do you mean the changes I made to index bulk delete API for this purpose
in last few versions of this patch? With it, I have observed that
reported scanned blocks count (that is incremented for every
ReadBufferExtended() call across a index vacuum cycle using the new
callback) can be non-deterministic. Would there be an accompanying
index_blocks_total (pg_indexes_size()-based) in the view, as well?

But I think for starters you should write a patch that reports the following:

1. phase
2. number of heap blocks scanned
3. number of heap blocks vacuumed
4. number of completed index vac cycles
5. number of dead tuples collected since the last index vac cycle
6. number of dead tuples that we can store before needing to perform
an index vac cycle

All of that should be pretty straightforward, and then we'd have
something we can ship. We can add the detailed index reporting later,
when we get to it, perhaps for 9.7.

OK, I agree with this plan. Attached updated patch implements this.

Sorta. Committed after renaming what you called heap blocks vacuumed
back to heap blocks scanned, adding heap blocks vacuumed, removing the
overall progress meter which I don't believe will be anything close to
accurate, fixing some stylistic stuff, arranging to update multiple
counters automatically where it could otherwise produce confusion,
moving the new view near similar ones in the file, reformatting it to
follow the style of the rest of the file, exposing the counter
#defines via a header file in case extensions want to use them, and
overhauling and substantially expanding the documentation.

Thanks a lot for committing this. The committed version is much better.
Some of the things therein should really have been part of the final
*submitted* patch; I will try to improve in that area in my submissions
henceforth.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#215Noname
pokurev@pm.nttdata.co.jp
In reply to: Robert Haas (#213)
1 attachment(s)
Re: [PROPOSAL] VACUUM Progress Checker.

Hi,

-----Original Message-----
From: Robert Haas [mailto:robertmhaas@gmail.com]
Sent: Wednesday, March 16, 2016 2:34 AM
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
Cc: Amit Langote <amitlangote09@gmail.com>; SPS ポクレ ヴィナヤック(
三技術) <pokurev@pm.nttdata.co.jp>; Kyotaro HORIGUCHI
<horiguchi.kyotaro@lab.ntt.co.jp>; pgsql-hackers@postgresql.org; SPS 坂野
昌平(三技術) <bannos@nttdata.co.jp>
Subject: Re: [HACKERS] [PROPOSAL] VACUUM Progress Checker.

On Tue, Mar 15, 2016 at 1:16 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2016/03/15 3:41, Robert Haas wrote:

On Sat, Mar 12, 2016 at 7:49 AM, Amit Langote

<amitlangote09@gmail.com> wrote:

Instead, the attached patch adds a IndexBulkDeleteProgressCallback
which AMs should call for every block that's read (say, right before
a call to ReadBufferExtended) as part of a given vacuum run. The
callback with help of some bookkeeping state can count each block
and report to pgstat_progress API. Now, I am not sure if all AMs
read 1..N blocks for every vacuum or if it's possible that some
blocks are read more than once in single vacuum, etc. IOW, some
AM's processing may be non-linear and counting blocks 1..N (where N
is reported total index blocks) may not be possible. However, this
is the best I could think of as doing what we are trying to do here.
Maybe index AM experts can chime in on that.

Thoughts?

Well, I think you need to study the index AMs and figure this out.

OK. I tried to put calls to the callback in appropriate places, but
couldn't get the resulting progress numbers to look sane. So I ended
up concluding that any attempt to do so is futile unless I analyze
each AM's vacuum code carefully to be able to determine in advance the
max bound on the count of blocks that the callback will report.
Anyway, as you suggest, we can improve it later.

I don't think there is any way to bound that, because new blocks can get
added to the index concurrently, and we might end up needing to scan
them. Reporting the number of blocks scanned can still be useful, though -
any chance you can just implement that part of it?

But I think for starters you should write a patch that reports the following:

1. phase
2. number of heap blocks scanned
3. number of heap blocks vacuumed
4. number of completed index vac cycles 5. number of dead tuples
collected since the last index vac cycle 6. number of dead tuples
that we can store before needing to perform an index vac cycle

All of that should be pretty straightforward, and then we'd have
something we can ship. We can add the detailed index reporting
later, when we get to it, perhaps for 9.7.

OK, I agree with this plan. Attached updated patch implements this.

Sorta. Committed after renaming what you called heap blocks vacuumed
back to heap blocks scanned, adding heap blocks vacuumed, removing the
overall progress meter which I don't believe will be anything close to
accurate, fixing some stylistic stuff, arranging to update multiple counters
automatically where it could otherwise produce confusion, moving the new
view near similar ones in the file, reformatting it to follow the style of the
rest of the file, exposing the counter #defines via a header file in case
extensions want to use them, and overhauling and substantially expanding
the documentation.

Thank you for committing this feature.
There is one minor bug.
s/ pgstat_progress_update_params/ pgstat_progress_update_multi_param/g
Attached patch fixes a minor bug.

Regards,
Vinayak Pokale

Attachments:

pgstat_progress_function-typo.patchapplication/octet-stream; name=pgstat_progress_function-typo.patchDownload
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index bfe70fc..bed7bb1 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -2903,7 +2903,7 @@ pgstat_progress_update_param(int index, int64 val)
 }
 
 /*-----------
- * pgstat_progress_update_params() -
+ * pgstat_progress_update_multi_param() -
  *
  * Automatically update multiple members in st_progress_param[] of own backend
  * entry.
#216Rahila Syed
rahilasyed90@gmail.com
In reply to: Robert Haas (#213)
Re: [PROPOSAL] VACUUM Progress Checker.

Sorta. Committed after renaming what you called heap blocks vacuumed
back to heap blocks scanned, adding heap blocks vacuumed, removing the
overall progress meter which I don't believe will be anything close to
accurate, fixing some stylistic stuff, arranging to update multiple
counters automatically where it could otherwise produce confusion,
moving the new view near similar ones in the file, reformatting it to
follow the style of the rest of the file, exposing the counter
#defines via a header file in case extensions want to use them, and
overhauling and substantially expanding the documentation

We have following lines,

/* report that everything is scanned and vacuumed */
pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_SCANNED,
blkno);
pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_VACUUMED,
blkno);

which appear before final vacuum cycle happens for any remaining dead
tuples which may span few pages if I am not mistaken.

IMO, reporting final count of heap_blks_scanned is correct here, but
reporting final heap_blks_vacuumed can happen after the final VACUUM cycle
for more accuracy.

Thank you,
Rahila Syed

#217Robert Haas
robertmhaas@gmail.com
In reply to: Rahila Syed (#216)
Re: [PROPOSAL] VACUUM Progress Checker.

On Wed, Mar 16, 2016 at 6:44 AM, Rahila Syed <rahilasyed90@gmail.com> wrote:

Sorta. Committed after renaming what you called heap blocks vacuumed
back to heap blocks scanned, adding heap blocks vacuumed, removing the
overall progress meter which I don't believe will be anything close to
accurate, fixing some stylistic stuff, arranging to update multiple
counters automatically where it could otherwise produce confusion,
moving the new view near similar ones in the file, reformatting it to
follow the style of the rest of the file, exposing the counter
#defines via a header file in case extensions want to use them, and
overhauling and substantially expanding the documentation

We have following lines,

/* report that everything is scanned and vacuumed */
pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_SCANNED,
blkno);
pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_VACUUMED,
blkno);

which appear before final vacuum cycle happens for any remaining dead tuples
which may span few pages if I am not mistaken.

IMO, reporting final count of heap_blks_scanned is correct here, but
reporting final heap_blks_vacuumed can happen after the final VACUUM cycle
for more accuracy.

You are quite right. Good catch. Fixed that, and applied Vinayak's
patch too, and fixed another mistake I saw while I was at it.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#218Rahila Syed
rahilasyed90@gmail.com
In reply to: Robert Haas (#217)
1 attachment(s)
Re: [PROPOSAL] VACUUM Progress Checker.

Hello,

Server crash was reported on running vacuum progress checker view on
32-bit machine.
Please find attached a fix for the same.

Crash was because 32 bit machine considers int8 as being passed by
reference while creating the tuple descriptor. At the time of filling the
tuple store, the code (heap_fill_tuple) checks this tuple descriptor before
inserting the value into the tuple store. It finds the attribute type pass
by reference and hence it treats the value as a pointer when it is not and
thus it fails at the time of memcpy.

This happens because appropriate conversion function is not employed while
storing the value of that particular attribute into the values array before
copying it into tuple store.

-                               values[i+3] =
UInt32GetDatum(beentry->st_progress_param[i]);
+                               values[i+3] =
Int64GetDatum(beentry->st_progress_param[i]);

Attached patch fixes this.

Thank you,
Rahila Syed

On Wed, Mar 16, 2016 at 11:30 PM, Robert Haas <robertmhaas@gmail.com> wrote:

Show quoted text

On Wed, Mar 16, 2016 at 6:44 AM, Rahila Syed <rahilasyed90@gmail.com>
wrote:

Sorta. Committed after renaming what you called heap blocks vacuumed
back to heap blocks scanned, adding heap blocks vacuumed, removing the
overall progress meter which I don't believe will be anything close to
accurate, fixing some stylistic stuff, arranging to update multiple
counters automatically where it could otherwise produce confusion,
moving the new view near similar ones in the file, reformatting it to
follow the style of the rest of the file, exposing the counter
#defines via a header file in case extensions want to use them, and
overhauling and substantially expanding the documentation

We have following lines,

/* report that everything is scanned and vacuumed */
pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_SCANNED,
blkno);
pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_VACUUMED,
blkno);

which appear before final vacuum cycle happens for any remaining dead

tuples

which may span few pages if I am not mistaken.

IMO, reporting final count of heap_blks_scanned is correct here, but
reporting final heap_blks_vacuumed can happen after the final VACUUM

cycle

for more accuracy.

You are quite right. Good catch. Fixed that, and applied Vinayak's
patch too, and fixed another mistake I saw while I was at it.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Attachments:

vacuum_progress_checker_bugfix.patchapplication/x-patch; name=vacuum_progress_checker_bugfix.patchDownload
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 0f6f891..64c4cc4 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -612,7 +612,7 @@ pg_stat_get_progress_info(PG_FUNCTION_ARGS)
 		{
 			values[2] = ObjectIdGetDatum(beentry->st_progress_command_target);
 			for(i = 0; i < PGSTAT_NUM_PROGRESS_PARAM; i++)
-				values[i+3] = UInt32GetDatum(beentry->st_progress_param[i]);
+				values[i+3] = Int64GetDatum(beentry->st_progress_param[i]);
 		}
 		else
 		{
#219Robert Haas
robertmhaas@gmail.com
In reply to: Rahila Syed (#218)
Re: [PROPOSAL] VACUUM Progress Checker.

On Thu, Mar 24, 2016 at 8:45 AM, Rahila Syed <rahilasyed90@gmail.com> wrote:

Server crash was reported on running vacuum progress checker view on 32-bit
machine.
Please find attached a fix for the same.

Crash was because 32 bit machine considers int8 as being passed by reference
while creating the tuple descriptor. At the time of filling the tuple store,
the code (heap_fill_tuple) checks this tuple descriptor before inserting the
value into the tuple store. It finds the attribute type pass by reference
and hence it treats the value as a pointer when it is not and thus it fails
at the time of memcpy.

This happens because appropriate conversion function is not employed while
storing the value of that particular attribute into the values array before
copying it into tuple store.

-                               values[i+3] =
UInt32GetDatum(beentry->st_progress_param[i]);
+                               values[i+3] =
Int64GetDatum(beentry->st_progress_param[i]);

Attached patch fixes this.

Uggh, what a stupid mistake on my part.

Committed. Thanks for the patch.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#220Robert Haas
robertmhaas@gmail.com
In reply to: Robert Haas (#219)
Re: [PROPOSAL] VACUUM Progress Checker.

On Thu, Mar 24, 2016 at 9:01 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Thu, Mar 24, 2016 at 8:45 AM, Rahila Syed <rahilasyed90@gmail.com> wrote:

Server crash was reported on running vacuum progress checker view on 32-bit
machine.
Please find attached a fix for the same.

Crash was because 32 bit machine considers int8 as being passed by reference
while creating the tuple descriptor. At the time of filling the tuple store,
the code (heap_fill_tuple) checks this tuple descriptor before inserting the
value into the tuple store. It finds the attribute type pass by reference
and hence it treats the value as a pointer when it is not and thus it fails
at the time of memcpy.

This happens because appropriate conversion function is not employed while
storing the value of that particular attribute into the values array before
copying it into tuple store.

-                               values[i+3] =
UInt32GetDatum(beentry->st_progress_param[i]);
+                               values[i+3] =
Int64GetDatum(beentry->st_progress_param[i]);

Attached patch fixes this.

Uggh, what a stupid mistake on my part.

Committed. Thanks for the patch.

Oops. I forgot to credit you in the commit message. Sorry about that. :-(

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#221Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#219)
Re: [PROPOSAL] VACUUM Progress Checker.

On 2016/03/24 22:01, Robert Haas wrote:

On Thu, Mar 24, 2016 at 8:45 AM, Rahila Syed <rahilasyed90@gmail.com> wrote:

-                               values[i+3] =
UInt32GetDatum(beentry->st_progress_param[i]);
+                               values[i+3] =
Int64GetDatum(beentry->st_progress_param[i]);

Attached patch fixes this.

Uggh, what a stupid mistake on my part.

Committed. Thanks for the patch.

Thanks Rahila and Robert.

- Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#222Rahila Syed
rahilasyed90@gmail.com
In reply to: Robert Haas (#220)
Re: [PROPOSAL] VACUUM Progress Checker.

Oops. I forgot to credit you in the commit message. Sorry about that.

:-(
No problem :). Thanks for the commit.

Thank you,
Rahila Syed