/src/postgres/src/backend/utils/activity/pgstat_database.c
Line | Count | Source |
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 | | pgstat_drop_database(Oid databaseid) |
45 | 0 | { |
46 | 0 | pgstat_drop_transactional(PGSTAT_KIND_DATABASE, databaseid, InvalidOid); |
47 | 0 | } |
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 | | pgstat_report_autovac(Oid dboid) |
56 | 0 | { |
57 | 0 | PgStat_EntryRef *entry_ref; |
58 | 0 | PgStatShared_Database *dbentry; |
59 | | |
60 | | /* can't get here in single user mode */ |
61 | 0 | 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 | 0 | entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE, |
69 | 0 | dboid, InvalidOid, false); |
70 | |
|
71 | 0 | dbentry = (PgStatShared_Database *) entry_ref->shared_stats; |
72 | 0 | dbentry->stats.last_autovac_time = GetCurrentTimestamp(); |
73 | |
|
74 | 0 | pgstat_unlock_entry(entry_ref); |
75 | 0 | } |
76 | | |
77 | | /* |
78 | | * Report a Hot Standby recovery conflict. |
79 | | */ |
80 | | void |
81 | | pgstat_report_recovery_conflict(int reason) |
82 | 0 | { |
83 | 0 | PgStat_StatDBEntry *dbentry; |
84 | |
|
85 | 0 | Assert(IsUnderPostmaster); |
86 | 0 | if (!pgstat_track_counts) |
87 | 0 | return; |
88 | | |
89 | 0 | dbentry = pgstat_prep_database_pending(MyDatabaseId); |
90 | |
|
91 | 0 | switch ((RecoveryConflictReason) reason) |
92 | 0 | { |
93 | 0 | 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 | 0 | break; |
100 | 0 | case RECOVERY_CONFLICT_TABLESPACE: |
101 | 0 | dbentry->conflict_tablespace++; |
102 | 0 | break; |
103 | 0 | case RECOVERY_CONFLICT_LOCK: |
104 | 0 | dbentry->conflict_lock++; |
105 | 0 | break; |
106 | 0 | case RECOVERY_CONFLICT_SNAPSHOT: |
107 | 0 | dbentry->conflict_snapshot++; |
108 | 0 | break; |
109 | 0 | case RECOVERY_CONFLICT_BUFFERPIN: |
110 | 0 | dbentry->conflict_bufferpin++; |
111 | 0 | break; |
112 | 0 | case RECOVERY_CONFLICT_LOGICALSLOT: |
113 | 0 | dbentry->conflict_logicalslot++; |
114 | 0 | break; |
115 | 0 | case RECOVERY_CONFLICT_STARTUP_DEADLOCK: |
116 | 0 | dbentry->conflict_startup_deadlock++; |
117 | 0 | break; |
118 | 0 | 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 | 0 | dbentry->conflict_startup_deadlock++; |
127 | 0 | break; |
128 | 0 | } |
129 | 0 | } |
130 | | |
131 | | /* |
132 | | * Report a detected deadlock. |
133 | | */ |
134 | | void |
135 | | pgstat_report_deadlock(void) |
136 | 0 | { |
137 | 0 | PgStat_StatDBEntry *dbent; |
138 | |
|
139 | 0 | if (!pgstat_track_counts) |
140 | 0 | return; |
141 | | |
142 | 0 | dbent = pgstat_prep_database_pending(MyDatabaseId); |
143 | 0 | dbent->deadlocks++; |
144 | 0 | } |
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 | | pgstat_prepare_report_checksum_failure(Oid dboid) |
156 | 0 | { |
157 | 0 | 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 | 0 | pgstat_get_entry_ref(PGSTAT_KIND_DATABASE, dboid, InvalidOid, |
165 | 0 | true, NULL); |
166 | 0 | } |
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 | | pgstat_report_checksum_failures_in_db(Oid dboid, int failurecount) |
177 | | { |
178 | | PgStat_EntryRef *entry_ref; |
179 | | PgStatShared_Database *sharedent; |
180 | | |
181 | | if (!pgstat_track_counts) |
182 | | 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 | | 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 | | if (!entry_ref) |
202 | | { |
203 | | elog(WARNING, "could not report %d checksum failures for database %u", |
204 | | failurecount, dboid); |
205 | | return; |
206 | | } |
207 | | |
208 | | (void) pgstat_lock_entry(entry_ref, false); |
209 | | |
210 | | sharedent = (PgStatShared_Database *) entry_ref->shared_stats; |
211 | | sharedent->stats.checksum_failures += failurecount; |
212 | | sharedent->stats.last_checksum_failure = GetCurrentTimestamp(); |
213 | | |
214 | | pgstat_unlock_entry(entry_ref); |
215 | | } |
216 | | |
217 | | /* |
218 | | * Report creation of temporary file. |
219 | | */ |
220 | | void |
221 | | pgstat_report_tempfile(size_t filesize) |
222 | 0 | { |
223 | 0 | PgStat_StatDBEntry *dbent; |
224 | |
|
225 | 0 | if (!pgstat_track_counts) |
226 | 0 | return; |
227 | | |
228 | 0 | dbent = pgstat_prep_database_pending(MyDatabaseId); |
229 | 0 | dbent->temp_bytes += filesize; |
230 | 0 | dbent->temp_files++; |
231 | 0 | } |
232 | | |
233 | | /* |
234 | | * Notify stats system of a new connection. |
235 | | */ |
236 | | void |
237 | | pgstat_report_connect(Oid dboid) |
238 | 0 | { |
239 | 0 | PgStat_StatDBEntry *dbentry; |
240 | |
|
241 | 0 | if (!pgstat_should_report_connstat()) |
242 | 0 | return; |
243 | | |
244 | 0 | pgLastSessionReportTime = MyStartTimestamp; |
245 | |
|
246 | 0 | dbentry = pgstat_prep_database_pending(dboid); |
247 | 0 | dbentry->sessions++; |
248 | 0 | } |
249 | | |
250 | | /* |
251 | | * Notify the stats system of a disconnect. |
252 | | */ |
253 | | void |
254 | | pgstat_report_disconnect(Oid dboid) |
255 | 0 | { |
256 | 0 | PgStat_StatDBEntry *dbentry; |
257 | |
|
258 | 0 | if (!pgstat_should_report_connstat()) |
259 | 0 | return; |
260 | | |
261 | 0 | dbentry = pgstat_prep_database_pending(dboid); |
262 | |
|
263 | 0 | switch (pgStatSessionEndCause) |
264 | 0 | { |
265 | 0 | case DISCONNECT_NOT_YET: |
266 | 0 | case DISCONNECT_NORMAL: |
267 | | /* we don't collect these */ |
268 | 0 | break; |
269 | 0 | case DISCONNECT_CLIENT_EOF: |
270 | 0 | dbentry->sessions_abandoned++; |
271 | 0 | break; |
272 | 0 | case DISCONNECT_FATAL: |
273 | 0 | dbentry->sessions_fatal++; |
274 | 0 | break; |
275 | 0 | case DISCONNECT_KILLED: |
276 | 0 | dbentry->sessions_killed++; |
277 | 0 | break; |
278 | 0 | } |
279 | 0 | } |
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 | | pgstat_fetch_stat_dbentry(Oid dboid) |
289 | 0 | { |
290 | 0 | return (PgStat_StatDBEntry *) |
291 | 0 | pgstat_fetch_entry(PGSTAT_KIND_DATABASE, dboid, InvalidOid, NULL); |
292 | 0 | } |
293 | | |
294 | | void |
295 | | AtEOXact_PgStat_Database(bool isCommit, bool parallel) |
296 | 0 | { |
297 | | /* Don't count parallel worker transaction stats */ |
298 | 0 | if (!parallel) |
299 | 0 | { |
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 | 0 | if (isCommit) |
305 | 0 | pgStatXactCommit++; |
306 | 0 | else |
307 | 0 | pgStatXactRollback++; |
308 | 0 | } |
309 | 0 | } |
310 | | |
311 | | /* |
312 | | * Notify the stats system about parallel worker information. |
313 | | */ |
314 | | void |
315 | | pgstat_update_parallel_workers_stats(PgStat_Counter workers_to_launch, |
316 | | PgStat_Counter workers_launched) |
317 | 0 | { |
318 | 0 | PgStat_StatDBEntry *dbentry; |
319 | |
|
320 | 0 | if (!OidIsValid(MyDatabaseId)) |
321 | 0 | return; |
322 | | |
323 | 0 | dbentry = pgstat_prep_database_pending(MyDatabaseId); |
324 | 0 | dbentry->parallel_workers_to_launch += workers_to_launch; |
325 | 0 | dbentry->parallel_workers_launched += workers_launched; |
326 | 0 | } |
327 | | |
328 | | /* |
329 | | * Subroutine for pgstat_report_stat(): Handle xact commit/rollback and I/O |
330 | | * timings. |
331 | | */ |
332 | | void |
333 | | pgstat_update_dbstats(TimestampTz ts) |
334 | 0 | { |
335 | 0 | 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 | 0 | if (!OidIsValid(MyDatabaseId)) |
342 | 0 | return; |
343 | | |
344 | 0 | 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 | 0 | dbentry->xact_commit += pgStatXactCommit; |
351 | 0 | dbentry->xact_rollback += pgStatXactRollback; |
352 | 0 | dbentry->blk_read_time += pgStatBlockReadTime; |
353 | 0 | dbentry->blk_write_time += pgStatBlockWriteTime; |
354 | |
|
355 | 0 | if (pgstat_should_report_connstat()) |
356 | 0 | { |
357 | 0 | long secs; |
358 | 0 | int usecs; |
359 | | |
360 | | /* |
361 | | * pgLastSessionReportTime is initialized to MyStartTimestamp by |
362 | | * pgstat_report_connect(). |
363 | | */ |
364 | 0 | TimestampDifference(pgLastSessionReportTime, ts, &secs, &usecs); |
365 | 0 | pgLastSessionReportTime = ts; |
366 | 0 | dbentry->session_time += (PgStat_Counter) secs * 1000000 + usecs; |
367 | 0 | dbentry->active_time += pgStatActiveTime; |
368 | 0 | dbentry->idle_in_transaction_time += pgStatTransactionIdleTime; |
369 | 0 | } |
370 | |
|
371 | 0 | pgStatXactCommit = 0; |
372 | 0 | pgStatXactRollback = 0; |
373 | 0 | pgStatBlockReadTime = 0; |
374 | 0 | pgStatBlockWriteTime = 0; |
375 | 0 | pgStatActiveTime = 0; |
376 | 0 | pgStatTransactionIdleTime = 0; |
377 | 0 | } |
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 | | pgstat_should_report_connstat(void) |
388 | 0 | { |
389 | 0 | return MyBackendType == B_BACKEND; |
390 | 0 | } |
391 | | |
392 | | /* |
393 | | * Find or create a local PgStat_StatDBEntry entry for dboid. |
394 | | */ |
395 | | PgStat_StatDBEntry * |
396 | | pgstat_prep_database_pending(Oid dboid) |
397 | 0 | { |
398 | 0 | PgStat_EntryRef *entry_ref; |
399 | | |
400 | | /* |
401 | | * This should not report stats on database objects before having |
402 | | * connected to a database. |
403 | | */ |
404 | 0 | Assert(!OidIsValid(dboid) || OidIsValid(MyDatabaseId)); |
405 | |
|
406 | 0 | entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_DATABASE, dboid, InvalidOid, |
407 | 0 | NULL); |
408 | |
|
409 | 0 | return entry_ref->pending; |
410 | 0 | } |
411 | | |
412 | | /* |
413 | | * Reset the database's reset timestamp, without resetting the contents of the |
414 | | * database stats. |
415 | | */ |
416 | | void |
417 | | pgstat_reset_database_timestamp(Oid dboid, TimestampTz ts) |
418 | 0 | { |
419 | 0 | PgStat_EntryRef *dbref; |
420 | 0 | PgStatShared_Database *dbentry; |
421 | |
|
422 | 0 | dbref = pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE, dboid, InvalidOid, |
423 | 0 | false); |
424 | |
|
425 | 0 | dbentry = (PgStatShared_Database *) dbref->shared_stats; |
426 | 0 | dbentry->stats.stat_reset_timestamp = ts; |
427 | |
|
428 | 0 | pgstat_unlock_entry(dbref); |
429 | 0 | } |
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 | | pgstat_database_flush_cb(PgStat_EntryRef *entry_ref, bool nowait) |
439 | 0 | { |
440 | 0 | PgStatShared_Database *sharedent; |
441 | 0 | PgStat_StatDBEntry *pendingent; |
442 | |
|
443 | 0 | pendingent = (PgStat_StatDBEntry *) entry_ref->pending; |
444 | 0 | sharedent = (PgStatShared_Database *) entry_ref->shared_stats; |
445 | |
|
446 | 0 | if (!pgstat_lock_entry(entry_ref, nowait)) |
447 | 0 | return false; |
448 | | |
449 | 0 | #define PGSTAT_ACCUM_DBCOUNT(item) \ |
450 | 0 | (sharedent)->stats.item += (pendingent)->item |
451 | | |
452 | 0 | PGSTAT_ACCUM_DBCOUNT(xact_commit); |
453 | 0 | PGSTAT_ACCUM_DBCOUNT(xact_rollback); |
454 | 0 | PGSTAT_ACCUM_DBCOUNT(blocks_fetched); |
455 | 0 | PGSTAT_ACCUM_DBCOUNT(blocks_hit); |
456 | |
|
457 | 0 | PGSTAT_ACCUM_DBCOUNT(tuples_returned); |
458 | 0 | PGSTAT_ACCUM_DBCOUNT(tuples_fetched); |
459 | 0 | PGSTAT_ACCUM_DBCOUNT(tuples_inserted); |
460 | 0 | PGSTAT_ACCUM_DBCOUNT(tuples_updated); |
461 | 0 | PGSTAT_ACCUM_DBCOUNT(tuples_deleted); |
462 | | |
463 | | /* last_autovac_time is reported immediately */ |
464 | 0 | Assert(pendingent->last_autovac_time == 0); |
465 | |
|
466 | 0 | PGSTAT_ACCUM_DBCOUNT(conflict_tablespace); |
467 | 0 | PGSTAT_ACCUM_DBCOUNT(conflict_lock); |
468 | 0 | PGSTAT_ACCUM_DBCOUNT(conflict_snapshot); |
469 | 0 | PGSTAT_ACCUM_DBCOUNT(conflict_logicalslot); |
470 | 0 | PGSTAT_ACCUM_DBCOUNT(conflict_bufferpin); |
471 | 0 | PGSTAT_ACCUM_DBCOUNT(conflict_startup_deadlock); |
472 | |
|
473 | 0 | PGSTAT_ACCUM_DBCOUNT(temp_bytes); |
474 | 0 | PGSTAT_ACCUM_DBCOUNT(temp_files); |
475 | 0 | PGSTAT_ACCUM_DBCOUNT(deadlocks); |
476 | | |
477 | | /* checksum failures are reported immediately */ |
478 | 0 | Assert(pendingent->checksum_failures == 0); |
479 | 0 | Assert(pendingent->last_checksum_failure == 0); |
480 | |
|
481 | 0 | PGSTAT_ACCUM_DBCOUNT(blk_read_time); |
482 | 0 | PGSTAT_ACCUM_DBCOUNT(blk_write_time); |
483 | |
|
484 | 0 | PGSTAT_ACCUM_DBCOUNT(sessions); |
485 | 0 | PGSTAT_ACCUM_DBCOUNT(session_time); |
486 | 0 | PGSTAT_ACCUM_DBCOUNT(active_time); |
487 | 0 | PGSTAT_ACCUM_DBCOUNT(idle_in_transaction_time); |
488 | 0 | PGSTAT_ACCUM_DBCOUNT(sessions_abandoned); |
489 | 0 | PGSTAT_ACCUM_DBCOUNT(sessions_fatal); |
490 | 0 | PGSTAT_ACCUM_DBCOUNT(sessions_killed); |
491 | 0 | PGSTAT_ACCUM_DBCOUNT(parallel_workers_to_launch); |
492 | 0 | PGSTAT_ACCUM_DBCOUNT(parallel_workers_launched); |
493 | 0 | #undef PGSTAT_ACCUM_DBCOUNT |
494 | |
|
495 | 0 | pgstat_unlock_entry(entry_ref); |
496 | |
|
497 | 0 | memset(pendingent, 0, sizeof(*pendingent)); |
498 | |
|
499 | 0 | return true; |
500 | 0 | } |
501 | | |
502 | | void |
503 | | pgstat_database_reset_timestamp_cb(PgStatShared_Common *header, TimestampTz ts) |
504 | 0 | { |
505 | 0 | ((PgStatShared_Database *) header)->stats.stat_reset_timestamp = ts; |
506 | 0 | } |