Skip to content

Emission-Only Local Run

Use this page when you want synthetic data in an existing Grafana Cloud stack, for example to give infrastructure dashboards something to render, and you want nothing else to happen on that stack. The run delivers metrics, logs and traces for a chosen set of bundled blueprints. It does not provision Synthetic Monitoring checks, and it does not register or heartbeat Fleet Management collectors.

It builds on the Quick Start: that page previews one blueprint offline and walks the full first run, and this page changes only what an emission-only operator needs different.


What emission-only means

SettingValueEffect
DRY_RUNfalseDelivers to the configured sinks. The shipped default is true, which pushes nothing.
STARTUP_DISABLED_KINDSsynthetic_monitoring,fleet_managementDisables both construct kinds from the first tick.
BLUEPRINT_NAMESexact names, comma-separatedLoads only the named blueprints.

With synthetic_monitoring in STARTUP_DISABLED_KINDS, the Synthetic Monitoring lane is removed for the lifetime of the process and no provisioning happens. With fleet_management listed, Fleet registration and heartbeats are suspended and no Fleet alloy_* series are emitted. The full semantics, the precedence against the persisted snapshot and the effect of POST /control/kinds are documented once, in Disabling kinds at startup and Disabling Fleet Management from the first tick. The variable itself is listed in Configuration.

Do not run Grafana write recipes

Three just recipes mutate a live Grafana stack: just provision, just selfobs-dashboard and just corpus-gcx. None of them is part of an emission-only run. Do not run them.


Example blueprint set

This set suits infrastructure dashboards: Kubernetes, a host fleet, native OTLP, Linux hosts in Docker, and AWS over native OTLP.

BLUEPRINT_NAMES=k8s-full-stack,hostfleet,otlp-native,hosts-linux-docker,aws-otlp-native

These are bundled blueprint names under blueprints/. Any other combination of bundled names works the same way. An unknown name stops startup and lists the names that are available. Keep the set small: every selected blueprint adds series, and each one is pushed to your stack.


Step 1: Build

Follow Step 1 of the Quick Start. The commands are identical on macOS and Linux and need Bash 5+, which on macOS means a Homebrew Bash ahead of /bin/bash on PATH (see Installation).

git clone https://github.com/rknightion/synthkit.git
cd synthkit
just ui </dev/null
go build ./cmd/synthkit

Step 2: Create the token and .env

Create a Grafana Cloud Access Policy token with these scopes, and no others for this run:

  • metrics:write
  • logs:write
  • traces:write

These are the scope names used in Credentials; that page also lists where to find the three numeric identifiers the endpoints below need. profiles:write is only needed if you configure a Pyroscope destination, which this run does not.

Create .env without replacing an existing file, then add the secret through the hidden-prompt helper so it never appears in shell history or a process listing:

if [ -L .env ] || { [ -e .env ] && [ ! -f .env ]; }; then
  echo "refusing symlink or non-file .env" >&2
  exit 1
fi
if [ ! -e .env ]; then
  install -m 600 .env.example .env
else
  echo "Keeping existing .env; review its settings before continuing."
fi
chmod 600 .env
./plugins/synthkit/skills/initial-setup/scripts/add-secret.sh GC_TOKEN .env
./plugins/synthkit/skills/initial-setup/scripts/add-secret.sh CONTROL_TOKEN .env

Then set the endpoint and user values with the same placeholders the Quick Start uses. Edit .env so these keys read:

GC_PROM_RW=https://prometheus-prod-XX-<region>.grafana.net/api/prom/push
GC_PROM_USER=<mimir-instance-id>
GC_OTLP_ENDPOINT=https://otlp-gateway-<region>.grafana.net/otlp
GC_OTLP_USER=<stack-id>
GC_LOKI=https://logs-prod-XXX.grafana.net/loki/api/v1/push
GC_LOKI_USER=<loki-instance-id>
BLUEPRINT_NAMES=k8s-full-stack,hostfleet,otlp-native,hosts-linux-docker,aws-otlp-native
STARTUP_DISABLED_KINDS=synthetic_monitoring,fleet_management
DRY_RUN=false

Leave every GC_SM_* and GC_FM_* key empty. Leaving them empty is not enough on its own to stop Fleet series, which is why STARTUP_DISABLED_KINDS is set. Synthkit's own telemetry (SELFOBS_ENABLED) uses a separate credential triplet and stack and is not part of this run; never reuse GC_TOKEN for it.

CONTROL_TOKEN is the Basic-auth password (username control) for the control plane used in the verification step. Do not paste either token into a command line or a note.


Step 3: Preview, preflight, then run

Prove the selection loads before anything is delivered. This run is offline, ignores .env, and -once exits after one tick:

DRY_RUN=true SELFOBS_ENABLED=false \
  BLUEPRINT_NAMES=k8s-full-stack,hostfleet,otlp-native,hosts-linux-docker,aws-otlp-native \
  STARTUP_DISABLED_KINDS=synthetic_monitoring,fleet_management \
  ./synthkit -env /dev/null -once

The output names the five selected blueprints before any sink can push, as selected blueprints: 5 [aws-otlp-native hostfleet hosts-linux-docker k8s-full-stack otlp-native], followed by control: disabled kinds from first tick: [synthetic_monitoring fleet_management] and [dry-run ...] summaries. Then check the live endpoints and start the loop:

./synthkit -preflight
./synthkit

./synthkit reads .env and runs continuously; exported process variables override file values. Preflight sends empty authenticated requests and must report ready for the Prometheus, Loki and OTLP lanes first. To use Docker Compose instead of the binary, set the same keys in .env and follow the Compose path; the variables above are passed through the selected env_file.

Stop the foreground process with Ctrl-C.


No backfill, and warm-up

Synthkit emits only from the moment it starts. There is no backfill: it never writes samples with earlier timestamps, so a panel's time range before the process started stays empty, and the generator is not a way to fill history. A restart does not recover or replay what was missed (see checkpoint foundations for what the optional checkpoint package does and does not provide).

Panels therefore fill as cumulative series accumulate:

  • The master clock ticks every TICK_DEFAULT, 5s by default (see Configuration). Request traffic is minted on that cadence.
  • Metric lanes sample at 60 seconds by default, unless a blueprint explicitly opts into a faster high_dpm.metric_interval, bounded by MAX_DPM_PER_SERIES (Architecture).
  • Counters and histograms are cumulative across ticks and held in process memory (internal/state), and the sinks push running totals, never deltas. A restart starts them again from zero.

rate(), increase() and histogram_quantile() over a counter or histogram need at least two samples inside the panel's range. At the default 60 second cadence that is at least two minutes after start before any such panel shows a value, and a panel with a five minute range keeps filling until that window holds a full set of samples. Gauge panels show a value after the first push. Allow roughly five to ten minutes before judging a rate or histogram panel, and expect a panel with a long range, such as one hour, to look partial until that much time has elapsed since the start.


Confirm emission

Everything below is read-only against the local control plane. None of it touches Grafana.

Operator UI. Open http://127.0.0.1:8088/control/ui and sign in with username control and your CONTROL_TOKEN. The sink-readiness strip shows the last push result for promrw, loki and otlp; each should show a recent success and no failures.

Status API. curl -u control prompts for CONTROL_TOKEN without echoing it:

curl -fsS -u control http://127.0.0.1:8088/control/status | jq -e '.dry_run == false'

This must print true. If jq is not installed, run the same request without the pipe and confirm the response contains "dry_run":false.

Inventory and disabled kinds. GET /control/inventory lists what is declared for each blueprint and construct, with exact metric names and a safe query identity. GET /control/state reports the effective disabled kinds:

curl -fsS -u control http://127.0.0.1:8088/control/inventory | jq '.totals, [.blueprints[].blueprint]'
curl -fsS -u control http://127.0.0.1:8088/control/state | jq '.disabled_kinds'

Expect totals.blueprints to be 5 with the five blueprint names listed, and disabled_kinds to be ["synthetic_monitoring","fleet_management"]. The inventory is a structural view, so a fleet_management entry can still appear in it; the disabled kind is what stops its ticks, and it pushes no series. Use the inventory's metric names and identity when you query the stack, rather than a hand-kept list. Per Control Plane, wait two declared emission intervals plus the delivery deadline before treating a missing signal as evidence.

If a lane fails, GET /control/status and GET /control/diagnostics name it; see Troubleshooting.