@@ -229,6 +229,8 @@ sub poll_sql_eq
229229my @two_node_tps ;
230230my $two_node_started = 0;
231231my $two_node_ready = 0;
232+ my $two_init_ok = 0;
233+ my $two_err ;
232234eval {
233235 my @pair_perf_conf = map { my $line = $_ ; chomp $line ; $line } @perf_conf ;
234236 my $pair = PostgreSQL::Test::ClusterPair-> new_pair(
@@ -249,21 +251,25 @@ sub poll_sql_eq
249251 && poll_sql_eq($pair -> node0, ' SELECT in_quorum FROM pg_cluster_quorum_state' , ' t' , 20)
250252 && poll_sql_eq($pair -> node1, ' SELECT in_quorum FROM pg_cluster_quorum_state' , ' t' , 20);
251253
252- if ($two_node_ready && pgbench_init( $pair -> node0) )
254+ if ($two_node_ready )
253255 {
254- for my $r (1 .. $TWO_NODE_ROUNDS )
256+ $two_init_ok = pgbench_init($pair -> node0) ? 1 : 0;
257+ if ($two_init_ok )
255258 {
256- my $t = pgbench_one($pair -> node0);
257- next unless defined $t && $t > 0;
258- push @two_node_tps , $t ;
259- note(sprintf (" two-node round %d : node0 tps=%.0f" , $r , $t ));
259+ for my $r (1 .. $TWO_NODE_ROUNDS )
260+ {
261+ my $t = pgbench_one($pair -> node0);
262+ next unless defined $t && $t > 0;
263+ push @two_node_tps , $t ;
264+ note(sprintf (" two-node round %d : node0 tps=%.0f" , $r , $t ));
265+ }
260266 }
261267 }
262268 $pair -> stop_pair;
263269 1;
264270} or do {
265- my $err = $@ || ' unknown error' ;
266- diag(" M3 two-node report-only measurement failed before completion: $err " );
271+ $two_err = $@ || ' unknown error' ;
272+ diag(" M3 two-node report-only measurement failed before completion: $two_err " );
267273};
268274
269275my $two_have = ($two_node_started && $two_node_ready && scalar (@two_node_tps ) > 0
@@ -273,29 +279,59 @@ sub poll_sql_eq
273279 ? 100.0 * (1.0 - $two_tps / $tps_native ) : undef ;
274280my $two_tax_s = defined $two_tax ? sprintf (' %.2f' , $two_tax ) : ' n/a' ;
275281
276- note(" MG-B two-node peer-online write-path REPORT-ONLY measurement:" );
277- note(" native single-node TPC-B median tps = "
278- . (defined $tps_native ? sprintf (' %.0f' , $tps_native ) : ' n/a' ));
279- note(" two-node peer-online node0 TPC-B median tps = "
280- . (defined $two_tps ? sprintf (' %.0f' , $two_tps ) : ' n/a' ));
281- note(" two-node write tax % (report-only) = $two_tax_s " );
282+ my $native_s = defined $tps_native ? sprintf (' %.0f' , $tps_native ) : ' n/a' ;
283+ my $two_tps_s = defined $two_tps ? sprintf (' %.0f' , $two_tps ) : ' n/a' ;
284+
285+ # Specific unavailable reason (reported even on PASS so the report-only leg is
286+ # never a silent black box).
287+ my $two_reason ;
288+ if (!$two_node_started )
289+ {
290+ $two_reason = " ClusterPair failed to boot/start"
291+ . (defined $two_err ? " : $two_err " : " " );
292+ }
293+ elsif (!$two_node_ready )
294+ {
295+ $two_reason = " peers did not reach connected + in_quorum within timeout" ;
296+ }
297+ elsif (!$two_init_ok )
298+ {
299+ $two_reason = " pgbench init on node0 failed" ;
300+ }
301+ elsif (!scalar (@two_node_tps ))
302+ {
303+ $two_reason = " pgbench produced no valid (>0 tps) rounds" ;
304+ }
305+ elsif (!(defined $tps_native && $tps_native > 0))
306+ {
307+ $two_reason = " native single-node baseline tps missing" ;
308+ }
309+
310+ # diag() reaches the captured CI log even on PASS / non-verbose prove, so the
311+ # 2-node report-only numbers are always visible alongside the M1 single-node
312+ # gate -- not just when run with -v.
313+ diag(" MG-B two-node peer-online write-path REPORT-ONLY measurement:" );
314+ diag(" native single-node TPC-B median tps = $native_s " );
315+ diag(" two-node peer-online node0 TPC-B median tps = $two_tps_s " );
316+ diag(" two-node write tax % (report-only) = $two_tax_s "
317+ . (defined $two_reason ? " (unavailable: $two_reason )" : " " ));
282318
283319# REPORT ONLY: this leg must never fail the single-node hard gate. If the
284320# 2-node ClusterPair could not boot / reach quorum / produce a number this run
285321# (transient runner shmem pressure, etc.), pass with an explicit unavailable
286322# note rather than failing -- the HARD gate is the single-node M1 tax only.
287323if ($two_have )
288324{
289- diag(" MG-B two-node peer-online write tax (report-only) = ${two_tax_s} %" );
290325 ok(1,
291326 " M3 two-node peer-online single-writer write tax measured: ${two_tax_s} % "
292- . " (REPORT ONLY; no threshold asserted)" );
327+ . " (native= $native_s two-node= $two_tps_s tps; REPORT ONLY; no threshold asserted)" );
293328}
294329else
295330{
296331 ok(1,
297- " M3 two-node peer-online write tax unavailable this run "
298- . " (REPORT ONLY; never fails the single-node hard gate)" );
332+ " M3 two-node peer-online write tax unavailable this run: "
333+ . ($two_reason // ' unknown reason' )
334+ . " (REPORT ONLY; never fails the single-node hard gate)" );
299335}
300336$report -> record_multinode_write_value(2, ' tpcb-peer-online-single-writer' ,
301337 tps_native => (defined $tps_native ? $tps_native : 0),
0 commit comments