postgresql/contrib/pg_buffercache/pg_buffercache_pages.c
Tom Lane 07eeb9d109 Do all accesses to shared buffer headers through volatile-qualified
pointers, to ensure that compilers won't rearrange accesses to occur
while we're not holding the buffer header spinlock.  It's probably
not necessary to mark volatile in every single place in bufmgr.c,
but better safe than sorry.  Per trouble report from Kevin Grittner.
2005-10-12 16:45:14 +00:00

232 lines
6.0 KiB
C

/*-------------------------------------------------------------------------
*
* pg_buffercache_pages.c
* display some contents of the buffer cache
*
* $PostgreSQL: pgsql/contrib/pg_buffercache/pg_buffercache_pages.c,v 1.5 2005/10/12 16:45:13 tgl Exp $
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "funcapi.h"
#include "catalog/pg_type.h"
#include "storage/buf_internals.h"
#include "storage/bufmgr.h"
#include "utils/relcache.h"
#define NUM_BUFFERCACHE_PAGES_ELEM 6
#if defined(WIN32) || defined(__CYGWIN__)
extern DLLIMPORT BufferDesc *BufferDescriptors;
extern DLLIMPORT volatile uint32 InterruptHoldoffCount;
#endif
Datum pg_buffercache_pages(PG_FUNCTION_ARGS);
/*
* Record structure holding the to be exposed cache data.
*/
typedef struct
{
uint32 bufferid;
Oid relfilenode;
Oid reltablespace;
Oid reldatabase;
BlockNumber blocknum;
bool isvalid;
bool isdirty;
} BufferCachePagesRec;
/*
* Function context for data persisting over repeated calls.
*/
typedef struct
{
AttInMetadata *attinmeta;
BufferCachePagesRec *record;
char *values[NUM_BUFFERCACHE_PAGES_ELEM];
} BufferCachePagesContext;
/*
* Function returning data from the shared buffer cache - buffer number,
* relation node/tablespace/database/blocknum and dirty indicator.
*/
PG_FUNCTION_INFO_V1(pg_buffercache_pages);
Datum
pg_buffercache_pages(PG_FUNCTION_ARGS)
{
FuncCallContext *funcctx;
Datum result;
MemoryContext oldcontext;
BufferCachePagesContext *fctx; /* User function context. */
TupleDesc tupledesc;
HeapTuple tuple;
if (SRF_IS_FIRSTCALL())
{
uint32 i;
volatile BufferDesc *bufHdr;
funcctx = SRF_FIRSTCALL_INIT();
/* Switch context when allocating stuff to be used in later calls */
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
/* Construct a tuple to return. */
tupledesc = CreateTemplateTupleDesc(NUM_BUFFERCACHE_PAGES_ELEM, false);
TupleDescInitEntry(tupledesc, (AttrNumber) 1, "bufferid",
INT4OID, -1, 0);
TupleDescInitEntry(tupledesc, (AttrNumber) 2, "relfilenode",
OIDOID, -1, 0);
TupleDescInitEntry(tupledesc, (AttrNumber) 3, "reltablespace",
OIDOID, -1, 0);
TupleDescInitEntry(tupledesc, (AttrNumber) 4, "reldatabase",
OIDOID, -1, 0);
TupleDescInitEntry(tupledesc, (AttrNumber) 5, "relblocknumber",
INT8OID, -1, 0);
TupleDescInitEntry(tupledesc, (AttrNumber) 6, "isdirty",
BOOLOID, -1, 0);
/* Generate attribute metadata needed later to produce tuples */
funcctx->attinmeta = TupleDescGetAttInMetadata(tupledesc);
/*
* Create a function context for cross-call persistence
* and initialize the buffer counters.
*/
fctx = (BufferCachePagesContext *) palloc(sizeof(BufferCachePagesContext));
funcctx->max_calls = NBuffers;
funcctx->user_fctx = fctx;
/* Allocate NBuffers worth of BufferCachePagesRec records. */
fctx->record = (BufferCachePagesRec *) palloc(sizeof(BufferCachePagesRec) * NBuffers);
/* allocate the strings for tuple formation */
fctx->values[0] = (char *) palloc(3 * sizeof(uint32) + 1);
fctx->values[1] = (char *) palloc(3 * sizeof(uint32) + 1);
fctx->values[2] = (char *) palloc(3 * sizeof(uint32) + 1);
fctx->values[3] = (char *) palloc(3 * sizeof(uint32) + 1);
fctx->values[4] = (char *) palloc(3 * sizeof(uint32) + 1);
fctx->values[5] = (char *) palloc(2);
/* Return to original context when allocating transient memory */
MemoryContextSwitchTo(oldcontext);
/*
* Lock Buffer map and scan though all the buffers, saving the
* relevant fields in the fctx->record structure.
*/
LWLockAcquire(BufMappingLock, LW_SHARED);
for (i = 0, bufHdr = BufferDescriptors; i < NBuffers; i++, bufHdr++)
{
/* Lock each buffer header before inspecting. */
LockBufHdr(bufHdr);
fctx->record[i].bufferid = BufferDescriptorGetBuffer(bufHdr);
fctx->record[i].relfilenode = bufHdr->tag.rnode.relNode;
fctx->record[i].reltablespace = bufHdr->tag.rnode.spcNode;
fctx->record[i].reldatabase = bufHdr->tag.rnode.dbNode;
fctx->record[i].blocknum = bufHdr->tag.blockNum;
if (bufHdr->flags & BM_DIRTY)
fctx->record[i].isdirty = true;
else
fctx->record[i].isdirty = false;
/* Note if the buffer is valid, and has storage created */
if ((bufHdr->flags & BM_VALID) && (bufHdr->flags & BM_TAG_VALID))
fctx->record[i].isvalid = true;
else
fctx->record[i].isvalid = false;
UnlockBufHdr(bufHdr);
}
/* Release Buffer map. */
LWLockRelease(BufMappingLock);
}
funcctx = SRF_PERCALL_SETUP();
/* Get the saved state */
fctx = funcctx->user_fctx;
if (funcctx->call_cntr < funcctx->max_calls)
{
uint32 i = funcctx->call_cntr;
char *values[NUM_BUFFERCACHE_PAGES_ELEM];
int j;
/*
* Use a temporary values array, initially pointing to
* fctx->values, so it can be reassigned w/o losing the storage
* for subsequent calls.
*/
for (j = 0; j < NUM_BUFFERCACHE_PAGES_ELEM; j++)
{
values[j] = fctx->values[j];
}
/*
* Set all fields except the bufferid to null if the buffer is
* unused or not valid.
*/
if (fctx->record[i].blocknum == InvalidBlockNumber ||
fctx->record[i].isvalid == false )
{
sprintf(values[0], "%u", fctx->record[i].bufferid);
values[1] = NULL;
values[2] = NULL;
values[3] = NULL;
values[4] = NULL;
values[5] = NULL;
}
else
{
sprintf(values[0], "%u", fctx->record[i].bufferid);
sprintf(values[1], "%u", fctx->record[i].relfilenode);
sprintf(values[2], "%u", fctx->record[i].reltablespace);
sprintf(values[3], "%u", fctx->record[i].reldatabase);
sprintf(values[4], "%u", fctx->record[i].blocknum);
if (fctx->record[i].isdirty)
{
strcpy(values[5], "t");
}
else
{
strcpy(values[5], "f");
}
}
/* Build and return the tuple. */
tuple = BuildTupleFromCStrings(funcctx->attinmeta, values);
result = HeapTupleGetDatum(tuple);
SRF_RETURN_NEXT(funcctx, result);
}
else
SRF_RETURN_DONE(funcctx);
}