/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 | } |