Age Owner Branch data TLA Line data Source code
1 : : /* -------------------------------------------------------------------------
2 : : *
3 : : * pgstat_database.c
4 : : * Implementation of database statistics.
5 : : *
6 : : * This file contains the implementation of database statistics. It is kept
7 : : * separate from pgstat.c to enforce the line between the statistics access /
8 : : * storage implementation and the details about individual types of
9 : : * statistics.
10 : : *
11 : : * Copyright (c) 2001-2025, PostgreSQL Global Development Group
12 : : *
13 : : * IDENTIFICATION
14 : : * src/backend/utils/activity/pgstat_database.c
15 : : * -------------------------------------------------------------------------
16 : : */
17 : :
18 : : #include "postgres.h"
19 : :
20 : : #include "storage/procsignal.h"
21 : : #include "utils/pgstat_internal.h"
22 : : #include "utils/timestamp.h"
23 : :
24 : :
25 : : static bool pgstat_should_report_connstat(void);
26 : :
27 : :
28 : : PgStat_Counter pgStatBlockReadTime = 0;
29 : : PgStat_Counter pgStatBlockWriteTime = 0;
30 : : PgStat_Counter pgStatActiveTime = 0;
31 : : PgStat_Counter pgStatTransactionIdleTime = 0;
32 : : SessionEndType pgStatSessionEndCause = DISCONNECT_NORMAL;
33 : :
34 : :
35 : : static int pgStatXactCommit = 0;
36 : : static int pgStatXactRollback = 0;
37 : : static PgStat_Counter pgLastSessionReportTime = 0;
38 : :
39 : :
40 : : /*
41 : : * Remove entry for the database being dropped.
42 : : */
43 : : void
1265 andres@anarazel.de 44 :CBC 44 : pgstat_drop_database(Oid databaseid)
45 : : {
1249 46 : 44 : pgstat_drop_transactional(PGSTAT_KIND_DATABASE, databaseid, InvalidOid);
1265 47 : 44 : }
48 : :
49 : : /*
50 : : * Called from autovacuum.c to report startup of an autovacuum process.
51 : : * We are called before InitPostgres is done, so can't rely on MyDatabaseId;
52 : : * the db OID must be passed in, instead.
53 : : */
54 : : void
1249 55 : 33 : pgstat_report_autovac(Oid dboid)
56 : : {
57 : : PgStat_EntryRef *entry_ref;
58 : : PgStatShared_Database *dbentry;
59 : :
60 : : /* can't get here in single user mode */
61 [ - + ]: 33 : Assert(IsUnderPostmaster);
62 : :
63 : : /*
64 : : * End-of-vacuum is reported instantly. Report the start the same way for
65 : : * consistency. Vacuum doesn't run frequently and is a long-lasting
66 : : * operation so it doesn't matter if we get blocked here a little.
67 : : */
68 : 33 : entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE,
69 : : dboid, InvalidOid, false);
70 : :
71 : 33 : dbentry = (PgStatShared_Database *) entry_ref->shared_stats;
72 : 33 : dbentry->stats.last_autovac_time = GetCurrentTimestamp();
73 : :
74 : 33 : pgstat_unlock_entry(entry_ref);
75 : 33 : }
76 : :
77 : : /*
78 : : * Report a Hot Standby recovery conflict.
79 : : */
80 : : void
1265 81 : 6 : pgstat_report_recovery_conflict(int reason)
82 : : {
83 : : PgStat_StatDBEntry *dbentry;
84 : :
1249 85 [ - + ]: 6 : Assert(IsUnderPostmaster);
86 [ - + ]: 6 : if (!pgstat_track_counts)
1265 andres@anarazel.de 87 :UBC 0 : return;
88 : :
1249 andres@anarazel.de 89 :CBC 6 : dbentry = pgstat_prep_database_pending(MyDatabaseId);
90 : :
91 [ + + + + : 6 : switch (reason)
+ - + - ]
92 : : {
93 : 1 : case PROCSIG_RECOVERY_CONFLICT_DATABASE:
94 : :
95 : : /*
96 : : * Since we drop the information about the database as soon as it
97 : : * replicates, there is no point in counting these conflicts.
98 : : */
99 : 1 : break;
100 : 1 : case PROCSIG_RECOVERY_CONFLICT_TABLESPACE:
1004 michael@paquier.xyz 101 : 1 : dbentry->conflict_tablespace++;
1249 andres@anarazel.de 102 : 1 : break;
103 : 1 : case PROCSIG_RECOVERY_CONFLICT_LOCK:
1004 michael@paquier.xyz 104 : 1 : dbentry->conflict_lock++;
1249 andres@anarazel.de 105 : 1 : break;
106 : 1 : case PROCSIG_RECOVERY_CONFLICT_SNAPSHOT:
1004 michael@paquier.xyz 107 : 1 : dbentry->conflict_snapshot++;
1249 andres@anarazel.de 108 : 1 : break;
109 : 1 : case PROCSIG_RECOVERY_CONFLICT_BUFFERPIN:
1004 michael@paquier.xyz 110 : 1 : dbentry->conflict_bufferpin++;
1249 andres@anarazel.de 111 : 1 : break;
883 andres@anarazel.de 112 :UBC 0 : case PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT:
113 : 0 : dbentry->conflict_logicalslot++;
114 : 0 : break;
1249 andres@anarazel.de 115 :CBC 1 : case PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK:
1004 michael@paquier.xyz 116 : 1 : dbentry->conflict_startup_deadlock++;
1249 andres@anarazel.de 117 : 1 : break;
118 : : }
119 : : }
120 : :
121 : : /*
122 : : * Report a detected deadlock.
123 : : */
124 : : void
1265 125 : 6 : pgstat_report_deadlock(void)
126 : : {
127 : : PgStat_StatDBEntry *dbent;
128 : :
1249 129 [ - + ]: 6 : if (!pgstat_track_counts)
1265 andres@anarazel.de 130 :UBC 0 : return;
131 : :
1249 andres@anarazel.de 132 :CBC 6 : dbent = pgstat_prep_database_pending(MyDatabaseId);
1004 michael@paquier.xyz 133 : 6 : dbent->deadlocks++;
134 : : }
135 : :
136 : : /*
137 : : * Allow this backend to later report checksum failures for dboid, even if in
138 : : * a critical section at the time of the report.
139 : : *
140 : : * Without this function having been called first, the backend might need to
141 : : * allocate an EntryRef or might need to map in DSM segments. Neither should
142 : : * happen in a critical section.
143 : : */
144 : : void
160 andres@anarazel.de 145 : 1247695 : pgstat_prepare_report_checksum_failure(Oid dboid)
146 : : {
147 [ - + ]: 1247695 : Assert(!CritSectionCount);
148 : :
149 : : /*
150 : : * Just need to ensure this backend has an entry ref for the database.
151 : : * That will allows us to report checksum failures without e.g. needing to
152 : : * map in DSM segments.
153 : : */
154 : 1247695 : pgstat_get_entry_ref(PGSTAT_KIND_DATABASE, dboid, InvalidOid,
155 : : true, NULL);
156 : 1247695 : }
157 : :
158 : : /*
159 : : * Report one or more checksum failures.
160 : : *
161 : : * To be allowed to report checksum failures in critical sections, we require
162 : : * pgstat_prepare_report_checksum_failure() to have been called before this
163 : : * function is called.
164 : : */
165 : : void
1265 166 : 41 : pgstat_report_checksum_failures_in_db(Oid dboid, int failurecount)
167 : : {
168 : : PgStat_EntryRef *entry_ref;
169 : : PgStatShared_Database *sharedent;
170 : :
1249 171 [ - + ]: 41 : if (!pgstat_track_counts)
1265 andres@anarazel.de 172 :UBC 0 : return;
173 : :
174 : : /*
175 : : * Update the shared stats directly - checksum failures should never be
176 : : * common enough for that to be a problem. Note that we pass create=false
177 : : * here, as we want to be sure to not require memory allocations, so this
178 : : * can be called in critical sections.
179 : : */
160 andres@anarazel.de 180 :CBC 41 : entry_ref = pgstat_get_entry_ref(PGSTAT_KIND_DATABASE, dboid, InvalidOid,
181 : : false, NULL);
182 : :
183 : : /*
184 : : * Should always have been created by
185 : : * pgstat_prepare_report_checksum_failure().
186 : : *
187 : : * When not using assertions, we don't want to crash should something have
188 : : * gone wrong, so just return.
189 : : */
190 [ - + ]: 41 : Assert(entry_ref);
191 [ - + ]: 41 : if (!entry_ref)
192 : : {
160 andres@anarazel.de 193 [ # # ]:UBC 0 : elog(WARNING, "could not report %d conflicts for DB %u",
194 : : failurecount, dboid);
195 : 0 : return;
196 : : }
197 : :
153 tgl@sss.pgh.pa.us 198 :CBC 41 : (void) pgstat_lock_entry(entry_ref, false);
199 : :
1249 andres@anarazel.de 200 : 41 : sharedent = (PgStatShared_Database *) entry_ref->shared_stats;
1004 michael@paquier.xyz 201 : 41 : sharedent->stats.checksum_failures += failurecount;
1249 andres@anarazel.de 202 : 41 : sharedent->stats.last_checksum_failure = GetCurrentTimestamp();
203 : :
204 : 41 : pgstat_unlock_entry(entry_ref);
205 : : }
206 : :
207 : : /*
208 : : * Report creation of temporary file.
209 : : */
210 : : void
1265 211 : 2533 : pgstat_report_tempfile(size_t filesize)
212 : : {
213 : : PgStat_StatDBEntry *dbent;
214 : :
1249 215 [ - + ]: 2533 : if (!pgstat_track_counts)
1265 andres@anarazel.de 216 :UBC 0 : return;
217 : :
1249 andres@anarazel.de 218 :CBC 2533 : dbent = pgstat_prep_database_pending(MyDatabaseId);
1004 michael@paquier.xyz 219 : 2533 : dbent->temp_bytes += filesize;
220 : 2533 : dbent->temp_files++;
221 : : }
222 : :
223 : : /*
224 : : * Notify stats system of a new connection.
225 : : */
226 : : void
1265 andres@anarazel.de 227 : 11905 : pgstat_report_connect(Oid dboid)
228 : : {
229 : : PgStat_StatDBEntry *dbentry;
230 : :
231 [ + + ]: 11905 : if (!pgstat_should_report_connstat())
232 : 1167 : return;
233 : :
234 : 10738 : pgLastSessionReportTime = MyStartTimestamp;
235 : :
1249 236 : 10738 : dbentry = pgstat_prep_database_pending(MyDatabaseId);
1004 michael@paquier.xyz 237 : 10738 : dbentry->sessions++;
238 : : }
239 : :
240 : : /*
241 : : * Notify the stats system of a disconnect.
242 : : */
243 : : void
1265 andres@anarazel.de 244 : 13454 : pgstat_report_disconnect(Oid dboid)
245 : : {
246 : : PgStat_StatDBEntry *dbentry;
247 : :
248 [ + + ]: 13454 : if (!pgstat_should_report_connstat())
249 : 2716 : return;
250 : :
1249 251 : 10738 : dbentry = pgstat_prep_database_pending(MyDatabaseId);
252 : :
253 [ + + + + : 10738 : switch (pgStatSessionEndCause)
- ]
254 : : {
255 : 10679 : case DISCONNECT_NOT_YET:
256 : : case DISCONNECT_NORMAL:
257 : : /* we don't collect these */
258 : 10679 : break;
259 : 33 : case DISCONNECT_CLIENT_EOF:
1004 michael@paquier.xyz 260 : 33 : dbentry->sessions_abandoned++;
1249 andres@anarazel.de 261 : 33 : break;
262 : 11 : case DISCONNECT_FATAL:
1004 michael@paquier.xyz 263 : 11 : dbentry->sessions_fatal++;
1249 andres@anarazel.de 264 : 11 : break;
265 : 15 : case DISCONNECT_KILLED:
1004 michael@paquier.xyz 266 : 15 : dbentry->sessions_killed++;
1249 andres@anarazel.de 267 : 15 : break;
268 : : }
269 : : }
270 : :
271 : : /*
272 : : * Support function for the SQL-callable pgstat* functions. Returns
273 : : * the collected statistics for one database or NULL. NULL doesn't mean
274 : : * that the database doesn't exist, just that there are no statistics, so the
275 : : * caller is better off to report ZERO instead.
276 : : */
277 : : PgStat_StatDBEntry *
278 : 1669 : pgstat_fetch_stat_dbentry(Oid dboid)
279 : : {
280 : 1669 : return (PgStat_StatDBEntry *)
281 : 1669 : pgstat_fetch_entry(PGSTAT_KIND_DATABASE, dboid, InvalidOid);
282 : : }
283 : :
284 : : void
1265 285 : 318630 : AtEOXact_PgStat_Database(bool isCommit, bool parallel)
286 : : {
287 : : /* Don't count parallel worker transaction stats */
288 [ + + ]: 318630 : if (!parallel)
289 : : {
290 : : /*
291 : : * Count transaction commit or abort. (We use counters, not just
292 : : * bools, in case the reporting message isn't sent right away.)
293 : : */
294 [ + + ]: 317252 : if (isCommit)
295 : 292263 : pgStatXactCommit++;
296 : : else
297 : 24989 : pgStatXactRollback++;
298 : : }
299 : 318630 : }
300 : :
301 : : /*
302 : : * Notify the stats system about parallel worker information.
303 : : */
304 : : void
299 michael@paquier.xyz 305 : 342 : pgstat_update_parallel_workers_stats(PgStat_Counter workers_to_launch,
306 : : PgStat_Counter workers_launched)
307 : : {
308 : : PgStat_StatDBEntry *dbentry;
309 : :
310 [ - + ]: 342 : if (!OidIsValid(MyDatabaseId))
299 michael@paquier.xyz 311 :UBC 0 : return;
312 : :
299 michael@paquier.xyz 313 :CBC 342 : dbentry = pgstat_prep_database_pending(MyDatabaseId);
314 : 342 : dbentry->parallel_workers_to_launch += workers_to_launch;
315 : 342 : dbentry->parallel_workers_launched += workers_launched;
316 : : }
317 : :
318 : : /*
319 : : * Subroutine for pgstat_report_stat(): Handle xact commit/rollback and I/O
320 : : * timings.
321 : : */
322 : : void
1249 andres@anarazel.de 323 : 31854 : pgstat_update_dbstats(TimestampTz ts)
324 : : {
325 : : PgStat_StatDBEntry *dbentry;
326 : :
327 : : /*
328 : : * If not connected to a database yet, don't attribute time to "shared
329 : : * state" (InvalidOid is used to track stats for shared relations, etc.).
330 : : */
733 michael@paquier.xyz 331 [ + + ]: 31854 : if (!OidIsValid(MyDatabaseId))
332 : 3828 : return;
333 : :
1249 andres@anarazel.de 334 : 28026 : dbentry = pgstat_prep_database_pending(MyDatabaseId);
335 : :
336 : : /*
337 : : * Accumulate xact commit/rollback and I/O timings to stats entry of the
338 : : * current database.
339 : : */
1004 michael@paquier.xyz 340 : 28026 : dbentry->xact_commit += pgStatXactCommit;
341 : 28026 : dbentry->xact_rollback += pgStatXactRollback;
342 : 28026 : dbentry->blk_read_time += pgStatBlockReadTime;
343 : 28026 : dbentry->blk_write_time += pgStatBlockWriteTime;
344 : :
1249 andres@anarazel.de 345 [ + + ]: 28026 : if (pgstat_should_report_connstat())
346 : : {
347 : : long secs;
348 : : int usecs;
349 : :
350 : : /*
351 : : * pgLastSessionReportTime is initialized to MyStartTimestamp by
352 : : * pgstat_report_connect().
353 : : */
354 : 23301 : TimestampDifference(pgLastSessionReportTime, ts, &secs, &usecs);
355 : 23301 : pgLastSessionReportTime = ts;
1004 michael@paquier.xyz 356 : 23301 : dbentry->session_time += (PgStat_Counter) secs * 1000000 + usecs;
357 : 23301 : dbentry->active_time += pgStatActiveTime;
358 : 23301 : dbentry->idle_in_transaction_time += pgStatTransactionIdleTime;
359 : : }
360 : :
1249 andres@anarazel.de 361 : 28026 : pgStatXactCommit = 0;
362 : 28026 : pgStatXactRollback = 0;
363 : 28026 : pgStatBlockReadTime = 0;
364 : 28026 : pgStatBlockWriteTime = 0;
365 : 28026 : pgStatActiveTime = 0;
366 : 28026 : pgStatTransactionIdleTime = 0;
367 : : }
368 : :
369 : : /*
370 : : * We report session statistics only for normal backend processes. Parallel
371 : : * workers run in parallel, so they don't contribute to session times, even
372 : : * though they use CPU time. Walsender processes could be considered here,
373 : : * but they have different session characteristics from normal backends (for
374 : : * example, they are always "active"), so they would skew session statistics.
375 : : */
376 : : static bool
1265 377 : 53385 : pgstat_should_report_connstat(void)
378 : : {
379 : 53385 : return MyBackendType == B_BACKEND;
380 : : }
381 : :
382 : : /*
383 : : * Find or create a local PgStat_StatDBEntry entry for dboid.
384 : : */
385 : : PgStat_StatDBEntry *
1249 386 : 823825 : pgstat_prep_database_pending(Oid dboid)
387 : : {
388 : : PgStat_EntryRef *entry_ref;
389 : :
390 : : /*
391 : : * This should not report stats on database objects before having
392 : : * connected to a database.
393 : : */
733 michael@paquier.xyz 394 [ + + - + ]: 823825 : Assert(!OidIsValid(dboid) || OidIsValid(MyDatabaseId));
395 : :
1249 andres@anarazel.de 396 : 823825 : entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_DATABASE, dboid, InvalidOid,
397 : : NULL);
398 : :
399 : 823825 : return entry_ref->pending;
400 : : }
401 : :
402 : : /*
403 : : * Reset the database's reset timestamp, without resetting the contents of the
404 : : * database stats.
405 : : */
406 : : void
407 : 8 : pgstat_reset_database_timestamp(Oid dboid, TimestampTz ts)
408 : : {
409 : : PgStat_EntryRef *dbref;
410 : : PgStatShared_Database *dbentry;
411 : :
412 : 8 : dbref = pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE, MyDatabaseId, InvalidOid,
413 : : false);
414 : :
415 : 8 : dbentry = (PgStatShared_Database *) dbref->shared_stats;
416 : 8 : dbentry->stats.stat_reset_timestamp = ts;
417 : :
418 : 8 : pgstat_unlock_entry(dbref);
419 : 8 : }
420 : :
421 : : /*
422 : : * Flush out pending stats for the entry
423 : : *
424 : : * If nowait is true and the lock could not be immediately acquired, returns
425 : : * false without flushing the entry. Otherwise returns true.
426 : : */
427 : : bool
428 : 51634 : pgstat_database_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
429 : : {
430 : : PgStatShared_Database *sharedent;
431 : : PgStat_StatDBEntry *pendingent;
432 : :
433 : 51634 : pendingent = (PgStat_StatDBEntry *) entry_ref->pending;
434 : 51634 : sharedent = (PgStatShared_Database *) entry_ref->shared_stats;
435 : :
436 [ + + ]: 51634 : if (!pgstat_lock_entry(entry_ref, nowait))
1249 andres@anarazel.de 437 :GBC 1 : return false;
438 : :
439 : : #define PGSTAT_ACCUM_DBCOUNT(item) \
440 : : (sharedent)->stats.item += (pendingent)->item
441 : :
1004 michael@paquier.xyz 442 :CBC 51633 : PGSTAT_ACCUM_DBCOUNT(xact_commit);
443 : 51633 : PGSTAT_ACCUM_DBCOUNT(xact_rollback);
444 : 51633 : PGSTAT_ACCUM_DBCOUNT(blocks_fetched);
445 : 51633 : PGSTAT_ACCUM_DBCOUNT(blocks_hit);
446 : :
447 : 51633 : PGSTAT_ACCUM_DBCOUNT(tuples_returned);
448 : 51633 : PGSTAT_ACCUM_DBCOUNT(tuples_fetched);
449 : 51633 : PGSTAT_ACCUM_DBCOUNT(tuples_inserted);
450 : 51633 : PGSTAT_ACCUM_DBCOUNT(tuples_updated);
451 : 51633 : PGSTAT_ACCUM_DBCOUNT(tuples_deleted);
452 : :
453 : : /* last_autovac_time is reported immediately */
1249 andres@anarazel.de 454 [ - + ]: 51633 : Assert(pendingent->last_autovac_time == 0);
455 : :
1004 michael@paquier.xyz 456 : 51633 : PGSTAT_ACCUM_DBCOUNT(conflict_tablespace);
457 : 51633 : PGSTAT_ACCUM_DBCOUNT(conflict_lock);
458 : 51633 : PGSTAT_ACCUM_DBCOUNT(conflict_snapshot);
883 andres@anarazel.de 459 : 51633 : PGSTAT_ACCUM_DBCOUNT(conflict_logicalslot);
1004 michael@paquier.xyz 460 : 51633 : PGSTAT_ACCUM_DBCOUNT(conflict_bufferpin);
461 : 51633 : PGSTAT_ACCUM_DBCOUNT(conflict_startup_deadlock);
462 : :
463 : 51633 : PGSTAT_ACCUM_DBCOUNT(temp_bytes);
464 : 51633 : PGSTAT_ACCUM_DBCOUNT(temp_files);
465 : 51633 : PGSTAT_ACCUM_DBCOUNT(deadlocks);
466 : :
467 : : /* checksum failures are reported immediately */
468 [ - + ]: 51633 : Assert(pendingent->checksum_failures == 0);
1249 andres@anarazel.de 469 [ - + ]: 51633 : Assert(pendingent->last_checksum_failure == 0);
470 : :
1004 michael@paquier.xyz 471 : 51633 : PGSTAT_ACCUM_DBCOUNT(blk_read_time);
472 : 51633 : PGSTAT_ACCUM_DBCOUNT(blk_write_time);
473 : :
474 : 51633 : PGSTAT_ACCUM_DBCOUNT(sessions);
475 : 51633 : PGSTAT_ACCUM_DBCOUNT(session_time);
476 : 51633 : PGSTAT_ACCUM_DBCOUNT(active_time);
477 : 51633 : PGSTAT_ACCUM_DBCOUNT(idle_in_transaction_time);
478 : 51633 : PGSTAT_ACCUM_DBCOUNT(sessions_abandoned);
479 : 51633 : PGSTAT_ACCUM_DBCOUNT(sessions_fatal);
480 : 51633 : PGSTAT_ACCUM_DBCOUNT(sessions_killed);
299 481 : 51633 : PGSTAT_ACCUM_DBCOUNT(parallel_workers_to_launch);
482 : 51633 : PGSTAT_ACCUM_DBCOUNT(parallel_workers_launched);
483 : : #undef PGSTAT_ACCUM_DBCOUNT
484 : :
1249 andres@anarazel.de 485 : 51633 : pgstat_unlock_entry(entry_ref);
486 : :
487 : 51633 : memset(pendingent, 0, sizeof(*pendingent));
488 : :
489 : 51633 : return true;
490 : : }
491 : :
492 : : void
493 : 7 : pgstat_database_reset_timestamp_cb(PgStatShared_Common *header, TimestampTz ts)
494 : : {
495 : 7 : ((PgStatShared_Database *) header)->stats.stat_reset_timestamp = ts;
496 : 7 : }
|