github.com/grafana/synthetic-monitoring-agent@v0.64.2/internal/k6version/k6version.go (about) 1 package k6version 2 3 import ( 4 "context" 5 "fmt" 6 "time" 7 8 "github.com/grafana/synthetic-monitoring-agent/internal/k6runner" 9 "github.com/grafana/synthetic-monitoring-agent/pkg/pb/synthetic_monitoring" 10 "github.com/rs/zerolog" 11 ) 12 13 type HandlerOpts struct { 14 K6Runner k6runner.Runner 15 K6Client synthetic_monitoring.K6Client 16 Logger *zerolog.Logger 17 } 18 19 func (ho HandlerOpts) WithDefaults() HandlerOpts { 20 if ho.Logger == nil { 21 nop := zerolog.Nop() 22 ho.Logger = &nop 23 } 24 25 return ho 26 } 27 28 type Handler struct { 29 HandlerOpts 30 client synthetic_monitoring.K6Client 31 } 32 33 func NewHandler(opts HandlerOpts) (*Handler, error) { 34 opts = opts.WithDefaults() 35 36 return &Handler{HandlerOpts: opts, client: opts.K6Client}, nil 37 } 38 39 func (h *Handler) Handle(ctx context.Context) error { 40 versionsCh := h.K6Runner.Versions(ctx) 41 42 var sendCtx context.Context 43 44 cancel := func() {} 45 46 h.Logger.Debug().Msg("Starting k6 version reporter") 47 48 for { 49 select { 50 case <-ctx.Done(): 51 // Cancel ongoing attempt, if any. 52 cancel() 53 return ctx.Err() 54 55 case versions, ok := <-versionsCh: 56 if !ok { 57 // Versions channel closed, there won't be further updates. Nil the channel so we can continue the loop 58 // waiting only for context cancellation. 59 h.Logger.Debug().Msg("k6 runner done reporting versions") 60 61 versionsCh = nil 62 63 continue 64 } 65 66 // Cancel retries for previous attempt, if any. 67 cancel() 68 69 h.Logger.Debug().Strs("versions", versions).Msg("Received k6 versions from runner") 70 71 // Send the report asynchronously, with retries and backoff. 72 sendCtx, cancel = context.WithCancel(ctx) 73 go h.report(sendCtx, versions) 74 } 75 } 76 } 77 78 func (h *Handler) report(ctx context.Context, versions []string) { 79 backoff := time.Second 80 81 for { 82 err := func() error { 83 request := synthetic_monitoring.RegisterK6VersionRequest{ 84 Versions: toK6Versions(versions), 85 } 86 87 reqCtx, cancel := context.WithTimeout(ctx, 5*time.Second) 88 defer cancel() 89 90 resp, err := h.client.RegisterK6Version(reqCtx, &request) 91 if err != nil { 92 return fmt.Errorf("submitting k6 versions to API: %w", err) 93 } 94 95 if resp.Status.Code != synthetic_monitoring.StatusCode_OK { 96 return fmt.Errorf("%d: %s", resp.Status.Code, resp.Status.Message) 97 } 98 99 h.Logger.Info().Strs("versions", versions).Msg("Reported k6 versions to API") 100 101 return nil 102 }() 103 if err != nil { 104 h.Logger.Error().Err(err).Float64("afterSeconds", backoff.Seconds()).Msg("Could not send k6 versions report, will retry") 105 106 select { 107 case <-ctx.Done(): 108 h.Logger.Error().Err(ctx.Err()).Msg("Context canceled, giving up on unfinished k6 version report") 109 return 110 case <-time.After(backoff): 111 backoff = min(backoff*2, time.Minute) 112 continue 113 } 114 } 115 116 return 117 } 118 } 119 120 func toK6Versions(versions []string) []synthetic_monitoring.K6Version { 121 k6Versions := make([]synthetic_monitoring.K6Version, 0, len(versions)) 122 123 for _, v := range versions { 124 k6Versions = append(k6Versions, synthetic_monitoring.K6Version{Version: v}) 125 } 126 127 return k6Versions 128 }