benchnotify

package
v0.18.13 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 3, 2026 License: AGPL-3.0 Imports: 10 Imported by: 0

Documentation

Overview

Package benchnotify pushes benchmark lifecycle events to an SQS queue so an operator can watch a remote run with a blocking receive loop instead of grepping a remembered log format over SSM. Producer (the bench binaries) and consumer (deploy/benchmark/watch-events.sh) ship in the same commit, so the two cannot drift.

Event schema

One JSON object per SQS message, single line, no envelope:

{
  "run_id":        "20260817-142233",  // results timestamp id: results/<run_id>/
  "event":         "run_started" | "query_completed" | "run_completed" |
                   "suite_completed" | "fatal",
  "query":         "Q01",     // query_completed
  "try":           1,         // query_completed, clickbench only (1 = cold)
  "wall_seconds":  12.345,    // query_completed
  "rows":          4,         // query_completed, tpch only
  "ok":            true,      // query_completed
  "run_index":     2,         // run_completed (1-based)
  "total_runs":    3,         // run_started, run_completed
  "total_seconds": 198.4,     // run_completed, suite_completed
  "cold_seconds":  256.5,     // suite_completed, clickbench only (sum of try 1)
  "hot_seconds":   164.4,     // suite_completed, clickbench only (sum of min(try2,try3))
  "error":         "...",     // fatal
  "ts":            "2026-08-17T14:22:33Z"  // RFC3339, always present
}

Omitted fields are absent from the JSON, not zero-valued — a consumer should treat a missing field as "not applicable to this event".

Event order for a TPC-H run: one run_started, then per run N query_completed events followed by a run_completed, then one suite_completed. For ClickBench: one run_started, then per query one query_completed per try, then one suite_completed. Either suite can end on a fatal instead — a fatal is always terminal.

Delivery is at-least-once

The queue is a standard SQS queue, so delivery is at-least-once, not exactly-once: a message can arrive more than once, with no ordering guarantee across messages. Observed directly on the first SF100 run using this emitter (2026-08-19, run_id 20260819-112820) — one run_started delivered twice, 30 seconds apart, same run_id, same ts:

07:28:36 {"run_id":"20260819-112820","event":"run_started","total_runs":1,"ts":"2026-08-19T11:28:20Z"}
07:29:06 {"run_id":"20260819-112820","event":"run_started","total_runs":1,"ts":"2026-08-19T11:28:20Z"}

That run also emitted 78 query_completed events for 22 distinct queries across 1 run, so duplicates are not rare enough to ignore by hope.

A consumer that only prints the stream (watch-events.sh) is unaffected. Any consumer that counts events to track progress, accumulates timings, or treats an event as an edge (e.g. run_started resetting state) MUST dedupe on (RunID, Event, Query) before acting — Try additionally distinguishes ClickBench's per-query retries. If a consumer needs ordering as well as exactly-once delivery, use a FIFO queue with content-based deduplication instead of a dedupe key.

Fire-and-forget

Emission never affects the benchmark. Sends happen outside every timed region, carry a short timeout (default 2s), take no retries beyond the SDK default, and log a warning on failure. After a few consecutive failures the notifier disables itself so an unreachable queue cannot add wall time to a long suite. A nil *Notifier is a valid disabled notifier: every method is a no-op, which is what an unset --notify-sqs-url yields.

Index

Constants

View Source
const (
	EventRunStarted     = "run_started"
	EventQueryCompleted = "query_completed"
	EventRunCompleted   = "run_completed"
	EventSuiteCompleted = "suite_completed"
	EventFatal          = "fatal"
)

Event names.

Variables

This section is empty.

Functions

func OK

func OK(b bool) *bool

OK returns a pointer to b, for Event.OK — a plain bool cannot be distinguished from "not set" under omitempty, and `"ok": false` is the field's most important value.

func RegionFromQueueURL

func RegionFromQueueURL(raw string) string

RegionFromQueueURL extracts the region from an SQS queue URL. Both the current host form (sqs.<region>.amazonaws.com) and the legacy one (<region>.queue.amazonaws.com) are recognized; anything else returns "" and the caller falls back to the ambient AWS config.

func Rows

func Rows(n int64) *int64

Rows returns a pointer to n, for Event.Rows (a zero row count is a real observation, not an unset field).

func Seconds

func Seconds(d time.Duration) *float64

Seconds returns a pointer to d in seconds, for the duration fields.

func SecondsFloat

func SecondsFloat(v float64) *float64

SecondsFloat returns a pointer to v, for callers that already hold seconds (the ClickBench per-try triples are float64 seconds).

Types

type Config

type Config struct {
	// QueueURL is the SQS queue URL. Empty disables notifications entirely
	// (New returns nil).
	QueueURL string
	// Region overrides the region derived from QueueURL.
	Region string
	// RunID identifies the run; defaults to a UTC timestamp in the same
	// format the bench result directories use (20060102-150405).
	RunID string
	// Timeout bounds a single send. Defaults to 2s.
	Timeout time.Duration
	// Logf receives warnings. Defaults to discarding them.
	Logf func(string, ...any)
}

Config configures a Notifier.

type Emitter

type Emitter interface {
	Emit(ctx context.Context, ev Event) error
}

Emitter delivers one event to the transport. Implementations are synchronous; the Notifier bounds them with a timeout.

type Event

type Event struct {
	RunID string `json:"run_id"`
	Event string `json:"event"`

	Query        string   `json:"query,omitempty"`
	Try          int      `json:"try,omitempty"`
	WallSeconds  *float64 `json:"wall_seconds,omitempty"`
	Rows         *int64   `json:"rows,omitempty"`
	OK           *bool    `json:"ok,omitempty"`
	RunIndex     int      `json:"run_index,omitempty"`
	TotalRuns    int      `json:"total_runs,omitempty"`
	TotalSeconds *float64 `json:"total_seconds,omitempty"`
	ColdSeconds  *float64 `json:"cold_seconds,omitempty"`
	HotSeconds   *float64 `json:"hot_seconds,omitempty"`
	Error        string   `json:"error,omitempty"`

	TS string `json:"ts"`
}

Event is one lifecycle message. See the package comment for which fields each event carries; every field but RunID/Event/TS is optional.

func (Event) Marshal

func (e Event) Marshal() ([]byte, error)

Marshal renders the event as the single-line JSON body that goes on the queue.

type Notifier

type Notifier struct {
	// contains filtered or unexported fields
}

Notifier stamps events with the run id and timestamp and hands them to an Emitter. The zero value is unusable; a nil *Notifier is a disabled one.

func New

func New(ctx context.Context, cfg Config) *Notifier

New builds an SQS-backed Notifier. An empty cfg.QueueURL — the default of --notify-sqs-url — returns nil, the disabled notifier, so callers need no enabled/disabled branch. A client that cannot be built logs a warning and also returns nil: notifications are never a gate on the benchmark.

func NewWithEmitter

func NewWithEmitter(em Emitter, cfg Config) *Notifier

NewWithEmitter builds a Notifier over an arbitrary emitter. Used by tests and by New once it has an SQS client.

func (*Notifier) Fatal

func (n *Notifier) Fatal(format string, args ...any)

Fatal sends a terminal fatal event carrying the formatted message. Call it immediately before the process aborts — the send is synchronous, so no flush is needed afterwards.

func (*Notifier) RunID

func (n *Notifier) RunID() string

RunID reports the id stamped on every event.

func (*Notifier) Send

func (n *Notifier) Send(ev Event)

Send stamps and delivers one event. It never blocks longer than the configured timeout and never reports an error to the caller: a failed send logs a warning, and repeated failures disable the notifier.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL