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  }