github.com/grafana/synthetic-monitoring-agent@v0.64.2/DEVELOPMENT.md (about) 1 # Developer's guide to the Synthetic Monitoring agent 2 3 This file provides some guidance as to how to work with the codebase. 4 5 ## Project Overview 6 7 This is the Grafana Synthetic Monitoring agent: a distributed probe that 8 executes network checks (HTTP, DNS, TCP, ICMP, gRPC, Traceroute, k6-based 9 scripted/browser checks) from various locations and pushes metrics/logs to 10 Grafana Cloud (Prometheus/Loki). 11 12 ## Common Commands 13 14 The build system is organized around make. Make is ubiquitous and the chances 15 are that if you have `go` installed, you also have `make` installed. If not, 16 it's straightforward to install (possibly using your distribution's preferred 17 package manager), with very little dependencies. 18 19 You can get up-to-date documentation by running `make help`, but some common 20 targets are mentioned below. 21 22 ### Building 23 ```bash 24 make build # Build all Go binaries for all platforms 25 make build-native # Build only for current OS/arch 26 make deps # Download and verify Go dependencies 27 ``` 28 29 To run scripted, browser, and multihttp checks the agent uses the `sm-k6` 30 binary (a k6 build with the Synthetic Monitoring extensions). Use the 31 following commands to download it: 32 33 ```bash 34 make sm-k6 # Download sm-k6 for all supported platforms (iterates over XK6_PLATFORMS) 35 make sm-k6-native # Download sm-k6 only for the current host OS/arch 36 ``` 37 38 Binaries are placed under `dist/<os>-<arch>/k6-v1`. 39 40 ### Testing 41 ```bash 42 make test # Run all tests with coverage and race detection 43 make test-fast # Run only fast tests (with -short flag) 44 45 # Run specific package tests 46 make test-go GO_TEST_ARGS=./internal/prober/... 47 48 # Run single test 49 make test-go GO_TEST_ARGS="-v -run TestSpecificTest ./internal/package/..." 50 ``` 51 52 Note that tests are run inside a docker container, to ensure some level of 53 reproducibility and isolation from the local environment. If you want, you can 54 use `go test` directly, but keep in mind that this might end up using a 55 different version of the Go compiler, and it might be affected by other things 56 in your local environment. 57 58 ### Linting 59 ```bash 60 make lint # Run all linters (golangci-lint + go vet + shellcheck) 61 make lint-go # Run only Go linters 62 ``` 63 64 Again, the linting tools are executed in a docker container, to ensure that 65 versions match. You can use `golangci-lint` directly, but keep in mind that the 66 configuration file targets v2. 67 68 ### Code generation 69 70 Some of the code and support files are generated, specifically: 71 72 - internal/scraper/testdata/*.txt 73 - pkg/accounting/data.go 74 - pkg/pb/synthetic_monitoring/checks.pb.go 75 76 In order to update `internal/scraper/testdata/*.txt` you can use the 77 `testdata` target in the Makefile. 78 79 `pkg/accounting/data.go` and `pkg/pb/synthetic_monitoring/checks.pb.go` 80 are updated by the `generate` target in the Makefile. 81 82 Code generation is *not* run automatically, but there are some tests 83 that try to detect discrepancies. 84 85 ### Development 86 87 ```bash 88 # Run the agent locally (requires API token and sm-k6 binary) 89 ./dist/synthetic-monitoring-agent \ 90 -api-server-address=synthetic-monitoring-grpc.grafana.net:443 \ 91 -api-token=<your-token> \ 92 -k6-repository=./dist/<os>-<arch> \ 93 -verbose 94 ``` 95 96 The `-k6-repository` flag points the agent at a directory containing the 97 `sm-k6` binary. It defaults to `/usr/libexec/sm-k6`. 98 99 Other k6 flags: 100 101 - **`-k6-uri=<path>`** — direct path to a k6 binary; overrides the 102 repository directory. 103 - **`-disable-k6`** — run without the `sm-k6` binary. The agent 104 will not be able to execute k6 checks. 105 106 Build Docker images: 107 108 ```bash 109 docker build --target release . # Without Chromium (smaller) 110 docker build --target with-browser . # With Chromium (for browser checks) 111 ``` 112 113 ### Livereload development 114 115 Install [air](https://github.com/air-verse/air?tab=readme-ov-file#installation) 116 and ensure `SM_AGENT_API_TOKEN` is set prior to running. 117 118 ```bash 119 air 120 ``` 121 122 Any code changes will now trigger air to rebuild the agent and run the 123 generated executable. 124 125 ## Architecture Overview 126 127 ### Core Flow 128 ``` 129 API Server (gRPC) → Updater → Scrapers → Probers → Publisher → Prometheus/Loki 130 ↓ 131 Adhoc Handler (on-demand checks) 132 ``` 133 134 ### Key Components 135 136 **Updater** (`internal/checks/checks.go`) 137 - Maintains long-lived gRPC stream to API server via `GetChanges()` 138 - Receives check ADD/UPDATE/DELETE operations 139 - Manages life cycle of all scrapers (one per check) 140 - Handles reconnection with back off on failures 141 - Responds to SIGUSR1 for graceful disconnect (enables zero-downtime upgrades) 142 143 **Scraper** (`internal/scraper/scraper.go`) 144 - One instance per active check, runs on scheduled interval 145 - Executes probe via prober interface 146 - Adds user-defined labels and check metadata to all telemetry 147 - Implements "republishing" - resends metrics with updated timestamps when check interval > 2min 148 - Sends stale markers (NaN) on shutdown for proper Prometheus metric life cycle 149 - Tracks check state machine (passing/failing thresholds) 150 151 **Prober** (`internal/prober/prober.go`) 152 - Factory pattern for check-type-specific implementations 153 - Types: HTTP, DNS, TCP, ICMP, gRPC, Traceroute, Scripted (k6), Browser, MultiHTTP 154 - Interface: `Probe(ctx, target, registry, logger) (success bool, duration time.Duration)` 155 - Each prober registers metrics into Prometheus registry 156 157 **k6Runner** (`internal/k6runner/`) 158 - Handles scripted, browser, and multihttp checks via k6 159 - Two modes: Local (executes k6 binary) or HTTP (remote k6 runner service) 160 - HTTP runner includes retry logic with back off for transient failures 161 - Grace time: adds 20s to timeout for runner communication overhead 162 - Processor parses k6 output and registers metrics in Prometheus format 163 164 **Publisher** (`internal/pusher/v2/publisher.go`) 165 - V2 is current (V1 exists for compatibility) 166 - Per-tenant push handlers with automatic life cycle management 167 - Batches and sends remote write to Prometheus/Loki 168 - Publishers created via registry/factory pattern 169 170 **Adhoc Handler** (`internal/adhoc/adhoc.go`) 171 - Separate gRPC stream for on-demand checks (instant testing from UI) 172 - Creates ephemeral runners for single-shot execution 173 - Results published as logs with special labels 174 - No retry or scheduling logic 175 176 **Tenant Manager** (`internal/tenants/manager.go`) 177 - Caches tenant metadata with TTL (15min default with jitter) 178 - Provides tenant info to publisher for authentication 179 - Thread-safe with per-tenant locks 180 181 ### Check Types 182 183 **Simple probers**: DNS, HTTP, TCP, ICMP, gRPC, Traceroute 184 - Direct implementation using 185 [blackbox_exporter](https://github.com/prometheus/blackbox_exporter) as backend 186 - ICMP and Traceroute implement the same interface, but are custom 187 implementations 188 - Return metrics via Prometheus registry 189 190 **k6-Based**: Scripted, Browser, MultiHTTP 191 - Convert check settings to k6 script 192 - Execute via k6runner (local process or remote HTTP service) 193 - MultiHTTP generates k6 script from assertion-based configuration 194 - Parse k6's Prometheus output format provided by a custom extension 195 196 ### Data Flow 197 198 ![agent process][process] 199 200 **Metrics Path:** 201 ``` 202 Prober → Prometheus Registry → Scraper (extract + label) → Publisher → Remote Write → Prometheus 203 ``` 204 205 **Logs Path:** 206 ``` 207 Prober (logfmt logger) → Scraper (parse + label) → Publisher → Loki Push API 208 ``` 209 210 **Special Metric:** 211 - `sm_check_info`: Contains check metadata (geohash, frequency, alert sensitivity) 212 - Used for joining check data in queries 213 214 ## Important Patterns 215 216 ### Error Handling 217 - Custom error types: `FatalError` (stop scraper), `TransientError` (retry with back off) 218 - Use `pkg/errors.Wrap()` to add context 219 - k6 runner retries on retriable errors (timeout, connection refused, etc.) 220 221 ### Metrics Philosophy 222 - **Derived metrics**: Creates summaries/histograms from gauge metrics for flexibility 223 - **Stale markers**: Sends NaN values on scraper shutdown so Prometheus knows metric is gone 224 - **Republishing**: Resends metrics with updated timestamps to handle long intervals (keeps data fresh) 225 - **Check info metric**: Special `sm_check_info` with labels for metadata joins 226 227 ### Concurrency 228 - Heavy use of `errgroup.Group` for coordinated goroutine management 229 - Per-tenant locks in publisher to avoid contention 230 - Each scraper has independent context for life cycle control 231 - Context-aware sleep functions for clean shutdown 232 233 ### Configuration 234 - Check configuration comes from API (not local files) 235 - Probe capabilities negotiated during registration 236 - Feature flags control which check types are enabled 237 - Environment-based configuration via command-line flags 238 239 ### Tenant Isolation 240 - Every payload tagged with tenant ID 241 - Publisher maintains separate handlers per tenant 242 - Tenant metadata cached with expiration 243 - Secrets stored in remote secret store (not in agent) 244 245 ## Code Organization 246 247 ``` 248 cmd/ 249 synthetic-monitoring-agent/ # Main agent binary 250 test-api/ # Test API server (outdated) 251 synthetic-monitoring-proto/ # Proto manipulation 252 253 internal/ 254 adhoc/ # On-demand check execution 255 checks/ # Check lifecycle management (Updater) 256 k6runner/ # K6 script execution (local/remote) 257 prober/ # Check type implementations 258 pusher/ # Metrics/logs publishing (v1 and v2) 259 scraper/ # Scheduled check execution 260 tenants/ # Tenant metadata caching 261 usage/ # Usage reporting 262 secrets/ # Secret store integration 263 limits/ # Rate limiting 264 telemetry/ # Internal metrics 265 version/ # Version information 266 267 pkg/pb/synthetic_monitoring/ # Generated protobuf code 268 ``` 269 270 ## Development Notes 271 272 ### Testing 273 - Tests use CGO for race detection (`CGO_ENABLED=1` in Makefile) 274 - Use `-short` flag for fast tests that skip integration tests 275 - Testhelper package (`internal/testhelper/`) provides common test utilities 276 277 ### Linting 278 - Configuration in `.golangci.yaml` 279 - Custom rules via ruleguard in `internal/rules/rules.go` 280 - Import aliases enforced: `sm` for synthetic_monitoring, `dto` for Prometheus client model 281 - Maximum cyclomatic complexity: 20 282 283 ### Docker Images 284 - Two variants: with/without Chromium (tagged `*-browser`) 285 - Use `--target release` for non-browser builds to keep images small 286 - Multi-stage Dockerfile defaults to browser variant 287 288 ### Signals 289 - `SIGTERM`: Graceful shutdown 290 - `SIGUSR1`: Disconnect from API but keep running checks (allows another agent to connect) 291 - After 1 minute, attempts to reconnect if no other agent has connected 292 - Also available via `/disconnect` HTTP endpoint 293 - Use case: Zero-downtime agent upgrades 294 295 ### HTTP Endpoints 296 - `/ready`: Returns 200 when connected to API, 503 otherwise 297 - `/metrics`: Prometheus metrics for agent internals 298 - `/disconnect`: Sends SIGUSR1 to process 299 - `/debug/pprof`: Performance profiling (if enabled) 300 301 ### Build System 302 - Uses make with modular includes from `scripts/make/` 303 - Builds for multiple platforms: `linux/amd64`, `linux/arm64` 304 - Version info injected at build time via ldflags 305 - Output in `dist/` directory 306 307 ### API Integration 308 - gRPC with streaming: `GetChanges()` for continuous check updates 309 - Registration includes probe info (version, capabilities, location) 310 - Health checks via `Ping()` RPC with 30s interval 311 - Tenant metadata fetched via `GetTenant()` RPC and cached 312 - Adhoc checks via separate `GetAdHocChecks()` stream 313 314 ### Performance Considerations 315 - Auto-memory limit based on cgroup/system memory (90% by default) 316 - Random offset per check within frequency to spread load 317 - Republishing keeps metrics fresh without overloading backends 318 319 [process]: https://www.planttext.com/api/plantuml/svg/dLHDRy8m3BtdLqGz3wHTEKo88KsxJVi78VLA14swn47ityzE0ZHLcIQua8zdl-Tdf-k0ocFiZqAq2jLE1P3DTjE8WOwDDeEoA9lmOt4Fj5_qpXfqtjXkeGRJI1Ka_Sl_m3kmc0DuLKUGY0xoRLxMruDtFL3A61BajgrXHtV8adWXHEAHYvUaS2MrinOqIdS2Bzy-FrwdW02svTmxaCP-ETyhDCuAfT6S54BHpLWAsMuemeDsliG83nYzBGdUjwA5IUIKpyDtX81Ixq4VP40Fgh-apz2YAGEUnNAv_EFUYiwxE53nRf0alnoVXQJVbJhRotQaMpY3ZgdCXBe8BatWir8M6UwDfkOHtz5r8TsDIXn5P1dEQaZRYhwk_DP8EkeTPkDlKJErpkBci_CKF9oNpchZHbfNSeXXVx6aXYNI0hZwn8shXHPgD3suY8BPKdkdCzAQKERsxk2DCRCv7XxyUuIygJHLJbuVWB3iQ69DWAVyBjc8e7fWe8OG-C7kW6Y1RP0S9CIQblHL-WK0 320 [PlantUML]: https://www.planttext.com/?text=dLHDRy8m3BtdLqGz3wHTEKo88KsxJVi78VLA14swn47ityzE0ZHLcIQua8zdl-Tdf-k0ocFiZqAq2jLE1P3DTjE8WOwDDeEoA9lmOt4Fj5_qpXfqtjXkeGRJI1Ka_Sl_m3kmc0DuLKUGY0xoRLxMruDtFL3A61BajgrXHtV8adWXHEAHYvUaS2MrinOqIdS2Bzy-FrwdW02svTmxaCP-ETyhDCuAfT6S54BHpLWAsMuemeDsliG83nYzBGdUjwA5IUIKpyDtX81Ixq4VP40Fgh-apz2YAGEUnNAv_EFUYiwxE53nRf0alnoVXQJVbJhRotQaMpY3ZgdCXBe8BatWir8M6UwDfkOHtz5r8TsDIXn5P1dEQaZRYhwk_DP8EkeTPkDlKJErpkBci_CKF9oNpchZHbfNSeXXVx6aXYNI0hZwn8shXHPgD3suY8BPKdkdCzAQKERsxk2DCRCv7XxyUuIygJHLJbuVWB3iQ69DWAVyBjc8e7fWe8OG-C7kW6Y1RP0S9CIQblHL-WK0