diff options
author | Robert Haas <rhaas@postgresql.org> | 2015-12-09 13:18:09 -0500 |
---|---|---|
committer | Robert Haas <rhaas@postgresql.org> | 2015-12-09 13:21:19 -0500 |
commit | b287df70e4080350aa471ecca428be145581dd4d (patch) | |
tree | 10f57a8c7d1029fa1c1b7caabf24ce0925f23b07 /src/backend/commands/explain.c | |
parent | 25c539233044c235e97fd7c9dc600fb5f08fe065 (diff) | |
download | postgresql-b287df70e4080350aa471ecca428be145581dd4d.tar.gz postgresql-b287df70e4080350aa471ecca428be145581dd4d.zip |
Allow EXPLAIN (ANALYZE, VERBOSE) to display per-worker statistics.
The original parallel sequential scan commit included only very limited
changes to the EXPLAIN output. Aggregated totals from all workers were
displayed, but there was no way to see what each individual worker did
or to distinguish the effort made by the workers from the effort made by
the leader.
Per a gripe by Thom Brown (and maybe others). Patch by me, reviewed
by Amit Kapila.
Diffstat (limited to 'src/backend/commands/explain.c')
-rw-r--r-- | src/backend/commands/explain.c | 251 |
1 files changed, 162 insertions, 89 deletions
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 183d3d9bcb7..12dae778a76 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -103,6 +103,7 @@ static void show_instrumentation_count(const char *qlabel, int which, PlanState *planstate, ExplainState *es); static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es); static const char *explain_get_index_name(Oid indexId); +static void show_buffer_usage(ExplainState *es, const BufferUsage *usage); static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir, ExplainState *es); static void ExplainScanTarget(Scan *plan, ExplainState *es); @@ -1437,108 +1438,73 @@ ExplainNode(PlanState *planstate, List *ancestors, /* Show buffer usage */ if (es->buffers && planstate->instrument) + show_buffer_usage(es, &planstate->instrument->bufusage); + + /* Show worker detail */ + if (es->analyze && es->verbose && planstate->worker_instrument) { - const BufferUsage *usage = &planstate->instrument->bufusage; + WorkerInstrumentation *w = planstate->worker_instrument; + bool opened_group = false; + int n; - if (es->format == EXPLAIN_FORMAT_TEXT) + for (n = 0; n < w->num_workers; ++n) { - bool has_shared = (usage->shared_blks_hit > 0 || - usage->shared_blks_read > 0 || - usage->shared_blks_dirtied > 0 || - usage->shared_blks_written > 0); - bool has_local = (usage->local_blks_hit > 0 || - usage->local_blks_read > 0 || - usage->local_blks_dirtied > 0 || - usage->local_blks_written > 0); - bool has_temp = (usage->temp_blks_read > 0 || - usage->temp_blks_written > 0); - bool has_timing = (!INSTR_TIME_IS_ZERO(usage->blk_read_time) || - !INSTR_TIME_IS_ZERO(usage->blk_write_time)); + Instrumentation *instrument = &w->instrument[n]; + double nloops = instrument->nloops; + double startup_sec; + double total_sec; + double rows; + + if (nloops <= 0) + continue; + startup_sec = 1000.0 * instrument->startup / nloops; + total_sec = 1000.0 * instrument->total / nloops; + rows = instrument->ntuples / nloops; - /* Show only positive counter values. */ - if (has_shared || has_local || has_temp) + if (es->format == EXPLAIN_FORMAT_TEXT) { appendStringInfoSpaces(es->str, es->indent * 2); - appendStringInfoString(es->str, "Buffers:"); - - if (has_shared) - { - appendStringInfoString(es->str, " shared"); - if (usage->shared_blks_hit > 0) - appendStringInfo(es->str, " hit=%ld", - usage->shared_blks_hit); - if (usage->shared_blks_read > 0) - appendStringInfo(es->str, " read=%ld", - usage->shared_blks_read); - if (usage->shared_blks_dirtied > 0) - appendStringInfo(es->str, " dirtied=%ld", - usage->shared_blks_dirtied); - if (usage->shared_blks_written > 0) - appendStringInfo(es->str, " written=%ld", - usage->shared_blks_written); - if (has_local || has_temp) - appendStringInfoChar(es->str, ','); - } - if (has_local) + appendStringInfo(es->str, "Worker %d: ", n); + if (es->timing) + appendStringInfo(es->str, + "actual time=%.3f..%.3f rows=%.0f loops=%.0f\n", + startup_sec, total_sec, rows, nloops); + else + appendStringInfo(es->str, + "actual rows=%.0f loops=%.0f\n", + rows, nloops); + es->indent++; + if (es->buffers) + show_buffer_usage(es, &instrument->bufusage); + es->indent--; + } + else + { + if (!opened_group) { - appendStringInfoString(es->str, " local"); - if (usage->local_blks_hit > 0) - appendStringInfo(es->str, " hit=%ld", - usage->local_blks_hit); - if (usage->local_blks_read > 0) - appendStringInfo(es->str, " read=%ld", - usage->local_blks_read); - if (usage->local_blks_dirtied > 0) - appendStringInfo(es->str, " dirtied=%ld", - usage->local_blks_dirtied); - if (usage->local_blks_written > 0) - appendStringInfo(es->str, " written=%ld", - usage->local_blks_written); - if (has_temp) - appendStringInfoChar(es->str, ','); + ExplainOpenGroup("Workers", "Workers", false, es); + opened_group = true; } - if (has_temp) + ExplainOpenGroup("Worker", NULL, true, es); + ExplainPropertyInteger("Worker Number", n, es); + + if (es->timing) { - appendStringInfoString(es->str, " temp"); - if (usage->temp_blks_read > 0) - appendStringInfo(es->str, " read=%ld", - usage->temp_blks_read); - if (usage->temp_blks_written > 0) - appendStringInfo(es->str, " written=%ld", - usage->temp_blks_written); + ExplainPropertyFloat("Actual Startup Time", startup_sec, 3, es); + ExplainPropertyFloat("Actual Total Time", total_sec, 3, es); } - appendStringInfoChar(es->str, '\n'); - } + ExplainPropertyFloat("Actual Rows", rows, 0, es); + ExplainPropertyFloat("Actual Loops", nloops, 0, es); - /* As above, show only positive counter values. */ - if (has_timing) - { - appendStringInfoSpaces(es->str, es->indent * 2); - appendStringInfoString(es->str, "I/O Timings:"); - if (!INSTR_TIME_IS_ZERO(usage->blk_read_time)) - appendStringInfo(es->str, " read=%0.3f", - INSTR_TIME_GET_MILLISEC(usage->blk_read_time)); - if (!INSTR_TIME_IS_ZERO(usage->blk_write_time)) - appendStringInfo(es->str, " write=%0.3f", - INSTR_TIME_GET_MILLISEC(usage->blk_write_time)); - appendStringInfoChar(es->str, '\n'); + if (es->buffers) + show_buffer_usage(es, &instrument->bufusage); + + ExplainCloseGroup("Worker", NULL, true, es); } } - else - { - ExplainPropertyLong("Shared Hit Blocks", usage->shared_blks_hit, es); - ExplainPropertyLong("Shared Read Blocks", usage->shared_blks_read, es); - ExplainPropertyLong("Shared Dirtied Blocks", usage->shared_blks_dirtied, es); - ExplainPropertyLong("Shared Written Blocks", usage->shared_blks_written, es); - ExplainPropertyLong("Local Hit Blocks", usage->local_blks_hit, es); - ExplainPropertyLong("Local Read Blocks", usage->local_blks_read, es); - ExplainPropertyLong("Local Dirtied Blocks", usage->local_blks_dirtied, es); - ExplainPropertyLong("Local Written Blocks", usage->local_blks_written, es); - ExplainPropertyLong("Temp Read Blocks", usage->temp_blks_read, es); - ExplainPropertyLong("Temp Written Blocks", usage->temp_blks_written, es); - ExplainPropertyFloat("I/O Read Time", INSTR_TIME_GET_MILLISEC(usage->blk_read_time), 3, es); - ExplainPropertyFloat("I/O Write Time", INSTR_TIME_GET_MILLISEC(usage->blk_write_time), 3, es); - } + + if (opened_group) + ExplainCloseGroup("Workers", "Workers", false, es); } /* Get ready to display the child plans */ @@ -2277,6 +2243,113 @@ explain_get_index_name(Oid indexId) } /* + * Show buffer usage details. + */ +static void +show_buffer_usage(ExplainState *es, const BufferUsage *usage) +{ + if (es->format == EXPLAIN_FORMAT_TEXT) + { + bool has_shared = (usage->shared_blks_hit > 0 || + usage->shared_blks_read > 0 || + usage->shared_blks_dirtied > 0 || + usage->shared_blks_written > 0); + bool has_local = (usage->local_blks_hit > 0 || + usage->local_blks_read > 0 || + usage->local_blks_dirtied > 0 || + usage->local_blks_written > 0); + bool has_temp = (usage->temp_blks_read > 0 || + usage->temp_blks_written > 0); + bool has_timing = (!INSTR_TIME_IS_ZERO(usage->blk_read_time) || + !INSTR_TIME_IS_ZERO(usage->blk_write_time)); + + /* Show only positive counter values. */ + if (has_shared || has_local || has_temp) + { + appendStringInfoSpaces(es->str, es->indent * 2); + appendStringInfoString(es->str, "Buffers:"); + + if (has_shared) + { + appendStringInfoString(es->str, " shared"); + if (usage->shared_blks_hit > 0) + appendStringInfo(es->str, " hit=%ld", + usage->shared_blks_hit); + if (usage->shared_blks_read > 0) + appendStringInfo(es->str, " read=%ld", + usage->shared_blks_read); + if (usage->shared_blks_dirtied > 0) + appendStringInfo(es->str, " dirtied=%ld", + usage->shared_blks_dirtied); + if (usage->shared_blks_written > 0) + appendStringInfo(es->str, " written=%ld", + usage->shared_blks_written); + if (has_local || has_temp) + appendStringInfoChar(es->str, ','); + } + if (has_local) + { + appendStringInfoString(es->str, " local"); + if (usage->local_blks_hit > 0) + appendStringInfo(es->str, " hit=%ld", + usage->local_blks_hit); + if (usage->local_blks_read > 0) + appendStringInfo(es->str, " read=%ld", + usage->local_blks_read); + if (usage->local_blks_dirtied > 0) + appendStringInfo(es->str, " dirtied=%ld", + usage->local_blks_dirtied); + if (usage->local_blks_written > 0) + appendStringInfo(es->str, " written=%ld", + usage->local_blks_written); + if (has_temp) + appendStringInfoChar(es->str, ','); + } + if (has_temp) + { + appendStringInfoString(es->str, " temp"); + if (usage->temp_blks_read > 0) + appendStringInfo(es->str, " read=%ld", + usage->temp_blks_read); + if (usage->temp_blks_written > 0) + appendStringInfo(es->str, " written=%ld", + usage->temp_blks_written); + } + appendStringInfoChar(es->str, '\n'); + } + + /* As above, show only positive counter values. */ + if (has_timing) + { + appendStringInfoSpaces(es->str, es->indent * 2); + appendStringInfoString(es->str, "I/O Timings:"); + if (!INSTR_TIME_IS_ZERO(usage->blk_read_time)) + appendStringInfo(es->str, " read=%0.3f", + INSTR_TIME_GET_MILLISEC(usage->blk_read_time)); + if (!INSTR_TIME_IS_ZERO(usage->blk_write_time)) + appendStringInfo(es->str, " write=%0.3f", + INSTR_TIME_GET_MILLISEC(usage->blk_write_time)); + appendStringInfoChar(es->str, '\n'); + } + } + else + { + ExplainPropertyLong("Shared Hit Blocks", usage->shared_blks_hit, es); + ExplainPropertyLong("Shared Read Blocks", usage->shared_blks_read, es); + ExplainPropertyLong("Shared Dirtied Blocks", usage->shared_blks_dirtied, es); + ExplainPropertyLong("Shared Written Blocks", usage->shared_blks_written, es); + ExplainPropertyLong("Local Hit Blocks", usage->local_blks_hit, es); + ExplainPropertyLong("Local Read Blocks", usage->local_blks_read, es); + ExplainPropertyLong("Local Dirtied Blocks", usage->local_blks_dirtied, es); + ExplainPropertyLong("Local Written Blocks", usage->local_blks_written, es); + ExplainPropertyLong("Temp Read Blocks", usage->temp_blks_read, es); + ExplainPropertyLong("Temp Written Blocks", usage->temp_blks_written, es); + ExplainPropertyFloat("I/O Read Time", INSTR_TIME_GET_MILLISEC(usage->blk_read_time), 3, es); + ExplainPropertyFloat("I/O Write Time", INSTR_TIME_GET_MILLISEC(usage->blk_write_time), 3, es); + } +} + +/* * Add some additional details about an IndexScan or IndexOnlyScan */ static void |