1--
2-- Test Statistics Collector
3--
4-- Must be run after tenk2 has been created (by create_table),
5-- populated (by create_misc) and indexed (by create_index).
6--
7-- conditio sine qua non
8SHOW track_counts;  -- must be on
9 track_counts
10--------------
11 on
12(1 row)
13
14-- ensure that both seqscan and indexscan plans are allowed
15SET enable_seqscan TO on;
16SET enable_indexscan TO on;
17-- for the moment, we don't want index-only scans here
18SET enable_indexonlyscan TO off;
19-- save counters
20CREATE TABLE prevstats AS
21SELECT t.seq_scan, t.seq_tup_read, t.idx_scan, t.idx_tup_fetch,
22       (b.heap_blks_read + b.heap_blks_hit) AS heap_blks,
23       (b.idx_blks_read + b.idx_blks_hit) AS idx_blks,
24       pg_stat_get_snapshot_timestamp() as snap_ts
25  FROM pg_catalog.pg_stat_user_tables AS t,
26       pg_catalog.pg_statio_user_tables AS b
27 WHERE t.relname='tenk2' AND b.relname='tenk2';
28-- function to wait for counters to advance
29create function wait_for_stats() returns void as $$
30declare
31  start_time timestamptz := clock_timestamp();
32  updated1 bool;
33  updated2 bool;
34  updated3 bool;
35  updated4 bool;
36begin
37  -- we don't want to wait forever; loop will exit after 30 seconds
38  for i in 1 .. 300 loop
39
40    -- With parallel query, the seqscan and indexscan on tenk2 might be done
41    -- in parallel worker processes, which will send their stats counters
42    -- asynchronously to what our own session does.  So we must check for
43    -- those counts to be registered separately from the update counts.
44
45    -- check to see if seqscan has been sensed
46    SELECT (st.seq_scan >= pr.seq_scan + 1) INTO updated1
47      FROM pg_stat_user_tables AS st, pg_class AS cl, prevstats AS pr
48     WHERE st.relname='tenk2' AND cl.relname='tenk2';
49
50    -- check to see if indexscan has been sensed
51    SELECT (st.idx_scan >= pr.idx_scan + 1) INTO updated2
52      FROM pg_stat_user_tables AS st, pg_class AS cl, prevstats AS pr
53     WHERE st.relname='tenk2' AND cl.relname='tenk2';
54
55    -- check to see if all updates have been sensed
56    SELECT (n_tup_ins > 0) INTO updated3
57      FROM pg_stat_user_tables WHERE relname='trunc_stats_test4';
58
59    -- We must also check explicitly that pg_stat_get_snapshot_timestamp has
60    -- advanced, because that comes from the global stats file which might
61    -- be older than the per-DB stats file we got the other values from.
62    SELECT (pr.snap_ts < pg_stat_get_snapshot_timestamp()) INTO updated4
63      FROM prevstats AS pr;
64
65    exit when updated1 and updated2 and updated3 and updated4;
66
67    -- wait a little
68    perform pg_sleep_for('100 milliseconds');
69
70    -- reset stats snapshot so we can test again
71    perform pg_stat_clear_snapshot();
72
73  end loop;
74
75  -- report time waited in postmaster log (where it won't change test output)
76  raise log 'wait_for_stats delayed % seconds',
77    extract(epoch from clock_timestamp() - start_time);
78end
79$$ language plpgsql;
80-- test effects of TRUNCATE on n_live_tup/n_dead_tup counters
81CREATE TABLE trunc_stats_test(id serial);
82CREATE TABLE trunc_stats_test1(id serial, stuff text);
83CREATE TABLE trunc_stats_test2(id serial);
84CREATE TABLE trunc_stats_test3(id serial, stuff text);
85CREATE TABLE trunc_stats_test4(id serial);
86-- check that n_live_tup is reset to 0 after truncate
87INSERT INTO trunc_stats_test DEFAULT VALUES;
88INSERT INTO trunc_stats_test DEFAULT VALUES;
89INSERT INTO trunc_stats_test DEFAULT VALUES;
90TRUNCATE trunc_stats_test;
91-- test involving a truncate in a transaction; 4 ins but only 1 live
92INSERT INTO trunc_stats_test1 DEFAULT VALUES;
93INSERT INTO trunc_stats_test1 DEFAULT VALUES;
94INSERT INTO trunc_stats_test1 DEFAULT VALUES;
95UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2);
96DELETE FROM trunc_stats_test1 WHERE id = 3;
97BEGIN;
98UPDATE trunc_stats_test1 SET id = id + 100;
99TRUNCATE trunc_stats_test1;
100INSERT INTO trunc_stats_test1 DEFAULT VALUES;
101COMMIT;
102-- use a savepoint: 1 insert, 1 live
103BEGIN;
104INSERT INTO trunc_stats_test2 DEFAULT VALUES;
105INSERT INTO trunc_stats_test2 DEFAULT VALUES;
106SAVEPOINT p1;
107INSERT INTO trunc_stats_test2 DEFAULT VALUES;
108TRUNCATE trunc_stats_test2;
109INSERT INTO trunc_stats_test2 DEFAULT VALUES;
110RELEASE SAVEPOINT p1;
111COMMIT;
112-- rollback a savepoint: this should count 4 inserts and have 2
113-- live tuples after commit (and 2 dead ones due to aborted subxact)
114BEGIN;
115INSERT INTO trunc_stats_test3 DEFAULT VALUES;
116INSERT INTO trunc_stats_test3 DEFAULT VALUES;
117SAVEPOINT p1;
118INSERT INTO trunc_stats_test3 DEFAULT VALUES;
119INSERT INTO trunc_stats_test3 DEFAULT VALUES;
120TRUNCATE trunc_stats_test3;
121INSERT INTO trunc_stats_test3 DEFAULT VALUES;
122ROLLBACK TO SAVEPOINT p1;
123COMMIT;
124-- rollback a truncate: this should count 2 inserts and produce 2 dead tuples
125BEGIN;
126INSERT INTO trunc_stats_test4 DEFAULT VALUES;
127INSERT INTO trunc_stats_test4 DEFAULT VALUES;
128TRUNCATE trunc_stats_test4;
129INSERT INTO trunc_stats_test4 DEFAULT VALUES;
130ROLLBACK;
131-- do a seqscan
132SELECT count(*) FROM tenk2;
133 count
134-------
135 10000
136(1 row)
137
138-- do an indexscan
139-- make sure it is not a bitmap scan, which might skip fetching heap tuples
140SET enable_bitmapscan TO off;
141SELECT count(*) FROM tenk2 WHERE unique1 = 1;
142 count
143-------
144     1
145(1 row)
146
147RESET enable_bitmapscan;
148-- We can't just call wait_for_stats() at this point, because we only
149-- transmit stats when the session goes idle, and we probably didn't
150-- transmit the last couple of counts yet thanks to the rate-limiting logic
151-- in pgstat_report_stat().  But instead of waiting for the rate limiter's
152-- timeout to elapse, let's just start a new session.  The old one will
153-- then send its stats before dying.
154\c -
155-- wait for stats collector to update
156SELECT wait_for_stats();
157 wait_for_stats
158----------------
159
160(1 row)
161
162-- check effects
163SELECT relname, n_tup_ins, n_tup_upd, n_tup_del, n_live_tup, n_dead_tup
164  FROM pg_stat_user_tables
165 WHERE relname like 'trunc_stats_test%' order by relname;
166      relname      | n_tup_ins | n_tup_upd | n_tup_del | n_live_tup | n_dead_tup
167-------------------+-----------+-----------+-----------+------------+------------
168 trunc_stats_test  |         3 |         0 |         0 |          0 |          0
169 trunc_stats_test1 |         4 |         2 |         1 |          1 |          0
170 trunc_stats_test2 |         1 |         0 |         0 |          1 |          0
171 trunc_stats_test3 |         4 |         0 |         0 |          2 |          2
172 trunc_stats_test4 |         2 |         0 |         0 |          0 |          2
173(5 rows)
174
175SELECT st.seq_scan >= pr.seq_scan + 1,
176       st.seq_tup_read >= pr.seq_tup_read + cl.reltuples,
177       st.idx_scan >= pr.idx_scan + 1,
178       st.idx_tup_fetch >= pr.idx_tup_fetch + 1
179  FROM pg_stat_user_tables AS st, pg_class AS cl, prevstats AS pr
180 WHERE st.relname='tenk2' AND cl.relname='tenk2';
181 ?column? | ?column? | ?column? | ?column?
182----------+----------+----------+----------
183 t        | t        | t        | t
184(1 row)
185
186SELECT st.heap_blks_read + st.heap_blks_hit >= pr.heap_blks + cl.relpages,
187       st.idx_blks_read + st.idx_blks_hit >= pr.idx_blks + 1
188  FROM pg_statio_user_tables AS st, pg_class AS cl, prevstats AS pr
189 WHERE st.relname='tenk2' AND cl.relname='tenk2';
190 ?column? | ?column?
191----------+----------
192 t        | t
193(1 row)
194
195SELECT pr.snap_ts < pg_stat_get_snapshot_timestamp() as snapshot_newer
196FROM prevstats AS pr;
197 snapshot_newer
198----------------
199 t
200(1 row)
201
202DROP TABLE trunc_stats_test, trunc_stats_test1, trunc_stats_test2, trunc_stats_test3, trunc_stats_test4;
203DROP TABLE prevstats;
204-- End of Stats Test
205