GCC Code Coverage Report


Directory: src/
File: src/pg/build_progress.c
Date: 2026-09-30 11:11:31
Exec Total Coverage
Lines: 103 105 98.1%
Functions: 10 10 100.0%
Branches: 36 42 85.7%

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