mirror of
https://github.com/XRPLF/rippled.git
synced 2026-09-26 23:19:07 +00:00
fix(telemetry): make the load generators fail loudly instead of exiting 0
Three ways a run could produce no traffic and still report success: - tx_submitter logged a funding shortfall and returned an empty stats object; main() then printed the summary and exited 0, so the failure only surfaced later as "spans missing", which points nowhere. It now records setup_failed in the summary and exits 1 after the report is written. - --weights was checked for valid JSON but not for a positive sum. An all-zero mapping reached random.choices, which raises ValueError from inside the dispatch loop where only CancelledError is caught. Rejected at parse time now, in both generators. - a profile phase declaring neither rpc nor tx logged a warning and returned no error. Both error rates short-circuit to 0.0 when nothing was sent, so a mistyped key produced zero traffic and still passed the exit gate. That phase is now an error.
This commit is contained in:
@@ -770,6 +770,12 @@ def main() -> None:
|
||||
try:
|
||||
custom = json.loads(args.weights)
|
||||
weights = {k: int(v) for k, v in custom.items()}
|
||||
if not weights or sum(weights.values()) <= 0:
|
||||
logger.error(
|
||||
"Invalid --weights: the values must sum to more than 0, got %s",
|
||||
weights,
|
||||
)
|
||||
sys.exit(1)
|
||||
logger.info("Using custom weights: %s", weights)
|
||||
except (json.JSONDecodeError, ValueError) as exc:
|
||||
logger.error("Invalid --weights JSON: %s", exc)
|
||||
|
||||
@@ -192,6 +192,8 @@ class TxStats:
|
||||
total_errors: Transactions that returned an error engine_result.
|
||||
by_type: Per-transaction-type count of submissions.
|
||||
errors_by_type: Per-transaction-type count of errors.
|
||||
setup_failed: True if account setup never produced enough funded
|
||||
accounts, so the timed loop never ran.
|
||||
"""
|
||||
|
||||
total_submitted: int = 0
|
||||
@@ -199,6 +201,7 @@ class TxStats:
|
||||
total_errors: int = 0
|
||||
by_type: dict[str, int] = field(default_factory=dict)
|
||||
errors_by_type: dict[str, int] = field(default_factory=dict)
|
||||
setup_failed: bool = False
|
||||
|
||||
def record(self, tx_type: str, success: bool) -> None:
|
||||
"""Record the result of a transaction submission."""
|
||||
@@ -223,6 +226,7 @@ class TxStats:
|
||||
),
|
||||
"by_type": self.by_type,
|
||||
"errors_by_type": self.errors_by_type,
|
||||
"setup_failed": self.setup_failed,
|
||||
}
|
||||
|
||||
|
||||
@@ -978,6 +982,10 @@ async def run_submitter(
|
||||
len(accounts),
|
||||
len(created),
|
||||
)
|
||||
# The caller turns this into a non-zero exit. Without it a funding
|
||||
# failure looks like a clean run of zero transactions, and the run
|
||||
# only fails later as "spans missing", which points nowhere.
|
||||
stats.setup_failed = True
|
||||
return stats
|
||||
|
||||
logger.info(
|
||||
@@ -1078,6 +1086,12 @@ def main() -> None:
|
||||
try:
|
||||
custom = json.loads(args.weights)
|
||||
weights = {k: int(v) for k, v in custom.items()}
|
||||
if not weights or sum(weights.values()) <= 0:
|
||||
logger.error(
|
||||
"Invalid --weights: the values must sum to more than 0, got %s",
|
||||
weights,
|
||||
)
|
||||
sys.exit(1)
|
||||
logger.info("Using custom weights: %s", weights)
|
||||
except (json.JSONDecodeError, ValueError) as exc:
|
||||
logger.error("Invalid --weights JSON: %s", exc)
|
||||
@@ -1101,6 +1115,11 @@ def main() -> None:
|
||||
json.dump(summary, f, indent=2)
|
||||
logger.info("Summary written to %s", args.output)
|
||||
|
||||
# After the report is written, so the failure is still diagnosable.
|
||||
if stats.setup_failed:
|
||||
logger.error("Account setup failed; no transactions were submitted.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -436,9 +436,16 @@ async def run_phase(
|
||||
tasks = _launch_phase_tasks(phase, endpoints, report_dir, prefix)
|
||||
|
||||
if not tasks:
|
||||
logger.warning(
|
||||
"Phase %d: %s — no workload configured, skipping", phase_idx + 1, name
|
||||
# An error, not a warning. The exit gate is built from phase errors and
|
||||
# from error RATES, and both rates short-circuit to 0.0 when nothing was
|
||||
# sent -- so a profile with a mistyped key ("rpcs", "RPC") would produce
|
||||
# no traffic at all and still exit 0.
|
||||
message = (
|
||||
f"phase {phase_idx + 1} '{name}' configures no workload: "
|
||||
"it declares neither 'rpc' nor 'tx'"
|
||||
)
|
||||
logger.error("%s", message)
|
||||
result.errors.append(message)
|
||||
return result
|
||||
|
||||
for label, report_path, task in tasks:
|
||||
|
||||
Reference in New Issue
Block a user