github.com/grafana/synthetic-monitoring-agent@v0.64.2/docs/architecture/prober.md (about) 1 --- 2 component: Probers 3 source_paths: 4 - internal/prober/ 5 last_reviewed_commit: f77bec5e 6 generated: 2026-05-23 7 --- 8 9 # Probers — `internal/prober` 10 11 ## Purpose 12 13 A Prober executes a single check of a given type. The package exposes a 14 single `Prober` interface and a factory that picks the right 15 implementation per check type. Each implementation either wraps a 16 `blackbox_exporter` module, runs a custom probe (ICMP, Traceroute), or 17 delegates to the [k6 runner](k6runner.md) for 18 scripted/browser/multihttp checks. 19 20 ## Where it lives 21 22 `internal/prober/` plus one subpackage per check type: 23 24 | Path | Check type / role | 25 | ----------------------------------- | ------------------------------------------------------------ | 26 | `prober.go` | `Prober` interface, `ProberFactory`, type dispatch. | 27 | `http/` | HTTP — wraps `blackbox_exporter/prober`. | 28 | `dns/` | DNS — uses an in-tree fork (`dns/internal/bbe`) of the upstream blackbox-exporter DNS prober due to an unmerged upstream PR. Has an experimental implementation gated on `feature.ExperimentalDnsProber`. | 29 | `tcp/` | TCP — wraps `blackbox_exporter/prober`. | 30 | `grpc/` | gRPC — wraps `blackbox_exporter/prober`. | 31 | `icmp/` | ICMP — custom implementation (`icmp_impl.go` + `utils.go`). | 32 | `traceroute/` | Traceroute — custom implementation. | 33 | `scripted/` | k6-backed scripted check. | 34 | `browser/` | k6-backed browser check. | 35 | `multihttp/` | k6-backed MultiHTTP; generates a k6 script from assertions (`script.go`, `script.tmpl`). | 36 | `interpolation/` | Helpers for substituting check-config values into request payloads (used by MultiHTTP). | 37 | `logger/` | The `logger.Logger` interface the prober contract uses to emit log lines. | 38 39 ## How it fits in 40 41 ```mermaid 42 flowchart LR 43 Scraper -->|Probe ctx, target, registry, logger| Prober 44 Prober -.->|scripted / browser / multihttp| K6Runner["k6 runner<br/>(see separate doc)"] 45 subgraph "Blackbox-based" 46 HTTP 47 DNS 48 TCP 49 gRPC 50 end 51 subgraph "Custom" 52 ICMP 53 Traceroute 54 end 55 subgraph "k6-backed" 56 Scripted 57 Browser 58 MultiHTTP 59 end 60 ``` 61 62 ## The `Prober` interface 63 64 ```go 65 type Prober interface { 66 Name() string 67 Probe(ctx context.Context, target string, registry *prometheus.Registry, logger logger.Logger) (bool, float64) 68 } 69 ``` 70 71 Contract: 72 73 - `Name()` returns the check-type string (used in metric labels and logs). 74 - `Probe(...)` runs *one* probe. It registers all of its metrics in the caller-supplied `prometheus.Registry`, emits log lines via the caller-supplied `logger.Logger`, and returns `(success, duration)`. 75 - The registry is fresh per-probe (created by `scraper.getProbeMetrics`) — implementations don't have to worry about deduplication or cleanup. 76 - The function `prober.Run(...)` is a convenience that just calls `p.Probe(...)`. Use it if you don't already have a Prober reference. 77 78 ## The `ProberFactory` 79 80 ```go 81 type ProberFactory interface { 82 New(ctx context.Context, logger zerolog.Logger, check model.Check) (Prober, string, error) 83 } 84 ``` 85 86 Returns `(prober, target, error)`. The `target` is the address the 87 scraper logs and exposes as the `instance` label — for most check types 88 that's `check.Target`, but for DNS it's the DNS server address 89 (`check.Settings.Dns.Server`), since the check.Target itself is the 90 *name being looked up*. 91 92 `NewProberFactory` (in `prober.go`) is constructed with the k6 runner, 93 the probe ID (used to inject the `x-sm-id` request header for HTTP / 94 MultiHTTP — see `getReservedHeaders`), the feature collection, and the 95 secret provider. 96 97 ### Type dispatch 98 99 Defined in `proberFactory.New` (`prober.go`): 100 101 | `sm.CheckType_*` | Implementation | 102 | -------------------- | ------------------------------------------------------------- | 103 | `CheckTypePing` | `icmp.NewProber(check)` | 104 | `CheckTypeHttp` | `httpProber.NewProber(ctx, check, logger, reservedHeaders, secretStore)` | 105 | `CheckTypeDns` | `dns.NewProber(check)` (or `dns.NewExperimentalProber` if `feature.ExperimentalDnsProber` is set) | 106 | `CheckTypeTcp` | `tcp.NewProber(ctx, check, logger)` | 107 | `CheckTypeTraceroute`| `traceroute.NewProber(check, logger)` | 108 | `CheckTypeScripted` | `scripted.NewProber(ctx, check, logger, runner, secretStore)` — requires k6 runner. | 109 | `CheckTypeBrowser` | `browser.NewProber(ctx, check, logger, runner, secretStore)` — requires k6 runner. | 110 | `CheckTypeMultiHttp` | `multihttp.NewProber(ctx, check, logger, runner, reservedHeaders, secretStore)` — requires k6 runner. | 111 | `CheckTypeGrpc` | `grpc.NewProber(ctx, check, logger)` | 112 | (anything else) | `errUnsupportedCheckType` | 113 114 If you add a new check type, this is the *only* place the agent learns 115 about it — but you also need to wire it through `internal/checks` 116 (feature gating) and `internal/scraper` (timeout selection, 117 `sm_check_info` labels if relevant). 118 119 ### Reserved headers 120 121 `getReservedHeaders` injects the `x-sm-id` request header for HTTP and 122 MultiHTTP checks. The header carries `"<globalCheckID>-<probeId>"` so 123 upstream services can correlate requests with check executions. If 124 `probeId == 0` (the test path, or before registration), nothing is 125 injected. 126 127 ## Implementation families 128 129 ### Blackbox-based (`http`, `dns`, `tcp`, `grpc`) 130 131 These wrap `github.com/prometheus/blackbox_exporter/prober`. The 132 per-type file translates the SM `Check` configuration into a 133 blackbox-exporter `config.Module` and calls the upstream 134 `ProbeHTTP` / `ProbeDNS` / `ProbeTCP` / `ProbeGRPC` function. 135 136 DNS is special: the project keeps an in-tree fork at 137 `prober/dns/internal/bbe` because the upstream DNS prober is missing a 138 behaviour change (see the PR link in `dns/internal/bbe/prober/dns.go`). 139 The fork is intentionally narrow — keep it in sync with upstream when 140 practical. 141 142 ### Custom (`icmp`, `traceroute`) 143 144 These implement the same interface but do not call into 145 blackbox-exporter. They emit the same general shape of metrics 146 (`probe_success`, `probe_duration_seconds`, plus type-specific gauges) 147 so the rest of the pipeline doesn't need to know they are custom. 148 149 ICMP splits across `icmp.go` and `icmp_impl.go` so the noisy raw-socket 150 work stays in one place. 151 152 ### k6-backed (`scripted`, `browser`, `multihttp`) 153 154 These three are thin shells around the k6 runner. See 155 [k6runner.md](k6runner.md) for everything 156 about k6 execution and output parsing. The relevant per-prober 157 responsibility is *script preparation*: 158 159 - **scripted**: the user supplies the script verbatim. 160 - **browser**: a small wrapper script around the user's browser-test code. 161 - **multihttp**: the user supplies a *list of HTTP requests* with assertions; `multihttp/script.go` renders a k6 script from `script.tmpl` plus optional `interpolation/` substitutions. If you change the rendered script, update the golden tests in `multihttp/script_test.go`. 162 163 ## Design details 164 165 - **Per-probe Prometheus registry**: the scraper hands a *fresh* `prometheus.Registry` to every probe. This is what lets all probers register metrics with stable names (`probe_*`) without colliding across check runs. 166 - **Logging via `logger.Logger`**: probers don't see Loki directly. They get a `logger.Logger` (a thin adapter around `go-kit/kit/log`) that the scraper has decorated with all the necessary labels. Anything written to it becomes a log line on the check's stream. 167 - **Capabilities**: the API can disable scripted or browser support per probe (`Probe_Capabilities`). The Updater enforces capability validation against the available k6 runner; the prober factory itself just refuses to construct k6-backed probers when no runner is configured. 168 - **Context lifetime**: the context handed to `New(...)` for k6-backed probers is the *scraper's* context (cancelled when the scraper is destroyed). The context handed to `Probe(...)` is the *per-probe* context with the timeout already applied. 169 170 ## Testing strategy 171 172 - **Table-driven** unit tests sit next to each implementation (`<type>_test.go`). 173 - HTTP uses `internal/prober/http/testserver` to spin up a real local HTTP server with a fixed handler set. 174 - DNS uses `github.com/miekg/dns` for an in-process DNS server. 175 - MultiHTTP has both behavioural tests (`multihttp_test.go`) and script-generation tests (`script_test.go`) — the latter verify the rendered k6 script byte-for-byte. 176 - k6-backed prober tests use the fake k6 binary at `internal/k6runner/testdata/k6-fake`, so no real k6 process is needed. 177 - The factory itself has `prober_test.go` covering each `case` in the dispatch. 178 179 End-to-end metric expectations are pinned by the **scraper** golden 180 files (`internal/scraper/testdata/*.txt`), not by the prober tests 181 themselves. If you change emitted metrics, regenerate those goldens. 182 183 Run a single prober's tests: 184 185 ```bash 186 make test-go GO_TEST_ARGS=./internal/prober/http/... 187 ``` 188 189 ## When to update this doc 190 191 Update this document when you: 192 193 - Add a new `case` in `proberFactory.New` (i.e. a new check type). 194 - Change the `Prober` interface signature. 195 - Change what `target` is returned from the factory for any check type. 196 - Add or remove a reserved header in `getReservedHeaders`. 197 - Add or remove a feature flag gating an alternative prober implementation (e.g. `ExperimentalDnsProber`). 198 - Re-sync the `dns/internal/bbe` fork with upstream `blackbox_exporter`, or move it back to the upstream module. 199 - Add a new prober subpackage, or change which family a check type belongs to (blackbox-based / custom / k6-backed). 200 - Change the contract of `logger.Logger` consumed by probers.