| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * Copyright (c) 2026 Tiger Data, Inc. | ||
| 3 | * Licensed under the PostgreSQL License. See LICENSE for details. | ||
| 4 | * | ||
| 5 | * build_progress.c - PostgreSQL body of the build-progress reporting seam. | ||
| 6 | * | ||
| 7 | * Live phase + % go to pg_stat_progress_create_index (always). Per-phase | ||
| 8 | * resource stats + summary go to the server log only when | ||
| 9 | * prism.log_build_stats is on, modeled on core btree's log_btree_build_stats | ||
| 10 | * (ResetUsage/ShowUsage). The planned-allocation line is always emitted so an | ||
| 11 | * OOM is pre-explained. The standalone no-op bodies live in | ||
| 12 | * src/index/build_progress.c. | ||
| 13 | */ | ||
| 14 | |||
| 15 | #include <postgres.h> | ||
| 16 | |||
| 17 | #include "vs_config.h" | ||
| 18 | |||
| 19 | #include <commands/progress.h> | ||
| 20 | #include <miscadmin.h> | ||
| 21 | #include <pgstat.h> | ||
| 22 | #include <portability/instr_time.h> | ||
| 23 | #include <tcop/tcopprot.h> /* ResetUsage / ShowUsage */ | ||
| 24 | #include <utils/backend_progress.h> | ||
| 25 | #include <utils/injection_point.h> | ||
| 26 | #include <utils/memutils.h> | ||
| 27 | |||
| 28 | #include "index/build_progress.h" | ||
| 29 | #include "index/index_build.h" /* PrismBuildStats */ | ||
| 30 | |||
| 31 | static int64 | ||
| 32 | 3355 | now_ns(void) | |
| 33 | { | ||
| 34 | 3355 | instr_time t; | |
| 35 | |||
| 36 | 3355 | INSTR_TIME_SET_CURRENT(t); | |
| 37 | 3355 | return INSTR_TIME_GET_NANOSEC(t); | |
| 38 | } | ||
| 39 | |||
| 40 | /* | ||
| 41 | * Test hook name fired at each phase boundary, so an isolation test can pause | ||
| 42 | * a build at any phase and read pg_stat_progress_create_index. SCAN and | ||
| 43 | * SCAN_PARALLEL share the long-standing "prism-build-load" name (the | ||
| 44 | * build_progress isolation test depends on it); the other phases get their | ||
| 45 | * own. Returns NULL for phases without a hook. No-op unless PG was built with | ||
| 46 | * injection points and a test attached an action. | ||
| 47 | */ | ||
| 48 | static const char * | ||
| 49 | 1386 | injection_name_for_phase(int phase) | |
| 50 | { | ||
| 51 |
7/7✓ Branch 0 taken 195 times.
✓ Branch 1 taken 20 times.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 194 times.
✓ Branch 4 taken 194 times.
✓ Branch 5 taken 583 times.
✓ Branch 6 taken 195 times.
|
1386 | switch (phase) |
| 52 | { | ||
| 53 | case PRISM_BUILD_PHASE_SAMPLE: | ||
| 54 | return "prism-build-sample"; | ||
| 55 | 195 | case PRISM_BUILD_PHASE_KMEANS: | |
| 56 | 195 | return "prism-build-kmeans"; | |
| 57 | 20 | case PRISM_BUILD_PHASE_SUBTREES: | |
| 58 | 20 | return "prism-build-subtrees"; | |
| 59 | 5 | case PRISM_BUILD_PHASE_REFINE: | |
| 60 | 5 | return "prism-build-refine"; | |
| 61 | 194 | case PRISM_BUILD_PHASE_SCAN: | |
| 62 | case PRISM_BUILD_PHASE_SCAN_PARALLEL: | ||
| 63 | 194 | return "prism-build-load"; | |
| 64 | 194 | case PRISM_BUILD_PHASE_POSTING: | |
| 65 | 194 | return "prism-build-posting"; | |
| 66 | 583 | default: | |
| 67 | 583 | return NULL; | |
| 68 | } | ||
| 69 | } | ||
| 70 | |||
| 71 | /* Add a finished phase's elapsed time to the matching PrismBuildStats field. | ||
| 72 | */ | ||
| 73 | static void | ||
| 74 | 1580 | accumulate_stats(PrismBuildStats *s, int phase, double ms) | |
| 75 | { | ||
| 76 |
1/2✓ Branch 0 taken 1580 times.
✗ Branch 1 not taken.
|
1580 | if (s == NULL) |
| 77 | return; | ||
| 78 | |||
| 79 |
7/7✓ Branch 0 taken 195 times.
✓ Branch 1 taken 214 times.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 195 times.
✓ Branch 4 taken 388 times.
✓ Branch 5 taken 194 times.
✓ Branch 6 taken 389 times.
|
1580 | switch (phase) |
| 80 | { | ||
| 81 | 195 | case PRISM_BUILD_PHASE_SAMPLE: | |
| 82 | 195 | s->ms_sample += ms; | |
| 83 | 195 | break; | |
| 84 | 214 | case PRISM_BUILD_PHASE_KMEANS: | |
| 85 | case PRISM_BUILD_PHASE_SUBTREES: | ||
| 86 | 214 | s->ms_kmeans += ms; | |
| 87 | 214 | break; | |
| 88 | 5 | case PRISM_BUILD_PHASE_REFINE: | |
| 89 | 5 | s->ms_refine += ms; | |
| 90 | 5 | break; | |
| 91 | 195 | case PRISM_BUILD_PHASE_SETUP: | |
| 92 | 195 | s->ms_setup += ms; | |
| 93 | 195 | break; | |
| 94 | 388 | case PRISM_BUILD_PHASE_SCAN: | |
| 95 | case PRISM_BUILD_PHASE_SCAN_PARALLEL: | ||
| 96 | case PRISM_BUILD_PHASE_POSTING: | ||
| 97 | 388 | s->ms_posting += ms; | |
| 98 | 388 | break; | |
| 99 | 194 | case PRISM_BUILD_PHASE_CENTROID: | |
| 100 | 194 | s->ms_centroid += ms; | |
| 101 | 194 | break; | |
| 102 | default: | ||
| 103 | break; | ||
| 104 | } | ||
| 105 | } | ||
| 106 | |||
| 107 | /* Emit the just-finished phase's resource line(s) and reset the usage window. | ||
| 108 | */ | ||
| 109 | static void | ||
| 110 | 1580 | flush_phase(PrismBuildProgress *p) | |
| 111 | { | ||
| 112 | 1580 | double ms = (double)(now_ns() - p->phase_start_ns) / 1e6; | |
| 113 | |||
| 114 | 1580 | accumulate_stats(p->stats, p->cur_phase, ms); | |
| 115 | |||
| 116 |
4/4✓ Branch 0 taken 9 times.
✓ Branch 1 taken 1571 times.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 1 times.
|
1580 | if (!p->log_stats || p->cur_phase == 0) |
| 117 | return; | ||
| 118 | |||
| 119 | 8 | double heap_mb = p->heap_ctx ? (double)MemoryContextMemAllocated( | |
| 120 | (MemoryContext)p->heap_ctx, true) / | ||
| 121 | (1024.0 * 1024.0) | ||
| 122 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
16 | : 0.0; |
| 123 | |||
| 124 |
1/2✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
|
8 | ereport(LOG, |
| 125 | (errmsg(VS_AM_NAME " build: phase \"%s\" done in %.0f ms " | ||
| 126 | "(build heap %.1f MB, DSM %.1f MB)", | ||
| 127 | prism_build_phase_name(p->cur_phase), | ||
| 128 | ms, | ||
| 129 | heap_mb, | ||
| 130 | (double)p->dsm_bytes / (1024.0 * 1024.0)), | ||
| 131 | errhidestmt(true))); | ||
| 132 | |||
| 133 | /* btree-style CPU + maxrss for the phase, then reset for the next one. */ | ||
| 134 | 8 | ShowUsage(VS_AM_NAME " build phase resource usage"); | |
| 135 | 8 | ResetUsage(); | |
| 136 | } | ||
| 137 | |||
| 138 | void | ||
| 139 | 195 | prism_build_progress_begin( | |
| 140 | PrismBuildProgress *p, | ||
| 141 | bool is_parallel, | ||
| 142 | bool log_stats, | ||
| 143 | void *heap_ctx, | ||
| 144 | struct PrismBuildStats *stats, | ||
| 145 | double tuples_total) | ||
| 146 | { | ||
| 147 | 195 | int64 t = now_ns(); | |
| 148 | |||
| 149 | 195 | p->cur_phase = 0; | |
| 150 | 195 | p->phase_start_ns = t; | |
| 151 | 195 | p->build_start_ns = t; | |
| 152 | 195 | p->log_stats = log_stats; | |
| 153 | 195 | p->is_parallel = is_parallel; | |
| 154 | 195 | p->total_set = false; | |
| 155 | 195 | p->heap_ctx = heap_ctx; | |
| 156 | 195 | p->dsm_bytes = 0; | |
| 157 | 195 | p->tuples_total = tuples_total; | |
| 158 | 195 | p->stats = stats; | |
| 159 | |||
| 160 | /* Publish the row total up front so percent_complete is meaningful for the | ||
| 161 | * whole build (the parallel path has no leader-side loop to publish it | ||
| 162 | * mid-scan). */ | ||
| 163 |
2/2✓ Branch 0 taken 188 times.
✓ Branch 1 taken 7 times.
|
195 | if (tuples_total > 0) |
| 164 | { | ||
| 165 | 188 | pgstat_progress_update_param( | |
| 166 | PROGRESS_CREATEIDX_TUPLES_TOTAL, (int64)tuples_total); | ||
| 167 | 188 | p->total_set = true; | |
| 168 | } | ||
| 169 | |||
| 170 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 194 times.
|
195 | if (log_stats) |
| 171 | 1 | ResetUsage(); | |
| 172 | 195 | } | |
| 173 | |||
| 174 | void | ||
| 175 | 1386 | prism_build_report_phase(PrismBuildProgress *p, int phase) | |
| 176 | { | ||
| 177 | 1386 | flush_phase(p); | |
| 178 | 1386 | p->cur_phase = phase; | |
| 179 | 1386 | p->phase_start_ns = now_ns(); | |
| 180 | 1386 | pgstat_progress_update_param(PROGRESS_CREATEIDX_SUBPHASE, phase); | |
| 181 | |||
| 182 | /* Each scan-shaped phase walks the heap from the start, so its | ||
| 183 | * tuples-done count restarts; the phases in between leave the previous | ||
| 184 | * scan's final count standing. */ | ||
| 185 |
2/2✓ Branch 0 taken 389 times.
✓ Branch 1 taken 997 times.
|
1386 | if (phase == PRISM_BUILD_PHASE_SAMPLE || phase == PRISM_BUILD_PHASE_SCAN || |
| 186 | phase == PRISM_BUILD_PHASE_SCAN_PARALLEL) | ||
| 187 | 389 | pgstat_progress_update_param(PROGRESS_CREATEIDX_TUPLES_DONE, 0); | |
| 188 | |||
| 189 | 1386 | const char *ip = injection_name_for_phase(phase); | |
| 190 |
2/2✓ Branch 0 taken 803 times.
✓ Branch 1 taken 583 times.
|
1386 | if (ip != NULL) |
| 191 | 803 | INJECTION_POINT(ip, NULL); | |
| 192 | 1385 | } | |
| 193 | |||
| 194 | void | ||
| 195 | 372 | prism_build_progress_incr_tuples(int64_t n) | |
| 196 | { | ||
| 197 | /* From a worker this piggybacks over the parallel message queue and the | ||
| 198 | * leader applies it (also while blocked at a barrier -- interrupt | ||
| 199 | * processing runs inside its condition-variable sleeps); in the leader | ||
| 200 | * it applies directly. */ | ||
| 201 | 372 | pgstat_progress_parallel_incr_param(PROGRESS_CREATEIDX_TUPLES_DONE, n); | |
| 202 | |||
| 203 | /* Test hook: lets an isolation test pause a build at its first | ||
| 204 | * mid-scan progress flush and observe the advanced counter. Fires once | ||
| 205 | * per backend so waking the build once suffices. */ | ||
| 206 | #ifdef USE_INJECTION_POINTS | ||
| 207 | 372 | static bool fired = false; | |
| 208 |
2/2✓ Branch 0 taken 95 times.
✓ Branch 1 taken 277 times.
|
372 | if (!fired) |
| 209 | { | ||
| 210 | 95 | fired = true; | |
| 211 | 95 | INJECTION_POINT("prism-scan-progress", NULL); | |
| 212 | } | ||
| 213 | #endif | ||
| 214 | 372 | } | |
| 215 | |||
| 216 | void | ||
| 217 | 58 | prism_build_report_progress(PrismBuildProgress *p, double done) | |
| 218 | { | ||
| 219 |
3/4✓ Branch 0 taken 1 times.
✓ Branch 1 taken 57 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
|
58 | if (!p->total_set && p->tuples_total > 0) |
| 220 | { | ||
| 221 | ✗ | pgstat_progress_update_param( | |
| 222 | PROGRESS_CREATEIDX_TUPLES_TOTAL, (int64)p->tuples_total); | ||
| 223 | ✗ | p->total_set = true; | |
| 224 | } | ||
| 225 | 58 | pgstat_progress_update_param(PROGRESS_CREATEIDX_TUPLES_DONE, (int64)done); | |
| 226 | 58 | } | |
| 227 | |||
| 228 | void | ||
| 229 | 45 | prism_build_report_dsm_bytes(PrismBuildProgress *p, uint64_t dsm_bytes) | |
| 230 | { | ||
| 231 | 45 | p->dsm_bytes = dsm_bytes; | |
| 232 | 45 | } | |
| 233 | |||
| 234 | void | ||
| 235 | 45 | prism_build_report_planned_alloc( | |
| 236 | PrismBuildProgress *p, | ||
| 237 | uint64_t sample_bytes, | ||
| 238 | uint64_t centroid_tree_bytes, | ||
| 239 | uint64_t dsm_total_bytes) | ||
| 240 | { | ||
| 241 | 45 | (void)p; | |
| 242 | 45 | const double mb = 1024.0 * 1024.0; | |
| 243 | |||
| 244 | /* Always emitted (not gated by the GUC) so an OOM in any of these is | ||
| 245 | * pre-explained in the log even on a default-configured server. */ | ||
| 246 |
1/2✓ Branch 1 taken 45 times.
✗ Branch 2 not taken.
|
45 | ereport(LOG, |
| 247 | (errmsg("prism build: planned allocations — samples %.0f MB, " | ||
| 248 | "centroid tree ~%.0f MB, DSM %.0f MB " | ||
| 249 | "(maintenance_work_mem %d kB)", | ||
| 250 | (double)sample_bytes / mb, | ||
| 251 | (double)centroid_tree_bytes / mb, | ||
| 252 | (double)dsm_total_bytes / mb, | ||
| 253 | maintenance_work_mem), | ||
| 254 | errhidestmt(true))); | ||
| 255 | 45 | } | |
| 256 | |||
| 257 | void | ||
| 258 | 194 | prism_build_progress_end(PrismBuildProgress *p) | |
| 259 | { | ||
| 260 | 194 | flush_phase(p); | |
| 261 | 194 | p->cur_phase = 0; | |
| 262 | |||
| 263 | /* Final per-phase summary, always logged (like the planned-allocation | ||
| 264 | * line), via the shared prism_build_stats_print so the PostgreSQL and | ||
| 265 | * standalone builds report identically. */ | ||
| 266 |
1/2✓ Branch 0 taken 194 times.
✗ Branch 1 not taken.
|
194 | if (p->stats != NULL) |
| 267 | { | ||
| 268 | 194 | p->stats->ms_total = (double)(now_ns() - p->build_start_ns) / 1e6; | |
| 269 | 194 | prism_build_stats_print(p->stats); | |
| 270 | } | ||
| 271 | 194 | } | |
| 272 |