DQL Queries

dtctl provides a powerful interface for executing Dynatrace Query Language (DQL) queries directly from your terminal. Run ad-hoc queries inline, load them from files, use template variables, and stream live results.

Simple Inline Queries

Pass a DQL query string directly as an argument:

dtctl query "fetch logs | limit 10"

dtctl query "fetch spans | limit 10"

dtctl query "fetch events | limit 10"

dtctl query "timeseries avg(dt.host.cpu.usage)"

File-Based Queries

Store complex queries in .dql files and execute them with -f:

dtctl query -f queries/errors.dql

This keeps queries version-controlled and avoids shell-escaping issues.

Stdin Input

Pipe queries or use heredocs to avoid shell quoting problems entirely:

# Heredoc
dtctl query <<'EOF'
fetch logs
| filter loglevel == "ERROR"
| summarize count = count(), by: {dt.entity.service}
| sort count desc
| limit 20
EOF

# Pipe from a file
cat queries/errors.dql | dtctl query

# Pipe from another command
echo 'fetch logs | limit 5' | dtctl query

PowerShell Quoting

On Windows PowerShell, use here-strings to avoid escaping issues:

# PowerShell here-string
dtctl query @'
fetch logs
| filter loglevel == "ERROR"
| limit 10
'@

Template Queries

Use Go template syntax with --set to parameterize queries:

dtctl query "fetch logs | filter environment == '{{ .env }}' | limit {{ .n }}" \
  --set env=production --set n=50

Template variables work with both inline queries and file-based queries:

dtctl query -f queries/service-errors.dql --set service=checkout --set hours=24

Output Formats

Control how results are displayed:

# Default table output
dtctl query "fetch logs | limit 10"

# JSON (for scripting and piping to jq)
dtctl query "fetch logs | limit 10" -o json

# YAML
dtctl query "fetch logs | limit 10" -o yaml

# CSV (for spreadsheets and data tools)
dtctl query "fetch logs | limit 10" -o csv

Large Dataset Downloads

Control result size limits for bulk data extraction:

# Increase max records returned (default varies by query type)
dtctl query "fetch logs" --max-result-records 100000

# Increase max result payload size
dtctl query "fetch logs" --max-result-bytes 52428800

# Control how much data Grail scans (in GB)
dtctl query "fetch logs" --default-scan-limit-gbytes 500

Spilling Large Results to a File

A large result is a context-window hazard for AI agents: tens of thousands of rows serialised into a model’s context can cost millions of tokens. Instead of returning the rows, dtctl query can spill the full result to a local file and return a compact summary in its place — per-column stats (type, nulls, distinct/top-K, min/max, mean), a few sample rows, and the file path — so the data can be interrogated locally without re-running the Grail query.

# Tri-state control. Bare --spill = always spill.
dtctl query "fetch logs" --spill            # always spill
dtctl query "fetch logs" --spill=auto       # spill only above the threshold
dtctl query "fetch logs" --spill=never      # force rows inline (the default for a bare command)

# Choose the destination or format
dtctl query "fetch logs" --spill-to ./out.jsonl    # explicit file (implies --spill; format from extension)
dtctl query "fetch logs" --spill --spill-format parquet # jsonl (default), json, csv, or parquet
dtctl query "fetch logs" --spill=auto --spill-threshold 100KB  # size that triggers a spill (default 50KB)
  • Defaults: never for a bare command (so the … -o csv > out.csv pipeline path is untouched), auto in agent mode. With the default record cap a typical result stays well under the threshold, so spill is expected to engage mainly when the cap is raised for an export or full-population scan (e.g. --max-result-records 1000000).
  • Where files go: the OS user cache dir (~/.cache/dtctl/results on Linux, ~/Library/Caches/dtctl/results on macOS, %LocalAppData%\dtctl\results on Windows), partitioned by context, written atomically with 0700/0600 permissions and pruned after a 24h TTL. On a read-only filesystem the command degrades to a summary without a path rather than dumping rows.
  • --spill-to vs > file: shell redirection (-o csv > out.csv) writes the raw bytes; --spill-to writes the file and returns the summary/manifest in its place. Use redirection when you want the bytes, --spill-to when you want the summary now and the bytes on disk for later. A user-chosen path opts out of the managed cache’s TTL pruning and per-context partitioning (surfaced as a warning).

Spill is configurable globally or per-context under a spill: config section and via the DTCTL_SPILL / DTCTL_SPILL_DIR environment variables — see Configuration.

Inspecting a Spilled File

Once a result has spilled, dtctl inspect interrogates the file locally — the expensive Grail scan already happened, so every inspect call reads only what it needs and emits only the answer, without re-querying. Its primary capability is row access (the summary only carried per-column stats and a few sample rows):

dtctl inspect ./out.jsonl --head 20                       # first 20 rows
dtctl inspect ./out.jsonl --page --offset 1000 --limit 50 # a window deep in the result
dtctl inspect ./out.jsonl --tail 10 --fields timestamp,content  # last rows, projected
dtctl inspect ./out.jsonl --jq 'select(.status == 500)'   # keep only matching rows (streaming, whole-file)
dtctl inspect ./out.jsonl --schema                        # re-derive columns + types + null counts
dtctl inspect ./out.jsonl --stats                         # re-derive the per-column profile
dtctl inspect --list                                      # lost the path? list spilled files in this context

--jq here is a full-file filter run per record (like jq over NDJSON), so it returns “only the matching rows” without re-querying Grail — a large match set re-spills via the same --spill* guard. inspect is not a query engine, though — for aggregate questions push the work back into DQL (… | summarize …) and re-query. See the Inspect Commands reference for the full flag set.

Filter Segments

Apply filter segments at query time to narrow results to specific data subsets. Segments are AND-combined when multiple are specified. See Filter Segments for how to manage segments.

# Apply a single segment by UID
dtctl query "fetch logs | limit 10" --segment abc123-def456

# Apply a segment by name (resolved via API)
dtctl query "fetch logs | limit 10" --segment my-k8s-segment

# Multiple segments (AND-combined per Grail semantics)
dtctl query "fetch logs | limit 10" --segment seg-1 --segment seg-2

# Short form
dtctl query "fetch logs | limit 10" -S my-segment-uid

Segment Variables

Some segments require variable bindings (e.g., a ready-made k8s.namespace.name segment needs a namespace value). Bind variables inline using URL-query-style syntax on -S:

# Bind a single variable
dtctl query "fetch logs | limit 10" -S "my-segment?host=HOST-001"

# Multiple values for a variable (comma-separated)
dtctl query "fetch logs | limit 10" -S "my-segment?host=HOST-001,HOST-002"

# Multiple variables on one segment
dtctl query "fetch logs | limit 10" -S "my-segment?host=HOST-001&ns=production"

# Works with segment names (resolved before variable binding)
dtctl query "fetch logs | limit 10" \
  -S "[READY-MADE] k8s.namespace.name?k8s.namespace.name=astroshop"

The format is SEGMENT?var=value&var2=value1,value2 where SEGMENT is a UID or name, ? separates the ID from variables, & separates multiple variables, and , separates multiple values.

You can also use --segment-var / -V to override variables from --segments-file:

# Override a file-defined variable
dtctl query "fetch logs" --segments-file segments.yaml -V "seg-1:host=HOST-NEW"

For complex multi-segment configurations with many variables, use a YAML file:

dtctl query "fetch logs | limit 10" --segments-file segments.yaml
# segments.yaml
- id: simple-segment-uid

- id: segment-with-variables
  variables:
    - name: host
      values: [HOST-0000000001, HOST-0000000002]

- id: segment-with-namespace
  variables:
    - name: ns
      values: [production, staging]

Both --segment and --segments-file can be combined. If the same segment ID appears in both, the file entry wins (it may carry variables). Variables from -V take precedence over file variables for the same name. A maximum of 10 segments per query is enforced client-side.

Additional Parameters

Fine-tune query execution with these options.

Timeframe, Timezone, and Locale

# Default timeframe applied when the query does not set its own (ISO-8601/RFC3339)
dtctl query "fetch logs | limit 10" \
  --default-timeframe-start "2024-01-01T00:00:00Z" \
  --default-timeframe-end   "2024-01-02T00:00:00Z"

# Set timezone and locale (affect time bucketing and formatting)
dtctl query "fetch logs | limit 10" --timezone "America/New_York" --locale "en_US"

--default-timeframe-start / --default-timeframe-end only fill in a timeframe when the query itself does not specify one (e.g. via a from/to in the query). There is no --timeframe flag — express relative ranges in DQL instead (e.g. fetch logs, from:now()-2h).

Sampling and Preview

# Enable sampling for faster results on large log/span datasets
# (normalized to a power of 10, e.g. 1000 samples 1/1000 of matching records)
dtctl query "fetch logs" --default-sampling-ratio 1000

# Request preview (partial) results if they are available within the timeout
dtctl query "fetch logs | limit 10" --enable-preview

There is no --sampling-ratio or --preview flag — use --default-sampling-ratio and --enable-preview.

Execution Limits

# Cap how long Grail spends fetching data
dtctl query "fetch logs" --fetch-timeout-seconds 30

# Enforce the tenant's query consumption limit (fail instead of over-consuming)
dtctl query "fetch logs" --enforce-query-consumption-limit

See Large Dataset Downloads for --max-result-records, --max-result-bytes, and --default-scan-limit-gbytes.

Metadata, Types, and Contributions

# Fetch execution metadata alongside results (bare flag = all fields)
dtctl query "fetch logs | limit 10" --metadata

# Select specific metadata fields
dtctl query "fetch logs | limit 10" --metadata=scannedRecords,scannedBytes,executionTimeMilliseconds

# Include DQL column type information in the result
dtctl query "fetch logs | limit 10" --include-types

# Include Grail bucket contribution information
dtctl query "fetch logs | limit 10" --include-contributions

Available --metadata fields: executionTimeMilliseconds, scannedRecords, scannedBytes, scannedDataPoints, sampled, queryId, dqlVersion, query, canonicalQuery, timezone, locale, analysisTimeframe, contributions, metrics.

Progress Indicator

Long-running queries are executed asynchronously and polled until they finish. While polling, dtctl can draw a live progress bar on stderr showing the query’s completion percentage, the volume of data scanned so far, the number of records scanned, and elapsed time:

⠹ scanning  57% ▕███████████▍░░░░░░░░▏  12.2 TB · 4.6B recs · 8.2s

When the query finishes, the bar is replaced with a one-line summary:

✓ scanned 17.3 TB · 6.0B records in 42.1s

The bar is shown by default for interactive terminals. Turn it off with --no-progress:

# default: progress bar shown when stderr is an interactive terminal
dtctl query "fetch logs, from:-24h | summarize count()"

# disable it
dtctl query "fetch logs | summarize count()" --no-progress

Notes:

  • The bar is drawn on stderr only, so it never corrupts piped or redirected results (-o json > file, | jq, etc.).
  • It is automatically suppressed for non-interactive output (non-TTY stderr), under --plain, and in agent mode. NO_COLOR keeps the bar but drops color.
  • Fast queries that complete before the first poll show nothing.

Caller Context

# Declare the caller's intent in the dt-client-context request header
# (useful for AI agents / scripts to tag why a query ran)
dtctl query "fetch logs | limit 10" --client-context "root-cause-analysis"

Chart Rendering

When rendering to a terminal chart (-o chart, sparkline, barchart, braille), control the drawing area:

dtctl query "timeseries avg(dt.host.cpu.usage)" -o chart --width 120 --height 20
dtctl query "timeseries avg(dt.host.cpu.usage)" -o chart --fullscreen  # use full terminal size

Full Parameter Reference

Flag Purpose
--default-timeframe-start / --default-timeframe-end Default query timeframe (ISO-8601/RFC3339) used when the query omits one
--timezone Query timezone (e.g. UTC, Europe/Paris)
--locale Query locale (e.g. en_US, de_DE)
--default-sampling-ratio Sampling ratio for faster approximate results (normalized to a power of 10)
--enable-preview Return preview results if available within the timeout
--fetch-timeout-seconds Time limit for fetching data (seconds)
--enforce-query-consumption-limit Enforce the tenant query consumption limit
--no-progress Disable the live progress bar shown on stderr for long queries
--max-result-records Maximum number of result records
--max-result-bytes Maximum result payload size in bytes
--default-scan-limit-gbytes Cap on how much data Grail scans (GB)
--metadata, -M Include execution metadata (bare = all, or =field1,field2)
--include-types Include DQL column type information
--include-contributions Include Grail bucket contribution information
--client-context Set the dt-client-context request header (caller intent)
--width / --height / --fullscreen Terminal chart dimensions (chart output formats)
--decode-snapshots Decode Live Debugger snapshot payloads (see Live Debugger)
--spill* Spill large results to a file (see above)
-S / --segment, -V / --segment-var, --segments-file Apply filter segments (see above)
--live / --interval Live mode with periodic refresh (see below)
--set Set a template variable (key=value)
-f / --file Read the query from a file (- for stdin)

Live Mode

Stream query results at a regular interval:

# Re-run every 5 seconds
dtctl query "fetch logs | filter loglevel == 'ERROR' | sort timestamp desc | limit 10" \
  --live --interval 5s

Press Ctrl+C to stop live mode.

Waiting for Query Conditions

dtctl wait query repeatedly runs a DQL query until a condition on the record count is met (or a timeout is reached), using exponential backoff between attempts. It is built for tests and CI/CD — waiting for instrumented data to land before making assertions.

# Wait until exactly one matching span arrives
dtctl wait query "fetch spans | filter test_id == 'test-123'" --for=count=1

# Wait for any error logs, up to 2 minutes
dtctl wait query "fetch logs | filter status == 'ERROR'" --for=any --timeout 2m

# Template variables and file-based queries work here too
dtctl wait query -f query.dql --set test_id=my-test --for=count-gte=1

# Tune the backoff (e.g. for CI)
dtctl wait query "..." --for=any --min-interval 500ms --max-interval 15s --backoff-multiplier 2

# Capture the result once the condition is met
dtctl wait query "..." --for=count=1 -o json > result.json

Conditions (--for, required)

Condition Meets when
count=N Exactly N records
count-gte=N At least N records (>=)
count-gt=N More than N records (>)
count-lte=N At most N records (<=)
count-lt=N Fewer than N records (<)
any Any records returned (count > 0)
none No records returned (count == 0)

Polling Controls

Flag Purpose
--timeout Maximum time to wait (default 5m, 0 = unlimited)
--max-attempts Maximum number of attempts (0 = unlimited)
--initial-delay Delay before the first attempt
--min-interval / --max-interval Backoff bounds (defaults 1s / 10s)
--backoff-multiplier Exponential backoff multiplier (must be > 1.0, default 2)
-q, --quiet Suppress progress messages

wait query also accepts the standard query tuning flags (--timezone, --locale, --default-timeframe-start/-end, --default-sampling-ratio, --max-result-records, etc.).

Exit Codes

Code Meaning
0 Condition met
1 Timeout reached
2 Max attempts exceeded
3 Query execution error
4 Invalid condition syntax
5 Invalid arguments

Cancelling Queries

Press Ctrl+C (or send SIGTERM) at any time to cancel a running query. dtctl sends a best-effort query:cancel request to Grail so the backend stops executing the query, then exits. A confirmation (Query cancelled.) or, if the cancel request fails, a Failed to cancel query message is written to stderr.

Query Warnings

DQL may emit warnings (e.g., result truncation, deprecated syntax). These are printed to stderr so they don’t interfere with piped output:

# Warnings appear on stderr, results on stdout
dtctl query "fetch logs" -o json > results.json
# Any warnings are still visible in the terminal

Query Verification

Validate DQL queries without executing them — useful for CI/CD pipelines and pre-commit hooks.

# Verify a query is syntactically valid
dtctl verify query "fetch logs | limit 10"

# Verify from a file
dtctl verify query -f queries/errors.dql

# Return the canonical (normalized) form of the query
dtctl verify query "fetch logs | limit 10" --canonical

# Treat warnings as errors (non-zero exit code)
dtctl verify query -f queries/errors.dql --fail-on-warn

Exit Codes

Code Meaning
0 Query is valid
1 Query has syntax or semantic errors
2 Query is valid but has warnings (with --fail-on-warn)

CI/CD Integration

# In a CI pipeline — verify all .dql files before deploying
for f in queries/*.dql; do
  dtctl verify query -f "$f" --fail-on-warn || exit 1
done