Create explain_dr.c and move DestReceiver-related code there.
authorRobert Haas <rhaas@postgresql.org>
Thu, 27 Feb 2025 18:14:16 +0000 (13:14 -0500)
committerRobert Haas <rhaas@postgresql.org>
Thu, 27 Feb 2025 18:14:16 +0000 (13:14 -0500)
explain.c has grown rather large, and the code that deals with the
DestReceiver that supports the SERIALIZE option is pretty easily severable
from the rest of explain.c; hence, move it to a separate file.

Reviewed-by: Peter Geoghegan <pg@bowt.ie>
Discussion: http://postgr.es/m/CA+TgmoYutMw1Jgo8BWUmB3TqnOhsEAJiYO=rOQufF4gPLWmkLQ@mail.gmail.com

src/backend/commands/Makefile
src/backend/commands/explain.c
src/backend/commands/explain_dr.c [new file with mode: 0644]
src/backend/commands/meson.build
src/include/commands/explain_dr.h [new file with mode: 0644]

index 04e406fb7cf40fe125f423fbe77cca54f8e0c272..85cfea6fd713298a3e495798a77f6e94769a99b7 100644 (file)
@@ -34,6 +34,7 @@ OBJS = \
    dropcmds.o \
    event_trigger.o \
    explain.o \
+   explain_dr.o \
    explain_format.o \
    extension.o \
    foreigncmds.o \
index 38430a2e314a5d9802a9f1ea5e999c5f4e52c7fa..7e4432f080a51f5281edd65b1e8c18e415c05225 100644 (file)
@@ -17,6 +17,7 @@
 #include "catalog/pg_type.h"
 #include "commands/createas.h"
 #include "commands/defrem.h"
+#include "commands/explain_dr.h"
 #include "commands/explain_format.h"
 #include "commands/prepare.h"
 #include "foreign/fdwapi.h"
@@ -50,14 +51,6 @@ ExplainOneQuery_hook_type ExplainOneQuery_hook = NULL;
 explain_get_index_name_hook_type explain_get_index_name_hook = NULL;
 
 
-/* Instrumentation data for SERIALIZE option */
-typedef struct SerializeMetrics
-{
-   uint64      bytesSent;      /* # of bytes serialized */
-   instr_time  timeSpent;      /* time spent serializing */
-   BufferUsage bufferUsage;    /* buffers accessed during serialization */
-} SerializeMetrics;
-
 /*
  * Various places within need to convert bytes to kilobytes.  Round these up
  * to the next whole kilobyte.
@@ -161,7 +154,6 @@ static ExplainWorkersState *ExplainCreateWorkersState(int num_workers);
 static void ExplainOpenWorker(int n, ExplainState *es);
 static void ExplainCloseWorker(int n, ExplainState *es);
 static void ExplainFlushWorkersState(ExplainState *es);
-static SerializeMetrics GetSerializationMetrics(DestReceiver *dest);
 
 
 
@@ -4939,292 +4931,3 @@ ExplainFlushWorkersState(ExplainState *es)
    pfree(wstate->worker_state_save);
    pfree(wstate);
 }
-
-/*
- * DestReceiver functions for SERIALIZE option
- *
- * A DestReceiver for query tuples, that serializes passed rows into RowData
- * messages while measuring the resources expended and total serialized size,
- * while never sending the data to the client.  This allows measuring the
- * overhead of deTOASTing and datatype out/sendfuncs, which are not otherwise
- * exercisable without actually hitting the network.
- */
-typedef struct SerializeDestReceiver
-{
-   DestReceiver pub;
-   ExplainState *es;           /* this EXPLAIN statement's ExplainState */
-   int8        format;         /* text or binary, like pq wire protocol */
-   TupleDesc   attrinfo;       /* the output tuple desc */
-   int         nattrs;         /* current number of columns */
-   FmgrInfo   *finfos;         /* precomputed call info for output fns */
-   MemoryContext tmpcontext;   /* per-row temporary memory context */
-   StringInfoData buf;         /* buffer to hold the constructed message */
-   SerializeMetrics metrics;   /* collected metrics */
-} SerializeDestReceiver;
-
-/*
- * Get the function lookup info that we'll need for output.
- *
- * This is a subset of what printtup_prepare_info() does.  We don't need to
- * cope with format choices varying across columns, so it's slightly simpler.
- */
-static void
-serialize_prepare_info(SerializeDestReceiver *receiver,
-                      TupleDesc typeinfo, int nattrs)
-{
-   /* get rid of any old data */
-   if (receiver->finfos)
-       pfree(receiver->finfos);
-   receiver->finfos = NULL;
-
-   receiver->attrinfo = typeinfo;
-   receiver->nattrs = nattrs;
-   if (nattrs <= 0)
-       return;
-
-   receiver->finfos = (FmgrInfo *) palloc0(nattrs * sizeof(FmgrInfo));
-
-   for (int i = 0; i < nattrs; i++)
-   {
-       FmgrInfo   *finfo = receiver->finfos + i;
-       Form_pg_attribute attr = TupleDescAttr(typeinfo, i);
-       Oid         typoutput;
-       Oid         typsend;
-       bool        typisvarlena;
-
-       if (receiver->format == 0)
-       {
-           /* wire protocol format text */
-           getTypeOutputInfo(attr->atttypid,
-                             &typoutput,
-                             &typisvarlena);
-           fmgr_info(typoutput, finfo);
-       }
-       else if (receiver->format == 1)
-       {
-           /* wire protocol format binary */
-           getTypeBinaryOutputInfo(attr->atttypid,
-                                   &typsend,
-                                   &typisvarlena);
-           fmgr_info(typsend, finfo);
-       }
-       else
-           ereport(ERROR,
-                   (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                    errmsg("unsupported format code: %d", receiver->format)));
-   }
-}
-
-/*
- * serializeAnalyzeReceive - collect tuples for EXPLAIN (SERIALIZE)
- *
- * This should match printtup() in printtup.c as closely as possible,
- * except for the addition of measurement code.
- */
-static bool
-serializeAnalyzeReceive(TupleTableSlot *slot, DestReceiver *self)
-{
-   TupleDesc   typeinfo = slot->tts_tupleDescriptor;
-   SerializeDestReceiver *myState = (SerializeDestReceiver *) self;
-   MemoryContext oldcontext;
-   StringInfo  buf = &myState->buf;
-   int         natts = typeinfo->natts;
-   instr_time  start,
-               end;
-   BufferUsage instr_start;
-
-   /* only measure time, buffers if requested */
-   if (myState->es->timing)
-       INSTR_TIME_SET_CURRENT(start);
-   if (myState->es->buffers)
-       instr_start = pgBufferUsage;
-
-   /* Set or update my derived attribute info, if needed */
-   if (myState->attrinfo != typeinfo || myState->nattrs != natts)
-       serialize_prepare_info(myState, typeinfo, natts);
-
-   /* Make sure the tuple is fully deconstructed */
-   slot_getallattrs(slot);
-
-   /* Switch into per-row context so we can recover memory below */
-   oldcontext = MemoryContextSwitchTo(myState->tmpcontext);
-
-   /*
-    * Prepare a DataRow message (note buffer is in per-query context)
-    *
-    * Note that we fill a StringInfo buffer the same as printtup() does, so
-    * as to capture the costs of manipulating the strings accurately.
-    */
-   pq_beginmessage_reuse(buf, PqMsg_DataRow);
-
-   pq_sendint16(buf, natts);
-
-   /*
-    * send the attributes of this tuple
-    */
-   for (int i = 0; i < natts; i++)
-   {
-       FmgrInfo   *finfo = myState->finfos + i;
-       Datum       attr = slot->tts_values[i];
-
-       if (slot->tts_isnull[i])
-       {
-           pq_sendint32(buf, -1);
-           continue;
-       }
-
-       if (myState->format == 0)
-       {
-           /* Text output */
-           char       *outputstr;
-
-           outputstr = OutputFunctionCall(finfo, attr);
-           pq_sendcountedtext(buf, outputstr, strlen(outputstr));
-       }
-       else
-       {
-           /* Binary output */
-           bytea      *outputbytes;
-
-           outputbytes = SendFunctionCall(finfo, attr);
-           pq_sendint32(buf, VARSIZE(outputbytes) - VARHDRSZ);
-           pq_sendbytes(buf, VARDATA(outputbytes),
-                        VARSIZE(outputbytes) - VARHDRSZ);
-       }
-   }
-
-   /*
-    * We mustn't call pq_endmessage_reuse(), since that would actually send
-    * the data to the client.  Just count the data, instead.  We can leave
-    * the buffer alone; it'll be reset on the next iteration (as would also
-    * happen in printtup()).
-    */
-   myState->metrics.bytesSent += buf->len;
-
-   /* Return to caller's context, and flush row's temporary memory */
-   MemoryContextSwitchTo(oldcontext);
-   MemoryContextReset(myState->tmpcontext);
-
-   /* Update timing data */
-   if (myState->es->timing)
-   {
-       INSTR_TIME_SET_CURRENT(end);
-       INSTR_TIME_ACCUM_DIFF(myState->metrics.timeSpent, end, start);
-   }
-
-   /* Update buffer metrics */
-   if (myState->es->buffers)
-       BufferUsageAccumDiff(&myState->metrics.bufferUsage,
-                            &pgBufferUsage,
-                            &instr_start);
-
-   return true;
-}
-
-/*
- * serializeAnalyzeStartup - start up the serializeAnalyze receiver
- */
-static void
-serializeAnalyzeStartup(DestReceiver *self, int operation, TupleDesc typeinfo)
-{
-   SerializeDestReceiver *receiver = (SerializeDestReceiver *) self;
-
-   Assert(receiver->es != NULL);
-
-   switch (receiver->es->serialize)
-   {
-       case EXPLAIN_SERIALIZE_NONE:
-           Assert(false);
-           break;
-       case EXPLAIN_SERIALIZE_TEXT:
-           receiver->format = 0;   /* wire protocol format text */
-           break;
-       case EXPLAIN_SERIALIZE_BINARY:
-           receiver->format = 1;   /* wire protocol format binary */
-           break;
-   }
-
-   /* Create per-row temporary memory context */
-   receiver->tmpcontext = AllocSetContextCreate(CurrentMemoryContext,
-                                                "SerializeTupleReceive",
-                                                ALLOCSET_DEFAULT_SIZES);
-
-   /* The output buffer is re-used across rows, as in printtup.c */
-   initStringInfo(&receiver->buf);
-
-   /* Initialize results counters */
-   memset(&receiver->metrics, 0, sizeof(SerializeMetrics));
-   INSTR_TIME_SET_ZERO(receiver->metrics.timeSpent);
-}
-
-/*
- * serializeAnalyzeShutdown - shut down the serializeAnalyze receiver
- */
-static void
-serializeAnalyzeShutdown(DestReceiver *self)
-{
-   SerializeDestReceiver *receiver = (SerializeDestReceiver *) self;
-
-   if (receiver->finfos)
-       pfree(receiver->finfos);
-   receiver->finfos = NULL;
-
-   if (receiver->buf.data)
-       pfree(receiver->buf.data);
-   receiver->buf.data = NULL;
-
-   if (receiver->tmpcontext)
-       MemoryContextDelete(receiver->tmpcontext);
-   receiver->tmpcontext = NULL;
-}
-
-/*
- * serializeAnalyzeDestroy - destroy the serializeAnalyze receiver
- */
-static void
-serializeAnalyzeDestroy(DestReceiver *self)
-{
-   pfree(self);
-}
-
-/*
- * Build a DestReceiver for EXPLAIN (SERIALIZE) instrumentation.
- */
-DestReceiver *
-CreateExplainSerializeDestReceiver(ExplainState *es)
-{
-   SerializeDestReceiver *self;
-
-   self = (SerializeDestReceiver *) palloc0(sizeof(SerializeDestReceiver));
-
-   self->pub.receiveSlot = serializeAnalyzeReceive;
-   self->pub.rStartup = serializeAnalyzeStartup;
-   self->pub.rShutdown = serializeAnalyzeShutdown;
-   self->pub.rDestroy = serializeAnalyzeDestroy;
-   self->pub.mydest = DestExplainSerialize;
-
-   self->es = es;
-
-   return (DestReceiver *) self;
-}
-
-/*
- * GetSerializationMetrics - collect metrics
- *
- * We have to be careful here since the receiver could be an IntoRel
- * receiver if the subject statement is CREATE TABLE AS.  In that
- * case, return all-zeroes stats.
- */
-static SerializeMetrics
-GetSerializationMetrics(DestReceiver *dest)
-{
-   SerializeMetrics empty;
-
-   if (dest->mydest == DestExplainSerialize)
-       return ((SerializeDestReceiver *) dest)->metrics;
-
-   memset(&empty, 0, sizeof(SerializeMetrics));
-   INSTR_TIME_SET_ZERO(empty.timeSpent);
-
-   return empty;
-}
diff --git a/src/backend/commands/explain_dr.c b/src/backend/commands/explain_dr.c
new file mode 100644 (file)
index 0000000..a0a37ea
--- /dev/null
@@ -0,0 +1,308 @@
+/*-------------------------------------------------------------------------
+ *
+ * explain_dr.c
+ *   Explain DestReceiver to measure serialization overhead
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *   src/backend/commands/explain.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "commands/explain_dr.h"
+#include "libpq/pqformat.h"
+#include "libpq/protocol.h"
+#include "utils/lsyscache.h"
+
+/*
+ * DestReceiver functions for SERIALIZE option
+ *
+ * A DestReceiver for query tuples, that serializes passed rows into RowData
+ * messages while measuring the resources expended and total serialized size,
+ * while never sending the data to the client.  This allows measuring the
+ * overhead of deTOASTing and datatype out/sendfuncs, which are not otherwise
+ * exercisable without actually hitting the network.
+ */
+typedef struct SerializeDestReceiver
+{
+   DestReceiver pub;
+   ExplainState *es;           /* this EXPLAIN statement's ExplainState */
+   int8        format;         /* text or binary, like pq wire protocol */
+   TupleDesc   attrinfo;       /* the output tuple desc */
+   int         nattrs;         /* current number of columns */
+   FmgrInfo   *finfos;         /* precomputed call info for output fns */
+   MemoryContext tmpcontext;   /* per-row temporary memory context */
+   StringInfoData buf;         /* buffer to hold the constructed message */
+   SerializeMetrics metrics;   /* collected metrics */
+} SerializeDestReceiver;
+
+/*
+ * Get the function lookup info that we'll need for output.
+ *
+ * This is a subset of what printtup_prepare_info() does.  We don't need to
+ * cope with format choices varying across columns, so it's slightly simpler.
+ */
+static void
+serialize_prepare_info(SerializeDestReceiver *receiver,
+                      TupleDesc typeinfo, int nattrs)
+{
+   /* get rid of any old data */
+   if (receiver->finfos)
+       pfree(receiver->finfos);
+   receiver->finfos = NULL;
+
+   receiver->attrinfo = typeinfo;
+   receiver->nattrs = nattrs;
+   if (nattrs <= 0)
+       return;
+
+   receiver->finfos = (FmgrInfo *) palloc0(nattrs * sizeof(FmgrInfo));
+
+   for (int i = 0; i < nattrs; i++)
+   {
+       FmgrInfo   *finfo = receiver->finfos + i;
+       Form_pg_attribute attr = TupleDescAttr(typeinfo, i);
+       Oid         typoutput;
+       Oid         typsend;
+       bool        typisvarlena;
+
+       if (receiver->format == 0)
+       {
+           /* wire protocol format text */
+           getTypeOutputInfo(attr->atttypid,
+                             &typoutput,
+                             &typisvarlena);
+           fmgr_info(typoutput, finfo);
+       }
+       else if (receiver->format == 1)
+       {
+           /* wire protocol format binary */
+           getTypeBinaryOutputInfo(attr->atttypid,
+                                   &typsend,
+                                   &typisvarlena);
+           fmgr_info(typsend, finfo);
+       }
+       else
+           ereport(ERROR,
+                   (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                    errmsg("unsupported format code: %d", receiver->format)));
+   }
+}
+
+/*
+ * serializeAnalyzeReceive - collect tuples for EXPLAIN (SERIALIZE)
+ *
+ * This should match printtup() in printtup.c as closely as possible,
+ * except for the addition of measurement code.
+ */
+static bool
+serializeAnalyzeReceive(TupleTableSlot *slot, DestReceiver *self)
+{
+   TupleDesc   typeinfo = slot->tts_tupleDescriptor;
+   SerializeDestReceiver *myState = (SerializeDestReceiver *) self;
+   MemoryContext oldcontext;
+   StringInfo  buf = &myState->buf;
+   int         natts = typeinfo->natts;
+   instr_time  start,
+               end;
+   BufferUsage instr_start;
+
+   /* only measure time, buffers if requested */
+   if (myState->es->timing)
+       INSTR_TIME_SET_CURRENT(start);
+   if (myState->es->buffers)
+       instr_start = pgBufferUsage;
+
+   /* Set or update my derived attribute info, if needed */
+   if (myState->attrinfo != typeinfo || myState->nattrs != natts)
+       serialize_prepare_info(myState, typeinfo, natts);
+
+   /* Make sure the tuple is fully deconstructed */
+   slot_getallattrs(slot);
+
+   /* Switch into per-row context so we can recover memory below */
+   oldcontext = MemoryContextSwitchTo(myState->tmpcontext);
+
+   /*
+    * Prepare a DataRow message (note buffer is in per-query context)
+    *
+    * Note that we fill a StringInfo buffer the same as printtup() does, so
+    * as to capture the costs of manipulating the strings accurately.
+    */
+   pq_beginmessage_reuse(buf, PqMsg_DataRow);
+
+   pq_sendint16(buf, natts);
+
+   /*
+    * send the attributes of this tuple
+    */
+   for (int i = 0; i < natts; i++)
+   {
+       FmgrInfo   *finfo = myState->finfos + i;
+       Datum       attr = slot->tts_values[i];
+
+       if (slot->tts_isnull[i])
+       {
+           pq_sendint32(buf, -1);
+           continue;
+       }
+
+       if (myState->format == 0)
+       {
+           /* Text output */
+           char       *outputstr;
+
+           outputstr = OutputFunctionCall(finfo, attr);
+           pq_sendcountedtext(buf, outputstr, strlen(outputstr));
+       }
+       else
+       {
+           /* Binary output */
+           bytea      *outputbytes;
+
+           outputbytes = SendFunctionCall(finfo, attr);
+           pq_sendint32(buf, VARSIZE(outputbytes) - VARHDRSZ);
+           pq_sendbytes(buf, VARDATA(outputbytes),
+                        VARSIZE(outputbytes) - VARHDRSZ);
+       }
+   }
+
+   /*
+    * We mustn't call pq_endmessage_reuse(), since that would actually send
+    * the data to the client.  Just count the data, instead.  We can leave
+    * the buffer alone; it'll be reset on the next iteration (as would also
+    * happen in printtup()).
+    */
+   myState->metrics.bytesSent += buf->len;
+
+   /* Return to caller's context, and flush row's temporary memory */
+   MemoryContextSwitchTo(oldcontext);
+   MemoryContextReset(myState->tmpcontext);
+
+   /* Update timing data */
+   if (myState->es->timing)
+   {
+       INSTR_TIME_SET_CURRENT(end);
+       INSTR_TIME_ACCUM_DIFF(myState->metrics.timeSpent, end, start);
+   }
+
+   /* Update buffer metrics */
+   if (myState->es->buffers)
+       BufferUsageAccumDiff(&myState->metrics.bufferUsage,
+                            &pgBufferUsage,
+                            &instr_start);
+
+   return true;
+}
+
+/*
+ * serializeAnalyzeStartup - start up the serializeAnalyze receiver
+ */
+static void
+serializeAnalyzeStartup(DestReceiver *self, int operation, TupleDesc typeinfo)
+{
+   SerializeDestReceiver *receiver = (SerializeDestReceiver *) self;
+
+   Assert(receiver->es != NULL);
+
+   switch (receiver->es->serialize)
+   {
+       case EXPLAIN_SERIALIZE_NONE:
+           Assert(false);
+           break;
+       case EXPLAIN_SERIALIZE_TEXT:
+           receiver->format = 0;   /* wire protocol format text */
+           break;
+       case EXPLAIN_SERIALIZE_BINARY:
+           receiver->format = 1;   /* wire protocol format binary */
+           break;
+   }
+
+   /* Create per-row temporary memory context */
+   receiver->tmpcontext = AllocSetContextCreate(CurrentMemoryContext,
+                                                "SerializeTupleReceive",
+                                                ALLOCSET_DEFAULT_SIZES);
+
+   /* The output buffer is re-used across rows, as in printtup.c */
+   initStringInfo(&receiver->buf);
+
+   /* Initialize results counters */
+   memset(&receiver->metrics, 0, sizeof(SerializeMetrics));
+   INSTR_TIME_SET_ZERO(receiver->metrics.timeSpent);
+}
+
+/*
+ * serializeAnalyzeShutdown - shut down the serializeAnalyze receiver
+ */
+static void
+serializeAnalyzeShutdown(DestReceiver *self)
+{
+   SerializeDestReceiver *receiver = (SerializeDestReceiver *) self;
+
+   if (receiver->finfos)
+       pfree(receiver->finfos);
+   receiver->finfos = NULL;
+
+   if (receiver->buf.data)
+       pfree(receiver->buf.data);
+   receiver->buf.data = NULL;
+
+   if (receiver->tmpcontext)
+       MemoryContextDelete(receiver->tmpcontext);
+   receiver->tmpcontext = NULL;
+}
+
+/*
+ * serializeAnalyzeDestroy - destroy the serializeAnalyze receiver
+ */
+static void
+serializeAnalyzeDestroy(DestReceiver *self)
+{
+   pfree(self);
+}
+
+/*
+ * Build a DestReceiver for EXPLAIN (SERIALIZE) instrumentation.
+ */
+DestReceiver *
+CreateExplainSerializeDestReceiver(ExplainState *es)
+{
+   SerializeDestReceiver *self;
+
+   self = (SerializeDestReceiver *) palloc0(sizeof(SerializeDestReceiver));
+
+   self->pub.receiveSlot = serializeAnalyzeReceive;
+   self->pub.rStartup = serializeAnalyzeStartup;
+   self->pub.rShutdown = serializeAnalyzeShutdown;
+   self->pub.rDestroy = serializeAnalyzeDestroy;
+   self->pub.mydest = DestExplainSerialize;
+
+   self->es = es;
+
+   return (DestReceiver *) self;
+}
+
+/*
+ * GetSerializationMetrics - collect metrics
+ *
+ * We have to be careful here since the receiver could be an IntoRel
+ * receiver if the subject statement is CREATE TABLE AS.  In that
+ * case, return all-zeroes stats.
+ */
+SerializeMetrics
+GetSerializationMetrics(DestReceiver *dest)
+{
+   SerializeMetrics empty;
+
+   if (dest->mydest == DestExplainSerialize)
+       return ((SerializeDestReceiver *) dest)->metrics;
+
+   memset(&empty, 0, sizeof(SerializeMetrics));
+   INSTR_TIME_SET_ZERO(empty.timeSpent);
+
+   return empty;
+}
index 0d0106ec096971c4087304b4835dea7917f54e89..ce8d1ab8bace059bc204682098de7c2afa2cb121 100644 (file)
@@ -22,6 +22,7 @@ backend_sources += files(
   'dropcmds.c',
   'event_trigger.c',
   'explain.c',
+  'explain_dr.c',
   'explain_format.c',
   'extension.c',
   'foreigncmds.c',
diff --git a/src/include/commands/explain_dr.h b/src/include/commands/explain_dr.h
new file mode 100644 (file)
index 0000000..8f86239
--- /dev/null
@@ -0,0 +1,30 @@
+/*-------------------------------------------------------------------------
+ *
+ * explain_dr.h
+ *   prototypes for explain_dr.c
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/explain_dr.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef EXPLAIN_DR_H
+#define EXPLAIN_DR_H
+
+#include "commands/explain.h"
+#include "executor/instrument.h"
+
+/* Instrumentation data for EXPLAIN's SERIALIZE option */
+typedef struct SerializeMetrics
+{
+   uint64      bytesSent;      /* # of bytes serialized */
+   instr_time  timeSpent;      /* time spent serializing */
+   BufferUsage bufferUsage;    /* buffers accessed during serialization */
+} SerializeMetrics;
+
+extern DestReceiver *CreateExplainSerializeDestReceiver(ExplainState *es);
+extern SerializeMetrics GetSerializationMetrics(DestReceiver *dest);
+
+#endif