Coverage Report

Created: 2026-10-16 06:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/utils/activity/pgstat_replslot.c
Line
Count
Source
1
/* -------------------------------------------------------------------------
2
 *
3
 * pgstat_replslot.c
4
 *    Implementation of replication slot statistics.
5
 *
6
 * This file contains the implementation of replication slot 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
 * Replication slot stats work a bit different than other variable-numbered
12
 * stats. Slots do not have oids (so they can be created on physical
13
 * replicas). Use the slot index as object id while running. However, the slot
14
 * index can change when restarting. That is addressed by using the name when
15
 * (de-)serializing. After a restart it is possible for slots to have been
16
 * dropped while shut down, which is addressed by not restoring stats for
17
 * slots that cannot be found by name when starting up.
18
 *
19
 * Copyright (c) 2001-2026, PostgreSQL Global Development Group
20
 *
21
 * IDENTIFICATION
22
 *    src/backend/utils/activity/pgstat_replslot.c
23
 * -------------------------------------------------------------------------
24
 */
25
26
#include "postgres.h"
27
28
#include "replication/slot.h"
29
#include "utils/pgstat_internal.h"
30
31
32
static int  get_replslot_index(const char *name, bool need_lock);
33
34
35
/*
36
 * Reset counters for a single replication slot.
37
 *
38
 * Permission checking for this function is managed through the normal
39
 * GRANT system.
40
 */
41
void
42
pgstat_reset_replslot(const char *name)
43
0
{
44
0
  ReplicationSlot *slot;
45
46
0
  Assert(name != NULL);
47
48
0
  LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
49
50
  /* Check if the slot exists with the given name. */
51
0
  slot = SearchNamedReplicationSlot(name, false);
52
0
  if (!slot)
53
0
    ereport(ERROR,
54
0
        (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
55
0
         errmsg("replication slot \"%s\" does not exist",
56
0
            name)));
57
58
  /*
59
   * Reset stats if it is a logical slot. Nothing to do for physical slots
60
   * as we collect stats only for logical slots.
61
   */
62
0
  if (SlotIsLogical(slot))
63
0
    pgstat_reset(PGSTAT_KIND_REPLSLOT, InvalidOid,
64
0
           ReplicationSlotIndex(slot));
65
66
0
  LWLockRelease(ReplicationSlotControlLock);
67
0
}
68
69
/*
70
 * Report replication slot statistics.
71
 *
72
 * We can rely on the stats for the slot to exist and to belong to this
73
 * slot. We can only get here if pgstat_create_replslot() or
74
 * pgstat_acquire_replslot() have already been called.
75
 */
76
void
77
pgstat_report_replslot(ReplicationSlot *slot, const PgStat_StatReplSlotEntry *repSlotStat)
78
0
{
79
0
  PgStat_EntryRef *entry_ref;
80
0
  PgStatShared_ReplSlot *shstatent;
81
0
  PgStat_StatReplSlotEntry *statent;
82
83
0
  entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_REPLSLOT, InvalidOid,
84
0
                      ReplicationSlotIndex(slot), false);
85
0
  shstatent = (PgStatShared_ReplSlot *) entry_ref->shared_stats;
86
0
  statent = &shstatent->stats;
87
88
  /* Update the replication slot statistics */
89
0
#define REPLSLOT_ACC(fld) statent->fld += repSlotStat->fld
90
0
  REPLSLOT_ACC(spill_txns);
91
0
  REPLSLOT_ACC(spill_count);
92
0
  REPLSLOT_ACC(spill_bytes);
93
0
  REPLSLOT_ACC(stream_txns);
94
0
  REPLSLOT_ACC(stream_count);
95
0
  REPLSLOT_ACC(stream_bytes);
96
0
  REPLSLOT_ACC(mem_exceeded_count);
97
0
  REPLSLOT_ACC(total_txns);
98
0
  REPLSLOT_ACC(total_bytes);
99
0
#undef REPLSLOT_ACC
100
101
0
  pgstat_unlock_entry(entry_ref);
102
0
}
103
104
/*
105
 * Report replication slot sync skip statistics.
106
 *
107
 * Similar to pgstat_report_replslot(), we can rely on the stats for the
108
 * slot to exist and to belong to this slot.
109
 */
110
void
111
pgstat_report_replslotsync(ReplicationSlot *slot)
112
0
{
113
0
  PgStat_EntryRef *entry_ref;
114
0
  PgStatShared_ReplSlot *shstatent;
115
0
  PgStat_StatReplSlotEntry *statent;
116
117
  /* Slot sync stats are valid only for synced logical slots on standby. */
118
0
  Assert(slot->data.synced);
119
0
  Assert(RecoveryInProgress());
120
121
0
  entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_REPLSLOT, InvalidOid,
122
0
                      ReplicationSlotIndex(slot), false);
123
0
  Assert(entry_ref != NULL);
124
125
0
  shstatent = (PgStatShared_ReplSlot *) entry_ref->shared_stats;
126
0
  statent = &shstatent->stats;
127
128
0
  statent->slotsync_skip_count += 1;
129
0
  statent->slotsync_last_skip = GetCurrentTimestamp();
130
131
0
  pgstat_unlock_entry(entry_ref);
132
0
}
133
134
/*
135
 * Report replication slot creation.
136
 *
137
 * NB: This gets called with ReplicationSlotAllocationLock already held, be
138
 * careful about calling back into slot.c.
139
 */
140
void
141
pgstat_create_replslot(ReplicationSlot *slot)
142
0
{
143
0
  PgStat_EntryRef *entry_ref;
144
0
  PgStatShared_ReplSlot *shstatent;
145
146
0
  Assert(LWLockHeldByMeInMode(ReplicationSlotAllocationLock, LW_EXCLUSIVE));
147
148
0
  entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_REPLSLOT, InvalidOid,
149
0
                      ReplicationSlotIndex(slot), false);
150
0
  shstatent = (PgStatShared_ReplSlot *) entry_ref->shared_stats;
151
152
  /*
153
   * NB: need to accept that there might be stats from an older slot, e.g.
154
   * if we previously crashed after dropping a slot.
155
   */
156
0
  memset(&shstatent->stats, 0, sizeof(shstatent->stats));
157
158
0
  pgstat_unlock_entry(entry_ref);
159
0
}
160
161
/*
162
 * Report replication slot has been acquired.
163
 *
164
 * This guarantees that a stats entry exists during later
165
 * pgstat_report_replslot() or pgstat_report_replslotsync() calls.
166
 *
167
 * If we previously crashed, no stats data exists. But if we did not crash,
168
 * the stats do belong to this slot:
169
 * - the stats cannot belong to a dropped slot, pgstat_drop_replslot() would
170
 *   have been called
171
 * - if the slot was removed while shut down,
172
 *   pgstat_replslot_from_serialized_name_cb() returning false would have
173
 *   caused the stats to be dropped
174
 */
175
void
176
pgstat_acquire_replslot(ReplicationSlot *slot)
177
0
{
178
0
  pgstat_get_entry_ref(PGSTAT_KIND_REPLSLOT, InvalidOid,
179
0
             ReplicationSlotIndex(slot), true, NULL);
180
0
}
181
182
/*
183
 * Report replication slot drop.
184
 */
185
void
186
pgstat_drop_replslot(ReplicationSlot *slot)
187
0
{
188
0
  Assert(LWLockHeldByMeInMode(ReplicationSlotAllocationLock, LW_EXCLUSIVE));
189
190
0
  if (!pgstat_drop_entry(PGSTAT_KIND_REPLSLOT, InvalidOid,
191
0
               ReplicationSlotIndex(slot), false))
192
0
    pgstat_request_entry_refs_gc();
193
0
}
194
195
/*
196
 * Support function for the SQL-callable pgstat* functions. Returns
197
 * a pointer to the replication slot statistics struct.
198
 */
199
PgStat_StatReplSlotEntry *
200
pgstat_fetch_replslot(NameData slotname)
201
0
{
202
0
  int     idx;
203
0
  PgStat_StatReplSlotEntry *slotentry = NULL;
204
205
0
  LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
206
207
0
  idx = get_replslot_index(NameStr(slotname), false);
208
209
0
  if (idx != -1)
210
0
    slotentry = (PgStat_StatReplSlotEntry *) pgstat_fetch_entry(PGSTAT_KIND_REPLSLOT,
211
0
                                  InvalidOid, idx,
212
0
                                  NULL);
213
214
0
  LWLockRelease(ReplicationSlotControlLock);
215
216
0
  return slotentry;
217
0
}
218
219
void
220
pgstat_replslot_to_serialized_name_cb(const PgStat_HashKey *key, const PgStatShared_Common *header, NameData *name)
221
0
{
222
  /*
223
   * This is only called late during shutdown. The set of existing slots
224
   * isn't allowed to change at this point, we can assume that a slot exists
225
   * at the offset.
226
   */
227
0
  if (!ReplicationSlotName(key->objid, name))
228
0
    elog(ERROR, "could not find name for replication slot index %" PRIu64,
229
0
       key->objid);
230
0
}
231
232
bool
233
pgstat_replslot_from_serialized_name_cb(const NameData *name, PgStat_HashKey *key)
234
0
{
235
0
  int     idx = get_replslot_index(NameStr(*name), true);
236
237
  /* slot might have been deleted */
238
0
  if (idx == -1)
239
0
    return false;
240
241
0
  key->kind = PGSTAT_KIND_REPLSLOT;
242
0
  key->dboid = InvalidOid;
243
0
  key->objid = idx;
244
245
0
  return true;
246
0
}
247
248
void
249
pgstat_replslot_reset_timestamp_cb(PgStatShared_Common *header, TimestampTz ts)
250
0
{
251
0
  ((PgStatShared_ReplSlot *) header)->stats.stat_reset_timestamp = ts;
252
0
}
253
254
static int
255
get_replslot_index(const char *name, bool need_lock)
256
0
{
257
0
  ReplicationSlot *slot;
258
259
0
  Assert(name != NULL);
260
261
0
  slot = SearchNamedReplicationSlot(name, need_lock);
262
263
0
  if (!slot)
264
0
    return -1;
265
266
0
  return ReplicationSlotIndex(slot);
267
0
}