Coverage Report

Created: 2026-11-09 07:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/commands/explain_dr.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 *
3
 * explain_dr.c
4
 *    Explain DestReceiver to measure serialization overhead
5
 *
6
 * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7
 * Portions Copyright (c) 1994-5, Regents of the University of California
8
 *
9
 * IDENTIFICATION
10
 *    src/backend/commands/explain_dr.c
11
 *
12
 *-------------------------------------------------------------------------
13
 */
14
#include "postgres.h"
15
16
#include "commands/explain.h"
17
#include "commands/explain_dr.h"
18
#include "commands/explain_state.h"
19
#include "libpq/pqformat.h"
20
#include "libpq/protocol.h"
21
#include "utils/lsyscache.h"
22
#include "varatt.h"
23
24
/*
25
 * DestReceiver functions for SERIALIZE option
26
 *
27
 * A DestReceiver for query tuples, that serializes passed rows into RowData
28
 * messages while measuring the resources expended and total serialized size,
29
 * while never sending the data to the client.  This allows measuring the
30
 * overhead of deTOASTing and datatype out/sendfuncs, which are not otherwise
31
 * exercisable without actually hitting the network.
32
 */
33
typedef struct SerializeDestReceiver
34
{
35
  DestReceiver pub;
36
  ExplainState *es;     /* this EXPLAIN statement's ExplainState */
37
  int8    format;     /* text or binary, like pq wire protocol */
38
  TupleDesc attrinfo;   /* the output tuple desc */
39
  int     nattrs;     /* current number of columns */
40
  FmgrInfo   *finfos;     /* precomputed call info for output fns */
41
  MemoryContext tmpcontext; /* per-row temporary memory context */
42
  StringInfoData buf;     /* buffer to hold the constructed message */
43
  SerializeMetrics metrics; /* collected metrics */
44
} SerializeDestReceiver;
45
46
/*
47
 * Get the function lookup info that we'll need for output.
48
 *
49
 * This is a subset of what printtup_prepare_info() does.  We don't need to
50
 * cope with format choices varying across columns, so it's slightly simpler.
51
 */
52
static void
53
serialize_prepare_info(SerializeDestReceiver *receiver,
54
             TupleDesc typeinfo, int nattrs)
55
0
{
56
  /* get rid of any old data */
57
0
  if (receiver->finfos)
58
0
    pfree(receiver->finfos);
59
0
  receiver->finfos = NULL;
60
61
0
  receiver->attrinfo = typeinfo;
62
0
  receiver->nattrs = nattrs;
63
0
  if (nattrs <= 0)
64
0
    return;
65
66
0
  receiver->finfos = palloc0_array(FmgrInfo, nattrs);
67
68
0
  for (int i = 0; i < nattrs; i++)
69
0
  {
70
0
    FmgrInfo   *finfo = receiver->finfos + i;
71
0
    Form_pg_attribute attr = TupleDescAttr(typeinfo, i);
72
0
    Oid     typoutput;
73
0
    Oid     typsend;
74
0
    bool    typisvarlena;
75
76
0
    if (receiver->format == 0)
77
0
    {
78
      /* wire protocol format text */
79
0
      getTypeOutputInfo(attr->atttypid,
80
0
                &typoutput,
81
0
                &typisvarlena);
82
0
      fmgr_info(typoutput, finfo);
83
0
    }
84
0
    else if (receiver->format == 1)
85
0
    {
86
      /* wire protocol format binary */
87
0
      getTypeBinaryOutputInfo(attr->atttypid,
88
0
                  &typsend,
89
0
                  &typisvarlena);
90
0
      fmgr_info(typsend, finfo);
91
0
    }
92
0
    else
93
0
      ereport(ERROR,
94
0
          (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
95
0
           errmsg("unsupported format code: %d", receiver->format)));
96
0
  }
97
0
}
98
99
/*
100
 * serializeAnalyzeReceive - collect tuples for EXPLAIN (SERIALIZE)
101
 *
102
 * This should match printtup() in printtup.c as closely as possible,
103
 * except for the addition of measurement code.
104
 */
105
static bool
106
serializeAnalyzeReceive(TupleTableSlot *slot, DestReceiver *self)
107
0
{
108
0
  TupleDesc typeinfo = slot->tts_tupleDescriptor;
109
0
  SerializeDestReceiver *myState = (SerializeDestReceiver *) self;
110
0
  MemoryContext oldcontext;
111
0
  StringInfo  buf = &myState->buf;
112
0
  int     natts = typeinfo->natts;
113
0
  instr_time  start,
114
0
        end;
115
0
  BufferUsage instr_start;
116
117
  /* only measure time, buffers if requested */
118
0
  if (myState->es->timing)
119
0
    INSTR_TIME_SET_CURRENT(start);
120
0
  if (myState->es->buffers)
121
0
    instr_start = pgBufferUsage;
122
123
  /* Set or update my derived attribute info, if needed */
124
0
  if (myState->attrinfo != typeinfo || myState->nattrs != natts)
125
0
    serialize_prepare_info(myState, typeinfo, natts);
126
127
  /* Make sure the tuple is fully deconstructed */
128
0
  slot_getallattrs(slot);
129
130
  /* Switch into per-row context so we can recover memory below */
131
0
  oldcontext = MemoryContextSwitchTo(myState->tmpcontext);
132
133
  /*
134
   * Prepare a DataRow message (note buffer is in per-query context)
135
   *
136
   * Note that we fill a StringInfo buffer the same as printtup() does, so
137
   * as to capture the costs of manipulating the strings accurately.
138
   */
139
0
  pq_beginmessage_reuse(buf, PqMsg_DataRow);
140
141
0
  pq_sendint16(buf, natts);
142
143
  /*
144
   * send the attributes of this tuple
145
   */
146
0
  for (int i = 0; i < natts; i++)
147
0
  {
148
0
    FmgrInfo   *finfo = myState->finfos + i;
149
0
    Datum   attr = slot->tts_values[i];
150
151
0
    if (slot->tts_isnull[i])
152
0
    {
153
0
      pq_sendint32(buf, -1);
154
0
      continue;
155
0
    }
156
157
0
    if (myState->format == 0)
158
0
    {
159
      /* Text output */
160
0
      char     *outputstr;
161
162
0
      outputstr = OutputFunctionCall(finfo, attr);
163
0
      pq_sendcountedtext(buf, outputstr, strlen(outputstr));
164
0
    }
165
0
    else
166
0
    {
167
      /* Binary output */
168
0
      bytea    *outputbytes;
169
170
0
      outputbytes = SendFunctionCall(finfo, attr);
171
0
      pq_sendint32(buf, VARSIZE(outputbytes) - VARHDRSZ);
172
0
      pq_sendbytes(buf, VARDATA(outputbytes),
173
0
             VARSIZE(outputbytes) - VARHDRSZ);
174
0
    }
175
0
  }
176
177
  /*
178
   * We mustn't call pq_endmessage_reuse(), since that would actually send
179
   * the data to the client.  Just count the data, instead.  We can leave
180
   * the buffer alone; it'll be reset on the next iteration (as would also
181
   * happen in printtup()).
182
   */
183
0
  myState->metrics.bytesSent += buf->len;
184
185
  /* Return to caller's context, and flush row's temporary memory */
186
0
  MemoryContextSwitchTo(oldcontext);
187
0
  MemoryContextReset(myState->tmpcontext);
188
189
  /* Update timing data */
190
0
  if (myState->es->timing)
191
0
  {
192
0
    INSTR_TIME_SET_CURRENT(end);
193
0
    INSTR_TIME_ACCUM_DIFF(myState->metrics.timeSpent, end, start);
194
0
  }
195
196
  /* Update buffer metrics */
197
0
  if (myState->es->buffers)
198
0
    BufferUsageAccumDiff(&myState->metrics.bufferUsage,
199
0
               &pgBufferUsage,
200
0
               &instr_start);
201
202
0
  return true;
203
0
}
204
205
/*
206
 * serializeAnalyzeStartup - start up the serializeAnalyze receiver
207
 */
208
static void
209
serializeAnalyzeStartup(DestReceiver *self, int operation, TupleDesc typeinfo)
210
0
{
211
0
  SerializeDestReceiver *receiver = (SerializeDestReceiver *) self;
212
213
0
  Assert(receiver->es != NULL);
214
215
0
  switch (receiver->es->serialize)
216
0
  {
217
0
    case EXPLAIN_SERIALIZE_NONE:
218
0
      Assert(false);
219
0
      break;
220
0
    case EXPLAIN_SERIALIZE_TEXT:
221
0
      receiver->format = 0; /* wire protocol format text */
222
0
      break;
223
0
    case EXPLAIN_SERIALIZE_BINARY:
224
0
      receiver->format = 1; /* wire protocol format binary */
225
0
      break;
226
0
  }
227
228
  /* Create per-row temporary memory context */
229
0
  receiver->tmpcontext = AllocSetContextCreate(CurrentMemoryContext,
230
0
                         "SerializeTupleReceive",
231
0
                         ALLOCSET_DEFAULT_SIZES);
232
233
  /* The output buffer is re-used across rows, as in printtup.c */
234
0
  initStringInfo(&receiver->buf);
235
236
  /* Initialize results counters */
237
0
  memset(&receiver->metrics, 0, sizeof(SerializeMetrics));
238
0
  INSTR_TIME_SET_ZERO(receiver->metrics.timeSpent);
239
0
}
240
241
/*
242
 * serializeAnalyzeShutdown - shut down the serializeAnalyze receiver
243
 */
244
static void
245
serializeAnalyzeShutdown(DestReceiver *self)
246
0
{
247
0
  SerializeDestReceiver *receiver = (SerializeDestReceiver *) self;
248
249
0
  if (receiver->finfos)
250
0
    pfree(receiver->finfos);
251
0
  receiver->finfos = NULL;
252
253
0
  if (receiver->buf.data)
254
0
    pfree(receiver->buf.data);
255
0
  receiver->buf.data = NULL;
256
257
0
  if (receiver->tmpcontext)
258
0
    MemoryContextDelete(receiver->tmpcontext);
259
0
  receiver->tmpcontext = NULL;
260
0
}
261
262
/*
263
 * serializeAnalyzeDestroy - destroy the serializeAnalyze receiver
264
 */
265
static void
266
serializeAnalyzeDestroy(DestReceiver *self)
267
0
{
268
0
  pfree(self);
269
0
}
270
271
/*
272
 * Build a DestReceiver for EXPLAIN (SERIALIZE) instrumentation.
273
 */
274
DestReceiver *
275
CreateExplainSerializeDestReceiver(ExplainState *es)
276
0
{
277
0
  SerializeDestReceiver *self;
278
279
0
  self = palloc0_object(SerializeDestReceiver);
280
281
0
  self->pub.receiveSlot = serializeAnalyzeReceive;
282
0
  self->pub.rStartup = serializeAnalyzeStartup;
283
0
  self->pub.rShutdown = serializeAnalyzeShutdown;
284
0
  self->pub.rDestroy = serializeAnalyzeDestroy;
285
0
  self->pub.mydest = DestExplainSerialize;
286
287
0
  self->es = es;
288
289
0
  return (DestReceiver *) self;
290
0
}
291
292
/*
293
 * GetSerializationMetrics - collect metrics
294
 *
295
 * We have to be careful here since the receiver could be an IntoRel
296
 * receiver if the subject statement is CREATE TABLE AS.  In that
297
 * case, return all-zeroes stats.
298
 */
299
SerializeMetrics
300
GetSerializationMetrics(DestReceiver *dest)
301
0
{
302
0
  SerializeMetrics empty;
303
304
0
  if (dest->mydest == DestExplainSerialize)
305
0
    return ((SerializeDestReceiver *) dest)->metrics;
306
307
0
  memset(&empty, 0, sizeof(SerializeMetrics));
308
0
  INSTR_TIME_SET_ZERO(empty.timeSpent);
309
310
0
  return empty;
311
0
}