Commit 2970afa6 authored by Tom Lane's avatar Tom Lane

Add PQresultMemorySize function to report allocated size of a PGresult.

This number can be useful for application memory management, and the
overhead to track it seems pretty trivial.

Lars Kanis, reviewed by Pavel Stehule, some mods by me

Discussion: https://postgr.es/m/fa16a288-9685-14f2-97c8-b8ac84365a4f@greiz-reinsdorf.de
parent e7a22179
...@@ -6387,6 +6387,32 @@ void *PQresultAlloc(PGresult *res, size_t nBytes); ...@@ -6387,6 +6387,32 @@ void *PQresultAlloc(PGresult *res, size_t nBytes);
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry id="libpq-pqresultmemorysize">
<term>
<function>PQresultMemorySize</function>
<indexterm>
<primary>PQresultMemorySize</primary>
</indexterm>
</term>
<listitem>
<para>
Retrieves the number of bytes allocated for
a <structname>PGresult</structname> object.
<synopsis>
size_t PQresultMemorySize(const PGresult *res);
</synopsis>
</para>
<para>
This value is the sum of all <function>malloc</function> requests
associated with the <structname>PGresult</structname> object, that is,
all the space that will be freed by <function>PQclear</function>.
This information can be useful for managing memory consumption.
</para>
</listitem>
</varlistentry>
<varlistentry id="libpq-pqlibversion"> <varlistentry id="libpq-pqlibversion">
<term> <term>
<function>PQlibVersion</function> <function>PQlibVersion</function>
...@@ -6960,6 +6986,14 @@ void *PQinstanceData(const PGconn *conn, PGEventProc proc); ...@@ -6960,6 +6986,14 @@ void *PQinstanceData(const PGconn *conn, PGEventProc proc);
int PQresultSetInstanceData(PGresult *res, PGEventProc proc, void *data); int PQresultSetInstanceData(PGresult *res, PGEventProc proc, void *data);
</synopsis> </synopsis>
</para> </para>
<para>
Beware that any storage represented by <parameter>data</parameter>
will not be accounted for by <function>PQresultMemorySize</function>,
unless it is allocated using <function>PQresultAlloc</function>.
(Doing so is recommendable because it eliminates the need to free
such storage explicitly when the result is destroyed.)
</para>
</listitem> </listitem>
</varlistentry> </varlistentry>
......
...@@ -172,3 +172,4 @@ PQsslAttribute 169 ...@@ -172,3 +172,4 @@ PQsslAttribute 169
PQsetErrorContextVisibility 170 PQsetErrorContextVisibility 170
PQresultVerboseErrorMessage 171 PQresultVerboseErrorMessage 171
PQencryptPasswordConn 172 PQencryptPasswordConn 172
PQresultMemorySize 173
...@@ -51,7 +51,7 @@ static int static_client_encoding = PG_SQL_ASCII; ...@@ -51,7 +51,7 @@ static int static_client_encoding = PG_SQL_ASCII;
static bool static_std_strings = false; static bool static_std_strings = false;
static PGEvent *dupEvents(PGEvent *events, int count); static PGEvent *dupEvents(PGEvent *events, int count, size_t *memSize);
static bool pqAddTuple(PGresult *res, PGresAttValue *tup, static bool pqAddTuple(PGresult *res, PGresAttValue *tup,
const char **errmsgp); const char **errmsgp);
static bool PQsendQueryStart(PGconn *conn); static bool PQsendQueryStart(PGconn *conn);
...@@ -166,6 +166,7 @@ PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status) ...@@ -166,6 +166,7 @@ PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status)
result->curBlock = NULL; result->curBlock = NULL;
result->curOffset = 0; result->curOffset = 0;
result->spaceLeft = 0; result->spaceLeft = 0;
result->memorySize = sizeof(PGresult);
if (conn) if (conn)
{ {
...@@ -193,7 +194,8 @@ PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status) ...@@ -193,7 +194,8 @@ PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status)
/* copy events last; result must be valid if we need to PQclear */ /* copy events last; result must be valid if we need to PQclear */
if (conn->nEvents > 0) if (conn->nEvents > 0)
{ {
result->events = dupEvents(conn->events, conn->nEvents); result->events = dupEvents(conn->events, conn->nEvents,
&result->memorySize);
if (!result->events) if (!result->events)
{ {
PQclear(result); PQclear(result);
...@@ -344,7 +346,8 @@ PQcopyResult(const PGresult *src, int flags) ...@@ -344,7 +346,8 @@ PQcopyResult(const PGresult *src, int flags)
/* Wants to copy PGEvents? */ /* Wants to copy PGEvents? */
if ((flags & PG_COPYRES_EVENTS) && src->nEvents > 0) if ((flags & PG_COPYRES_EVENTS) && src->nEvents > 0)
{ {
dest->events = dupEvents(src->events, src->nEvents); dest->events = dupEvents(src->events, src->nEvents,
&dest->memorySize);
if (!dest->events) if (!dest->events)
{ {
PQclear(dest); PQclear(dest);
...@@ -379,17 +382,20 @@ PQcopyResult(const PGresult *src, int flags) ...@@ -379,17 +382,20 @@ PQcopyResult(const PGresult *src, int flags)
* Copy an array of PGEvents (with no extra space for more). * Copy an array of PGEvents (with no extra space for more).
* Does not duplicate the event instance data, sets this to NULL. * Does not duplicate the event instance data, sets this to NULL.
* Also, the resultInitialized flags are all cleared. * Also, the resultInitialized flags are all cleared.
* The total space allocated is added to *memSize.
*/ */
static PGEvent * static PGEvent *
dupEvents(PGEvent *events, int count) dupEvents(PGEvent *events, int count, size_t *memSize)
{ {
PGEvent *newEvents; PGEvent *newEvents;
size_t msize;
int i; int i;
if (!events || count <= 0) if (!events || count <= 0)
return NULL; return NULL;
newEvents = (PGEvent *) malloc(count * sizeof(PGEvent)); msize = count * sizeof(PGEvent);
newEvents = (PGEvent *) malloc(msize);
if (!newEvents) if (!newEvents)
return NULL; return NULL;
...@@ -407,8 +413,10 @@ dupEvents(PGEvent *events, int count) ...@@ -407,8 +413,10 @@ dupEvents(PGEvent *events, int count)
free(newEvents); free(newEvents);
return NULL; return NULL;
} }
msize += strlen(events[i].name) + 1;
} }
*memSize += msize;
return newEvents; return newEvents;
} }
...@@ -567,9 +575,12 @@ pqResultAlloc(PGresult *res, size_t nBytes, bool isBinary) ...@@ -567,9 +575,12 @@ pqResultAlloc(PGresult *res, size_t nBytes, bool isBinary)
*/ */
if (nBytes >= PGRESULT_SEP_ALLOC_THRESHOLD) if (nBytes >= PGRESULT_SEP_ALLOC_THRESHOLD)
{ {
block = (PGresult_data *) malloc(nBytes + PGRESULT_BLOCK_OVERHEAD); size_t alloc_size = nBytes + PGRESULT_BLOCK_OVERHEAD;
block = (PGresult_data *) malloc(alloc_size);
if (!block) if (!block)
return NULL; return NULL;
res->memorySize += alloc_size;
space = block->space + PGRESULT_BLOCK_OVERHEAD; space = block->space + PGRESULT_BLOCK_OVERHEAD;
if (res->curBlock) if (res->curBlock)
{ {
...@@ -594,6 +605,7 @@ pqResultAlloc(PGresult *res, size_t nBytes, bool isBinary) ...@@ -594,6 +605,7 @@ pqResultAlloc(PGresult *res, size_t nBytes, bool isBinary)
block = (PGresult_data *) malloc(PGRESULT_DATA_BLOCKSIZE); block = (PGresult_data *) malloc(PGRESULT_DATA_BLOCKSIZE);
if (!block) if (!block)
return NULL; return NULL;
res->memorySize += PGRESULT_DATA_BLOCKSIZE;
block->next = res->curBlock; block->next = res->curBlock;
res->curBlock = block; res->curBlock = block;
if (isBinary) if (isBinary)
...@@ -615,6 +627,18 @@ pqResultAlloc(PGresult *res, size_t nBytes, bool isBinary) ...@@ -615,6 +627,18 @@ pqResultAlloc(PGresult *res, size_t nBytes, bool isBinary)
return space; return space;
} }
/*
* PQresultMemorySize -
* Returns total space allocated for the PGresult.
*/
size_t
PQresultMemorySize(const PGresult *res)
{
if (!res)
return 0;
return res->memorySize;
}
/* /*
* pqResultStrdup - * pqResultStrdup -
* Like strdup, but the space is subsidiary PGresult space. * Like strdup, but the space is subsidiary PGresult space.
...@@ -927,6 +951,8 @@ pqAddTuple(PGresult *res, PGresAttValue *tup, const char **errmsgp) ...@@ -927,6 +951,8 @@ pqAddTuple(PGresult *res, PGresAttValue *tup, const char **errmsgp)
realloc(res->tuples, newSize * sizeof(PGresAttValue *)); realloc(res->tuples, newSize * sizeof(PGresAttValue *));
if (!newTuples) if (!newTuples)
return false; /* malloc or realloc failed */ return false; /* malloc or realloc failed */
res->memorySize +=
(newSize - res->tupArrSize) * sizeof(PGresAttValue *);
res->tupArrSize = newSize; res->tupArrSize = newSize;
res->tuples = newTuples; res->tuples = newTuples;
} }
......
...@@ -516,6 +516,7 @@ extern PGresult *PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status); ...@@ -516,6 +516,7 @@ extern PGresult *PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status);
extern PGresult *PQcopyResult(const PGresult *src, int flags); extern PGresult *PQcopyResult(const PGresult *src, int flags);
extern int PQsetResultAttrs(PGresult *res, int numAttributes, PGresAttDesc *attDescs); extern int PQsetResultAttrs(PGresult *res, int numAttributes, PGresAttDesc *attDescs);
extern void *PQresultAlloc(PGresult *res, size_t nBytes); extern void *PQresultAlloc(PGresult *res, size_t nBytes);
extern size_t PQresultMemorySize(const PGresult *res);
extern int PQsetvalue(PGresult *res, int tup_num, int field_num, char *value, int len); extern int PQsetvalue(PGresult *res, int tup_num, int field_num, char *value, int len);
/* Quoting strings before inclusion in queries. */ /* Quoting strings before inclusion in queries. */
......
...@@ -208,6 +208,8 @@ struct pg_result ...@@ -208,6 +208,8 @@ struct pg_result
PGresult_data *curBlock; /* most recently allocated block */ PGresult_data *curBlock; /* most recently allocated block */
int curOffset; /* start offset of free space in block */ int curOffset; /* start offset of free space in block */
int spaceLeft; /* number of free bytes remaining in block */ int spaceLeft; /* number of free bytes remaining in block */
size_t memorySize; /* total space allocated for this PGresult */
}; };
/* PGAsyncStatusType defines the state of the query-execution state machine */ /* PGAsyncStatusType defines the state of the query-execution state machine */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment