Doctor#
The doctor command analyzes a config file and provides opinionated warnings about potential issues. Unlike validate, which catches hard errors that would prevent execution, doctor warns about things that are technically valid but likely problematic.
edg doctor --config config.edgChecks#
Unreferenced reference datasets#
Warns when a reference: block entry is never referenced by any ref_*() call. The data is loaded into memory but never used.
Unnamed query-type seeds#
Warns when a seed query uses type: query (or query_batch) but has no name. Query-type seeds store their results for later reference, but without a name the results are inaccessible.
Batch sizes that may OOM#
Warns when:
countis set withoutsizeand count exceeds 1,000 - the entire count becomes a single batch, building all rows in memory at oncesizeexceeds 10,000 - very large individual batches that may consume excessive memory
FK cardinality mismatches#
Warns when a seed query uses ref_diff() and its count exceeds the parent dataset size. ref_diff picks unique rows without replacement, so it will exhaust the parent dataset before the child count is reached.
Distribution parameter issues#
Warns about distribution function parameters that may produce unexpected results:
zipf.int(s, ...)wheresis close to 1.0 - produces a nearly uniform distribution, defeating the purpose of using Zipfiannorm(mean, stddev, min, max)wherestddevexceedsmax - min- values cluster at the edges due to clamping rather than forming a bell curve
Stages whose qps ramp leaves the rest flat#
Warns when a stage’s qps: ramp(start, end, ramp_duration) finishes in under 5% of the stage’s duration. A ramp holds at its end value for whatever is left of the stage, so a ramp much shorter than its stage spends almost all of it at one constant rate:
stages {
between_cuts(workers: 4, duration: 24h, qps: ramp(3, 25, 20m))
}- stages "between_cuts": qps ramps 3 to 25 over 20m0s of a 24h0m0s stage (1%), then holds flat at 25 for the remaining 23h40m0sThis is the quiet way a workload ends up flat. The config reads as though it has a shape, and the run is a constant - usually a ramp written for a much shorter stage, or a single stage left covering a whole simulated day. Split the stage up so the peak lands where it belongs:
stages {
cut_build(workers: 12, duration: 2h, qps: ramp(3, 300, 2h))
cut_peak(workers: 16, duration: 1h, qps: ramp(300, 120, 1h))
cut_drain(workers: 6, duration: 1h, qps: ramp(120, 5, 1h))
quiet(workers: 2, duration: 19h, qps: 4)
pre_cut(workers: 2, duration: 1h, qps: ramp(4, 8, 1h))
}A stage with no ramp at all is not reported - a deliberately flat stage is a normal thing to write, and qps: 25 says so plainly.
uniq() arguments that cannot be re-evaluated#
Warns when uniq() or uniq_across() is passed something it can only reject as a duplicate, never redraw from - a bare literal like uniq(500), or an identifier holding a value that is not an expression.
An unquoted call such as uniq(gen('email')) is not reported: the compiler quotes call-shaped arguments, so it redraws exactly as uniq("gen('email')") does. An identifier cannot be quoted that way, because it may already hold the expression as a string - uniq(email_expr) where a global holds "gen('email')" is a working config.
This check evaluates each remaining argument once, in an environment with no database, no AI clients and no plugins. Arguments that cannot be evaluated without a live database are passed over rather than guessed at.
uniq() that cannot de-duplicate in a run query#
Warns when a non-batch run query contains a uniq() call whose expression appears only once. uniq()’s seen set resets after every row of arg generation, and a non-batch run query generates one row per iteration, so that call compares its value against an empty set every time and can never reject a duplicate.
Two calls sharing an expression still see each other within the row, so only a lone call is reported. Use uniq_across() when the value must be unique across iterations - its pool lives as long as the environment.
Output#
Each warning is printed to stdout:
- seed query 3 ("populate_orders"): count (10000) set without size - entire count used as single batch, may OOM
- reference "regions": dataset never referenced by any ref_*() callIf no issues are found:
No issues found.The command always exits with code 0 - warnings are advisory, not errors.
Ignoring warnings#
Use # edg:ignore comments to suppress diagnostics in the editor.
Line-level ignore#
Place # edg:ignore on the line above to suppress all diagnostics on the next line:
# edg:ignore
populate_big(count: 200000)
`INSERT INTO big (id) VALUES ($1)` (uuid_v4())Or inline on the same line:
populate_big(count: 200000) # edg:ignore
`INSERT INTO big (id) VALUES ($1)` (uuid_v4())File-level ignore#
Place # edg:ignore-file anywhere in the file to suppress all doctor warnings. Parse and validation errors are still shown.
# edg:ignore-file
ref unused_regions [
{name: 'us-east-1'}
]Flags#
Global flags --config, --csv-file, and --csv-directory apply. No database connection is required.