Documentation
¶
Index ¶
- Constants
- Variables
- func BinarySize(b int64) string
- func BootRefusal(fatal error) error
- func BufferPoolSetting(b uint64) string
- func ClassifyConnectError(err error) string
- func DiskSpace(path string) (free, total uint64, err error)
- func DockerHostRetry(_ string, port string) string
- func DumpCompressedSentence(est DumpEstimate) string
- func DumpableTablesWhere(schemas []string) (string, []any)
- func IdentifyKinds() []string
- func Kinds() []string
- func LightFor(name, kind string) string
- func Lights() []string
- func ManagedFromHost(host string) (managed, proxy string)
- func PrepareDumpEstimateSession(ctx context.Context, conn DumpSessionExecer) (stale bool)
- func RecommendedBufferPool(memTotal uint64) (uint64, bool)
- func RetentionTooShort(everyMinutes, days int) bool
- func S3PrefixCovers(outer, inner string) bool
- func SourceTLSRefusalText(err error, dsn string, ssl config.SSL) (detail, remediation string, ok bool)
- func TLSSettingsText(err error) (detail, remediation string, ok bool)
- func TargetAddr(host, port string) (string, error)
- type BuildOption
- func ForConsole() BuildOption
- func ForUnsavedServer() BuildOption
- func WithDropsPaused(oldest time.Time) BuildOption
- func WithLoopbackRetry(retry func(host, port string) string) BuildOption
- func WithRetainNote(note string) BuildOption
- func WithRotationWindow(note string, paused bool, oldest time.Time) BuildOption
- func WithSourceSSL(ssl config.SSL) BuildOption
- type CapacityFreeReason
- type CapacityMeasurement
- type CapacityPartition
- type CapacityProbe
- type CapacityQueryError
- type CapacityReason
- type CheckResult
- func CheckArchiveObjectLock(ctx context.Context, archiveS3, region string, retain time.Duration) CheckResult
- func CheckFullReadDisk(ctx context.Context, stagingDir, sourceDSN string, schemas []string) CheckResult
- func CheckProxySQLRules(parent context.Context, adminDSN string) CheckResult
- func CheckSnapshotExpiry(ctx context.Context, snapshotS3, region string, every time.Duration) CheckResult
- type CheckStatus
- type DropsPaused
- type DumpEstimate
- type DumpSessionExecer
- type ExpiryState
- type Identification
- type LifecycleRule
- type PreflightError
- type Report
- type SnapshotExpiry
Constants ¶
const ( SourceConnectionCheckName = "Source MySQL connection" IndexConnectionCheckName = "Index MySQL connection" PGSourceConnectionCheckName = "Source PostgreSQL connection" ReplicationGrantsCheckName = "REPLICATION SLAVE + CLIENT grants" IndexWriteAccessCheckName = "Index write access" // SchemaAccessCheckName is the schema-visibility probe's grants branch: // no tables visible at all, whose remediation is GRANT SELECT. The // empty-schema branch keeps the "Schema visibility" name — it is not a // permission problem, and telling the operator to GRANT there sends // them down the wrong path (#402) — and so do the probe's query-error // branches, which grade config_invalid; an unreachable source is caught // by the connection check ahead of them. An extension check that reused // one of these names would be classified as if it were the built-in. SchemaAccessCheckName = "Schema access" // ExtensionPanicCheckName is the FAIL cliapp records when a registered // extension check panics: a bug, not a setting, so it classifies as // internal. ExtensionPanicCheckName = "extension doctor checks" )
Names of the checks whose failure means something other than "a setting on the server is wrong". PreflightError.TelemetryClass keys on them, so they are constants shared with the producers (pgstreamrun's doctor included) rather than literals that could drift apart.
const ( // KindNameNotFound: the name does not exist (the resolver said so; a // resolver that failed or timed out is KindHostUnreachable instead). KindNameNotFound = "name_not_found" // KindNotMySQL: something accepted the connection and did not greet like // MySQL or MariaDB. Identification.Answer says what it did instead. KindNotMySQL = "not_mysql" // KindHostBlocked: the server refused DBTrail's address in its greeting // after too many failed connections (error 1129) until someone flushes // its host cache. KindHostBlocked = "host_blocked" )
The failure kinds identification adds to the connection kinds of kind.go. They are NOT in Kinds(), the set the setup screen draws (#1804).
const ( FlavorMySQL = "mysql" FlavorMariaDB = "mariadb" )
Flavors read from a greeting.
const ( ManagedRDS = "rds" ManagedAurora = "aurora" )
Managed services read from the host name.
const ( ProxyRDSProxy = "rds_proxy" ProxyProxySQL = "proxysql" ProxyMaxScale = "maxscale" )
Proxies suspected from the host name, the port or the greeting. A proxy answers with its own version, so each is a question for the person ("confirm what is behind it"), never a verdict.
const ( AnswerSilent = answerSilent AnswerClosed = answerClosed AnswerOther = answerOther )
What a KindNotMySQL answer did instead of greeting.
const ( // KindHostUnreachable: the name did not resolve, or no route reached it. KindHostUnreachable = "host_unreachable" // KindPortClosed: the host answered, and nothing listens on the port. KindPortClosed = "port_closed" // KindTimeout: nothing answered within the connect budget. KindTimeout = "timeout" // KindAccessDenied: a database answered and refused the user or password. KindAccessDenied = "access_denied" // KindLoopbackInContainer: the typed address is this machine's own // (localhost, 127.0.0.1) and nothing answered there, while the host's // address from inside a container did. Only claimed on that proof. KindLoopbackInContainer = "loopback_in_container" // KindMissingPrivilege: the user lacks a privilege; Subjects names each. KindMissingPrivilege = "missing_privilege" // KindBinlogSettings: a binary log setting is wrong; Subjects names the // variable. KindBinlogSettings = "binlog_settings" // KindNotInnoDB: tables not on InnoDB, with or without a key; Subjects // names every one as schema.table, and Statements carries the Overview // card's statement for each (engine only, or engine and key). KindNotInnoDB = "not_innodb" // KindNoPrimaryKey: tables with no primary key; Subjects names every one // as schema.table, and Statements carries a statement per table. KindNoPrimaryKey = "no_primary_key" )
A check's Kind names WHAT went wrong in a fixed word a screen can switch on (#1803), where Detail and Remediation stay prose written for a person. The set is closed: every kind is a constant below, never built from input, so a kind cannot carry a hostname or a password past the scrubber that runs over Detail. An empty kind means "not one of these", and the prose is all there is. Kinds are set on findings only (fail or warn); the status still says how bad it is.
const ( ReasonDenied = "denied" // the credentials may not read it ReasonUnsupported = "unsupported" // the store does not implement the call ReasonError = "error" // anything else: network, deadline, a bucket that is gone )
Why a configuration could not be read, for a screen to pick its words.
const ( // LightReach: DBTrail reaches the server. The source connection check, // unless it failed on the password (then it is LightLogin's). LightReach = "reach" // LightLogin: the user logs in. LightLogin = "login" // LightRows: the change log keeps what capture needs (binary log on, // ROW, FULL row images, retention, crash safety). LightRows = "rows" // LightPermissions: the user may read what capture reads. LightPermissions = "permissions" // LightKeys: every table can be captured (a primary key, InnoDB). LightKeys = "keys" // LightOther: everything else, including checks an extension adds. LightOther = "other" )
The lights of the Connect screen's step 3 (#1953): each check belongs to one of a few plain questions, drawn as a vertical list, in this order. A check has a display name, not an id, so the grouping is keyed on the name here, beside the checks, and TestLightsCoverEveryCheck reads this package's code and fails on a check with no light: a new check is placed on purpose.
const BufferPoolCheckName = "Index buffer pool"
BufferPoolCheckName is the index buffer pool check (#2141).
const CapacityCheckName = "Index disk capacity"
CapacityCheckName is the check's display name — shared with `up`'s preflight, which treats this check as advisory (see runUp).
const DefaultBufferPool = uint64(128) << 20
DefaultBufferPool is InnoDB's innodb_buffer_pool_size when nobody sets it: 128 MiB on every machine. On a busy source it caps capture far below what the disk can do (measured: about 1,400 row changes per second with lag growing without end, against 16,000 with a sized pool).
const DumpEstimateServerLimit = 12 * time.Second
DumpEstimateServerLimit is how long the SOURCE may spend on the estimate's query before it stops it itself. Under DumpEstimateTimeout, so what comes back is the server's own error and not a connection closed under a query that goes on running. (When connecting and the session's settings take more than the difference, the wait ends first and the query runs on for at most this long: still bounded.)
const DumpEstimateTimeout = 15 * time.Second
DumpEstimateTimeout bounds the information_schema read. It runs before every full read, and a source that does not answer in time must cost a note, not the backup.
const DumpPeakTenths = 18
DumpPeakTenths is the dump-plus-Parquet peak as tenths of the estimate: 18 = 1.8x, the highest ratio the #1938 measurement saw against DATA_LENGTH + INDEX_LENGTH (random binary: 94 MiB dump + 84 MiB Parquet over 99 MiB).
It was measured with the whole dump on disk until the last table was converted. Since #1938 each table's dump data is removed when its Parquet file is written (baseline.Config.RemoveConvertedData), so the real peak is at most what it was and usually lower: how much lower depends on how many tables there are next to the CPUs converting them, and on whether one table dominates. The constant is deliberately left as it was until that is measured (#2256): it warns too early, never too late.
const DumpRefuseTenths = 5
DumpRefuseTenths is the refusal line as tenths of the tables' DATA size: below half of it no measured dump fit, so refusing there is not a guess.
const FKCascadeCheckName = "Foreign keys that cascade"
FKCascadeCheckName is the check that lists the source's foreign keys whose ON DELETE or ON UPDATE rule is CASCADE or SET NULL. It names what it looks at, not a rule: such keys are supported, they only change how a delete on the parent table is undone.
const FullReadDiskCheckName = "Disk for a full read"
FullReadDiskCheckName is the check `bintrail doctor --staging-dir` adds (#2259): the free space of a working folder beside what a full read of the source writes there.
const InnoDBCheckName = "Every table uses InnoDB"
InnoDBCheckName is the check that reports tables not on InnoDB, the other kind the snapshot refuses beside a missing primary key.
const MariaDBVersionCheckName = "MariaDB version"
MariaDBVersionCheckName is the doctor row that grades a MariaDB source's version against the supported minimum.
const ObjectLockCheckName = "Archive S3 Object Lock"
ObjectLockCheckName is the check's display name.
const PrimaryKeyCheckName = "Every table has a PRIMARY KEY"
PrimaryKeyCheckName is the check that reports tables with no primary key. Its FAIL (only while the first snapshot is pending) refuses boot in both daemons, like any other FAIL: the stream would refuse a moment later anyway.
const SnapshotExpiryCheckName = "Snapshot S3 expiry rule"
SnapshotExpiryCheckName is the check's display name.
Variables ¶
var ErrInvalidAddress = errors.New("invalid address")
ErrInvalidAddress is returned for a host or port that is not an address, before anything is dialed.
Functions ¶
func BinarySize ¶ added in v0.103.0
BinarySize renders bytes in binary units with one decimal. A value that would round up to 1024.0 moves to the next unit.
func BootRefusal ¶ added in v0.71.0
BootRefusal is the error a daemon exits with when the preflight refuses boot; `up` and `bintrail-console watch` share it. The %w is load-bearing: it keeps the *PreflightError reachable, so the refusal keeps its usage-telemetry class (config_invalid, db_connection, ...) instead of collapsing to unknown.
func BufferPoolSetting ¶ added in v0.100.0
BufferPoolSetting renders a pool size the way it is written in .env and on the mysqld command line: whole GiB as "4G", anything else as "384M".
func ClassifyConnectError ¶ added in v0.87.0
ClassifyConnectError names why opening a connection failed, or "" when the error is none of the connection kinds. It looks through every wrap (config.Connect wraps the driver's error), never at the message text.
func DiskSpace ¶ added in v0.82.0
DiskSpace reports the bytes available to non-root users and the total size of the filesystem holding path, the probe the capacity card uses. Exported for the backup disk check (#1614), so there is one statfs in the tree. A total of zero is what a mount that cannot answer reports; a full disk reports zero free with a real total.
func DockerHostRetry ¶ added in v0.87.0
DockerHostRetry is the retry address the web interface uses: the same port on host.docker.internal, the name a container reaches its host by (Docker Desktop sets it; the compose file maps it on Linux).
func DumpCompressedSentence ¶ added in v0.103.0
func DumpCompressedSentence(est DumpEstimate) string
DumpCompressedSentence is what a verdict says about tables stored compressed, "" when there are none. It starts with a space, like the other parts of a note's tail.
func DumpableTablesWhere ¶ added in v0.103.0
DumpableTablesWhere is the information_schema.TABLES filter for the tables a console dump selects, shared by the console's no-lock count (dumpableTableCountQuery) and the size estimate below (dumpSizeQuery, #1938) so the two cannot drift apart.
func IdentifyKinds ¶ added in v0.92.0
func IdentifyKinds() []string
IdentifyKinds lists every kind Identify may report.
func Kinds ¶ added in v0.87.0
func Kinds() []string
Kinds lists every kind, for tests and for a screen that wants to know what it may be sent.
func LightFor ¶ added in v0.92.0
LightFor places a check by its name and kind. A connection that failed on the user or password reached the server, so it belongs to LightLogin. A name this package does not know (an extension's check) is LightOther.
func Lights ¶ added in v0.92.0
func Lights() []string
Lights lists the lights in the order the screen draws them.
func ManagedFromHost ¶ added in v0.93.0
ManagedFromHost reads the managed service from an Amazon RDS endpoint name: <name>.cluster-[ro-|custom-]<id>.<region>.rds.amazonaws.com is Aurora, a proxy-<id> label is RDS Proxy (in front of RDS or Aurora), anything else under rds.amazonaws.com is RDS. An Aurora instance endpoint looks like RDS; the check after login settles it. Exported for the console's automatic snapshot lock mode (#1986), which picks lock-all for these hosts.
func PrepareDumpEstimateSession ¶ added in v0.103.0
func PrepareDumpEstimateSession(ctx context.Context, conn DumpSessionExecer) (stale bool)
PrepareDumpEstimateSession sets up the estimate's own connection, and reports whether the sizes it will read may be stale.
Fresh sizes: MySQL 8.0 and newer serve information_schema sizes from a cache that can be a day old (information_schema_stats_expiry), which undercounts a table that grew. A server without the variable (MariaDB, 5.7) refuses the SET with 1193, and its sizes are not cached. Any other refusal (a proxy that rejects the SET, say) leaves the day-old cache in play.
A limit the source enforces: the read's own timeout only ends the waiting. The driver then closes the connection without KILL QUERY, and with fresh sizes asked for the server opens every table, which on a source with tens of thousands of them goes on for minutes after nobody is listening. MySQL takes max_execution_time (milliseconds). MariaDB does not know it (1193) and takes max_statement_time (seconds), which is sent ONLY then: some Percona builds read that same name in milliseconds, where 12 would end every estimate at once. A limit that could not be set is not a reason to skip the check: the wait is still bounded.
func RecommendedBufferPool ¶ added in v0.100.0
RecommendedBufferPool is the index buffer pool for a machine (or Docker VM) with memTotal bytes: a quarter of it, rounded DOWN to whole GiB from 1 GiB up, in 128 MiB steps below that, at most 32 GiB. MySQL rounds the pool UP to a multiple of chunk size times instances, and 8.4 picks the instance count from the CPUs, so the pool it runs with is this value or a little above (exact when the count divides 8). A quarter, not InnoDB's usual "most of the RAM", because the index rarely has the machine to itself: the web interface's SQL engine takes up to 4 GB, and the source database may run on the same host. ok is false when a quarter is under 256 MiB: keep the default. install.sh carries a shell copy of this rule; test/installer holds the two together.
func RetentionTooShort ¶ added in v0.91.0
RetentionTooShort: an age rule no longer than the schedule interval expires the newest snapshot before the next one lands. Equal counts. An unknown interval never warns. The same rule as retentionTooShort in app.js, pinned against it by the same test.
func S3PrefixCovers ¶ added in v0.91.0
S3PrefixCovers reports whether a rule on outer (s3://bucket/prefix) also expires objects under inner: same bucket, and inner's prefix equals or sits under outer's, compared with the slash so that "dbtrail" does not cover "dbtrail-archives". It is the covering test the console page generates its rule with (s3PrefixCovers in app.js); a test in internal/console runs both over one table so they cannot drift.
func SourceTLSRefusalText ¶ added in v0.94.0
func SourceTLSRefusalText(err error, dsn string, ssl config.SSL) (detail, remediation string, ok bool)
SourceTLSRefusalText words the two ways a source connection fails over TLS for a person: the server demands encryption the connection did not use (3159, worded by unencryptedRefusal for the setting that decided it), or a TLS mode that requires encryption met a server that offers none. ok is false for any other error, and for a no-TLS failure under a DSN with its own tls= (that DSN setting, not the mode, decided the connection).
func TLSSettingsText ¶ added in v0.94.0
TLSSettingsText words a config.TLSSettingsError for the surfaces that set a source's TLS (a console entry's ssl_* fields, or --ssl-* / BINTRAIL_SSL_* for watch, up and stream): what is wrong, and the setting by every name. ok is false for any other error.
func TargetAddr ¶ added in v0.92.0
TargetAddr is the host:port Identify would probe for what was typed, in one spelling per server (host lower-cased): "DB.example.com" with port 3306 and "db.example.com:3306" with no port are the same address. A caller that spaces out probes per address keys on it, so a different spelling cannot buy a second probe. ErrInvalidAddress as Identify.
Types ¶
type BuildOption ¶ added in v0.85.0
type BuildOption func(*buildConfig)
Build runs every preflight check and returns the structured report without rendering it — the seam the control-plane supervisor uses to surface doctor results as cards in the console UI (runDoctorTo keeps the CLI's write-and-exit behavior on top of it). BuildOption tunes what Build reports without widening its signature for every caller that does not care.
func ForConsole ¶ added in v0.95.0
func ForConsole() BuildOption
ForConsole leaves out the checks a web-console user has nothing to act on. Today that is the foreign keys that cascade: they are the user's design, and the console's Restore includes the child rows when the parent table is picked. Left out at the source, the check is in no list, count or light the console draws. `bintrail doctor` and the watch preflight keep it.
func ForUnsavedServer ¶ added in v0.86.0
func ForUnsavedServer() BuildOption
ForUnsavedServer runs the source half of the checks for a server that is not saved yet: the web interface's Test connection on a new server (#1767). Its index does not exist, so the first schema snapshot is certainly still to be taken, and the index checks are left out: the write-access check creates and drops a probe database, and Test has to leave nothing behind. Save runs them.
func WithDropsPaused ¶ added in v0.104.0
func WithDropsPaused(oldest time.Time) BuildOption
WithDropsPaused tells the capacity check that the rotation loop is refusing to drop on the window it was given (the upgrade guard, #2323), so the index is graded as growing unbounded instead of projected over that window. Only for a caller that ASSUMED the window from the index's own record: with a window the operator passed, the guard does not apply.
func WithLoopbackRetry ¶ added in v0.87.0
func WithLoopbackRetry(retry func(host, port string) string) BuildOption
WithLoopbackRetry lets a failed connection to a loopback address be followed by one look at retry(host, port): a connect that reads the server's greeting and sends nothing — never the user, never the password (see proveLoopback). Only the web interface passes it: it runs where a container is likely, and the command line contacts the host it was given and nothing else. A greeting there proves the container case, so no container detection is needed.
func WithRetainNote ¶ added in v0.85.0
func WithRetainNote(note string) BuildOption
WithRetainNote names WHERE the retention window came from, for the capacity projection to print beside it (#1709): with no --retain set, the window is the one each index was created under, not the running default.
func WithRotationWindow ¶ added in v0.104.0
func WithRotationWindow(note string, paused bool, oldest time.Time) BuildOption
WithRotationWindow is how a DAEMON's doctor run (a preflight, the web interface's Checks report) hands over what its rotation loop does with the index (#2328): note names where an implicit window came from, and paused says the loop's upgrade guard is refusing to drop on it, with the oldest partition it protects. The caller resolves both with the loop's own settings (rotation.ResolveWindowDSN); this package only grades.
func WithSourceSSL ¶ added in v0.94.0
func WithSourceSSL(ssl config.SSL) BuildOption
WithSourceSSL makes the checks open the source the way capture does: config.ConnectSSL with the same TLS mode and files (#879/#946). Without it, a server that only accepts encrypted connections (require_secure_transport) failed the checks in cleartext while capture, over TLS, would have worked. Pass the value capture gets: a console entry's ServerEntry.SourceSSL, or watch's --ssl-* flags.
type CapacityFreeReason ¶ added in v0.73.0
type CapacityFreeReason string
CapacityFreeReason names the branch indexDatadirFree landed on. The two measured values say which path produced the number; the rest say what stood in the way, in the operator's terms. Reported alongside the verdict and never consulted by it: this check is advisory, and no reason here may move a grade (see classifyCapacity).
const ( // CapacityFreeFromMount: measured through the read-only datadir mount the // operator declared in BINTRAIL_INDEX_DATADIR_RO. CapacityFreeFromMount CapacityFreeReason = "mount" // CapacityFreeFromDatadir: measured through the index server's own // @@datadir, after the loopback + hostname check proved the server is // this host's. CapacityFreeFromDatadir CapacityFreeReason = "local_datadir" // CapacityFreeMountUnset: no read-only datadir mount is declared, and the // index is known to be reachable on this machine — either the DSN names // the bundled compose host (whose volume that compose file mounts), or it // is loopback/unix AND the server's @@hostname was CONFIRMED to be this // host's. Declaring the mount is then what would make the volume // measurable, and the advice can be unqualified. Every path that leaves // locality unconfirmed downgrades to CapacityFreeHostUnconfirmed instead // (unconfirmedLocality) — the promise in this sentence is exactly what // that guard keeps true. CapacityFreeMountUnset CapacityFreeReason = "mount_unset" // CapacityFreeMountUnusable: BINTRAIL_INDEX_DATADIR_RO is set to a path // this process cannot read (missing, not a directory, statfs failed). Said // ahead of every topology reason: an operator who declared a mount needs // to hear that the declaration is broken, not a guess about where the // index runs. CapacityFreeMountUnusable CapacityFreeReason = "mount_unusable" // CapacityFreeIndexNotLocal: the index DSN points at another address, so // no filesystem on this host is the index's and a local statfs would // measure the wrong volume. The one reason that must NOT carry a mount // suggestion. CapacityFreeIndexNotLocal CapacityFreeReason = "index_not_local" // CapacityFreeHostUnconfirmed: the index answers on a local address, but // the server could not be confirmed to run on this machine (@@hostname // differs, or this host's name could not be read). A port-forward or an // ssh tunnel presents a REMOTE MySQL at 127.0.0.1 while a local mysqld's // datadir sits right there on disk, so the mount suggestion has to name // its own precondition: pointed at that local datadir it would report a // volume that is not the index's, as a measured number, with the // thresholds live on it. Split from CapacityFreeMountUnset for exactly // that reason. CapacityFreeHostUnconfirmed CapacityFreeReason = "host_unconfirmed" // CapacityFreeReasonUnknown: the DSN could not be read, so the check // cannot even say which of the above applies. CapacityFreeReasonUnknown CapacityFreeReason = "unknown" )
type CapacityMeasurement ¶ added in v0.71.0
type CapacityMeasurement struct {
Status CheckStatus
Reason CapacityReason
// Measured is true when there was enough recent history for a write
// rate; the rate fields below are zero otherwise. CurrentBytes is
// summed regardless.
Measured bool
SampleHours int
// CurrentBytes is binlog_events' total footprint right now.
CurrentBytes uint64
EventsPerDay float64
BytesPerEvent float64
// GrowthBytesPerDay = EventsPerDay × BytesPerEvent.
GrowthBytesPerDay float64
// Retain is the retention window the projection was made over;
// RetainKnown false means the caller could not say (no projection).
Retain time.Duration
RetainKnown bool
// DropsPaused is true under CapacityRotationPaused: Retain is the window
// the index would rotate on, and nothing is being dropped on it.
// PausedOldest is how far back the history the loop will not touch goes.
DropsPaused bool
PausedOldest time.Time
PausedInDaemon bool
// ProjectedBytes is the steady-state size over Retain (0 when Retain is
// 0 or unknown); RemainingBytes the growth still ahead of CurrentBytes
// to reach it (floored at 0).
ProjectedBytes float64
RemainingBytes float64
FreeBytes uint64
FreeKnown bool
// FreeReason is the probe's free-space branch, carried through untouched
// so a surface can say what would make the volume measurable (#1527).
FreeReason CapacityFreeReason
// FreeDays is FreeBytes over the daily GROSS write rate: how long the
// free space lasts if nothing frees it. Present (FreeDaysKnown) only when
// free space is known and the rate is positive. It is not a forecast:
// under a retention window rotation frees space first, and an index at
// steady state does not grow at all. DaysUntilFull is the forecast.
FreeDays float64
FreeDaysKnown bool
}
CapacityMeasurement is the capacity check's structured outcome: the numbers behind the verdict plus the verdict itself. Status and Reason are the CLI check's decision, taken by classifyCapacity; the CLI renders its text from this same struct, so a second surface cannot drift from it.
func EvaluateCapacity ¶ added in v0.71.0
func EvaluateCapacity(probe CapacityProbe, retain time.Duration, retainKnown bool, paused *DropsPaused, now time.Time) CapacityMeasurement
EvaluateCapacity runs the projection and the verdict over a probe. retainKnown false is the console's standalone-serve case: the process that rotates the index is not this one, so the window is unknown and the check must not claim "unbounded" — it projects nothing and only the free-space floor can warn.
paused, when not nil, says the rotation loop is refusing to drop on retain (the upgrade guard, rotation.UpgradeGuard). The caller decides that: only a caller that knows the window is IMPLICIT may pass it, since under an explicit window the loop never consults the guard. The index is then graded as growing unbounded, with no steady-state projection.
func (CapacityMeasurement) DaysUntilFull ¶ added in v0.71.0
func (m CapacityMeasurement) DaysUntilFull() (float64, bool)
DaysUntilFull is when the volume fills, and whether that can be said. Only an index KNOWN to have no retention window, or one whose window the loop is known not to be dropping on (DropsPaused), grows at the gross rate until the disk is full. A window that is set bounds the index, and one that could not be read may be set, so neither is reported as a date the disk fills (#2263): a client printed "full in 1.3 days" for an index that was flat.
type CapacityPartition ¶ added in v0.71.0
CapacityPartition is one named hourly partition's measured footprint. Rows and bytes come from information_schema — InnoDB ESTIMATES, good for capacity planning, not exact (docs/capacity.md).
type CapacityProbe ¶ added in v0.71.0
type CapacityProbe struct {
// Partitions are binlog_events' named hourly partitions (p_future and
// unrecognised names carry no hour and are dropped at read time).
Partitions []CapacityPartition
// TableVisible reports whether binlog_events exists AND is visible to
// this user. Only probed when Partitions is empty (the one case where
// "not initialized yet" and "no history yet" need telling apart); true
// whenever partitions were read.
TableVisible bool
// FreeBytes is the index datadir's free space; FreeKnown is false when
// it is not measurable from this process (see indexDatadirFree).
FreeBytes uint64
FreeKnown bool
// FreeReason names HOW free space was measured, or WHY it was not, so a
// surface reporting "not measurable" can say what would make it
// measurable instead of asserting a topology it inferred (#1527).
FreeReason CapacityFreeReason
}
CapacityProbe is everything the capacity check reads from the index server, separated from the verdict so another surface (the console, #1444) can run the SAME projection and thresholds over its own connection — or a test can feed a fixture — without re-deriving either.
func ProbeCapacity ¶ added in v0.71.0
ProbeCapacity reads the capacity check's inputs over an open connection to the index server: partition statistics, table visibility when there are none, and the datadir's free space (best-effort: unknown, never a wrong number). dsn is the connection's DSN, used only to decide whether the datadir can be measured from this host. The connection may or may not have dbName selected — every query qualifies the schema explicitly.
type CapacityQueryError ¶ added in v0.71.0
CapacityQueryError is the error ProbeCapacity returns when one of its information_schema reads fails. What names the read in operator words, Table the system table it needed — the CLI check builds its remediation from it.
func (*CapacityQueryError) Error ¶ added in v0.71.0
func (e *CapacityQueryError) Error() string
func (*CapacityQueryError) Unwrap ¶ added in v0.71.0
func (e *CapacityQueryError) Unwrap() error
type CapacityReason ¶ added in v0.71.0
type CapacityReason string
CapacityReason names the branch of the verdict that fired, so a surface that writes its own copy (the console) keys on the decision, never on the CLI's detail text.
const ( // CapacityNotInitialized: binlog_events is not visible (skip). CapacityNotInitialized CapacityReason = "not_initialized" // CapacityNotEnoughHistory: fewer than capMinSampleHours recent hours or // capMinSampleRows rows to measure a write rate (skip). CapacityNotEnoughHistory CapacityReason = "not_enough_history" // CapacityRetentionUnknown: the caller does not know the retention // window, so no steady-state projection is made (skip). Only the free // floor can still WARN in this mode. CapacityRetentionUnknown CapacityReason = "retention_unknown" // CapacityNoRetention: no rotation configured, the index grows without // bound (warn). CapacityNoRetention CapacityReason = "no_retention" // CapacityRotationPaused: rotation is ON and the index has a window, but // the loop's upgrade guard is refusing to drop (history far beyond that // window that no operator chose to delete), so nothing bounds the index // until a retention is set (#2323). Same grade as no_retention, different // fix: the advice there is "turn rotation on", which is wrong here. CapacityRotationPaused CapacityReason = "rotation_paused" // CapacityFreeUnknown: the projection is known but the datadir's free // space is not measurable from this host, so the thresholds are skipped, // not passed (skip). CapacityFreeUnknown CapacityReason = "free_unknown" // CapacityGrowthExceedsFree: the growth still ahead exceeds the free // space, the disk fills before rotation bounds the index (fail). CapacityGrowthExceedsFree CapacityReason = "growth_exceeds_free" // CapacityFreeUnderFloor: free space is under capFreeFloorDays of the // measured write rate (warn). CapacityFreeUnderFloor CapacityReason = "free_under_floor" // CapacityHeadroomLow: the growth still ahead consumes over // capWarnFraction of the free space (warn). CapacityHeadroomLow CapacityReason = "headroom_low" // CapacityOK: the growth ahead fits with headroom (pass). CapacityOK CapacityReason = "ok" )
type CheckResult ¶
type CheckResult struct {
Name string `json:"name"`
Status CheckStatus `json:"status"`
// Detail and Remediation are typically empty for StatusPass/StatusSkip and
// populated for StatusFail/StatusWarn.
Detail string `json:"detail,omitempty"`
Remediation string `json:"remediation,omitempty"`
// Kind names the finding in a fixed word (kind.go), for a screen to
// switch on; empty when it is none of them. Subjects are the things it
// is about (the privileges missing, the tables without a key, the
// setting that is wrong), and Statements, for tables without a key, one
// statement per table. All three are empty on a pass.
Kind string `json:"kind,omitempty"`
Subjects []string `json:"subjects,omitempty"`
Statements []string `json:"statements,omitempty"`
// Optional marks a WARN about something capture works fine without (the
// SQL statement behind each change, noticing a column rename). The status
// stays "warn" so scripts that read it keep working, but the report
// counts it under Optional instead of Warnings, and screens fold it under
// "Optional improvements". It never hides a FAIL: add counts a failure as
// a failure whatever this says.
Optional bool `json:"optional,omitempty"`
}
func CheckArchiveObjectLock ¶ added in v0.49.0
func CheckArchiveObjectLock(ctx context.Context, archiveS3, region string, retain time.Duration) CheckResult
CheckArchiveObjectLock reports the Object Lock posture of the --archive-s3 bucket. retain is doctor's --retain window (0 = no rotation configured — the retention comparison is skipped, the posture checks still run).
func CheckFullReadDisk ¶ added in v0.103.0
func CheckFullReadDisk(ctx context.Context, stagingDir, sourceDSN string, schemas []string) CheckResult
CheckFullReadDisk compares the free space at stagingDir with what a full read of the source would write there. It is advisory: it warns at most and never changes the exit code, because doctor does not know where the snapshot is written (a local folder, or S3 only) and may run on another machine than the one that reads. With no folder it says where the check that does know runs.
The folder is measured on the machine this runs on, and the tables are the ones `--schemas` selects, which need not be what a server saved in the web interface reads. Every outcome says both.
func CheckProxySQLRules ¶ added in v0.30.0
func CheckProxySQLRules(parent context.Context, adminDSN string) CheckResult
CheckProxySQLRules verifies the six dbtrail query rules `bintrail proxysql-config` installs are live (present AND active) in ProxySQL's runtime_mysql_query_rules (#820). Opt-in via `doctor --proxysql-admin`; advisory only — every outcome short of all-six-live is WARN, never FAIL, because ProxySQL routing is an optional deployment and doctor's exit code must not go red over it.
The check exists chiefly for the /*+ DBTRAIL_AT */ hint form: it is valid vanilla MySQL (an unknown optimizer hint is a warning, not an error), so when routing is missing the query executes against the live table and returns PRESENT data with no error — the only statement shape that degrades silently on a misroute. The other forms fail loud (`_flashback.*` → ER_BAD_DB 1049; bare AS OF → 1064).
func CheckSnapshotExpiry ¶ added in v0.91.0
func CheckSnapshotExpiry(ctx context.Context, snapshotS3, region string, every time.Duration) CheckResult
CheckSnapshotExpiry reports whether the bucket behind an s3:// snapshot destination expires old snapshots. every is how often snapshots are taken (0 = unknown). A destination that is not S3 has no bucket rule to read: SKIP, the check does not apply.
type CheckStatus ¶
type CheckStatus string
CheckStatus is the outcome of a single preflight check. Constrained to the four constants below; unknown values are rejected by (*Report).add.
const ( StatusPass CheckStatus = "pass" StatusFail CheckStatus = "fail" StatusWarn CheckStatus = "warn" StatusSkip CheckStatus = "skip" )
type DropsPaused ¶ added in v0.104.0
DropsPaused is the upgrade guard's refusal, as the capacity check needs it: Oldest is the oldest partition the loop will not drop.
InDaemon is true when the caller IS the process that rotates this index (a daemon's preflight, the web interface's Checks report): it knows no retention was chosen, so the finding is stated as a fact. False is the standalone `bintrail doctor`, which cannot see the daemon's flags and words it as a condition, with --retain as the way to say otherwise.
type DumpEstimate ¶ added in v0.103.0
type DumpEstimate struct {
Bytes int64 // DATA_LENGTH + INDEX_LENGTH, summed: what the warnings read
// DataBytes is DATA_LENGTH alone, summed: what the refusal reads, since
// a dump holds no secondary indexes.
DataBytes int64
Tables int
// Compressed counts tables whose reported size is their compressed size;
// CompressedTop names the largest dumpCompressedNamed of them.
Compressed int
CompressedTop []string
// Unsized counts tables the server reported no size for (NULL). They add
// nothing to Bytes, so the note says the estimate is short by them.
Unsized int
// Stale: the source refused to serve current sizes (not the 1193 of a
// server that has no cache), so they may be up to a day old.
Stale bool
}
DumpEstimate is what the source reports about the tables a dump selects.
func ReadDumpEstimate ¶ added in v0.103.0
ReadDumpEstimate runs dumpSizeQuery on db, on one connection prepared by PrepareDumpEstimateSession: the SET SESSION statements hold for that connection only. The caller opens db, with its own TLS settings.
type DumpSessionExecer ¶ added in v0.103.0
type DumpSessionExecer interface {
ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
}
DumpSessionExecer is the part of a connection PrepareDumpEstimateSession uses.
type ExpiryState ¶ added in v0.91.0
type ExpiryState string
ExpiryState is which of the three answers the read gave.
const ( // ExpiryInForce: an enabled rule whose scope covers the snapshot prefix // expires objects, by age (Days) or on a fixed date (ExpiresOn). ExpiryInForce ExpiryState = "in_force" // ExpiryNone: the rules were read and none of them does. ExpiryNone ExpiryState = "none" // ExpiryUnreadable: the rules could not be read. Nothing is known. ExpiryUnreadable ExpiryState = "unreadable" )
type Identification ¶ added in v0.92.0
type Identification struct {
// Addr is the host:port probed, for the caller's log line.
Addr string `json:"addr"`
// Kind names the failure, or "" when a MySQL server answered.
Kind string `json:"kind,omitempty"`
// Version is the greeting's version string, with MariaDB's 5.5.5-
// compatibility prefix removed.
Version string `json:"version,omitempty"`
// Flavor is FlavorMySQL or FlavorMariaDB, or "" when unknown: no version,
// or a suspected proxy whose version says nothing about what is behind it.
Flavor string `json:"flavor,omitempty"`
// Managed is ManagedRDS or ManagedAurora when the host name says so.
Managed string `json:"managed,omitempty"`
// Proxy names a suspected proxy, or "".
Proxy string `json:"proxy,omitempty"`
// ServerError is the error code a greeting carried instead of a version.
ServerError int `json:"server_error,omitempty"`
// Answer says what a KindNotMySQL peer did: AnswerSilent, AnswerClosed
// or AnswerOther.
Answer string `json:"answer,omitempty"`
// Suggest is the address a MySQL server answered at, for
// KindLoopbackInContainer.
Suggest string `json:"suggest,omitempty"`
// From is, for KindTimeout, the address this machine sends from to reach
// Addr: the one a firewall or a security group has to let in. It is the
// local end of the route the system picks, read without sending a packet
// and without asking any outside service (DBTrail does not phone home).
// Across a NAT the server sees a different, public address, so a screen
// must say so. Empty when unknown.
From string `json:"from,omitempty"`
// InContainer is set, for KindTimeout, when DBTrail runs in a container:
// the local address is then the container's, which no firewall outside it
// sees, so From is left empty instead of naming the wrong address.
InContainer bool `json:"in_container,omitempty"`
}
Identification is what the probe learned. Kind is empty when a MySQL or MariaDB server greeted; a greeting that refused DBTrail's address for a reason other than a block (1130 before DBTrail's user exists is the common one) still has an empty Kind, with ServerError set and no Version.
func Identify ¶ added in v0.92.0
func Identify(ctx context.Context, host, port string, loopbackRetry func(host, port string) string) (Identification, error)
Identify probes host:port without logging in. loopbackRetry, when non-nil, is where a failed loopback address is looked for again (the web interface passes DockerHostRetry, see WithLoopbackRetry). An empty port means 3306.
The error is non-nil only when there is no cause to name: an invalid address (ErrInvalidAddress), a canceled ctx, or a dial error none of the kinds describe. Every named cause comes back in Identification.Kind.
Callers must not repeat it without bound. Each probe ends before the handshake does, and MySQL counts that as a connection error against DBTrail's address; at max_connect_errors (100 by default) the server blocks the address with 1129, and only a successful login from it resets the count, which nobody has before DBTrail's user exists. A probe re-run every few seconds would cause the very KindHostBlocked it then reports.
type LifecycleRule ¶ added in v0.91.0
type LifecycleRule struct {
ID string
Enabled bool
// Prefix is the rule's scope exactly as the bucket holds it; "" is the
// whole bucket.
Prefix string
// Conditional: the rule also filters by tag or by object size, so it
// applies to some objects under Prefix and not to others.
Conditional bool
// Days is the age current objects expire at; 0 when the rule has none
// (it moves objects to another class, expires old versions only, or
// aborts unfinished uploads).
Days int
// Date is a fixed expiry date; zero when the rule has none.
Date time.Time
// NoncurrentDays is the age OLD VERSIONS expire at; 0 when the rule
// leaves them. On a bucket that keeps versions, Days alone only writes
// a delete marker and every byte stays.
NoncurrentDays int
}
LifecycleRule is one bucket rule reduced to what the verdict reads, separated from the SDK types so the verdict is testable without a bucket.
type PreflightError ¶ added in v0.71.0
type PreflightError struct {
// Checks lists the failing checks' names in report order, after the
// caller's exclusions (Err: all of them; ErrExcluding: minus the named
// advisory checks). The count in the message is derived from it, so the
// two cannot disagree.
Checks []string
}
PreflightError is the typed form of "N preflight check(s) failed", so a consumer can recognise a preflight refusal with errors.As instead of by its text. It carries the failing checks' NAMES only (the fixed check names such as "binlog_format=ROW"; extensions registered through cliapp add their own) — never a Detail, which quotes server variables, grants and hostnames. The message bytes are the ones the untyped error used to print.
func (*PreflightError) Error ¶ added in v0.71.0
func (e *PreflightError) Error() string
func (*PreflightError) TelemetryClass ¶ added in v0.71.0
func (e *PreflightError) TelemetryClass() string
TelemetryClass implements telemetry.Classed, deriving the class from WHICH checks failed rather than reporting every refusal as one bucket: db_connection when the source or the index could not be reached, db_permission for grants, index write access or schema access, internal when a registered extension check panicked, and config_invalid for everything else — binlog_format, row image, log_bin, a malformed index DSN, an extension's own check, and on standalone `doctor` (which excludes nothing) the disk-capacity check: a setting the operator has to change. Keyed on the check NAME, so a check whose probe query itself failed grades as that check (config_invalid), not as the query's cause; an unreachable source is caught by the connection check ahead of it. Precedence when several fail together: connection, then internal, then permission, then configuration — a connection failure is the root of whatever failed after it (the index checks run in that order, so a connect failure fails both the connection and the write-access check). One imprecision is deliberate: the connection checks record a wrong password as a connection failure (the cause is stringified into Detail), so db_connection here reads as "could not connect as configured".
type Report ¶
type Report struct {
Checks []CheckResult `json:"checks"`
Passed int `json:"passed"`
Failed int `json:"failed"`
Warnings int `json:"warnings"`
Skipped int `json:"skipped"`
// Optional counts the optional improvements: WARNs marked Optional, which
// are not in Warnings. Passed+Failed+Warnings+Skipped+Optional is the
// number of checks with a known status.
Optional int `json:"optional"`
// ReadyFooter / FixFooter customize the trailing one-line guidance in the TEXT
// output (the all-passed line and the has-failures line respectively). They are
// excluded from JSON. Empty values fall back to the default MySQL-oriented
// guidance, so existing callers are unaffected; a non-MySQL caller (e.g.
// bintrail-pg doctor) sets them to its own command names.
}
func (*Report) Add ¶ added in v0.19.0
func (r *Report) Add(c CheckResult)
Add appends a check result and updates the counters. It is the exported entry point for callers building a Report outside this package — e.g. the PostgreSQL doctor, which cannot live in this package because internal/doctor must stay free of the pgx/pglogrepl dependency (cmd/bintrail's pgfree ban). MySQL's own Build uses the unexported add.
Add (and the internal add) are the ONLY sanctioned way to grow a Report: appending to the exported Checks slice directly would leave Passed/Failed/Warnings/Skipped stale. Callers must always go through Add.
func (*Report) Err ¶
Err returns a non-nil error when any required check failed, so the CLI exits non-zero for CI/scripting use cases. Warnings and optional improvements do not cause failure.
func (*Report) ErrExcluding ¶
ErrExcluding is Err but ignoring failures of the named advisory checks. `up`'s preflight uses it for the capacity projection: refusing to boot the stream over a disk forecast would manufacture the very forensic gap the check warns about — an unattended host reboot would crash-loop instead of capturing while there is still room. The standalone `doctor` command keeps full FAIL semantics (CI smoke tests SHOULD go red on a capacity overrun).
type SnapshotExpiry ¶ added in v0.91.0
type SnapshotExpiry struct {
State ExpiryState `json:"state"`
Bucket string `json:"bucket"`
// Prefix is the snapshot prefix as the daemon builds keys under it: one
// trailing slash trimmed, nothing else tidied.
Prefix string `json:"prefix"`
// Days and RuleID name the covering rule with the SHORTEST age: with
// several rules on one object, that is the one that deletes it.
Days int `json:"days,omitempty"`
RuleID string `json:"rule_id,omitempty"`
// WholeBucket: that rule's scope is the whole bucket, so it expires
// everything else in it at the same age.
WholeBucket bool `json:"whole_bucket,omitempty"`
// OldVersionsStay: that rule expires current objects only. Whether the
// bucket keeps versions is not read (it is another permission), so this
// is said as a condition, never as a finding.
OldVersionsStay bool `json:"old_versions_stay,omitempty"`
// ExpiresOn is the earliest fixed date (YYYY-MM-DD, UTC) a covering rule
// expires on, and DatePassed whether that day has come. Such a rule has
// no age: from that date on it expires every object in its scope.
ExpiresOn string `json:"expires_on,omitempty"`
DateRuleID string `json:"date_rule_id,omitempty"`
DatePassed bool `json:"date_passed,omitempty"`
// Conditional counts the enabled expiring rules on this prefix that
// apply by tag or size only. They are NOT counted as protection:
// bintrail sets no tag on what it uploads, and a size limit leaves part
// of every snapshot behind. They are counted here so the answer can say
// why a rule the operator sees in the bucket was not taken.
Conditional int `json:"conditional,omitempty"`
// Reason and Error are set on ExpiryUnreadable only.
Reason string `json:"reason,omitempty"`
Error string `json:"error,omitempty"`
}
SnapshotExpiry is the verdict, and the body of the console's answer.
func ReadSnapshotExpiry ¶ added in v0.91.0
func ReadSnapshotExpiry(ctx context.Context, snapshotS3, region string) SnapshotExpiry
ReadSnapshotExpiry answers for one s3:// snapshot destination, with the credentials and bucket routing every other S3 surface uses. The caller bounds ctx. A destination that is not an s3:// URL, or a client that cannot be built, is unreadable: nothing was read.
func SnapshotExpiryVerdict ¶ added in v0.91.0
func SnapshotExpiryVerdict(snapshotS3 string, rules []LifecycleRule, now time.Time) SnapshotExpiry
SnapshotExpiryVerdict turns the rules of a bucket into the answer for one snapshot destination. Pure: no network, and the clock is passed in.
- a disabled rule protects nothing;
- a rule that does not cover the prefix protects nothing;
- a rule limited by tag or size is counted apart, never as protection;
- of the rules left, the shortest age wins, since it deletes first.