|
| 1 | +package main |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "log" |
| 6 | + "os" |
| 7 | + "os/signal" |
| 8 | + "strconv" |
| 9 | + "strings" |
| 10 | + "syscall" |
| 11 | + "time" |
| 12 | +) |
| 13 | + |
| 14 | +type config struct { |
| 15 | + apiURL string |
| 16 | + token string |
| 17 | + region string |
| 18 | + agentID string |
| 19 | + interval time.Duration |
| 20 | + zones []string |
| 21 | + resolvers []string |
| 22 | + dialTimeout time.Duration |
| 23 | +} |
| 24 | + |
| 25 | +func env(key, def string) string { |
| 26 | + if v := os.Getenv(key); v != "" { |
| 27 | + return v |
| 28 | + } |
| 29 | + return def |
| 30 | +} |
| 31 | + |
| 32 | +func splitList(s string) []string { |
| 33 | + var out []string |
| 34 | + for _, p := range strings.Split(s, ",") { |
| 35 | + if t := strings.TrimSpace(p); t != "" { |
| 36 | + out = append(out, t) |
| 37 | + } |
| 38 | + } |
| 39 | + return out |
| 40 | +} |
| 41 | + |
| 42 | +func loadConfig() config { |
| 43 | + host, _ := os.Hostname() |
| 44 | + region := env("STACKRYZE_EDGE_REGION", "default") |
| 45 | + interval, _ := strconv.Atoi(env("STACKRYZE_EDGE_INTERVAL", "60")) |
| 46 | + if interval < 15 { |
| 47 | + interval = 15 |
| 48 | + } |
| 49 | + resolvers := splitList(env("STACKRYZE_EDGE_RESOLVERS", "8.8.8.8,1.1.1.1,9.9.9.9")) |
| 50 | + return config{ |
| 51 | + apiURL: env("STACKRYZE_API_URL", "https://api.stackryze.com/api"), |
| 52 | + token: os.Getenv("STACKRYZE_API_TOKEN"), |
| 53 | + region: region, |
| 54 | + agentID: env("STACKRYZE_EDGE_AGENT_ID", region+"-"+host), |
| 55 | + interval: time.Duration(interval) * time.Second, |
| 56 | + zones: splitList(os.Getenv("STACKRYZE_EDGE_ZONES")), |
| 57 | + resolvers: resolvers, |
| 58 | + dialTimeout: 4 * time.Second, |
| 59 | + } |
| 60 | +} |
| 61 | + |
| 62 | +func main() { |
| 63 | + cfg := loadConfig() |
| 64 | + if cfg.token == "" { |
| 65 | + log.Fatal("STACKRYZE_API_TOKEN is required") |
| 66 | + } |
| 67 | + client := newClient(cfg.apiURL, cfg.token) |
| 68 | + |
| 69 | + ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) |
| 70 | + defer stop() |
| 71 | + |
| 72 | + log.Printf("Stackryze Edge agent starting — region=%s agent=%s interval=%s", cfg.region, cfg.agentID, cfg.interval) |
| 73 | + |
| 74 | + runCycle(ctx, client, cfg) // run once immediately |
| 75 | + ticker := time.NewTicker(cfg.interval) |
| 76 | + defer ticker.Stop() |
| 77 | + for { |
| 78 | + select { |
| 79 | + case <-ctx.Done(): |
| 80 | + log.Println("shutting down") |
| 81 | + return |
| 82 | + case <-ticker.C: |
| 83 | + runCycle(ctx, client, cfg) |
| 84 | + } |
| 85 | + } |
| 86 | +} |
| 87 | + |
| 88 | +func runCycle(ctx context.Context, client *stackryzeClient, cfg config) { |
| 89 | + zones := cfg.zones |
| 90 | + if len(zones) == 0 { |
| 91 | + discovered, err := client.discoverZones(ctx) |
| 92 | + if err != nil { |
| 93 | + log.Printf("zone discovery failed: %v", err) |
| 94 | + return |
| 95 | + } |
| 96 | + zones = discovered |
| 97 | + } |
| 98 | + if len(zones) == 0 { |
| 99 | + log.Println("no zones to monitor") |
| 100 | + return |
| 101 | + } |
| 102 | + |
| 103 | + start := time.Now() |
| 104 | + for _, zone := range zones { |
| 105 | + snap := monitorZone(ctx, zone, cfg.resolvers, cfg.dialTimeout) |
| 106 | + payload := snap.toPayload(zone, cfg.region, cfg.agentID, start, time.Now()) |
| 107 | + if err := client.pushMetrics(ctx, payload); err != nil { |
| 108 | + log.Printf("push failed for %s: %v", zone, err) |
| 109 | + continue |
| 110 | + } |
| 111 | + log.Printf("%s: checks=%d success=%.0f%% p50=%.1fms p95=%.1fms up=%d down=%d", |
| 112 | + zone, payload.Checks, payload.SuccessRate, payload.LatencyP50, payload.LatencyP95, payload.TargetsUp, payload.TargetsDown) |
| 113 | + } |
| 114 | +} |
0 commit comments