Coverage Report

Created: 2026-09-28 06:55

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}