*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
***************
*** 13996,14001 **** SELECT set_config('log_statement_stats', 'off', false);
--- 13996,14004 ----
pg_current_xlog_location
+ pg_last_xact_insert_timestamp
+
+ pg_start_backup
***************
*** 14049,14054 **** SELECT set_config('log_statement_stats', 'off', false);
--- 14052,14064 ----
+ pg_last_xact_insert_timestamp()
+
+ timestamp with time zone
+ Get last transaction log insert time stamp
+
+
+ pg_start_backup(label> text> , fast> boolean> )text
***************
*** 14175,14180 **** postgres=# SELECT * FROM pg_xlogfile_name_offset(pg_stop_backup());
--- 14185,14197 ----
+ pg_last_xact_insert_timestamp> displays the time stamp of last inserted
+ transaction. This is the time at which the commit or abort WAL record for that transaction.
+ If there has been no transaction committed or aborted yet since the server has started,
+ this function returns NULL.
+
+
+
For details about proper usage of these functions, see
.
*** a/doc/src/sgml/high-availability.sgml
--- b/doc/src/sgml/high-availability.sgml
***************
*** 867,872 **** primary_conninfo = 'host=192.168.1.50 port=5432 user=foo password=foopass'
--- 867,881 ----
ps> command (see for details).
+ You can also calculate the lag in time stamp by comparing the last
+ WAL insert time stamp on the primary with the last WAL replay
+ time stamp on the standby. They can be retrieved using
+ pg_last_xact_insert_timestamp> on the primary and
+ the pg_last_xact_replay_timestamp> on the standby,
+ respectively (see and
+ for details).
+
+
You can retrieve a list of WAL sender processes via the
pg_stat_replication> view. Large differences between
*** a/src/backend/access/transam/xact.c
--- b/src/backend/access/transam/xact.c
***************
*** 1042,1047 **** RecordTransactionCommit(void)
--- 1042,1050 ----
rdata[lastrdata].next = NULL;
(void) XLogInsert(RM_XACT_ID, XLOG_XACT_COMMIT, rdata);
+
+ /* Save timestamp of latest transaction commit record */
+ pgstat_report_xact_end_timestamp(xlrec.xact_time);
}
else
{
***************
*** 1065,1070 **** RecordTransactionCommit(void)
--- 1068,1076 ----
rdata[lastrdata].next = NULL;
(void) XLogInsert(RM_XACT_ID, XLOG_XACT_COMMIT_COMPACT, rdata);
+
+ /* Save timestamp of latest transaction commit record */
+ pgstat_report_xact_end_timestamp(xlrec.xact_time);
}
}
***************
*** 1434,1439 **** RecordTransactionAbort(bool isSubXact)
--- 1440,1448 ----
(void) XLogInsert(RM_XACT_ID, XLOG_XACT_ABORT, rdata);
+ /* Save timestamp of latest transaction abort record */
+ pgstat_report_xact_end_timestamp(xlrec.xact_time);
+
/*
* Report the latest async abort LSN, so that the WAL writer knows to
* flush this abort. There's nothing to be gained by delaying this, since
*** a/src/backend/access/transam/xlog.c
--- b/src/backend/access/transam/xlog.c
***************
*** 5867,5872 **** pg_is_xlog_replay_paused(PG_FUNCTION_ARGS)
--- 5867,5907 ----
}
/*
+ * Returns timestamp of latest inserted commit/abort record.
+ *
+ * If there has been no transaction committed or aborted yet since
+ * the server has started, this function returns NULL.
+ */
+ Datum
+ pg_last_xact_insert_timestamp(PG_FUNCTION_ARGS)
+ {
+ TimestampTz result = 0;
+ TimestampTz xtime;
+ PgBackendStatus *beentry;
+ int i;
+
+ if (RecoveryInProgress())
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("recovery is in progress"),
+ errhint("WAL control functions cannot be executed during recovery.")));
+
+ beentry = pgstat_fetch_all_beentry();
+
+ for (i = 0; i < MaxBackends; i++, beentry++)
+ {
+ xtime = beentry->st_xact_end_timestamp;
+ if (result < xtime)
+ result = xtime;
+ }
+
+ if (result == 0)
+ PG_RETURN_NULL();
+
+ PG_RETURN_TIMESTAMPTZ(result);
+ }
+
+ /*
* Save timestamp of latest processed commit/abort record.
*
* We keep this in XLogCtl, not a simple static variable, so that it can be
*** a/src/backend/postmaster/pgstat.c
--- b/src/backend/postmaster/pgstat.c
***************
*** 254,260 **** static PgStat_StatTabEntry *pgstat_get_tab_entry(PgStat_StatDBEntry *dbentry,
static void pgstat_write_statsfile(bool permanent);
static HTAB *pgstat_read_statsfile(Oid onlydb, bool permanent);
static void backend_read_statsfile(void);
! static void pgstat_read_current_status(void);
static void pgstat_send_tabstat(PgStat_MsgTabstat *tsmsg);
static void pgstat_send_funcstats(void);
--- 254,260 ----
static void pgstat_write_statsfile(bool permanent);
static HTAB *pgstat_read_statsfile(Oid onlydb, bool permanent);
static void backend_read_statsfile(void);
! static void pgstat_read_current_status(bool all);
static void pgstat_send_tabstat(PgStat_MsgTabstat *tsmsg);
static void pgstat_send_funcstats(void);
***************
*** 2173,2179 **** pgstat_fetch_stat_funcentry(Oid func_id)
PgBackendStatus *
pgstat_fetch_stat_beentry(int beid)
{
! pgstat_read_current_status();
if (beid < 1 || beid > localNumBackends)
return NULL;
--- 2173,2179 ----
PgBackendStatus *
pgstat_fetch_stat_beentry(int beid)
{
! pgstat_read_current_status(false);
if (beid < 1 || beid > localNumBackends)
return NULL;
***************
*** 2192,2202 **** pgstat_fetch_stat_beentry(int beid)
int
pgstat_fetch_stat_numbackends(void)
{
! pgstat_read_current_status();
return localNumBackends;
}
/*
* ---------
* pgstat_fetch_global() -
--- 2192,2220 ----
int
pgstat_fetch_stat_numbackends(void)
{
! pgstat_read_current_status(false);
return localNumBackends;
}
+ /* ----------
+ * pgstat_fetch_all_beentry() -
+ *
+ * Support function for the SQL-callable pgstat* functions. Returns
+ * our local copy of all backend entries.
+ *
+ * NB: caller is responsible for a check if the user is permitted to see
+ * this info (especially the querystring).
+ * ----------
+ */
+ PgBackendStatus *
+ pgstat_fetch_all_beentry(void)
+ {
+ pgstat_read_current_status(true);
+
+ return localBackendStatusTable;
+ }
+
/*
* ---------
* pgstat_fetch_global() -
***************
*** 2414,2419 **** pgstat_bestart(void)
--- 2432,2442 ----
beentry->st_appname[NAMEDATALEN - 1] = '\0';
beentry->st_activity[pgstat_track_activity_query_size - 1] = '\0';
+ /*
+ * Don't reset st_xact_end_timestamp because the previous value can still
+ * be referenced to calculate the latest transaction insert timestamp.
+ */
+
beentry->st_changecount++;
Assert((beentry->st_changecount & 1) == 0);
***************
*** 2560,2565 **** pgstat_report_xact_timestamp(TimestampTz tstamp)
--- 2583,2611 ----
Assert((beentry->st_changecount & 1) == 0);
}
+ /*
+ * Report last transaction end timestamp as the specified value.
+ * Zero means there is no finished transaction.
+ */
+ void
+ pgstat_report_xact_end_timestamp(TimestampTz tstamp)
+ {
+ volatile PgBackendStatus *beentry = MyBEEntry;
+
+ if (!pgstat_track_activities || !beentry)
+ return;
+
+ /*
+ * Update my status entry, following the protocol of bumping
+ * st_changecount before and after. We use a volatile pointer here to
+ * ensure the compiler doesn't try to get cute.
+ */
+ beentry->st_changecount++;
+ beentry->st_xact_end_timestamp = tstamp;
+ beentry->st_changecount++;
+ Assert((beentry->st_changecount & 1) == 0);
+ }
+
/* ----------
* pgstat_report_waiting() -
*
***************
*** 2590,2600 **** pgstat_report_waiting(bool waiting)
* pgstat_read_current_status() -
*
* Copy the current contents of the PgBackendStatus array to local memory,
! * if not already done in this transaction.
* ----------
*/
static void
! pgstat_read_current_status(void)
{
volatile PgBackendStatus *beentry;
PgBackendStatus *localtable;
--- 2636,2647 ----
* pgstat_read_current_status() -
*
* Copy the current contents of the PgBackendStatus array to local memory,
! * if not already done in this transaction. If all is true, the local
! * array includes all entries. Otherwise, it includes only valid ones.
* ----------
*/
static void
! pgstat_read_current_status(bool all)
{
volatile PgBackendStatus *beentry;
PgBackendStatus *localtable;
***************
*** 2636,2642 **** pgstat_read_current_status(void)
int save_changecount = beentry->st_changecount;
localentry->st_procpid = beentry->st_procpid;
! if (localentry->st_procpid > 0)
{
memcpy(localentry, (char *) beentry, sizeof(PgBackendStatus));
--- 2683,2689 ----
int save_changecount = beentry->st_changecount;
localentry->st_procpid = beentry->st_procpid;
! if (localentry->st_procpid > 0 || all)
{
memcpy(localentry, (char *) beentry, sizeof(PgBackendStatus));
***************
*** 2659,2666 **** pgstat_read_current_status(void)
}
beentry++;
! /* Only valid entries get included into the local array */
! if (localentry->st_procpid > 0)
{
localentry++;
localappname += NAMEDATALEN;
--- 2706,2713 ----
}
beentry++;
! /* Only valid entries get included into the local array if all is false */
! if (localentry->st_procpid > 0 || all)
{
localentry++;
localappname += NAMEDATALEN;
*** a/src/include/access/xlog_internal.h
--- b/src/include/access/xlog_internal.h
***************
*** 272,277 **** extern Datum pg_current_xlog_location(PG_FUNCTION_ARGS);
--- 272,278 ----
extern Datum pg_current_xlog_insert_location(PG_FUNCTION_ARGS);
extern Datum pg_last_xlog_receive_location(PG_FUNCTION_ARGS);
extern Datum pg_last_xlog_replay_location(PG_FUNCTION_ARGS);
+ extern Datum pg_last_xact_insert_timestamp(PG_FUNCTION_ARGS);
extern Datum pg_last_xact_replay_timestamp(PG_FUNCTION_ARGS);
extern Datum pg_xlogfile_name_offset(PG_FUNCTION_ARGS);
extern Datum pg_xlogfile_name(PG_FUNCTION_ARGS);
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 2869,2874 **** DATA(insert OID = 2850 ( pg_xlogfile_name_offset PGNSP PGUID 12 1 0 0 0 f f f t
--- 2869,2876 ----
DESCR("xlog filename and byte offset, given an xlog location");
DATA(insert OID = 2851 ( pg_xlogfile_name PGNSP PGUID 12 1 0 0 0 f f f t f i 1 0 25 "25" _null_ _null_ _null_ _null_ pg_xlogfile_name _null_ _null_ _null_ ));
DESCR("xlog filename, given an xlog location");
+ DATA(insert OID = 3831 ( pg_last_xact_insert_timestamp PGNSP PGUID 12 1 0 0 0 f f f t f v 0 0 1184 "" _null_ _null_ _null_ _null_ pg_last_xact_insert_timestamp _null_ _null_ _null_ ));
+ DESCR("timestamp of last insert xact");
DATA(insert OID = 3810 ( pg_is_in_recovery PGNSP PGUID 12 1 0 0 0 f f f t f v 0 0 16 "" _null_ _null_ _null_ _null_ pg_is_in_recovery _null_ _null_ _null_ ));
DESCR("true if server is in recovery");
*** a/src/include/pgstat.h
--- b/src/include/pgstat.h
***************
*** 623,628 **** typedef struct PgBackendStatus
--- 623,631 ----
TimestampTz st_xact_start_timestamp;
TimestampTz st_activity_start_timestamp;
+ /* Time when last transaction ended */
+ TimestampTz st_xact_end_timestamp;
+
/* Database OID, owning user's OID, connection client address */
Oid st_databaseid;
Oid st_userid;
***************
*** 718,723 **** extern void pgstat_bestart(void);
--- 721,727 ----
extern void pgstat_report_activity(const char *cmd_str);
extern void pgstat_report_appname(const char *appname);
extern void pgstat_report_xact_timestamp(TimestampTz tstamp);
+ extern void pgstat_report_xact_end_timestamp(TimestampTz tstamp);
extern void pgstat_report_waiting(bool waiting);
extern const char *pgstat_get_backend_current_activity(int pid, bool checkUser);
***************
*** 795,800 **** extern void pgstat_send_bgwriter(void);
--- 799,805 ----
extern PgStat_StatDBEntry *pgstat_fetch_stat_dbentry(Oid dbid);
extern PgStat_StatTabEntry *pgstat_fetch_stat_tabentry(Oid relid);
extern PgBackendStatus *pgstat_fetch_stat_beentry(int beid);
+ extern PgBackendStatus *pgstat_fetch_all_beentry(void);
extern PgStat_StatFuncEntry *pgstat_fetch_stat_funcentry(Oid funcid);
extern int pgstat_fetch_stat_numbackends(void);
extern PgStat_GlobalStats *pgstat_fetch_global(void);