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
44 88 : pgstat_drop_database(Oid databaseid)
45 : {
46 88 : pgstat_drop_transactional(PGSTAT_KIND_DATABASE, databaseid, InvalidOid);
47 88 : }
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 2386 : 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 2386 : entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE,
69 : dboid, InvalidOid, false);
70 :
71 2386 : dbentry = (PgStatShared_Database *) entry_ref->shared_stats;
72 2386 : dbentry->stats.last_autovac_time = GetCurrentTimestamp();
73 :
74 2386 : pgstat_unlock_entry(entry_ref);
75 2386 : }
76 :
77 : /*
78 : * Report a Hot Standby recovery conflict.
79 : */
80 : void
81 24 : pgstat_report_recovery_conflict(int reason)
82 : {
83 : PgStat_StatDBEntry *dbentry;
84 :
85 : Assert(IsUnderPostmaster);
86 24 : if (!pgstat_track_counts)
87 0 : return;
88 :
89 24 : dbentry = pgstat_prep_database_pending(MyDatabaseId);
90 :
91 24 : switch (reason)
92 : {
93 4 : 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 4 : break;
100 2 : case PROCSIG_RECOVERY_CONFLICT_TABLESPACE:
101 2 : dbentry->conflict_tablespace++;
102 2 : break;
103 2 : case PROCSIG_RECOVERY_CONFLICT_LOCK:
104 2 : dbentry->conflict_lock++;
105 2 : break;
106 2 : case PROCSIG_RECOVERY_CONFLICT_SNAPSHOT:
107 2 : dbentry->conflict_snapshot++;
108 2 : break;
109 2 : case PROCSIG_RECOVERY_CONFLICT_BUFFERPIN:
110 2 : dbentry->conflict_bufferpin++;
111 2 : break;
112 10 : case PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT:
113 10 : dbentry->conflict_logicalslot++;
114 10 : break;
115 2 : case PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK:
116 2 : dbentry->conflict_startup_deadlock++;
117 2 : break;
118 : }
119 : }
120 :
121 : /*
122 : * Report a detected deadlock.
123 : */
124 : void
125 12 : pgstat_report_deadlock(void)
126 : {
127 : PgStat_StatDBEntry *dbent;
128 :
129 12 : if (!pgstat_track_counts)
130 0 : return;
131 :
132 12 : dbent = pgstat_prep_database_pending(MyDatabaseId);
133 12 : 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
145 2428600 : pgstat_prepare_report_checksum_failure(Oid dboid)
146 : {
147 : 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 2428600 : pgstat_get_entry_ref(PGSTAT_KIND_DATABASE, dboid, InvalidOid,
155 : true, NULL);
156 2428600 : }
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
166 4 : pgstat_report_checksum_failures_in_db(Oid dboid, int failurecount)
167 : {
168 : PgStat_EntryRef *entry_ref;
169 : PgStatShared_Database *sharedent;
170 :
171 4 : if (!pgstat_track_counts)
172 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 : */
180 4 : 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 : Assert(entry_ref);
191 4 : if (!entry_ref)
192 : {
193 0 : elog(WARNING, "could not report %d conflicts for DB %u",
194 : failurecount, dboid);
195 0 : return;
196 : }
197 :
198 4 : pgstat_lock_entry(entry_ref, false);
199 :
200 4 : sharedent = (PgStatShared_Database *) entry_ref->shared_stats;
201 4 : sharedent->stats.checksum_failures += failurecount;
202 4 : sharedent->stats.last_checksum_failure = GetCurrentTimestamp();
203 :
204 4 : pgstat_unlock_entry(entry_ref);
205 : }
206 :
207 : /*
208 : * Report creation of temporary file.
209 : */
210 : void
211 3686 : pgstat_report_tempfile(size_t filesize)
212 : {
213 : PgStat_StatDBEntry *dbent;
214 :
215 3686 : if (!pgstat_track_counts)
216 0 : return;
217 :
218 3686 : dbent = pgstat_prep_database_pending(MyDatabaseId);
219 3686 : dbent->temp_bytes += filesize;
220 3686 : dbent->temp_files++;
221 : }
222 :
223 : /*
224 : * Notify stats system of a new connection.
225 : */
226 : void
227 26682 : pgstat_report_connect(Oid dboid)
228 : {
229 : PgStat_StatDBEntry *dbentry;
230 :
231 26682 : if (!pgstat_should_report_connstat())
232 2304 : return;
233 :
234 24378 : pgLastSessionReportTime = MyStartTimestamp;
235 :
236 24378 : dbentry = pgstat_prep_database_pending(MyDatabaseId);
237 24378 : dbentry->sessions++;
238 : }
239 :
240 : /*
241 : * Notify the stats system of a disconnect.
242 : */
243 : void
244 31836 : pgstat_report_disconnect(Oid dboid)
245 : {
246 : PgStat_StatDBEntry *dbentry;
247 :
248 31836 : if (!pgstat_should_report_connstat())
249 7458 : return;
250 :
251 24378 : dbentry = pgstat_prep_database_pending(MyDatabaseId);
252 :
253 24378 : switch (pgStatSessionEndCause)
254 : {
255 24254 : case DISCONNECT_NOT_YET:
256 : case DISCONNECT_NORMAL:
257 : /* we don't collect these */
258 24254 : break;
259 74 : case DISCONNECT_CLIENT_EOF:
260 74 : dbentry->sessions_abandoned++;
261 74 : break;
262 24 : case DISCONNECT_FATAL:
263 24 : dbentry->sessions_fatal++;
264 24 : break;
265 26 : case DISCONNECT_KILLED:
266 26 : dbentry->sessions_killed++;
267 26 : 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 8754 : pgstat_fetch_stat_dbentry(Oid dboid)
279 : {
280 8754 : return (PgStat_StatDBEntry *)
281 8754 : pgstat_fetch_entry(PGSTAT_KIND_DATABASE, dboid, InvalidOid);
282 : }
283 :
284 : void
285 818122 : AtEOXact_PgStat_Database(bool isCommit, bool parallel)
286 : {
287 : /* Don't count parallel worker transaction stats */
288 818122 : 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 815386 : if (isCommit)
295 766960 : pgStatXactCommit++;
296 : else
297 48426 : pgStatXactRollback++;
298 : }
299 818122 : }
300 :
301 : /*
302 : * Notify the stats system about parallel worker information.
303 : */
304 : void
305 682 : pgstat_update_parallel_workers_stats(PgStat_Counter workers_to_launch,
306 : PgStat_Counter workers_launched)
307 : {
308 : PgStat_StatDBEntry *dbentry;
309 :
310 682 : if (!OidIsValid(MyDatabaseId))
311 0 : return;
312 :
313 682 : dbentry = pgstat_prep_database_pending(MyDatabaseId);
314 682 : dbentry->parallel_workers_to_launch += workers_to_launch;
315 682 : 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
323 68690 : 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 : */
331 68690 : if (!OidIsValid(MyDatabaseId))
332 6792 : return;
333 :
334 61898 : 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 : */
340 61898 : dbentry->xact_commit += pgStatXactCommit;
341 61898 : dbentry->xact_rollback += pgStatXactRollback;
342 61898 : dbentry->blk_read_time += pgStatBlockReadTime;
343 61898 : dbentry->blk_write_time += pgStatBlockWriteTime;
344 :
345 61898 : 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 50862 : TimestampDifference(pgLastSessionReportTime, ts, &secs, &usecs);
355 50862 : pgLastSessionReportTime = ts;
356 50862 : dbentry->session_time += (PgStat_Counter) secs * 1000000 + usecs;
357 50862 : dbentry->active_time += pgStatActiveTime;
358 50862 : dbentry->idle_in_transaction_time += pgStatTransactionIdleTime;
359 : }
360 :
361 61898 : pgStatXactCommit = 0;
362 61898 : pgStatXactRollback = 0;
363 61898 : pgStatBlockReadTime = 0;
364 61898 : pgStatBlockWriteTime = 0;
365 61898 : pgStatActiveTime = 0;
366 61898 : 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
377 120416 : pgstat_should_report_connstat(void)
378 : {
379 120416 : return MyBackendType == B_BACKEND;
380 : }
381 :
382 : /*
383 : * Find or create a local PgStat_StatDBEntry entry for dboid.
384 : */
385 : PgStat_StatDBEntry *
386 1848554 : 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 : */
394 : Assert(!OidIsValid(dboid) || OidIsValid(MyDatabaseId));
395 :
396 1848554 : entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_DATABASE, dboid, InvalidOid,
397 : NULL);
398 :
399 1848554 : 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 16 : pgstat_reset_database_timestamp(Oid dboid, TimestampTz ts)
408 : {
409 : PgStat_EntryRef *dbref;
410 : PgStatShared_Database *dbentry;
411 :
412 16 : dbref = pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE, MyDatabaseId, InvalidOid,
413 : false);
414 :
415 16 : dbentry = (PgStatShared_Database *) dbref->shared_stats;
416 16 : dbentry->stats.stat_reset_timestamp = ts;
417 :
418 16 : pgstat_unlock_entry(dbref);
419 16 : }
420 :
421 : /*
422 : * Flush out pending stats for the entry
423 : *
424 : * If nowait is true, this function returns false if lock could not
425 : * immediately acquired, otherwise true is returned.
426 : */
427 : bool
428 114694 : pgstat_database_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
429 : {
430 : PgStatShared_Database *sharedent;
431 : PgStat_StatDBEntry *pendingent;
432 :
433 114694 : pendingent = (PgStat_StatDBEntry *) entry_ref->pending;
434 114694 : sharedent = (PgStatShared_Database *) entry_ref->shared_stats;
435 :
436 114694 : if (!pgstat_lock_entry(entry_ref, nowait))
437 0 : return false;
438 :
439 : #define PGSTAT_ACCUM_DBCOUNT(item) \
440 : (sharedent)->stats.item += (pendingent)->item
441 :
442 114694 : PGSTAT_ACCUM_DBCOUNT(xact_commit);
443 114694 : PGSTAT_ACCUM_DBCOUNT(xact_rollback);
444 114694 : PGSTAT_ACCUM_DBCOUNT(blocks_fetched);
445 114694 : PGSTAT_ACCUM_DBCOUNT(blocks_hit);
446 :
447 114694 : PGSTAT_ACCUM_DBCOUNT(tuples_returned);
448 114694 : PGSTAT_ACCUM_DBCOUNT(tuples_fetched);
449 114694 : PGSTAT_ACCUM_DBCOUNT(tuples_inserted);
450 114694 : PGSTAT_ACCUM_DBCOUNT(tuples_updated);
451 114694 : PGSTAT_ACCUM_DBCOUNT(tuples_deleted);
452 :
453 : /* last_autovac_time is reported immediately */
454 : Assert(pendingent->last_autovac_time == 0);
455 :
456 114694 : PGSTAT_ACCUM_DBCOUNT(conflict_tablespace);
457 114694 : PGSTAT_ACCUM_DBCOUNT(conflict_lock);
458 114694 : PGSTAT_ACCUM_DBCOUNT(conflict_snapshot);
459 114694 : PGSTAT_ACCUM_DBCOUNT(conflict_logicalslot);
460 114694 : PGSTAT_ACCUM_DBCOUNT(conflict_bufferpin);
461 114694 : PGSTAT_ACCUM_DBCOUNT(conflict_startup_deadlock);
462 :
463 114694 : PGSTAT_ACCUM_DBCOUNT(temp_bytes);
464 114694 : PGSTAT_ACCUM_DBCOUNT(temp_files);
465 114694 : PGSTAT_ACCUM_DBCOUNT(deadlocks);
466 :
467 : /* checksum failures are reported immediately */
468 : Assert(pendingent->checksum_failures == 0);
469 : Assert(pendingent->last_checksum_failure == 0);
470 :
471 114694 : PGSTAT_ACCUM_DBCOUNT(blk_read_time);
472 114694 : PGSTAT_ACCUM_DBCOUNT(blk_write_time);
473 :
474 114694 : PGSTAT_ACCUM_DBCOUNT(sessions);
475 114694 : PGSTAT_ACCUM_DBCOUNT(session_time);
476 114694 : PGSTAT_ACCUM_DBCOUNT(active_time);
477 114694 : PGSTAT_ACCUM_DBCOUNT(idle_in_transaction_time);
478 114694 : PGSTAT_ACCUM_DBCOUNT(sessions_abandoned);
479 114694 : PGSTAT_ACCUM_DBCOUNT(sessions_fatal);
480 114694 : PGSTAT_ACCUM_DBCOUNT(sessions_killed);
481 114694 : PGSTAT_ACCUM_DBCOUNT(parallel_workers_to_launch);
482 114694 : PGSTAT_ACCUM_DBCOUNT(parallel_workers_launched);
483 : #undef PGSTAT_ACCUM_DBCOUNT
484 :
485 114694 : pgstat_unlock_entry(entry_ref);
486 :
487 114694 : memset(pendingent, 0, sizeof(*pendingent));
488 :
489 114694 : return true;
490 : }
491 :
492 : void
493 26 : pgstat_database_reset_timestamp_cb(PgStatShared_Common *header, TimestampTz ts)
494 : {
495 26 : ((PgStatShared_Database *) header)->stats.stat_reset_timestamp = ts;
496 26 : }
|