github.com/ethereum-optimism/optimism@v1.7.2/op-node/heartbeat/service.go (about) 1 // Package heartbeat provides a service for sending heartbeats to a server. 2 package heartbeat 3 4 import ( 5 "bytes" 6 "context" 7 "encoding/json" 8 "fmt" 9 "net/http" 10 "time" 11 12 "github.com/ethereum/go-ethereum/log" 13 ) 14 15 // SendInterval determines the delay between requests. This must be larger than the MinHeartbeatInterval in the server. 16 const SendInterval = 10 * time.Minute 17 18 type Payload struct { 19 Version string `json:"version"` 20 Meta string `json:"meta"` 21 Moniker string `json:"moniker"` 22 PeerID string `json:"peerID"` 23 ChainID uint64 `json:"chainID"` 24 } 25 26 // Beat sends a heartbeat to the server at the given URL. It will send a heartbeat immediately, and then every SendInterval. 27 // Beat spawns a goroutine that will send heartbeats until the context is canceled. 28 func Beat( 29 ctx context.Context, 30 log log.Logger, 31 url string, 32 payload *Payload, 33 ) error { 34 payloadJSON, err := json.Marshal(payload) 35 if err != nil { 36 return fmt.Errorf("telemetry crashed: %w", err) 37 } 38 39 client := &http.Client{ 40 Timeout: 10 * time.Second, 41 } 42 43 send := func() { 44 req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(payloadJSON)) 45 req.Header.Set("User-Agent", fmt.Sprintf("op-node/%s", payload.Version)) 46 req.Header.Set("Content-Type", "application/json") 47 if err != nil { 48 log.Error("error creating heartbeat HTTP request", "err", err) 49 return 50 } 51 res, err := client.Do(req) 52 if err != nil { 53 log.Warn("error sending heartbeat", "err", err) 54 return 55 } 56 res.Body.Close() 57 58 if res.StatusCode < 200 || res.StatusCode > 204 { 59 log.Warn("heartbeat server returned non-200 status code", "status", res.StatusCode) 60 return 61 } 62 63 log.Info("sent heartbeat") 64 } 65 66 send() 67 tick := time.NewTicker(SendInterval) 68 defer tick.Stop() 69 for { 70 select { 71 case <-tick.C: 72 send() 73 case <-ctx.Done(): 74 return nil 75 } 76 } 77 }