| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * Copyright (c) 2026 Tiger Data, Inc. | ||
| 3 | * Licensed under the PostgreSQL License. See LICENSE for details. | ||
| 4 | * | ||
| 5 | * explain.c - EXPLAIN ANALYZE output for prism scans | ||
| 6 | * | ||
| 7 | * Injects scan stats into EXPLAIN (ANALYZE, VERBOSE) output via | ||
| 8 | * PG's explain_per_node_hook. Shows centroid search, posting scan, | ||
| 9 | * and rerank statistics for prism index scans. | ||
| 10 | */ | ||
| 11 | |||
| 12 | #include <postgres.h> | ||
| 13 | |||
| 14 | #include "vs_config.h" | ||
| 15 | |||
| 16 | #include <commands/defrem.h> | ||
| 17 | #include <commands/explain.h> | ||
| 18 | #include <commands/explain_format.h> | ||
| 19 | #include <commands/explain_state.h> | ||
| 20 | #include <nodes/execnodes.h> | ||
| 21 | #include <utils/lsyscache.h> | ||
| 22 | #include <utils/rel.h> | ||
| 23 | |||
| 24 | #include "core/platform.h" | ||
| 25 | #include "explain.h" | ||
| 26 | #include "scan.h" | ||
| 27 | |||
| 28 | static explain_per_node_hook_type prev_hook = NULL; | ||
| 29 | |||
| 30 | static void | ||
| 31 | 196 | prism_explain_hook( | |
| 32 | PlanState *planstate, | ||
| 33 | List *ancestors, | ||
| 34 | const char *relationship, | ||
| 35 | const char *plan_name, | ||
| 36 | ExplainState *es) | ||
| 37 | { | ||
| 38 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 196 times.
|
196 | if (prev_hook) |
| 39 | ✗ | prev_hook(planstate, ancestors, relationship, plan_name, es); | |
| 40 | |||
| 41 |
4/4✓ Branch 0 taken 66 times.
✓ Branch 1 taken 130 times.
✓ Branch 2 taken 59 times.
✓ Branch 3 taken 7 times.
|
196 | if (!es->analyze || !es->verbose) |
| 42 | return; | ||
| 43 | |||
| 44 |
2/2✓ Branch 0 taken 20 times.
✓ Branch 1 taken 39 times.
|
59 | if (!IsA(planstate, IndexScanState)) |
| 45 | return; | ||
| 46 | |||
| 47 | 20 | IndexScanState *iss = (IndexScanState *)planstate; | |
| 48 | 20 | IndexScanDesc scan = iss->iss_ScanDesc; | |
| 49 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | if (scan == NULL) |
| 50 | return; | ||
| 51 | |||
| 52 | 20 | Relation rel = scan->indexRelation; | |
| 53 | 20 | char *am_name = get_am_name(rel->rd_rel->relam); | |
| 54 |
2/4✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 20 times.
|
20 | if (am_name == NULL || strcmp(am_name, VS_AM_NAME) != 0) |
| 55 | { | ||
| 56 | ✗ | if (am_name != NULL) | |
| 57 | ✗ | pfree(am_name); | |
| 58 | ✗ | return; | |
| 59 | } | ||
| 60 | 20 | pfree(am_name); | |
| 61 | |||
| 62 | 20 | const PrismScanStats *stats = prism_scan_get_stats(scan); | |
| 63 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | if (stats == NULL) |
| 64 | return; | ||
| 65 | |||
| 66 | 20 | ExplainOpenGroup("Prism", "Prism", true, es); | |
| 67 | |||
| 68 | 20 | ExplainPropertyInteger("Top-K", NULL, stats->top_k, es); | |
| 69 | 20 | ExplainPropertyInteger( | |
| 70 | 20 | "Posting Lists Scanned", NULL, stats->clusters_scanned, es); | |
| 71 | 20 | ExplainPropertyInteger( | |
| 72 | 20 | "Centroid Pages Read", NULL, stats->centroid_pages_read, es); | |
| 73 | 20 | ExplainPropertyInteger( | |
| 74 | 20 | "Posting Pages Read", NULL, stats->posting_pages_read, es); | |
| 75 | 20 | ExplainPropertyInteger( | |
| 76 | "Posting Pages Scanned", | ||
| 77 | NULL, | ||
| 78 | 20 | stats->posting_pages_read - stats->posting_pages_skipped, | |
| 79 | es); | ||
| 80 | 20 | ExplainPropertyInteger( | |
| 81 | "Posting Dead Pages Skipped", | ||
| 82 | NULL, | ||
| 83 | 20 | stats->posting_pages_skipped, | |
| 84 | es); | ||
| 85 | 20 | ExplainPropertyInteger( | |
| 86 | "Posting Entries Scanned", | ||
| 87 | NULL, | ||
| 88 | 20 | stats->posting_entries_scanned, | |
| 89 | es); | ||
| 90 | 20 | ExplainPropertyInteger( | |
| 91 | 20 | "Rerank Candidates", NULL, stats->rerank_candidates, es); | |
| 92 | 20 | ExplainPropertyInteger("Rerank Results", NULL, stats->rerank_results, es); | |
| 93 | 20 | ExplainPropertyInteger("Storage Reads", NULL, stats->storage_reads, es); | |
| 94 | |||
| 95 | /* Per-phase wall time (ms). Emitted only under EXPLAIN (ANALYZE, | ||
| 96 | * VERBOSE, TIMING ON) so regression output stays stable without it. */ | ||
| 97 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | if (es->timing) |
| 98 | { | ||
| 99 | 20 | ExplainPropertyFloat( | |
| 100 | "Centroid Search Time", | ||
| 101 | "ms", | ||
| 102 | 20 | (double)stats->centroid_ns / VS_NS_PER_MS, | |
| 103 | 3, | ||
| 104 | es); | ||
| 105 | 20 | ExplainPropertyFloat( | |
| 106 | "Posting Scan Time", | ||
| 107 | "ms", | ||
| 108 | 20 | (double)stats->posting_ns / VS_NS_PER_MS, | |
| 109 | 3, | ||
| 110 | es); | ||
| 111 | 20 | ExplainPropertyFloat( | |
| 112 | "Rerank Time", | ||
| 113 | "ms", | ||
| 114 | 20 | (double)stats->rerank_ns / VS_NS_PER_MS, | |
| 115 | 3, | ||
| 116 | es); | ||
| 117 | } | ||
| 118 | |||
| 119 | 20 | ExplainCloseGroup("Prism", "Prism", true, es); | |
| 120 | } | ||
| 121 | |||
| 122 | void | ||
| 123 | 255 | prism_explain_init(void) | |
| 124 | { | ||
| 125 | 255 | prev_hook = explain_per_node_hook; | |
| 126 | 255 | explain_per_node_hook = prism_explain_hook; | |
| 127 | 255 | } | |
| 128 |