Maciek Sakrejda <m.sakre...@gmail.com> writes: > For what it's worth, this version makes sense to me.
Thanks for looking. Here's a version that deals with the JIT instrumentation. As Andres noted far upthread, that was also really bogusly done before. Not only could you get multiple "JIT" subnodes on a Gather node, but we failed to print the info at all if the parallelism was expressed as Gather Merge rather than Gather. A side effect of this change is that per-worker JIT info is now printed one plan level further down: before it was printed on the Gather node, but now it's attached to the Gather's child, because that's where we print other per-worker data. I don't see anything particularly wrong with that, but it's another change from the behavior today. It's still really unclear to me how we could exercise any of this behavior meaningfully in a regression test. I thought for a little bit about using the TAP infrastructure instead of a traditional-style test, but it seems like that doesn't buy anything except for a bias towards ignoring details instead of overspecifying them. Which is not much of an improvement. regards, tom lane
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index d189b8d..fe3c83b 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -57,6 +57,8 @@ static void ExplainOneQuery(Query *query, int cursorOptions, IntoClause *into, ExplainState *es, const char *queryString, ParamListInfo params, QueryEnvironment *queryEnv); +static void ExplainPrintJIT(ExplainState *es, int jit_flags, + JitInstrumentation *ji); static void report_triggers(ResultRelInfo *rInfo, bool show_relname, ExplainState *es); static double elapsed_time(instr_time *starttime); @@ -123,11 +125,20 @@ static void ExplainSubPlans(List *plans, List *ancestors, const char *relationship, ExplainState *es); static void ExplainCustomChildren(CustomScanState *css, List *ancestors, ExplainState *es); +static ExplainWorkersState *ExplainCreateWorkersState(int num_workers); +static void ExplainOpenWorker(int n, ExplainState *es); +static void ExplainCloseWorker(int n, ExplainState *es); +static void ExplainFlushWorkersState(ExplainState *es); static void ExplainProperty(const char *qlabel, const char *unit, const char *value, bool numeric, ExplainState *es); +static void ExplainOpenSetAsideGroup(const char *objtype, const char *labelname, + bool labeled, int depth, ExplainState *es); +static void ExplainSaveGroup(ExplainState *es, int depth, int *state_save); +static void ExplainRestoreGroup(ExplainState *es, int depth, int *state_save); static void ExplainDummyGroup(const char *objtype, const char *labelname, ExplainState *es); static void ExplainXMLTag(const char *tagname, int flags, ExplainState *es); +static void ExplainIndentText(ExplainState *es); static void ExplainJSONLineEnding(ExplainState *es); static void ExplainYAMLLineStarting(ExplainState *es); static void escape_yaml(StringInfo buf, const char *str); @@ -790,31 +801,22 @@ ExplainPrintJITSummary(ExplainState *es, QueryDesc *queryDesc) if (queryDesc->estate->es_jit_worker_instr) InstrJitAgg(&ji, queryDesc->estate->es_jit_worker_instr); - ExplainPrintJIT(es, queryDesc->estate->es_jit_flags, &ji, -1); + ExplainPrintJIT(es, queryDesc->estate->es_jit_flags, &ji); } /* * ExplainPrintJIT - * Append information about JITing to es->str. - * - * Can be used to print the JIT instrumentation of the backend (worker_num = - * -1) or that of a specific worker (worker_num = ...). */ -void -ExplainPrintJIT(ExplainState *es, int jit_flags, - JitInstrumentation *ji, int worker_num) +static void +ExplainPrintJIT(ExplainState *es, int jit_flags, JitInstrumentation *ji) { instr_time total_time; - bool for_workers = (worker_num >= 0); /* don't print information if no JITing happened */ if (!ji || ji->created_functions == 0) return; - /* don't print per-worker info if we're supposed to hide that */ - if (for_workers && es->hide_workers) - return; - /* calculate total time */ INSTR_TIME_SET_ZERO(total_time); INSTR_TIME_ADD(total_time, ji->generation_counter); @@ -827,16 +829,13 @@ ExplainPrintJIT(ExplainState *es, int jit_flags, /* for higher density, open code the text output format */ if (es->format == EXPLAIN_FORMAT_TEXT) { - appendStringInfoSpaces(es->str, es->indent * 2); - if (for_workers) - appendStringInfo(es->str, "JIT for worker %u:\n", worker_num); - else - appendStringInfoString(es->str, "JIT:\n"); - es->indent += 1; + ExplainIndentText(es); + appendStringInfoString(es->str, "JIT:\n"); + es->indent++; ExplainPropertyInteger("Functions", NULL, ji->created_functions, es); - appendStringInfoSpaces(es->str, es->indent * 2); + ExplainIndentText(es); appendStringInfo(es->str, "Options: %s %s, %s %s, %s %s, %s %s\n", "Inlining", jit_flags & PGJIT_INLINE ? "true" : "false", "Optimization", jit_flags & PGJIT_OPT3 ? "true" : "false", @@ -845,7 +844,7 @@ ExplainPrintJIT(ExplainState *es, int jit_flags, if (es->analyze && es->timing) { - appendStringInfoSpaces(es->str, es->indent * 2); + ExplainIndentText(es); appendStringInfo(es->str, "Timing: %s %.3f ms, %s %.3f ms, %s %.3f ms, %s %.3f ms, %s %.3f ms\n", "Generation", 1000.0 * INSTR_TIME_GET_DOUBLE(ji->generation_counter), @@ -855,11 +854,10 @@ ExplainPrintJIT(ExplainState *es, int jit_flags, "Total", 1000.0 * INSTR_TIME_GET_DOUBLE(total_time)); } - es->indent -= 1; + es->indent--; } else { - ExplainPropertyInteger("Worker Number", NULL, worker_num, es); ExplainPropertyInteger("Functions", NULL, ji->created_functions, es); ExplainOpenGroup("Options", "Options", true, es); @@ -1074,9 +1072,10 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used) * optional name to be attached to the node. * * In text format, es->indent is controlled in this function since we only - * want it to change at plan-node boundaries. In non-text formats, es->indent - * corresponds to the nesting depth of logical output groups, and therefore - * is controlled by ExplainOpenGroup/ExplainCloseGroup. + * want it to change at plan-node boundaries (but a few subroutines will + * transiently increment it). In non-text formats, es->indent corresponds + * to the nesting depth of logical output groups, and therefore is controlled + * by ExplainOpenGroup/ExplainCloseGroup. */ static void ExplainNode(PlanState *planstate, List *ancestors, @@ -1090,9 +1089,20 @@ ExplainNode(PlanState *planstate, List *ancestors, const char *partialmode = NULL; const char *operation = NULL; const char *custom_name = NULL; + ExplainWorkersState *save_workers_state = es->workers_state; int save_indent = es->indent; bool haschildren; + /* + * Prepare per-worker output buffers, if needed. We'll append the data in + * these to the main output string further down. + */ + if (planstate->worker_instrument && es->analyze && !es->hide_workers) + es->workers_state = ExplainCreateWorkersState(planstate->worker_instrument->num_workers); + else + es->workers_state = NULL; + + /* Identify plan node type, and print generic details */ switch (nodeTag(plan)) { case T_Result: @@ -1324,13 +1334,13 @@ ExplainNode(PlanState *planstate, List *ancestors, { if (plan_name) { - appendStringInfoSpaces(es->str, es->indent * 2); + ExplainIndentText(es); appendStringInfo(es->str, "%s\n", plan_name); es->indent++; } if (es->indent) { - appendStringInfoSpaces(es->str, es->indent * 2); + ExplainIndentText(es); appendStringInfoString(es->str, "-> "); es->indent += 2; } @@ -1574,6 +1584,56 @@ ExplainNode(PlanState *planstate, List *ancestors, if (es->format == EXPLAIN_FORMAT_TEXT) appendStringInfoChar(es->str, '\n'); + /* prepare per-worker general execution details */ + if (es->workers_state && es->verbose) + { + WorkerInstrumentation *w = planstate->worker_instrument; + + for (int n = 0; n < w->num_workers; n++) + { + Instrumentation *instrument = &w->instrument[n]; + double nloops = instrument->nloops; + double startup_ms; + double total_ms; + double rows; + + if (nloops <= 0) + continue; + startup_ms = 1000.0 * instrument->startup / nloops; + total_ms = 1000.0 * instrument->total / nloops; + rows = instrument->ntuples / nloops; + + ExplainOpenWorker(n, es); + + if (es->format == EXPLAIN_FORMAT_TEXT) + { + ExplainIndentText(es); + if (es->timing) + appendStringInfo(es->str, + "actual time=%.3f..%.3f rows=%.0f loops=%.0f\n", + startup_ms, total_ms, rows, nloops); + else + appendStringInfo(es->str, + "actual rows=%.0f loops=%.0f\n", + rows, nloops); + } + else + { + if (es->timing) + { + ExplainPropertyFloat("Actual Startup Time", "ms", + startup_ms, 3, es); + ExplainPropertyFloat("Actual Total Time", "ms", + total_ms, 3, es); + } + ExplainPropertyFloat("Actual Rows", NULL, rows, 0, es); + ExplainPropertyFloat("Actual Loops", NULL, nloops, 0, es); + } + + ExplainCloseWorker(n, es); + } + } + /* target list */ if (es->verbose) show_plan_tlist(planstate, ancestors, es); @@ -1684,24 +1744,6 @@ ExplainNode(PlanState *planstate, List *ancestors, nworkers, es); } - /* - * Print per-worker Jit instrumentation. Use same conditions - * as for the leader's JIT instrumentation, see comment there. - */ - if (es->costs && es->verbose && - outerPlanState(planstate)->worker_jit_instrument) - { - PlanState *child = outerPlanState(planstate); - int n; - SharedJitInstrumentation *w = child->worker_jit_instrument; - - for (n = 0; n < w->num_workers; ++n) - { - ExplainPrintJIT(es, child->state->es_jit_flags, - &w->jit_instr[n], n); - } - } - if (gather->single_copy || es->format != EXPLAIN_FORMAT_TEXT) ExplainPropertyBool("Single Copy", gather->single_copy, es); } @@ -1881,80 +1923,54 @@ ExplainNode(PlanState *planstate, List *ancestors, break; } + /* + * Prepare per-worker JIT instrumentation. As with the overall JIT + * summary, this is printed only if printing costs is enabled. + */ + if (es->workers_state && es->costs && es->verbose) + { + SharedJitInstrumentation *w = planstate->worker_jit_instrument; + + if (w) + { + for (int n = 0; n < w->num_workers; n++) + { + ExplainOpenWorker(n, es); + ExplainPrintJIT(es, planstate->state->es_jit_flags, + &w->jit_instr[n]); + ExplainCloseWorker(n, es); + } + } + } + /* Show buffer usage */ if (es->buffers && planstate->instrument) show_buffer_usage(es, &planstate->instrument->bufusage); - /* Show worker detail */ - if (es->analyze && es->verbose && !es->hide_workers && - planstate->worker_instrument) + /* Prepare per-worker buffer usage */ + if (es->workers_state && es->buffers && es->verbose) { WorkerInstrumentation *w = planstate->worker_instrument; - bool opened_group = false; - int n; - for (n = 0; n < w->num_workers; ++n) + for (int n = 0; n < w->num_workers; n++) { Instrumentation *instrument = &w->instrument[n]; double nloops = instrument->nloops; - double startup_ms; - double total_ms; - double rows; if (nloops <= 0) continue; - startup_ms = 1000.0 * instrument->startup / nloops; - total_ms = 1000.0 * instrument->total / nloops; - rows = instrument->ntuples / nloops; - - if (es->format == EXPLAIN_FORMAT_TEXT) - { - appendStringInfoSpaces(es->str, es->indent * 2); - appendStringInfo(es->str, "Worker %d: ", n); - if (es->timing) - appendStringInfo(es->str, - "actual time=%.3f..%.3f rows=%.0f loops=%.0f\n", - startup_ms, total_ms, 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) - { - ExplainOpenGroup("Workers", "Workers", false, es); - opened_group = true; - } - ExplainOpenGroup("Worker", NULL, true, es); - ExplainPropertyInteger("Worker Number", NULL, n, es); - - if (es->timing) - { - ExplainPropertyFloat("Actual Startup Time", "ms", - startup_ms, 3, es); - ExplainPropertyFloat("Actual Total Time", "ms", - total_ms, 3, es); - } - ExplainPropertyFloat("Actual Rows", NULL, rows, 0, es); - ExplainPropertyFloat("Actual Loops", NULL, nloops, 0, es); - if (es->buffers) - show_buffer_usage(es, &instrument->bufusage); - - ExplainCloseGroup("Worker", NULL, true, es); - } + ExplainOpenWorker(n, es); + show_buffer_usage(es, &instrument->bufusage); + ExplainCloseWorker(n, es); } - - if (opened_group) - ExplainCloseGroup("Workers", "Workers", false, es); } + /* Show per-worker details for this plan node, then pop that stack */ + if (es->workers_state) + ExplainFlushWorkersState(es); + es->workers_state = save_workers_state; + /* Get ready to display the child plans */ haschildren = planstate->initPlan || outerPlanState(planstate) || @@ -2525,7 +2541,7 @@ show_tablesample(TableSampleClause *tsc, PlanState *planstate, { bool first = true; - appendStringInfoSpaces(es->str, es->indent * 2); + ExplainIndentText(es); appendStringInfo(es->str, "Sampling: %s (", method_name); foreach(lc, params) { @@ -2572,7 +2588,7 @@ show_sort_info(SortState *sortstate, ExplainState *es) if (es->format == EXPLAIN_FORMAT_TEXT) { - appendStringInfoSpaces(es->str, es->indent * 2); + ExplainIndentText(es); appendStringInfo(es->str, "Sort Method: %s %s: %ldkB\n", sortMethod, spaceType, spaceUsed); } @@ -2588,12 +2604,14 @@ show_sort_info(SortState *sortstate, ExplainState *es) * You might think we should just skip this stanza entirely when * es->hide_workers is true, but then we'd get no sort-method output at * all. We have to make it look like worker 0's data is top-level data. - * Currently, we only bother with that for text-format output. + * This is easily done by just skipping the OpenWorker/CloseWorker calls. + * Currently, we don't worry about the possibility that there are multiple + * workers in such a case; if there are, duplicate output fields will be + * emitted. */ if (sortstate->shared_info != NULL) { int n; - bool opened_group = false; for (n = 0; n < sortstate->shared_info->num_workers; n++) { @@ -2609,32 +2627,26 @@ show_sort_info(SortState *sortstate, ExplainState *es) spaceType = tuplesort_space_type_name(sinstrument->spaceType); spaceUsed = sinstrument->spaceUsed; + if (es->workers_state) + ExplainOpenWorker(n, es); + if (es->format == EXPLAIN_FORMAT_TEXT) { - appendStringInfoSpaces(es->str, es->indent * 2); - if (n > 0 || !es->hide_workers) - appendStringInfo(es->str, "Worker %d: ", n); + ExplainIndentText(es); appendStringInfo(es->str, "Sort Method: %s %s: %ldkB\n", sortMethod, spaceType, spaceUsed); } else { - if (!opened_group) - { - ExplainOpenGroup("Workers", "Workers", false, es); - opened_group = true; - } - ExplainOpenGroup("Worker", NULL, true, es); - ExplainPropertyInteger("Worker Number", NULL, n, es); ExplainPropertyText("Sort Method", sortMethod, es); ExplainPropertyInteger("Sort Space Used", "kB", spaceUsed, es); ExplainPropertyText("Sort Space Type", spaceType, es); - ExplainCloseGroup("Worker", NULL, true, es); } + + if (es->workers_state) + ExplainCloseWorker(n, es); } - if (opened_group) - ExplainCloseGroup("Workers", "Workers", false, es); } } @@ -2721,7 +2733,7 @@ show_hash_info(HashState *hashstate, ExplainState *es) else if (hinstrument.nbatch_original != hinstrument.nbatch || hinstrument.nbuckets_original != hinstrument.nbuckets) { - appendStringInfoSpaces(es->str, es->indent * 2); + ExplainIndentText(es); appendStringInfo(es->str, "Buckets: %d (originally %d) Batches: %d (originally %d) Memory Usage: %ldkB\n", hinstrument.nbuckets, @@ -2732,7 +2744,7 @@ show_hash_info(HashState *hashstate, ExplainState *es) } else { - appendStringInfoSpaces(es->str, es->indent * 2); + ExplainIndentText(es); appendStringInfo(es->str, "Buckets: %d Batches: %d Memory Usage: %ldkB\n", hinstrument.nbuckets, hinstrument.nbatch, @@ -2758,7 +2770,7 @@ show_tidbitmap_info(BitmapHeapScanState *planstate, ExplainState *es) { if (planstate->exact_pages > 0 || planstate->lossy_pages > 0) { - appendStringInfoSpaces(es->str, es->indent * 2); + ExplainIndentText(es); appendStringInfoString(es->str, "Heap Blocks:"); if (planstate->exact_pages > 0) appendStringInfo(es->str, " exact=%ld", planstate->exact_pages); @@ -2894,7 +2906,7 @@ show_buffer_usage(ExplainState *es, const BufferUsage *usage) /* Show only positive counter values. */ if (has_shared || has_local || has_temp) { - appendStringInfoSpaces(es->str, es->indent * 2); + ExplainIndentText(es); appendStringInfoString(es->str, "Buffers:"); if (has_shared) @@ -2949,7 +2961,7 @@ show_buffer_usage(ExplainState *es, const BufferUsage *usage) /* As above, show only positive counter values. */ if (has_timing) { - appendStringInfoSpaces(es->str, es->indent * 2); + ExplainIndentText(es); appendStringInfoString(es->str, "I/O Timings:"); if (!INSTR_TIME_IS_ZERO(usage->blk_read_time)) appendStringInfo(es->str, " read=%0.3f", @@ -3237,7 +3249,7 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors, */ if (es->format == EXPLAIN_FORMAT_TEXT) { - appendStringInfoSpaces(es->str, es->indent * 2); + ExplainIndentText(es); appendStringInfoString(es->str, fdwroutine ? foperation : operation); } @@ -3427,6 +3439,158 @@ ExplainCustomChildren(CustomScanState *css, List *ancestors, ExplainState *es) } /* + * Create a per-plan-node workspace for collecting per-worker data. + * + * Output related to each worker will be temporarily "set aside" into a + * separate buffer, which we'll merge into the main output stream once + * we've processed all data for the plan node. This makes it feasible to + * generate a coherent sub-group of fields for each worker, even though the + * code that produces the fields is in several different places in this file. + * Formatting of such a set-aside field group is managed by + * ExplainOpenSetAsideGroup and ExplainSaveGroup/ExplainRestoreGroup. + */ +static ExplainWorkersState * +ExplainCreateWorkersState(int num_workers) +{ + ExplainWorkersState *wstate; + + wstate = (ExplainWorkersState *) palloc(sizeof(ExplainWorkersState)); + wstate->num_workers = num_workers; + wstate->worker_inited = (bool *) palloc0(num_workers * sizeof(bool)); + wstate->worker_str = (StringInfoData *) + palloc0(num_workers * sizeof(StringInfoData)); + wstate->worker_state_save = (int *) palloc(num_workers * sizeof(int)); + return wstate; +} + +/* + * Begin or resume output into the set-aside group for worker N. + */ +static void +ExplainOpenWorker(int n, ExplainState *es) +{ + ExplainWorkersState *wstate = es->workers_state; + + Assert(wstate); + Assert(n >= 0 && n < wstate->num_workers); + + /* Save prior output buffer pointer */ + wstate->prev_str = es->str; + + if (!wstate->worker_inited[n]) + { + /* First time through, so create the buffer for this worker */ + initStringInfo(&wstate->worker_str[n]); + es->str = &wstate->worker_str[n]; + + /* + * Push suitable initial formatting state for this worker's field + * group. We allow one extra logical nesting level, since this group + * will eventually be wrapped in an outer "Workers" group. + */ + ExplainOpenSetAsideGroup("Worker", NULL, true, 2, es); + + /* + * In non-TEXT formats we always emit a "Worker Number" field, even if + * there's no other data for this worker. + */ + if (es->format != EXPLAIN_FORMAT_TEXT) + ExplainPropertyInteger("Worker Number", NULL, n, es); + + wstate->worker_inited[n] = true; + } + else + { + /* Resuming output for a worker we've already emitted some data for */ + es->str = &wstate->worker_str[n]; + + /* Restore formatting state saved by last ExplainCloseWorker() */ + ExplainRestoreGroup(es, 2, &wstate->worker_state_save[n]); + } + + /* + * In TEXT format, prefix the first output line for this worker with + * "Worker N:". Then, any additional lines should be indented one more + * stop than the "Worker N" line is. + */ + if (es->format == EXPLAIN_FORMAT_TEXT) + { + if (es->str->len == 0) + { + ExplainIndentText(es); + appendStringInfo(es->str, "Worker %d: ", n); + } + + es->indent++; + } +} + +/* + * End output for worker N --- must pair with previous ExplainOpenWorker call + */ +static void +ExplainCloseWorker(int n, ExplainState *es) +{ + ExplainWorkersState *wstate = es->workers_state; + + Assert(wstate); + Assert(n >= 0 && n < wstate->num_workers); + Assert(wstate->worker_inited[n]); + + /* + * Save formatting state in case we do another ExplainOpenWorker(), then + * pop the formatting stack. + */ + ExplainSaveGroup(es, 2, &wstate->worker_state_save[n]); + + /* + * In TEXT format, if we didn't actually produce any output line(s) then + * truncate off the partial line. (This is to avoid bogus output if, for + * instance, show_buffer_usage chooses not to print anything for the + * worker.) Also fix up the indent level. + */ + if (es->format == EXPLAIN_FORMAT_TEXT) + { + while (es->str->len > 0 && es->str->data[es->str->len - 1] != '\n') + es->str->data[--(es->str->len)] = '\0'; + + es->indent--; + } + + /* Restore prior output buffer pointer */ + es->str = wstate->prev_str; +} + +/* + * Print per-worker info for current node, then free the ExplainWorkersState. + */ +static void +ExplainFlushWorkersState(ExplainState *es) +{ + ExplainWorkersState *wstate = es->workers_state; + + ExplainOpenGroup("Workers", "Workers", false, es); + for (int i = 0; i < wstate->num_workers; i++) + { + if (wstate->worker_inited[i]) + { + /* This must match previous ExplainOpenSetAsideGroup call */ + ExplainOpenGroup("Worker", NULL, true, es); + appendStringInfoString(es->str, wstate->worker_str[i].data); + ExplainCloseGroup("Worker", NULL, true, es); + + pfree(wstate->worker_str[i].data); + } + } + ExplainCloseGroup("Workers", "Workers", false, es); + + pfree(wstate->worker_inited); + pfree(wstate->worker_str); + pfree(wstate->worker_state_save); + pfree(wstate); +} + +/* * Explain a property, such as sort keys or targets, that takes the form of * a list of unlabeled items. "data" is a list of C strings. */ @@ -3439,7 +3603,7 @@ ExplainPropertyList(const char *qlabel, List *data, ExplainState *es) switch (es->format) { case EXPLAIN_FORMAT_TEXT: - appendStringInfoSpaces(es->str, es->indent * 2); + ExplainIndentText(es); appendStringInfo(es->str, "%s: ", qlabel); foreach(lc, data) { @@ -3560,7 +3724,7 @@ ExplainProperty(const char *qlabel, const char *unit, const char *value, switch (es->format) { case EXPLAIN_FORMAT_TEXT: - appendStringInfoSpaces(es->str, es->indent * 2); + ExplainIndentText(es); if (unit) appendStringInfo(es->str, "%s: %s %s\n", qlabel, value, unit); else @@ -3752,6 +3916,117 @@ ExplainCloseGroup(const char *objtype, const char *labelname, } /* + * Open a group of related objects, without emitting actual data. + * + * Prepare the formatting state as though we were beginning a group with + * the identified properties, but don't actually emit anything. Output + * subsequent to this call can be redirected into a separate output buffer, + * and then eventually appended to the main output buffer after doing a + * regular ExplainOpenGroup call (with the same parameters). + * + * The extra "depth" parameter is the new group's depth compared to current. + * It could be more than one, in case the eventual output will be enclosed + * in additional nesting group levels. We assume we don't need to track + * formatting state for those levels while preparing this group's output. + * + * There is no ExplainCloseSetAsideGroup --- in current usage, we always + * pop this state with ExplainSaveGroup. + */ +static void +ExplainOpenSetAsideGroup(const char *objtype, const char *labelname, + bool labeled, int depth, ExplainState *es) +{ + switch (es->format) + { + case EXPLAIN_FORMAT_TEXT: + /* nothing to do */ + break; + + case EXPLAIN_FORMAT_XML: + es->indent += depth; + break; + + case EXPLAIN_FORMAT_JSON: + es->grouping_stack = lcons_int(0, es->grouping_stack); + es->indent += depth; + break; + + case EXPLAIN_FORMAT_YAML: + if (labelname) + es->grouping_stack = lcons_int(1, es->grouping_stack); + else + es->grouping_stack = lcons_int(0, es->grouping_stack); + es->indent += depth; + break; + } +} + +/* + * Pop one level of grouping state, allowing for a re-push later. + * + * This is typically used after ExplainOpenSetAsideGroup; pass the + * same "depth" used for that. + * + * This should not emit any output. If state needs to be saved, + * save it at *state_save. Currently, an integer save area is sufficient + * for all formats, but we might need to revisit that someday. + */ +static void +ExplainSaveGroup(ExplainState *es, int depth, int *state_save) +{ + switch (es->format) + { + case EXPLAIN_FORMAT_TEXT: + /* nothing to do */ + break; + + case EXPLAIN_FORMAT_XML: + es->indent -= depth; + break; + + case EXPLAIN_FORMAT_JSON: + es->indent -= depth; + *state_save = linitial_int(es->grouping_stack); + es->grouping_stack = list_delete_first(es->grouping_stack); + break; + + case EXPLAIN_FORMAT_YAML: + es->indent -= depth; + *state_save = linitial_int(es->grouping_stack); + es->grouping_stack = list_delete_first(es->grouping_stack); + break; + } +} + +/* + * Re-push one level of grouping state, undoing the effects of ExplainSaveGroup. + */ +static void +ExplainRestoreGroup(ExplainState *es, int depth, int *state_save) +{ + switch (es->format) + { + case EXPLAIN_FORMAT_TEXT: + /* nothing to do */ + break; + + case EXPLAIN_FORMAT_XML: + es->indent += depth; + break; + + case EXPLAIN_FORMAT_JSON: + es->grouping_stack = lcons_int(*state_save, es->grouping_stack); + es->indent += depth; + break; + + case EXPLAIN_FORMAT_YAML: + es->grouping_stack = lcons_int(*state_save, es->grouping_stack); + es->indent += depth; + break; + } +} + +/* * Emit a "dummy" group that never has any members. * * objtype is the type of the group object, labelname is its label within @@ -3913,6 +4188,21 @@ ExplainXMLTag(const char *tagname, int flags, ExplainState *es) } /* + * Indent a text-format line. + * + * We indent by two spaces per indentation level. However, when emitting + * data for a parallel worker there might already be data on the current line + * (cf. ExplainOpenWorker); in that case, don't indent any more. + */ +static void +ExplainIndentText(ExplainState *es) +{ + Assert(es->format == EXPLAIN_FORMAT_TEXT); + if (es->str->len == 0 || es->str->data[es->str->len - 1] == '\n') + appendStringInfoSpaces(es->str, es->indent * 2); +} + +/* * Emit a JSON line ending. * * JSON requires a comma after each property but the last. To facilitate this, diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h index 45027a7..54f6240 100644 --- a/src/include/commands/explain.h +++ b/src/include/commands/explain.h @@ -25,6 +25,15 @@ typedef enum ExplainFormat EXPLAIN_FORMAT_YAML } ExplainFormat; +typedef struct ExplainWorkersState +{ + int num_workers; /* # of worker processes the plan used */ + bool *worker_inited; /* per-worker state-initialized flags */ + StringInfoData *worker_str; /* per-worker transient output buffers */ + int *worker_state_save; /* per-worker grouping state save areas */ + StringInfo prev_str; /* saved output buffer while redirecting */ +} ExplainWorkersState; + typedef struct ExplainState { StringInfo str; /* output buffer */ @@ -47,6 +56,8 @@ typedef struct ExplainState List *deparse_cxt; /* context list for deparsing expressions */ Bitmapset *printed_subplans; /* ids of SubPlans we've printed */ bool hide_workers; /* set if we find an invisible Gather */ + /* state related to the current plan node */ + ExplainWorkersState *workers_state; /* needed if parallel plan */ } ExplainState; /* Hook for plugins to get control in ExplainOneQuery() */ @@ -84,8 +95,6 @@ extern void ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc); extern void ExplainPrintTriggers(ExplainState *es, QueryDesc *queryDesc); extern void ExplainPrintJITSummary(ExplainState *es, QueryDesc *queryDesc); -extern void ExplainPrintJIT(ExplainState *es, int jit_flags, - struct JitInstrumentation *jit_instr, int worker_num); extern void ExplainQueryText(ExplainState *es, QueryDesc *queryDesc);