Skip to content

Commit 08821a5

Browse files
committed
instrumentation: Track I/O prefetch info and show with EXPLAIN (IO)
This adds details about AIO / prefetch for executor nodes using the ReadStream API, currently SeqScan and BitmapHeapScan, into the new IOUsage information added to Instrumentation. This can be viewed through the new EXPLAIN (IO) information, or could be tracked by other interested callers through the stack-based instrumentation mechanism. The ReadStream tracks the statistics unconditionally, i.e. even outside EXPLAIN ANALYZE etc. The amount of statistics is trivial (a handful of integer counters), it's not worth gating this by a flag. Author: Lukas Fittl <lukas@fittl.com> Author: Tomas Vondra <tomas@vondra.me> Reviewed By: Discussion: https://www.postgresql.org/message-id/flat/a177a6dd-240b-455a-8f25-aca0b1c08c6e%40vondra.me
1 parent 418d18f commit 08821a5

8 files changed

Lines changed: 189 additions & 20 deletions

File tree

src/backend/commands/explain.c

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ static const char *explain_get_index_name(Oid indexId);
145145
static bool peek_buffer_usage(ExplainState *es, const BufferUsage *usage);
146146
static void show_buffer_usage(ExplainState *es, const BufferUsage *usage, const char *title);
147147
static void show_wal_usage(ExplainState *es, const WalUsage *usage);
148+
static void show_io_usage(ExplainState *es, const IOUsage *usage);
148149
static void show_memory_counters(ExplainState *es,
149150
const MemoryContextCounters *mem_counters);
150151
static void show_result_replacement_info(Result *result, ExplainState *es);
@@ -509,6 +510,8 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
509510
instrument_option |= INSTRUMENT_BUFFERS;
510511
if (es->wal)
511512
instrument_option |= INSTRUMENT_WAL;
513+
if (es->io)
514+
instrument_option |= INSTRUMENT_IO;
512515

513516
/*
514517
* We always collect timing for the entire statement, even when node-level
@@ -2281,14 +2284,16 @@ ExplainNode(PlanState *planstate, List *ancestors,
22812284
}
22822285
}
22832286

2284-
/* Show buffer/WAL usage */
2287+
/* Show buffer/WAL/IO usage */
22852288
if (es->buffers && planstate->instrument)
22862289
show_buffer_usage(es, &planstate->instrument->instr.bufusage, NULL);
22872290
if (es->wal && planstate->instrument)
22882291
show_wal_usage(es, &planstate->instrument->instr.walusage);
2292+
if (es->io && planstate->instrument)
2293+
show_io_usage(es, &planstate->instrument->instr.iousage);
22892294

2290-
/* Prepare per-worker buffer/WAL usage */
2291-
if (es->workers_state && (es->buffers || es->wal) && es->verbose)
2295+
/* Prepare per-worker buffer/WAL/IO usage */
2296+
if (es->workers_state && (es->buffers || es->wal || es->io) && es->verbose)
22922297
{
22932298
WorkerNodeInstrumentation *w = planstate->worker_instrument;
22942299

@@ -2305,6 +2310,8 @@ ExplainNode(PlanState *planstate, List *ancestors,
23052310
show_buffer_usage(es, &instrument->instr.bufusage, NULL);
23062311
if (es->wal)
23072312
show_wal_usage(es, &instrument->instr.walusage);
2313+
if (es->io)
2314+
show_io_usage(es, &instrument->instr.iousage);
23082315
ExplainCloseWorker(n, es);
23092316
}
23102317
}
@@ -4343,6 +4350,68 @@ show_wal_usage(ExplainState *es, const WalUsage *usage)
43434350
}
43444351
}
43454352

4353+
/*
4354+
* Show I/O prefetch usage details.
4355+
*/
4356+
static void
4357+
show_io_usage(ExplainState *es, const IOUsage *usage)
4358+
{
4359+
/* Nothing to show if no buffers were returned */
4360+
if (usage->count <= 0)
4361+
return;
4362+
4363+
if (es->format == EXPLAIN_FORMAT_TEXT)
4364+
{
4365+
/* prefetch distance info */
4366+
ExplainIndentText(es);
4367+
appendStringInfo(es->str, "Prefetch: avg=%.3f max=%" PRId64 " capacity=%" PRId64,
4368+
(usage->distance_sum * 1.0 / usage->count),
4369+
usage->distance_max,
4370+
usage->distance_capacity);
4371+
appendStringInfoChar(es->str, '\n');
4372+
4373+
/* prefetch I/O info (only if there were actual I/Os) */
4374+
if (usage->stall_count > 0 || usage->io_count > 0)
4375+
{
4376+
ExplainIndentText(es);
4377+
appendStringInfo(es->str, "I/O: stalls=%" PRId64,
4378+
usage->stall_count);
4379+
4380+
if (usage->io_count > 0)
4381+
{
4382+
appendStringInfo(es->str, " size=%.3f inprogress=%.3f",
4383+
(usage->io_blocks * 1.0 / usage->io_count),
4384+
(usage->ios_in_progress * 1.0 / usage->io_count));
4385+
}
4386+
4387+
appendStringInfoChar(es->str, '\n');
4388+
}
4389+
}
4390+
else
4391+
{
4392+
ExplainOpenGroup("Prefetch", "I/O", true, es);
4393+
4394+
ExplainPropertyFloat("Average Distance", NULL,
4395+
(usage->distance_sum * 1.0 / usage->count), 3, es);
4396+
ExplainPropertyInteger("Max Distance", NULL,
4397+
usage->distance_max, es);
4398+
ExplainPropertyInteger("Capacity", NULL,
4399+
usage->distance_capacity, es);
4400+
ExplainPropertyInteger("Stalls", NULL,
4401+
usage->stall_count, es);
4402+
4403+
if (usage->io_count > 0)
4404+
{
4405+
ExplainPropertyFloat("Average IO Size", NULL,
4406+
(usage->io_blocks * 1.0 / usage->io_count), 3, es);
4407+
ExplainPropertyFloat("Average IOs In Progress", NULL,
4408+
(usage->ios_in_progress * 1.0 / usage->io_count), 3, es);
4409+
}
4410+
4411+
ExplainCloseGroup("Prefetch", "I/O", true, es);
4412+
}
4413+
}
4414+
43464415
/*
43474416
* Show memory usage details.
43484417
*/

src/backend/commands/explain_state.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ ParseExplainOptionList(ExplainState *es, List *options, ParseState *pstate)
115115
}
116116
else if (strcmp(opt->defname, "memory") == 0)
117117
es->memory = defGetBoolean(opt);
118+
else if (strcmp(opt->defname, "io") == 0)
119+
es->io = defGetBoolean(opt);
118120
else if (strcmp(opt->defname, "serialize") == 0)
119121
{
120122
if (opt->arg)
@@ -185,6 +187,12 @@ ParseExplainOptionList(ExplainState *es, List *options, ParseState *pstate)
185187
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
186188
errmsg("EXPLAIN option %s requires ANALYZE", "TIMING")));
187189

190+
/* check that IO is used with EXPLAIN ANALYZE */
191+
if (es->io && !es->analyze)
192+
ereport(ERROR,
193+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
194+
errmsg("EXPLAIN option %s requires ANALYZE", "IO")));
195+
188196
/* check that serialize is used with EXPLAIN ANALYZE */
189197
if (es->serialize != EXPLAIN_SERIALIZE_NONE && !es->analyze)
190198
ereport(ERROR,

src/backend/executor/execMain.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ standard_ExecutorRun(QueryDesc *queryDesc,
334334
* Start up required top-level instrumentation stack for WAL/buffer
335335
* tracking
336336
*/
337-
if (!queryDesc->totaltime && (estate->es_instrument & (INSTRUMENT_BUFFERS | INSTRUMENT_WAL)))
337+
if (!queryDesc->totaltime && (estate->es_instrument & (INSTRUMENT_BUFFERS | INSTRUMENT_WAL | INSTRUMENT_IO)))
338338
queryDesc->totaltime = InstrQueryAlloc(estate->es_instrument);
339339

340340
if (queryDesc->totaltime)
@@ -347,7 +347,7 @@ standard_ExecutorRun(QueryDesc *queryDesc,
347347
* after the first call to InstrQueryStart has pushed the parent
348348
* entry.
349349
*/
350-
if ((estate->es_instrument & (INSTRUMENT_BUFFERS | INSTRUMENT_WAL)) &&
350+
if ((estate->es_instrument & (INSTRUMENT_BUFFERS | INSTRUMENT_WAL | INSTRUMENT_IO)) &&
351351
!queryDesc->already_executed)
352352
ExecRememberNodeInstrumentation(queryDesc->planstate,
353353
queryDesc->totaltime);
@@ -1535,7 +1535,7 @@ ExecFinalizeTriggerInstrumentation(EState *estate)
15351535
{
15361536
TriggerInstrumentation *ti = rInfo->ri_TrigInstrument;
15371537

1538-
if (ti && (ti->instr.need_bufusage || ti->instr.need_walusage))
1538+
if (ti && (ti->instr.need_bufusage || ti->instr.need_walusage || ti->instr.need_iousage))
15391539
InstrAccum(instr_stack.current, &ti->instr);
15401540
}
15411541
}

src/backend/executor/instrument.c

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ InstrInitOptions(Instrumentation *instr, int instrument_options)
4646
{
4747
instr->need_bufusage = (instrument_options & INSTRUMENT_BUFFERS) != 0;
4848
instr->need_walusage = (instrument_options & INSTRUMENT_WAL) != 0;
49+
instr->need_iousage = (instrument_options & INSTRUMENT_IO) != 0;
4950
instr->need_timer = (instrument_options & INSTRUMENT_TIMER) != 0;
5051
}
5152

@@ -76,7 +77,7 @@ InstrStart(Instrumentation *instr)
7677
if (instr->need_timer)
7778
InstrStartTimer(instr);
7879

79-
if (instr->need_bufusage || instr->need_walusage)
80+
if (instr->need_bufusage || instr->need_walusage || instr->need_iousage)
8081
InstrPushStack(instr);
8182
}
8283

@@ -86,7 +87,7 @@ InstrStop(Instrumentation *instr)
8687
if (instr->need_timer)
8788
InstrStopTimer(instr);
8889

89-
if (instr->need_bufusage || instr->need_walusage)
90+
if (instr->need_bufusage || instr->need_walusage || instr->need_iousage)
9091
InstrPopStack(instr);
9192
}
9293

@@ -197,7 +198,9 @@ InstrQueryAlloc(int instrument_options)
197198
* survives transaction abort — ResourceOwner release needs to access
198199
* it.
199200
*/
200-
if ((instrument_options & INSTRUMENT_BUFFERS) != 0 || (instrument_options & INSTRUMENT_WAL) != 0)
201+
if ((instrument_options & INSTRUMENT_BUFFERS) != 0 ||
202+
(instrument_options & INSTRUMENT_WAL) != 0 ||
203+
(instrument_options & INSTRUMENT_IO) != 0)
201204
instr = MemoryContextAllocZero(TopMemoryContext, sizeof(QueryInstrumentation));
202205
else
203206
instr = palloc0(sizeof(QueryInstrumentation));
@@ -213,7 +216,7 @@ InstrQueryStart(QueryInstrumentation *qinstr)
213216
{
214217
InstrStart(&qinstr->instr);
215218

216-
if (qinstr->instr.need_bufusage || qinstr->instr.need_walusage)
219+
if (qinstr->instr.need_bufusage || qinstr->instr.need_walusage || qinstr->instr.need_iousage)
217220
{
218221
Assert(CurrentResourceOwner != NULL);
219222
qinstr->owner = CurrentResourceOwner;
@@ -228,7 +231,7 @@ InstrQueryStop(QueryInstrumentation *qinstr)
228231
{
229232
InstrStop(&qinstr->instr);
230233

231-
if (qinstr->instr.need_bufusage || qinstr->instr.need_walusage)
234+
if (qinstr->instr.need_bufusage || qinstr->instr.need_walusage || qinstr->instr.need_iousage)
232235
{
233236
Assert(qinstr->owner != NULL);
234237
ResourceOwnerForgetInstrumentation(qinstr->owner, qinstr);
@@ -243,7 +246,7 @@ InstrQueryStopFinalize(QueryInstrumentation *qinstr)
243246

244247
InstrStopFinalize(&qinstr->instr);
245248

246-
if (!qinstr->instr.need_bufusage && !qinstr->instr.need_walusage)
249+
if (!qinstr->instr.need_bufusage && !qinstr->instr.need_walusage && !qinstr->instr.need_iousage)
247250
return qinstr;
248251

249252
Assert(qinstr->owner != NULL);
@@ -270,15 +273,15 @@ InstrQueryStopFinalize(QueryInstrumentation *qinstr)
270273
void
271274
InstrQueryRememberNode(QueryInstrumentation *parent, NodeInstrumentation *child)
272275
{
273-
if (child->instr.need_bufusage || child->instr.need_walusage)
276+
if (child->instr.need_bufusage || child->instr.need_walusage || child->instr.need_iousage)
274277
dlist_push_head(&parent->unfinalized_children, &child->unfinalized_node);
275278
}
276279

277280
/* start instrumentation during parallel executor startup */
278281
QueryInstrumentation *
279282
InstrStartParallelQuery(void)
280283
{
281-
QueryInstrumentation *qinstr = InstrQueryAlloc(INSTRUMENT_BUFFERS | INSTRUMENT_WAL);
284+
QueryInstrumentation *qinstr = InstrQueryAlloc(INSTRUMENT_BUFFERS | INSTRUMENT_WAL | INSTRUMENT_IO);
282285

283286
InstrQueryStart(qinstr);
284287
return qinstr;
@@ -291,6 +294,7 @@ InstrEndParallelQuery(QueryInstrumentation *qinstr, Instrumentation *dst)
291294
qinstr = InstrQueryStopFinalize(qinstr);
292295
memcpy(&dst->bufusage, &qinstr->instr.bufusage, sizeof(BufferUsage));
293296
memcpy(&dst->walusage, &qinstr->instr.walusage, sizeof(WalUsage));
297+
memcpy(&dst->iousage, &qinstr->instr.iousage, sizeof(IOUsage));
294298
}
295299

296300
/*
@@ -310,6 +314,7 @@ InstrAccumParallelQuery(Instrumentation *instr)
310314
{
311315
BufferUsageAdd(&instr_stack.current->bufusage, &instr->bufusage);
312316
WalUsageAdd(&instr_stack.current->walusage, &instr->walusage);
317+
IOUsageAdd(&instr_stack.current->iousage, &instr->iousage);
313318

314319
WalUsageAdd(&pgWalUsage, &instr->walusage);
315320
}
@@ -329,7 +334,9 @@ InstrAllocNode(int instrument_options, bool async_mode)
329334
* utility commands that restart transactions, which would require a
330335
* context that survives longer (EXPLAIN ANALYZE is fine).
331336
*/
332-
if ((instrument_options & INSTRUMENT_BUFFERS) != 0 || (instrument_options & INSTRUMENT_WAL) != 0)
337+
if ((instrument_options & INSTRUMENT_BUFFERS) != 0 ||
338+
(instrument_options & INSTRUMENT_WAL) != 0 ||
339+
(instrument_options & INSTRUMENT_IO) != 0)
333340
instr = MemoryContextAlloc(TopTransactionContext, sizeof(NodeInstrumentation));
334341
else
335342
instr = palloc(sizeof(NodeInstrumentation));
@@ -392,7 +399,7 @@ InstrStopNode(NodeInstrumentation *instr, double nTuples)
392399
InstrStopNodeTimer(instr);
393400

394401
/* Only pop the stack, accumulation runs in InstrFinalizeNode */
395-
if (instr->instr.need_bufusage || instr->instr.need_walusage)
402+
if (instr->instr.need_bufusage || instr->instr.need_walusage || instr->instr.need_iousage)
396403
InstrPopStack(&instr->instr);
397404

398405
instr->running = true;
@@ -407,7 +414,7 @@ InstrFinalizeNode(NodeInstrumentation *instr, Instrumentation *parent)
407414
NodeInstrumentation *dst;
408415

409416
/* If we didn't use stack based instrumentation, nothing to be done */
410-
if (!instr->instr.need_bufusage && !instr->instr.need_walusage)
417+
if (!instr->instr.need_bufusage && !instr->instr.need_walusage && !instr->instr.need_iousage)
411418
return instr;
412419

413420
/* Copy into per-query memory context */
@@ -418,7 +425,7 @@ InstrFinalizeNode(NodeInstrumentation *instr, Instrumentation *parent)
418425
InstrAccum(parent, &dst->instr);
419426

420427
/* Unregister from query's unfinalized list before freeing */
421-
if (instr->instr.need_bufusage || instr->instr.need_walusage)
428+
if (instr->instr.need_bufusage || instr->instr.need_walusage || instr->instr.need_iousage)
422429
dlist_delete(&instr->unfinalized_node);
423430

424431
pfree(instr);
@@ -489,6 +496,9 @@ InstrAggNode(NodeInstrumentation *dst, NodeInstrumentation *add)
489496

490497
if (dst->instr.need_walusage)
491498
WalUsageAdd(&dst->instr.walusage, &add->instr.walusage);
499+
500+
if (dst->instr.need_iousage)
501+
IOUsageAdd(&dst->instr.iousage, &add->instr.iousage);
492502
}
493503

494504
/*
@@ -598,7 +608,8 @@ InstrNodeSetupExecProcNode(NodeInstrumentation *instr)
598608
{
599609
bool need_timer = instr->instr.need_timer;
600610
bool need_buf = (instr->instr.need_bufusage ||
601-
instr->instr.need_walusage);
611+
instr->instr.need_walusage ||
612+
instr->instr.need_iousage);
602613

603614
if (need_timer && need_buf)
604615
return ExecProcNodeInstrFull;
@@ -649,6 +660,7 @@ InstrAccum(Instrumentation *dst, Instrumentation *add)
649660

650661
BufferUsageAdd(&dst->bufusage, &add->bufusage);
651662
WalUsageAdd(&dst->walusage, &add->walusage);
663+
IOUsageAdd(&dst->iousage, &add->iousage);
652664
}
653665

654666
/* dst += add */
@@ -684,6 +696,22 @@ WalUsageAdd(WalUsage *dst, const WalUsage *add)
684696
dst->wal_buffers_full += add->wal_buffers_full;
685697
}
686698

699+
/* dst += add (using max semantics for distance_max and distance_capacity) */
700+
void
701+
IOUsageAdd(IOUsage *dst, const IOUsage *add)
702+
{
703+
dst->count += add->count;
704+
dst->distance_sum += add->distance_sum;
705+
if (add->distance_max > dst->distance_max)
706+
dst->distance_max = add->distance_max;
707+
if (add->distance_capacity > dst->distance_capacity)
708+
dst->distance_capacity = add->distance_capacity;
709+
dst->stall_count += add->stall_count;
710+
dst->io_count += add->io_count;
711+
dst->io_blocks += add->io_blocks;
712+
dst->ios_in_progress += add->ios_in_progress;
713+
}
714+
687715
void
688716
WalUsageAccumDiff(WalUsage *dst, const WalUsage *add, const WalUsage *sub)
689717
{

0 commit comments

Comments
 (0)