Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * pg_buffercache_pages.c
4 : : * display some contents of the buffer cache
5 : : *
6 : : * contrib/pg_buffercache/pg_buffercache_pages.c
7 : : *-------------------------------------------------------------------------
8 : : */
9 : : #include "postgres.h"
10 : :
11 : : #include "access/htup_details.h"
12 : : #include "catalog/pg_type.h"
13 : : #include "funcapi.h"
14 : : #include "storage/buf_internals.h"
15 : : #include "storage/bufmgr.h"
16 : :
17 : :
18 : : #define NUM_BUFFERCACHE_PAGES_MIN_ELEM 8
19 : : #define NUM_BUFFERCACHE_PAGES_ELEM 9
20 : : #define NUM_BUFFERCACHE_SUMMARY_ELEM 5
21 : : #define NUM_BUFFERCACHE_USAGE_COUNTS_ELEM 4
22 : :
6529 tgl@sss.pgh.pa.us 23 :CBC 1 : PG_MODULE_MAGIC;
24 : :
25 : : /*
26 : : * Record structure holding the to be exposed cache data.
27 : : */
28 : : typedef struct
29 : : {
30 : : uint32 bufferid;
31 : : RelFileNumber relfilenumber;
32 : : Oid reltablespace;
33 : : Oid reldatabase;
34 : : ForkNumber forknum;
35 : : BlockNumber blocknum;
36 : : bool isvalid;
37 : : bool isdirty;
38 : : uint16 usagecount;
39 : :
40 : : /*
41 : : * An int32 is sufficiently large, as MAX_BACKENDS prevents a buffer from
42 : : * being pinned by too many backends and each backend will only pin once
43 : : * because of bufmgr.c's PrivateRefCount infrastructure.
44 : : */
45 : : int32 pinning_backends;
46 : : } BufferCachePagesRec;
47 : :
48 : :
49 : : /*
50 : : * Function context for data persisting over repeated calls.
51 : : */
52 : : typedef struct
53 : : {
54 : : TupleDesc tupdesc;
55 : : BufferCachePagesRec *record;
56 : : } BufferCachePagesContext;
57 : :
58 : :
59 : : /*
60 : : * Function returning data from the shared buffer cache - buffer number,
61 : : * relation node/tablespace/database/blocknum and dirty indicator.
62 : : */
6973 neilc@samurai.com 63 : 2 : PG_FUNCTION_INFO_V1(pg_buffercache_pages);
549 andres@anarazel.de 64 : 2 : PG_FUNCTION_INFO_V1(pg_buffercache_summary);
373 tgl@sss.pgh.pa.us 65 : 2 : PG_FUNCTION_INFO_V1(pg_buffercache_usage_counts);
7 tmunro@postgresql.or 66 :GNC 1 : PG_FUNCTION_INFO_V1(pg_buffercache_evict);
67 : :
68 : : Datum
564 rhaas@postgresql.org 69 :CBC 32770 : pg_buffercache_pages(PG_FUNCTION_ARGS)
70 : : {
71 : : FuncCallContext *funcctx;
72 : : Datum result;
73 : : MemoryContext oldcontext;
74 : : BufferCachePagesContext *fctx; /* User function context. */
75 : : TupleDesc tupledesc;
76 : : TupleDesc expected_tupledesc;
77 : : HeapTuple tuple;
78 : :
6973 neilc@samurai.com 79 [ + + ]: 32770 : if (SRF_IS_FIRSTCALL())
80 : : {
81 : : int i;
82 : :
83 : 2 : funcctx = SRF_FIRSTCALL_INIT();
84 : :
85 : : /* Switch context when allocating stuff to be used in later calls */
86 : 2 : oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
87 : :
88 : : /* Create a user function context for cross-call persistence */
6384 tgl@sss.pgh.pa.us 89 : 2 : fctx = (BufferCachePagesContext *) palloc(sizeof(BufferCachePagesContext));
90 : :
91 : : /*
92 : : * To smoothly support upgrades from version 1.0 of this extension
93 : : * transparently handle the (non-)existence of the pinning_backends
94 : : * column. We unfortunately have to get the result type for that... -
95 : : * we can't use the result type determined by the function definition
96 : : * without potentially crashing when somebody uses the old (or even
97 : : * wrong) function definition though.
98 : : */
3523 andres@anarazel.de 99 [ - + ]: 2 : if (get_call_result_type(fcinfo, NULL, &expected_tupledesc) != TYPEFUNC_COMPOSITE)
3523 andres@anarazel.de 100 [ # # ]:UBC 0 : elog(ERROR, "return type must be a row type");
101 : :
3523 andres@anarazel.de 102 [ + - ]:CBC 2 : if (expected_tupledesc->natts < NUM_BUFFERCACHE_PAGES_MIN_ELEM ||
103 [ - + ]: 2 : expected_tupledesc->natts > NUM_BUFFERCACHE_PAGES_ELEM)
3523 andres@anarazel.de 104 [ # # ]:UBC 0 : elog(ERROR, "incorrect number of output arguments");
105 : :
106 : : /* Construct a tuple descriptor for the result rows. */
1972 andres@anarazel.de 107 :CBC 2 : tupledesc = CreateTemplateTupleDesc(expected_tupledesc->natts);
6973 neilc@samurai.com 108 : 2 : TupleDescInitEntry(tupledesc, (AttrNumber) 1, "bufferid",
109 : : INT4OID, -1, 0);
110 : 2 : TupleDescInitEntry(tupledesc, (AttrNumber) 2, "relfilenode",
111 : : OIDOID, -1, 0);
112 : 2 : TupleDescInitEntry(tupledesc, (AttrNumber) 3, "reltablespace",
113 : : OIDOID, -1, 0);
114 : 2 : TupleDescInitEntry(tupledesc, (AttrNumber) 4, "reldatabase",
115 : : OIDOID, -1, 0);
5722 heikki.linnakangas@i 116 : 2 : TupleDescInitEntry(tupledesc, (AttrNumber) 5, "relforknumber",
117 : : INT2OID, -1, 0);
118 : 2 : TupleDescInitEntry(tupledesc, (AttrNumber) 6, "relblocknumber",
119 : : INT8OID, -1, 0);
120 : 2 : TupleDescInitEntry(tupledesc, (AttrNumber) 7, "isdirty",
121 : : BOOLOID, -1, 0);
122 : 2 : TupleDescInitEntry(tupledesc, (AttrNumber) 8, "usage_count",
123 : : INT2OID, -1, 0);
124 : :
3523 andres@anarazel.de 125 [ + - ]: 2 : if (expected_tupledesc->natts == NUM_BUFFERCACHE_PAGES_ELEM)
126 : 2 : TupleDescInitEntry(tupledesc, (AttrNumber) 9, "pinning_backends",
127 : : INT4OID, -1, 0);
128 : :
6384 tgl@sss.pgh.pa.us 129 : 2 : fctx->tupdesc = BlessTupleDesc(tupledesc);
130 : :
131 : : /* Allocate NBuffers worth of BufferCachePagesRec records. */
2768 rhaas@postgresql.org 132 : 2 : fctx->record = (BufferCachePagesRec *)
133 : 2 : MemoryContextAllocHuge(CurrentMemoryContext,
134 : : sizeof(BufferCachePagesRec) * NBuffers);
135 : :
136 : : /* Set max calls and remember the user function context. */
6384 tgl@sss.pgh.pa.us 137 : 2 : funcctx->max_calls = NBuffers;
138 : 2 : funcctx->user_fctx = fctx;
139 : :
140 : : /* Return to original context when allocating transient memory */
6973 neilc@samurai.com 141 : 2 : MemoryContextSwitchTo(oldcontext);
142 : :
143 : : /*
144 : : * Scan through all the buffers, saving the relevant fields in the
145 : : * fctx->record structure.
146 : : *
147 : : * We don't hold the partition locks, so we don't get a consistent
148 : : * snapshot across all buffers, but we do grab the buffer header
149 : : * locks, so the information of each buffer is self-consistent.
150 : : */
3363 andres@anarazel.de 151 [ + + ]: 32770 : for (i = 0; i < NBuffers; i++)
152 : : {
153 : : BufferDesc *bufHdr;
154 : : uint32 buf_state;
155 : :
156 : 32768 : bufHdr = GetBufferDescriptor(i);
157 : : /* Lock each buffer header before inspecting. */
2926 158 : 32768 : buf_state = LockBufHdr(bufHdr);
159 : :
6973 neilc@samurai.com 160 : 32768 : fctx->record[i].bufferid = BufferDescriptorGetBuffer(bufHdr);
599 rhaas@postgresql.org 161 : 32768 : fctx->record[i].relfilenumber = BufTagGetRelNumber(&bufHdr->tag);
162 : 32768 : fctx->record[i].reltablespace = bufHdr->tag.spcOid;
163 : 32768 : fctx->record[i].reldatabase = bufHdr->tag.dbOid;
164 : 32768 : fctx->record[i].forknum = BufTagGetForkNum(&bufHdr->tag);
6973 neilc@samurai.com 165 : 32768 : fctx->record[i].blocknum = bufHdr->tag.blockNum;
2926 andres@anarazel.de 166 : 32768 : fctx->record[i].usagecount = BUF_STATE_GET_USAGECOUNT(buf_state);
167 : 32768 : fctx->record[i].pinning_backends = BUF_STATE_GET_REFCOUNT(buf_state);
168 : :
169 [ + + ]: 32768 : if (buf_state & BM_DIRTY)
6973 neilc@samurai.com 170 : 1886 : fctx->record[i].isdirty = true;
171 : : else
172 : 30882 : fctx->record[i].isdirty = false;
173 : :
174 : : /* Note if the buffer is valid, and has storage created */
2926 andres@anarazel.de 175 [ + + + - ]: 32768 : if ((buf_state & BM_VALID) && (buf_state & BM_TAG_VALID))
6973 neilc@samurai.com 176 : 3684 : fctx->record[i].isvalid = true;
177 : : else
178 : 29084 : fctx->record[i].isvalid = false;
179 : :
2926 andres@anarazel.de 180 : 32768 : UnlockBufHdr(bufHdr, buf_state);
181 : : }
182 : : }
183 : :
6973 neilc@samurai.com 184 : 32770 : funcctx = SRF_PERCALL_SETUP();
185 : :
186 : : /* Get the saved state */
187 : 32770 : fctx = funcctx->user_fctx;
188 : :
189 [ + + ]: 32770 : if (funcctx->call_cntr < funcctx->max_calls)
190 : : {
6756 bruce@momjian.us 191 : 32768 : uint32 i = funcctx->call_cntr;
192 : : Datum values[NUM_BUFFERCACHE_PAGES_ELEM];
193 : : bool nulls[NUM_BUFFERCACHE_PAGES_ELEM];
194 : :
6384 tgl@sss.pgh.pa.us 195 : 32768 : values[0] = Int32GetDatum(fctx->record[i].bufferid);
196 : 32768 : nulls[0] = false;
197 : :
198 : : /*
199 : : * Set all fields except the bufferid to null if the buffer is unused
200 : : * or not valid.
201 : : */
6973 neilc@samurai.com 202 [ + + ]: 32768 : if (fctx->record[i].blocknum == InvalidBlockNumber ||
6756 bruce@momjian.us 203 [ - + ]: 3684 : fctx->record[i].isvalid == false)
204 : : {
6384 tgl@sss.pgh.pa.us 205 : 29084 : nulls[1] = true;
206 : 29084 : nulls[2] = true;
207 : 29084 : nulls[3] = true;
208 : 29084 : nulls[4] = true;
209 : 29084 : nulls[5] = true;
6217 bruce@momjian.us 210 : 29084 : nulls[6] = true;
5722 heikki.linnakangas@i 211 : 29084 : nulls[7] = true;
212 : : /* unused for v1.0 callers, but the array is always long enough */
3523 andres@anarazel.de 213 : 29084 : nulls[8] = true;
214 : : }
215 : : else
216 : : {
564 rhaas@postgresql.org 217 : 3684 : values[1] = ObjectIdGetDatum(fctx->record[i].relfilenumber);
6384 tgl@sss.pgh.pa.us 218 : 3684 : nulls[1] = false;
219 : 3684 : values[2] = ObjectIdGetDatum(fctx->record[i].reltablespace);
220 : 3684 : nulls[2] = false;
221 : 3684 : values[3] = ObjectIdGetDatum(fctx->record[i].reldatabase);
222 : 3684 : nulls[3] = false;
5722 heikki.linnakangas@i 223 : 3684 : values[4] = ObjectIdGetDatum(fctx->record[i].forknum);
6384 tgl@sss.pgh.pa.us 224 : 3684 : nulls[4] = false;
5722 heikki.linnakangas@i 225 : 3684 : values[5] = Int64GetDatum((int64) fctx->record[i].blocknum);
6384 tgl@sss.pgh.pa.us 226 : 3684 : nulls[5] = false;
5722 heikki.linnakangas@i 227 : 3684 : values[6] = BoolGetDatum(fctx->record[i].isdirty);
6217 bruce@momjian.us 228 : 3684 : nulls[6] = false;
5722 heikki.linnakangas@i 229 : 3684 : values[7] = Int16GetDatum(fctx->record[i].usagecount);
230 : 3684 : nulls[7] = false;
231 : : /* unused for v1.0 callers, but the array is always long enough */
3523 andres@anarazel.de 232 : 3684 : values[8] = Int32GetDatum(fctx->record[i].pinning_backends);
233 : 3684 : nulls[8] = false;
234 : : }
235 : :
236 : : /* Build and return the tuple. */
6384 tgl@sss.pgh.pa.us 237 : 32768 : tuple = heap_form_tuple(fctx->tupdesc, values, nulls);
6973 neilc@samurai.com 238 : 32768 : result = HeapTupleGetDatum(tuple);
239 : :
240 : 32768 : SRF_RETURN_NEXT(funcctx, result);
241 : : }
242 : : else
243 : 2 : SRF_RETURN_DONE(funcctx);
244 : : }
245 : :
246 : : Datum
549 andres@anarazel.de 247 : 2 : pg_buffercache_summary(PG_FUNCTION_ARGS)
248 : : {
249 : : Datum result;
250 : : TupleDesc tupledesc;
251 : : HeapTuple tuple;
252 : : Datum values[NUM_BUFFERCACHE_SUMMARY_ELEM];
253 : : bool nulls[NUM_BUFFERCACHE_SUMMARY_ELEM];
254 : :
255 : 2 : int32 buffers_used = 0;
256 : 2 : int32 buffers_unused = 0;
257 : 2 : int32 buffers_dirty = 0;
258 : 2 : int32 buffers_pinned = 0;
259 : 2 : int64 usagecount_total = 0;
260 : :
261 [ - + ]: 2 : if (get_call_result_type(fcinfo, NULL, &tupledesc) != TYPEFUNC_COMPOSITE)
549 andres@anarazel.de 262 [ # # ]:UBC 0 : elog(ERROR, "return type must be a row type");
263 : :
549 andres@anarazel.de 264 [ + + ]:CBC 32770 : for (int i = 0; i < NBuffers; i++)
265 : : {
266 : : BufferDesc *bufHdr;
267 : : uint32 buf_state;
268 : :
269 : : /*
270 : : * This function summarizes the state of all headers. Locking the
271 : : * buffer headers wouldn't provide an improved result as the state of
272 : : * the buffer can still change after we release the lock and it'd
273 : : * noticeably increase the cost of the function.
274 : : */
275 : 32768 : bufHdr = GetBufferDescriptor(i);
276 : 32768 : buf_state = pg_atomic_read_u32(&bufHdr->state);
277 : :
278 [ + + ]: 32768 : if (buf_state & BM_VALID)
279 : : {
280 : 3684 : buffers_used++;
281 : 3684 : usagecount_total += BUF_STATE_GET_USAGECOUNT(buf_state);
282 : :
283 [ + + ]: 3684 : if (buf_state & BM_DIRTY)
284 : 1886 : buffers_dirty++;
285 : : }
286 : : else
287 : 29084 : buffers_unused++;
288 : :
289 [ - + ]: 32768 : if (BUF_STATE_GET_REFCOUNT(buf_state) > 0)
549 andres@anarazel.de 290 :UBC 0 : buffers_pinned++;
291 : : }
292 : :
549 andres@anarazel.de 293 :CBC 2 : memset(nulls, 0, sizeof(nulls));
294 : 2 : values[0] = Int32GetDatum(buffers_used);
295 : 2 : values[1] = Int32GetDatum(buffers_unused);
296 : 2 : values[2] = Int32GetDatum(buffers_dirty);
297 : 2 : values[3] = Int32GetDatum(buffers_pinned);
298 : :
299 [ + - ]: 2 : if (buffers_used != 0)
300 : 2 : values[4] = Float8GetDatum((double) usagecount_total / buffers_used);
301 : : else
549 andres@anarazel.de 302 :UBC 0 : nulls[4] = true;
303 : :
304 : : /* Build and return the tuple. */
549 andres@anarazel.de 305 :CBC 2 : tuple = heap_form_tuple(tupledesc, values, nulls);
306 : 2 : result = HeapTupleGetDatum(tuple);
307 : :
308 : 2 : PG_RETURN_DATUM(result);
309 : : }
310 : :
311 : : Datum
373 tgl@sss.pgh.pa.us 312 : 2 : pg_buffercache_usage_counts(PG_FUNCTION_ARGS)
313 : : {
314 : 2 : ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
315 : 2 : int usage_counts[BM_MAX_USAGE_COUNT + 1] = {0};
316 : 2 : int dirty[BM_MAX_USAGE_COUNT + 1] = {0};
317 : 2 : int pinned[BM_MAX_USAGE_COUNT + 1] = {0};
318 : : Datum values[NUM_BUFFERCACHE_USAGE_COUNTS_ELEM];
319 : 2 : bool nulls[NUM_BUFFERCACHE_USAGE_COUNTS_ELEM] = {0};
320 : :
321 : 2 : InitMaterializedSRF(fcinfo, 0);
322 : :
323 [ + + ]: 32770 : for (int i = 0; i < NBuffers; i++)
324 : : {
325 : 32768 : BufferDesc *bufHdr = GetBufferDescriptor(i);
326 : 32768 : uint32 buf_state = pg_atomic_read_u32(&bufHdr->state);
327 : : int usage_count;
328 : :
329 : 32768 : usage_count = BUF_STATE_GET_USAGECOUNT(buf_state);
330 : 32768 : usage_counts[usage_count]++;
331 : :
332 [ + + ]: 32768 : if (buf_state & BM_DIRTY)
333 : 1886 : dirty[usage_count]++;
334 : :
335 [ - + ]: 32768 : if (BUF_STATE_GET_REFCOUNT(buf_state) > 0)
373 tgl@sss.pgh.pa.us 336 :UBC 0 : pinned[usage_count]++;
337 : : }
338 : :
373 tgl@sss.pgh.pa.us 339 [ + + ]:CBC 14 : for (int i = 0; i < BM_MAX_USAGE_COUNT + 1; i++)
340 : : {
341 : 12 : values[0] = Int32GetDatum(i);
342 : 12 : values[1] = Int32GetDatum(usage_counts[i]);
343 : 12 : values[2] = Int32GetDatum(dirty[i]);
344 : 12 : values[3] = Int32GetDatum(pinned[i]);
345 : :
346 : 12 : tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
347 : : }
348 : :
349 : 2 : return (Datum) 0;
350 : : }
351 : :
352 : : /*
353 : : * Try to evict a shared buffer.
354 : : */
355 : : Datum
7 tmunro@postgresql.or 356 :UNC 0 : pg_buffercache_evict(PG_FUNCTION_ARGS)
357 : : {
358 : 0 : Buffer buf = PG_GETARG_INT32(0);
359 : :
360 [ # # ]: 0 : if (!superuser())
361 [ # # ]: 0 : ereport(ERROR,
362 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
363 : : errmsg("must be superuser to use pg_buffercache_evict function")));
364 : :
365 [ # # # # ]: 0 : if (buf < 1 || buf > NBuffers)
366 [ # # ]: 0 : elog(ERROR, "bad buffer ID: %d", buf);
367 : :
368 : 0 : PG_RETURN_BOOL(EvictUnpinnedBuffer(buf));
369 : : }
|