(1 row)
CREATE TABLE stats_test(data text);
--- function to wait for counters to advance
-CREATE FUNCTION wait_for_decode_stats(check_reset bool, check_spill_txns bool) RETURNS void AS $$
-DECLARE
- start_time timestamptz := clock_timestamp();
- updated bool;
-BEGIN
- -- we don't want to wait forever; loop will exit after 30 seconds
- FOR i IN 1 .. 300 LOOP
-
- IF check_spill_txns THEN
-
- -- check to see if all updates have been reset/updated
- SELECT CASE WHEN check_reset THEN (spill_txns = 0)
- ELSE (spill_txns > 0)
- END
- INTO updated
- FROM pg_stat_replication_slots WHERE slot_name='regression_slot_stats';
-
- ELSE
-
- -- check to see if all updates have been reset/updated
- SELECT CASE WHEN check_reset THEN (total_txns = 0)
- ELSE (total_txns > 0)
- END
- INTO updated
- FROM pg_stat_replication_slots WHERE slot_name='regression_slot_stats';
-
- END IF;
-
- exit WHEN updated;
-
- -- wait a little
- perform pg_sleep_for('100 milliseconds');
-
- -- reset stats snapshot so we can test again
- perform pg_stat_clear_snapshot();
-
- END LOOP;
-
- -- report time waited in postmaster log (where it won't change test output)
- RAISE LOG 'wait_for_decode_stats delayed % seconds',
- extract(epoch from clock_timestamp() - start_time);
-END
-$$ LANGUAGE plpgsql;
-- non-spilled xact
SET logical_decoding_work_mem to '64MB';
INSERT INTO stats_test values(1);
3
(1 row)
-SELECT wait_for_decode_stats(false, false);
- wait_for_decode_stats
------------------------
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush
+--------------------------
(1 row)
(1 row)
RESET logical_decoding_work_mem;
--- reset the slot stats, and wait for stats collector's total txn to reset
+-- reset the slot stats
SELECT pg_stat_reset_replication_slot('regression_slot_stats');
pg_stat_reset_replication_slot
--------------------------------
(1 row)
-SELECT wait_for_decode_stats(true, false);
- wait_for_decode_stats
------------------------
-
-(1 row)
-
SELECT slot_name, spill_txns, spill_count, total_txns, total_bytes FROM pg_stat_replication_slots;
slot_name | spill_txns | spill_count | total_txns | total_bytes
-----------------------+------------+-------------+------------+-------------
5002
(1 row)
--- Check stats, wait for the stats collector to update. We can't test the
--- exact stats count as that can vary if any background transaction (say by
--- autovacuum) happens in parallel to the main transaction.
-SELECT wait_for_decode_stats(false, true);
- wait_for_decode_stats
------------------------
+-- Check stats. We can't test the exact stats count as that can vary if any
+-- background transaction (say by autovacuum) happens in parallel to the main
+-- transaction.
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush
+--------------------------
(1 row)
(1 row)
COMMIT;
-DROP FUNCTION wait_for_decode_stats(bool, bool);
DROP TABLE stats_test;
SELECT pg_drop_replication_slot('regression_slot_stats');
pg_drop_replication_slot
CREATE TABLE stats_test(data text);
--- function to wait for counters to advance
-CREATE FUNCTION wait_for_decode_stats(check_reset bool, check_spill_txns bool) RETURNS void AS $$
-DECLARE
- start_time timestamptz := clock_timestamp();
- updated bool;
-BEGIN
- -- we don't want to wait forever; loop will exit after 30 seconds
- FOR i IN 1 .. 300 LOOP
-
- IF check_spill_txns THEN
-
- -- check to see if all updates have been reset/updated
- SELECT CASE WHEN check_reset THEN (spill_txns = 0)
- ELSE (spill_txns > 0)
- END
- INTO updated
- FROM pg_stat_replication_slots WHERE slot_name='regression_slot_stats';
-
- ELSE
-
- -- check to see if all updates have been reset/updated
- SELECT CASE WHEN check_reset THEN (total_txns = 0)
- ELSE (total_txns > 0)
- END
- INTO updated
- FROM pg_stat_replication_slots WHERE slot_name='regression_slot_stats';
-
- END IF;
-
- exit WHEN updated;
-
- -- wait a little
- perform pg_sleep_for('100 milliseconds');
-
- -- reset stats snapshot so we can test again
- perform pg_stat_clear_snapshot();
-
- END LOOP;
-
- -- report time waited in postmaster log (where it won't change test output)
- RAISE LOG 'wait_for_decode_stats delayed % seconds',
- extract(epoch from clock_timestamp() - start_time);
-END
-$$ LANGUAGE plpgsql;
-
-- non-spilled xact
SET logical_decoding_work_mem to '64MB';
INSERT INTO stats_test values(1);
SELECT count(*) FROM pg_logical_slot_get_changes('regression_slot_stats', NULL, NULL, 'skip-empty-xacts', '1');
-SELECT wait_for_decode_stats(false, false);
+SELECT pg_stat_force_next_flush();
SELECT slot_name, spill_txns = 0 AS spill_txns, spill_count = 0 AS spill_count, total_txns > 0 AS total_txns, total_bytes > 0 AS total_bytes FROM pg_stat_replication_slots;
RESET logical_decoding_work_mem;
--- reset the slot stats, and wait for stats collector's total txn to reset
+-- reset the slot stats
SELECT pg_stat_reset_replication_slot('regression_slot_stats');
-SELECT wait_for_decode_stats(true, false);
SELECT slot_name, spill_txns, spill_count, total_txns, total_bytes FROM pg_stat_replication_slots;
-- spilling the xact
COMMIT;
SELECT count(*) FROM pg_logical_slot_peek_changes('regression_slot_stats', NULL, NULL, 'skip-empty-xacts', '1');
--- Check stats, wait for the stats collector to update. We can't test the
--- exact stats count as that can vary if any background transaction (say by
--- autovacuum) happens in parallel to the main transaction.
-SELECT wait_for_decode_stats(false, true);
+-- Check stats. We can't test the exact stats count as that can vary if any
+-- background transaction (say by autovacuum) happens in parallel to the main
+-- transaction.
+SELECT pg_stat_force_next_flush();
SELECT slot_name, spill_txns > 0 AS spill_txns, spill_count > 0 AS spill_count FROM pg_stat_replication_slots;
-- Ensure stats can be repeatedly accessed using the same stats snapshot. See
SELECT slot_name FROM pg_stat_replication_slots;
COMMIT;
-DROP FUNCTION wait_for_decode_stats(bool, bool);
DROP TABLE stats_test;
SELECT pg_drop_replication_slot('regression_slot_stats');
static dlist_head pgStatPending = DLIST_STATIC_INIT(pgStatPending);
+/*
+ * Force the next stats flush to happen regardless of
+ * PGSTAT_MIN_INTERVAL. Useful in test scripts.
+ */
+static bool pgStatForceNextFlush = false;
+
/*
* For assertions that check pgstat is not used before initialization / after
* shutdown.
pgstat_assert_is_up();
Assert(!IsTransactionBlock());
+ /* "absorb" the forced flush even if there's nothing to flush */
+ if (pgStatForceNextFlush)
+ {
+ force = true;
+ pgStatForceNextFlush = false;
+ }
+
/* Don't expend a clock check if nothing to do */
if (dlist_is_empty(&pgStatPending) &&
!have_slrustats &&
return 0;
}
+/*
+ * Force locally pending stats to be flushed during the next
+ * pgstat_report_stat() call. This is useful for writing tests.
+ */
+void
+pgstat_force_next_flush(void)
+{
+ pgStatForceNextFlush = true;
+}
+
/*
* Only for use by pgstat_reset_counters()
*/
}
+/* Force statistics to be reported at the next occasion */
+Datum
+pg_stat_force_next_flush(PG_FUNCTION_ARGS)
+{
+ pgstat_force_next_flush();
+
+ PG_RETURN_VOID();
+}
+
+
/* Reset all counters for the current database */
Datum
pg_stat_reset(PG_FUNCTION_ARGS)
proname => 'pg_stat_clear_snapshot', proisstrict => 'f', provolatile => 'v',
proparallel => 'r', prorettype => 'void', proargtypes => '',
prosrc => 'pg_stat_clear_snapshot' },
+{ oid => '2137',
+ descr => 'statistics: force stats to be flushed after the next commit',
+ proname => 'pg_stat_force_next_flush', proisstrict => 'f', provolatile => 'v',
+ proparallel => 'r', prorettype => 'void', proargtypes => '',
+ prosrc => 'pg_stat_force_next_flush' },
{ oid => '2274',
descr => 'statistics: reset collected statistics for current database',
proname => 'pg_stat_reset', proisstrict => 'f', provolatile => 'v',
/* Functions called from backends */
extern long pgstat_report_stat(bool force);
+extern void pgstat_force_next_flush(void);
extern void pgstat_reset_counters(void);
extern void pgstat_reset(PgStat_Kind kind, Oid dboid, Oid objectid);
1
(1 row)
+-- test BRIN index doesn't block HOT update
+CREATE TABLE brin_hot (
+ id integer PRIMARY KEY,
+ val integer NOT NULL
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+INSERT INTO brin_hot SELECT *, 0 FROM generate_series(1, 235);
+CREATE INDEX val_brin ON brin_hot using brin(val);
+UPDATE brin_hot SET val = -3 WHERE id = 42;
+-- ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush
+--------------------------
+
+(1 row)
+
+SELECT pg_stat_get_tuples_hot_updated('brin_hot'::regclass::oid);
+ pg_stat_get_tuples_hot_updated
+--------------------------------
+ 1
+(1 row)
+
+DROP TABLE brin_hot;
pg_catalog.pg_statio_user_tables AS b
WHERE t.relname='tenk2' AND b.relname='tenk2';
COMMIT;
--- function to wait for counters to advance
-create function wait_for_stats() returns void as $$
-declare
- start_time timestamptz := clock_timestamp();
- updated1 bool;
- updated2 bool;
- updated3 bool;
- updated4 bool;
-begin
- SET LOCAL stats_fetch_consistency = snapshot;
-
- -- We don't want to wait forever. No timeout suffices if the OS drops our
- -- stats traffic because an earlier test file left a full UDP buffer.
- -- Hence, don't use PG_TEST_TIMEOUT_DEFAULT, which may be large for
- -- can't-happen timeouts. Exit after 30 seconds.
- for i in 1 .. 300 loop
-
- -- With parallel query, the seqscan and indexscan on tenk2 might be done
- -- in parallel worker processes, which will send their stats counters
- -- asynchronously to what our own session does. So we must check for
- -- those counts to be registered separately from the update counts.
-
- -- check to see if seqscan has been sensed
- SELECT (st.seq_scan >= pr.seq_scan + 1) INTO updated1
- FROM pg_stat_user_tables AS st, pg_class AS cl, prevstats AS pr
- WHERE st.relname='tenk2' AND cl.relname='tenk2';
-
- -- check to see if indexscan has been sensed
- SELECT (st.idx_scan >= pr.idx_scan + 1) INTO updated2
- FROM pg_stat_user_tables AS st, pg_class AS cl, prevstats AS pr
- WHERE st.relname='tenk2' AND cl.relname='tenk2';
-
- -- check to see if all updates have been sensed
- SELECT (n_tup_ins > 0) INTO updated3
- FROM pg_stat_user_tables WHERE relname='trunc_stats_test4';
-
- -- We must also check explicitly that pg_stat_get_snapshot_timestamp has
- -- advanced, because that comes from the global stats file which might
- -- be older than the per-DB stats file we got the other values from.
- SELECT (pr.snap_ts < pg_stat_get_snapshot_timestamp()) INTO updated4
- FROM prevstats AS pr;
-
- exit when updated1 and updated2 and updated3 and updated4;
-
- -- wait a little
- perform pg_sleep_for('100 milliseconds');
-
- -- reset stats snapshot so we can test again
- perform pg_stat_clear_snapshot();
-
- end loop;
-
- -- report time waited in postmaster log (where it won't change test output)
- raise log 'wait_for_stats delayed % seconds',
- extract(epoch from clock_timestamp() - start_time);
-end
-$$ language plpgsql;
-- test effects of TRUNCATE on n_live_tup/n_dead_tup counters
CREATE TABLE trunc_stats_test(id serial);
CREATE TABLE trunc_stats_test1(id serial, stuff text);
(1 row)
RESET enable_bitmapscan;
--- We can't just call wait_for_stats() at this point, because we only
--- transmit stats when the session goes idle, and we probably didn't
--- transmit the last couple of counts yet thanks to the rate-limiting logic
--- in pgstat_report_stat(). But instead of waiting for the rate limiter's
--- timeout to elapse, let's just start a new session. The old one will
--- then send its stats before dying.
-\c -
--- wait for stats collector to update
-SELECT wait_for_stats();
- wait_for_stats
-----------------
+-- ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush
+--------------------------
(1 row)
COMMIT;
DROP TABLE trunc_stats_test, trunc_stats_test1, trunc_stats_test2, trunc_stats_test3, trunc_stats_test4;
DROP TABLE prevstats;
--- test BRIN index doesn't block HOT update - we include this test here, as it
--- relies on statistics collector and so it may occasionally fail, especially
--- on slower systems
-CREATE TABLE brin_hot (
- id integer PRIMARY KEY,
- val integer NOT NULL
-) WITH (autovacuum_enabled = off, fillfactor = 70);
-INSERT INTO brin_hot SELECT *, 0 FROM generate_series(1, 235);
-CREATE INDEX val_brin ON brin_hot using brin(val);
-CREATE FUNCTION wait_for_hot_stats() RETURNS void AS $$
-DECLARE
- start_time timestamptz := clock_timestamp();
- updated bool;
-BEGIN
- -- we don't want to wait forever; loop will exit after 30 seconds
- FOR i IN 1 .. 300 LOOP
- SELECT (pg_stat_get_tuples_hot_updated('brin_hot'::regclass::oid) > 0) INTO updated;
- EXIT WHEN updated;
-
- -- wait a little
- PERFORM pg_sleep_for('100 milliseconds');
- -- reset stats snapshot so we can test again
- PERFORM pg_stat_clear_snapshot();
- END LOOP;
- -- report time waited in postmaster log (where it won't change test output)
- RAISE log 'wait_for_hot_stats delayed % seconds',
- EXTRACT(epoch FROM clock_timestamp() - start_time);
-END
-$$ LANGUAGE plpgsql;
-UPDATE brin_hot SET val = -3 WHERE id = 42;
--- We can't just call wait_for_hot_stats() at this point, because we only
--- transmit stats when the session goes idle, and we probably didn't
--- transmit the last couple of counts yet thanks to the rate-limiting logic
--- in pgstat_report_stat(). But instead of waiting for the rate limiter's
--- timeout to elapse, let's just start a new session. The old one will
--- then send its stats before dying.
-\c -
-SELECT wait_for_hot_stats();
- wait_for_hot_stats
---------------------
-
-(1 row)
-
-SELECT pg_stat_get_tuples_hot_updated('brin_hot'::regclass::oid);
- pg_stat_get_tuples_hot_updated
---------------------------------
- 1
-(1 row)
-
-DROP TABLE brin_hot;
-DROP FUNCTION wait_for_hot_stats();
-- ensure that stats accessors handle NULL input correctly
SELECT pg_stat_get_replication_slot(NULL);
pg_stat_get_replication_slot
# ----------
# Another group of parallel tests
# ----------
-test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info tuplesort explain compression memoize
+test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info tuplesort explain compression memoize stats
# event_trigger cannot run concurrently with any test that runs DDL
# oidjoins is read-only, though, and should run late for best coverage
# this test also uses event triggers, so likewise run it by itself
test: fast_default
-
-# run stats by itself because its delay may be insufficient under heavy load
-test: stats
EXPLAIN (COSTS OFF) SELECT * FROM brin_hot_2 WHERE a = 2 AND b = 100;
SELECT COUNT(*) FROM brin_hot_2 WHERE a = 2 AND b = 100;
+
+
+-- test BRIN index doesn't block HOT update
+CREATE TABLE brin_hot (
+ id integer PRIMARY KEY,
+ val integer NOT NULL
+) WITH (autovacuum_enabled = off, fillfactor = 70);
+
+INSERT INTO brin_hot SELECT *, 0 FROM generate_series(1, 235);
+CREATE INDEX val_brin ON brin_hot using brin(val);
+
+UPDATE brin_hot SET val = -3 WHERE id = 42;
+
+-- ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+
+SELECT pg_stat_get_tuples_hot_updated('brin_hot'::regclass::oid);
+
+DROP TABLE brin_hot;
WHERE t.relname='tenk2' AND b.relname='tenk2';
COMMIT;
--- function to wait for counters to advance
-create function wait_for_stats() returns void as $$
-declare
- start_time timestamptz := clock_timestamp();
- updated1 bool;
- updated2 bool;
- updated3 bool;
- updated4 bool;
-begin
- SET LOCAL stats_fetch_consistency = snapshot;
-
- -- We don't want to wait forever. No timeout suffices if the OS drops our
- -- stats traffic because an earlier test file left a full UDP buffer.
- -- Hence, don't use PG_TEST_TIMEOUT_DEFAULT, which may be large for
- -- can't-happen timeouts. Exit after 30 seconds.
- for i in 1 .. 300 loop
-
- -- With parallel query, the seqscan and indexscan on tenk2 might be done
- -- in parallel worker processes, which will send their stats counters
- -- asynchronously to what our own session does. So we must check for
- -- those counts to be registered separately from the update counts.
-
- -- check to see if seqscan has been sensed
- SELECT (st.seq_scan >= pr.seq_scan + 1) INTO updated1
- FROM pg_stat_user_tables AS st, pg_class AS cl, prevstats AS pr
- WHERE st.relname='tenk2' AND cl.relname='tenk2';
-
- -- check to see if indexscan has been sensed
- SELECT (st.idx_scan >= pr.idx_scan + 1) INTO updated2
- FROM pg_stat_user_tables AS st, pg_class AS cl, prevstats AS pr
- WHERE st.relname='tenk2' AND cl.relname='tenk2';
-
- -- check to see if all updates have been sensed
- SELECT (n_tup_ins > 0) INTO updated3
- FROM pg_stat_user_tables WHERE relname='trunc_stats_test4';
-
- -- We must also check explicitly that pg_stat_get_snapshot_timestamp has
- -- advanced, because that comes from the global stats file which might
- -- be older than the per-DB stats file we got the other values from.
- SELECT (pr.snap_ts < pg_stat_get_snapshot_timestamp()) INTO updated4
- FROM prevstats AS pr;
-
- exit when updated1 and updated2 and updated3 and updated4;
-
- -- wait a little
- perform pg_sleep_for('100 milliseconds');
-
- -- reset stats snapshot so we can test again
- perform pg_stat_clear_snapshot();
-
- end loop;
-
- -- report time waited in postmaster log (where it won't change test output)
- raise log 'wait_for_stats delayed % seconds',
- extract(epoch from clock_timestamp() - start_time);
-end
-$$ language plpgsql;
-
-- test effects of TRUNCATE on n_live_tup/n_dead_tup counters
CREATE TABLE trunc_stats_test(id serial);
CREATE TABLE trunc_stats_test1(id serial, stuff text);
SELECT count(*) FROM tenk2 WHERE unique1 = 1;
RESET enable_bitmapscan;
--- We can't just call wait_for_stats() at this point, because we only
--- transmit stats when the session goes idle, and we probably didn't
--- transmit the last couple of counts yet thanks to the rate-limiting logic
--- in pgstat_report_stat(). But instead of waiting for the rate limiter's
--- timeout to elapse, let's just start a new session. The old one will
--- then send its stats before dying.
-\c -
-
--- wait for stats collector to update
-SELECT wait_for_stats();
+-- ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
-- check effects
BEGIN;
DROP TABLE trunc_stats_test, trunc_stats_test1, trunc_stats_test2, trunc_stats_test3, trunc_stats_test4;
DROP TABLE prevstats;
--- test BRIN index doesn't block HOT update - we include this test here, as it
--- relies on statistics collector and so it may occasionally fail, especially
--- on slower systems
-CREATE TABLE brin_hot (
- id integer PRIMARY KEY,
- val integer NOT NULL
-) WITH (autovacuum_enabled = off, fillfactor = 70);
-
-INSERT INTO brin_hot SELECT *, 0 FROM generate_series(1, 235);
-CREATE INDEX val_brin ON brin_hot using brin(val);
-
-CREATE FUNCTION wait_for_hot_stats() RETURNS void AS $$
-DECLARE
- start_time timestamptz := clock_timestamp();
- updated bool;
-BEGIN
- -- we don't want to wait forever; loop will exit after 30 seconds
- FOR i IN 1 .. 300 LOOP
- SELECT (pg_stat_get_tuples_hot_updated('brin_hot'::regclass::oid) > 0) INTO updated;
- EXIT WHEN updated;
-
- -- wait a little
- PERFORM pg_sleep_for('100 milliseconds');
- -- reset stats snapshot so we can test again
- PERFORM pg_stat_clear_snapshot();
- END LOOP;
- -- report time waited in postmaster log (where it won't change test output)
- RAISE log 'wait_for_hot_stats delayed % seconds',
- EXTRACT(epoch FROM clock_timestamp() - start_time);
-END
-$$ LANGUAGE plpgsql;
-
-UPDATE brin_hot SET val = -3 WHERE id = 42;
-
--- We can't just call wait_for_hot_stats() at this point, because we only
--- transmit stats when the session goes idle, and we probably didn't
--- transmit the last couple of counts yet thanks to the rate-limiting logic
--- in pgstat_report_stat(). But instead of waiting for the rate limiter's
--- timeout to elapse, let's just start a new session. The old one will
--- then send its stats before dying.
-\c -
-
-SELECT wait_for_hot_stats();
-SELECT pg_stat_get_tuples_hot_updated('brin_hot'::regclass::oid);
-
-DROP TABLE brin_hot;
-DROP FUNCTION wait_for_hot_stats();
-
-
-- ensure that stats accessors handle NULL input correctly
SELECT pg_stat_get_replication_slot(NULL);
SELECT pg_stat_get_subscription_stats(NULL);