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-2026, 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/standby.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
44 51 : pgstat_drop_database(Oid databaseid)
45 : {
46 51 : pgstat_drop_transactional(PGSTAT_KIND_DATABASE, databaseid, InvalidOid);
47 51 : }
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
55 1774 : 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 : 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 1774 : entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE,
69 : dboid, InvalidOid, false);
70 :
71 1774 : dbentry = (PgStatShared_Database *) entry_ref->shared_stats;
72 1774 : dbentry->stats.last_autovac_time = GetCurrentTimestamp();
73 :
74 1774 : pgstat_unlock_entry(entry_ref);
75 1774 : }
76 :
77 : /*
78 : * Report a Hot Standby recovery conflict.
79 : */
80 : void
81 12 : pgstat_report_recovery_conflict(int reason)
82 : {
83 : PgStat_StatDBEntry *dbentry;
84 :
85 : Assert(IsUnderPostmaster);
86 12 : if (!pgstat_track_counts)
87 0 : return;
88 :
89 12 : dbentry = pgstat_prep_database_pending(MyDatabaseId);
90 :
91 12 : switch ((RecoveryConflictReason) reason)
92 : {
93 2 : case 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 2 : break;
100 1 : case RECOVERY_CONFLICT_TABLESPACE:
101 1 : dbentry->conflict_tablespace++;
102 1 : break;
103 1 : case RECOVERY_CONFLICT_LOCK:
104 1 : dbentry->conflict_lock++;
105 1 : break;
106 1 : case RECOVERY_CONFLICT_SNAPSHOT:
107 1 : dbentry->conflict_snapshot++;
108 1 : break;
109 1 : case RECOVERY_CONFLICT_BUFFERPIN:
110 1 : dbentry->conflict_bufferpin++;
111 1 : break;
112 5 : case RECOVERY_CONFLICT_LOGICALSLOT:
113 5 : dbentry->conflict_logicalslot++;
114 5 : break;
115 0 : case RECOVERY_CONFLICT_STARTUP_DEADLOCK:
116 0 : dbentry->conflict_startup_deadlock++;
117 0 : break;
118 1 : case RECOVERY_CONFLICT_BUFFERPIN_DEADLOCK:
119 :
120 : /*
121 : * The difference between RECOVERY_CONFLICT_STARTUP_DEADLOCK and
122 : * RECOVERY_CONFLICT_BUFFERPIN_DEADLOCK is merely whether a buffer
123 : * pin was part of the deadlock. We use the same counter for both
124 : * reasons.
125 : */
126 1 : dbentry->conflict_startup_deadlock++;
127 1 : break;
128 : }
129 : }
130 :
131 : /*
132 : * Report a detected deadlock.
133 : */
134 : void
135 6 : pgstat_report_deadlock(void)
136 : {
137 : PgStat_StatDBEntry *dbent;
138 :
139 6 : if (!pgstat_track_counts)
140 0 : return;
141 :
142 6 : dbent = pgstat_prep_database_pending(MyDatabaseId);
143 6 : dbent->deadlocks++;
144 : }
145 :
146 : /*
147 : * Allow this backend to later report checksum failures for dboid, even if in
148 : * a critical section at the time of the report.
149 : *
150 : * Without this function having been called first, the backend might need to
151 : * allocate an EntryRef or might need to map in DSM segments. Neither should
152 : * happen in a critical section.
153 : */
154 : void
155 1320114 : pgstat_prepare_report_checksum_failure(Oid dboid)
156 : {
157 : Assert(!CritSectionCount);
158 :
159 : /*
160 : * Just need to ensure this backend has an entry ref for the database.
161 : * That will allows us to report checksum failures without e.g. needing to
162 : * map in DSM segments.
163 : */
164 1320114 : pgstat_get_entry_ref(PGSTAT_KIND_DATABASE, dboid, InvalidOid,
165 : true, NULL);
166 1320114 : }
167 :
168 : /*
169 : * Report one or more checksum failures.
170 : *
171 : * To be allowed to report checksum failures in critical sections, we require
172 : * pgstat_prepare_report_checksum_failure() to have been called before this
173 : * function is called.
174 : */
175 : void
176 28 : pgstat_report_checksum_failures_in_db(Oid dboid, int failurecount)
177 : {
178 : PgStat_EntryRef *entry_ref;
179 : PgStatShared_Database *sharedent;
180 :
181 28 : if (!pgstat_track_counts)
182 0 : return;
183 :
184 : /*
185 : * Update the shared stats directly - checksum failures should never be
186 : * common enough for that to be a problem. Note that we pass create=false
187 : * here, as we want to be sure to not require memory allocations, so this
188 : * can be called in critical sections.
189 : */
190 28 : entry_ref = pgstat_get_entry_ref(PGSTAT_KIND_DATABASE, dboid, InvalidOid,
191 : false, NULL);
192 :
193 : /*
194 : * Should always have been created by
195 : * pgstat_prepare_report_checksum_failure().
196 : *
197 : * When not using assertions, we don't want to crash should something have
198 : * gone wrong, so just return.
199 : */
200 : Assert(entry_ref);
201 28 : if (!entry_ref)
202 : {
203 0 : elog(WARNING, "could not report %d checksum failures for database %u",
204 : failurecount, dboid);
205 0 : return;
206 : }
207 :
208 28 : (void) pgstat_lock_entry(entry_ref, false);
209 :
210 28 : sharedent = (PgStatShared_Database *) entry_ref->shared_stats;
211 28 : sharedent->stats.checksum_failures += failurecount;
212 28 : sharedent->stats.last_checksum_failure = GetCurrentTimestamp();
213 :
214 28 : pgstat_unlock_entry(entry_ref);
215 : }
216 :
217 : /*
218 : * Report creation of temporary file.
219 : */
220 : void
221 2979 : pgstat_report_tempfile(size_t filesize)
222 : {
223 : PgStat_StatDBEntry *dbent;
224 :
225 2979 : if (!pgstat_track_counts)
226 0 : return;
227 :
228 2979 : dbent = pgstat_prep_database_pending(MyDatabaseId);
229 2979 : dbent->temp_bytes += filesize;
230 2979 : dbent->temp_files++;
231 : }
232 :
233 : /*
234 : * Notify stats system of a new connection.
235 : */
236 : void
237 13960 : pgstat_report_connect(Oid dboid)
238 : {
239 : PgStat_StatDBEntry *dbentry;
240 :
241 13960 : if (!pgstat_should_report_connstat())
242 1299 : return;
243 :
244 12661 : pgLastSessionReportTime = MyStartTimestamp;
245 :
246 12661 : dbentry = pgstat_prep_database_pending(MyDatabaseId);
247 12661 : dbentry->sessions++;
248 : }
249 :
250 : /*
251 : * Notify the stats system of a disconnect.
252 : */
253 : void
254 17361 : pgstat_report_disconnect(Oid dboid)
255 : {
256 : PgStat_StatDBEntry *dbentry;
257 :
258 17361 : if (!pgstat_should_report_connstat())
259 4700 : return;
260 :
261 12661 : dbentry = pgstat_prep_database_pending(MyDatabaseId);
262 :
263 12661 : switch (pgStatSessionEndCause)
264 : {
265 12598 : case DISCONNECT_NOT_YET:
266 : case DISCONNECT_NORMAL:
267 : /* we don't collect these */
268 12598 : break;
269 37 : case DISCONNECT_CLIENT_EOF:
270 37 : dbentry->sessions_abandoned++;
271 37 : break;
272 12 : case DISCONNECT_FATAL:
273 12 : dbentry->sessions_fatal++;
274 12 : break;
275 14 : case DISCONNECT_KILLED:
276 14 : dbentry->sessions_killed++;
277 14 : break;
278 : }
279 : }
280 :
281 : /*
282 : * Support function for the SQL-callable pgstat* functions. Returns
283 : * the collected statistics for one database or NULL. NULL doesn't mean
284 : * that the database doesn't exist, just that there are no statistics, so the
285 : * caller is better off to report ZERO instead.
286 : */
287 : PgStat_StatDBEntry *
288 5122 : pgstat_fetch_stat_dbentry(Oid dboid)
289 : {
290 5122 : return (PgStat_StatDBEntry *)
291 5122 : pgstat_fetch_entry(PGSTAT_KIND_DATABASE, dboid, InvalidOid);
292 : }
293 :
294 : void
295 562159 : AtEOXact_PgStat_Database(bool isCommit, bool parallel)
296 : {
297 : /* Don't count parallel worker transaction stats */
298 562159 : if (!parallel)
299 : {
300 : /*
301 : * Count transaction commit or abort. (We use counters, not just
302 : * bools, in case the reporting message isn't sent right away.)
303 : */
304 560677 : if (isCommit)
305 534185 : pgStatXactCommit++;
306 : else
307 26492 : pgStatXactRollback++;
308 : }
309 562159 : }
310 :
311 : /*
312 : * Notify the stats system about parallel worker information.
313 : */
314 : void
315 365 : pgstat_update_parallel_workers_stats(PgStat_Counter workers_to_launch,
316 : PgStat_Counter workers_launched)
317 : {
318 : PgStat_StatDBEntry *dbentry;
319 :
320 365 : if (!OidIsValid(MyDatabaseId))
321 0 : return;
322 :
323 365 : dbentry = pgstat_prep_database_pending(MyDatabaseId);
324 365 : dbentry->parallel_workers_to_launch += workers_to_launch;
325 365 : dbentry->parallel_workers_launched += workers_launched;
326 : }
327 :
328 : /*
329 : * Subroutine for pgstat_report_stat(): Handle xact commit/rollback and I/O
330 : * timings.
331 : */
332 : void
333 38588 : pgstat_update_dbstats(TimestampTz ts)
334 : {
335 : PgStat_StatDBEntry *dbentry;
336 :
337 : /*
338 : * If not connected to a database yet, don't attribute time to "shared
339 : * state" (InvalidOid is used to track stats for shared relations, etc.).
340 : */
341 38588 : if (!OidIsValid(MyDatabaseId))
342 4239 : return;
343 :
344 34349 : dbentry = pgstat_prep_database_pending(MyDatabaseId);
345 :
346 : /*
347 : * Accumulate xact commit/rollback and I/O timings to stats entry of the
348 : * current database.
349 : */
350 34349 : dbentry->xact_commit += pgStatXactCommit;
351 34349 : dbentry->xact_rollback += pgStatXactRollback;
352 34349 : dbentry->blk_read_time += pgStatBlockReadTime;
353 34349 : dbentry->blk_write_time += pgStatBlockWriteTime;
354 :
355 34349 : if (pgstat_should_report_connstat())
356 : {
357 : long secs;
358 : int usecs;
359 :
360 : /*
361 : * pgLastSessionReportTime is initialized to MyStartTimestamp by
362 : * pgstat_report_connect().
363 : */
364 27313 : TimestampDifference(pgLastSessionReportTime, ts, &secs, &usecs);
365 27313 : pgLastSessionReportTime = ts;
366 27313 : dbentry->session_time += (PgStat_Counter) secs * 1000000 + usecs;
367 27313 : dbentry->active_time += pgStatActiveTime;
368 27313 : dbentry->idle_in_transaction_time += pgStatTransactionIdleTime;
369 : }
370 :
371 34349 : pgStatXactCommit = 0;
372 34349 : pgStatXactRollback = 0;
373 34349 : pgStatBlockReadTime = 0;
374 34349 : pgStatBlockWriteTime = 0;
375 34349 : pgStatActiveTime = 0;
376 34349 : pgStatTransactionIdleTime = 0;
377 : }
378 :
379 : /*
380 : * We report session statistics only for normal backend processes. Parallel
381 : * workers run in parallel, so they don't contribute to session times, even
382 : * though they use CPU time. Walsender processes could be considered here,
383 : * but they have different session characteristics from normal backends (for
384 : * example, they are always "active"), so they would skew session statistics.
385 : */
386 : static bool
387 65670 : pgstat_should_report_connstat(void)
388 : {
389 65670 : return MyBackendType == B_BACKEND;
390 : }
391 :
392 : /*
393 : * Find or create a local PgStat_StatDBEntry entry for dboid.
394 : */
395 : PgStat_StatDBEntry *
396 1097503 : pgstat_prep_database_pending(Oid dboid)
397 : {
398 : PgStat_EntryRef *entry_ref;
399 :
400 : /*
401 : * This should not report stats on database objects before having
402 : * connected to a database.
403 : */
404 : Assert(!OidIsValid(dboid) || OidIsValid(MyDatabaseId));
405 :
406 1097503 : entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_DATABASE, dboid, InvalidOid,
407 : NULL);
408 :
409 1097503 : return entry_ref->pending;
410 : }
411 :
412 : /*
413 : * Reset the database's reset timestamp, without resetting the contents of the
414 : * database stats.
415 : */
416 : void
417 11 : pgstat_reset_database_timestamp(Oid dboid, TimestampTz ts)
418 : {
419 : PgStat_EntryRef *dbref;
420 : PgStatShared_Database *dbentry;
421 :
422 11 : dbref = pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE, MyDatabaseId, InvalidOid,
423 : false);
424 :
425 11 : dbentry = (PgStatShared_Database *) dbref->shared_stats;
426 11 : dbentry->stats.stat_reset_timestamp = ts;
427 :
428 11 : pgstat_unlock_entry(dbref);
429 11 : }
430 :
431 : /*
432 : * Flush out pending stats for the entry
433 : *
434 : * If nowait is true and the lock could not be immediately acquired, returns
435 : * false without flushing the entry. Otherwise returns true.
436 : */
437 : bool
438 62266 : pgstat_database_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
439 : {
440 : PgStatShared_Database *sharedent;
441 : PgStat_StatDBEntry *pendingent;
442 :
443 62266 : pendingent = (PgStat_StatDBEntry *) entry_ref->pending;
444 62266 : sharedent = (PgStatShared_Database *) entry_ref->shared_stats;
445 :
446 62266 : if (!pgstat_lock_entry(entry_ref, nowait))
447 1 : return false;
448 :
449 : #define PGSTAT_ACCUM_DBCOUNT(item) \
450 : (sharedent)->stats.item += (pendingent)->item
451 :
452 62265 : PGSTAT_ACCUM_DBCOUNT(xact_commit);
453 62265 : PGSTAT_ACCUM_DBCOUNT(xact_rollback);
454 62265 : PGSTAT_ACCUM_DBCOUNT(blocks_fetched);
455 62265 : PGSTAT_ACCUM_DBCOUNT(blocks_hit);
456 :
457 62265 : PGSTAT_ACCUM_DBCOUNT(tuples_returned);
458 62265 : PGSTAT_ACCUM_DBCOUNT(tuples_fetched);
459 62265 : PGSTAT_ACCUM_DBCOUNT(tuples_inserted);
460 62265 : PGSTAT_ACCUM_DBCOUNT(tuples_updated);
461 62265 : PGSTAT_ACCUM_DBCOUNT(tuples_deleted);
462 :
463 : /* last_autovac_time is reported immediately */
464 : Assert(pendingent->last_autovac_time == 0);
465 :
466 62265 : PGSTAT_ACCUM_DBCOUNT(conflict_tablespace);
467 62265 : PGSTAT_ACCUM_DBCOUNT(conflict_lock);
468 62265 : PGSTAT_ACCUM_DBCOUNT(conflict_snapshot);
469 62265 : PGSTAT_ACCUM_DBCOUNT(conflict_logicalslot);
470 62265 : PGSTAT_ACCUM_DBCOUNT(conflict_bufferpin);
471 62265 : PGSTAT_ACCUM_DBCOUNT(conflict_startup_deadlock);
472 :
473 62265 : PGSTAT_ACCUM_DBCOUNT(temp_bytes);
474 62265 : PGSTAT_ACCUM_DBCOUNT(temp_files);
475 62265 : PGSTAT_ACCUM_DBCOUNT(deadlocks);
476 :
477 : /* checksum failures are reported immediately */
478 : Assert(pendingent->checksum_failures == 0);
479 : Assert(pendingent->last_checksum_failure == 0);
480 :
481 62265 : PGSTAT_ACCUM_DBCOUNT(blk_read_time);
482 62265 : PGSTAT_ACCUM_DBCOUNT(blk_write_time);
483 :
484 62265 : PGSTAT_ACCUM_DBCOUNT(sessions);
485 62265 : PGSTAT_ACCUM_DBCOUNT(session_time);
486 62265 : PGSTAT_ACCUM_DBCOUNT(active_time);
487 62265 : PGSTAT_ACCUM_DBCOUNT(idle_in_transaction_time);
488 62265 : PGSTAT_ACCUM_DBCOUNT(sessions_abandoned);
489 62265 : PGSTAT_ACCUM_DBCOUNT(sessions_fatal);
490 62265 : PGSTAT_ACCUM_DBCOUNT(sessions_killed);
491 62265 : PGSTAT_ACCUM_DBCOUNT(parallel_workers_to_launch);
492 62265 : PGSTAT_ACCUM_DBCOUNT(parallel_workers_launched);
493 : #undef PGSTAT_ACCUM_DBCOUNT
494 :
495 62265 : pgstat_unlock_entry(entry_ref);
496 :
497 62265 : memset(pendingent, 0, sizeof(*pendingent));
498 :
499 62265 : return true;
500 : }
501 :
502 : void
503 13 : pgstat_database_reset_timestamp_cb(PgStatShared_Common *header, TimestampTz ts)
504 : {
505 13 : ((PgStatShared_Database *) header)->stats.stat_reset_timestamp = ts;
506 13 : }
|