]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
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