github.com/grafana/pyroscope@v1.18.0/pkg/segmentwriter/client/distributor/placement/adaptiveplacement/placement_agent.go (about)

     1  package adaptiveplacement
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"time"
     8  
     9  	"github.com/go-kit/log"
    10  	"github.com/go-kit/log/level"
    11  	"github.com/grafana/dskit/services"
    12  	"github.com/prometheus/client_golang/prometheus"
    13  
    14  	"github.com/grafana/pyroscope/pkg/segmentwriter/client/distributor/placement/adaptiveplacement/adaptive_placementpb"
    15  	"github.com/grafana/pyroscope/pkg/util"
    16  )
    17  
    18  type Agent struct {
    19  	service services.Service
    20  	logger  log.Logger
    21  	store   StoreReader
    22  	config  Config
    23  	metrics *agentMetrics
    24  
    25  	placement *AdaptivePlacement
    26  	rules     *adaptive_placementpb.PlacementRules
    27  }
    28  
    29  func NewAgent(
    30  	logger log.Logger,
    31  	reg prometheus.Registerer,
    32  	config Config,
    33  	limits Limits,
    34  	store Store,
    35  ) *Agent {
    36  	a := Agent{
    37  		logger:    logger,
    38  		store:     store,
    39  		config:    config,
    40  		placement: NewAdaptivePlacement(limits),
    41  		metrics:   newAgentMetrics(reg),
    42  	}
    43  	a.service = services.NewTimerService(
    44  		config.PlacementUpdateInterval,
    45  		a.starting,
    46  		a.loadRulesNoError,
    47  		a.stopping,
    48  	)
    49  	return &a
    50  }
    51  
    52  func (a *Agent) Service() services.Service { return a.service }
    53  
    54  func (a *Agent) Placement() *AdaptivePlacement { return a.placement }
    55  
    56  func (a *Agent) starting(ctx context.Context) error {
    57  	_ = a.loadRulesNoError(ctx)
    58  	if a.rules == nil {
    59  		// The exact reason is logged in loadRules.
    60  		return fmt.Errorf("failed to load placement rules")
    61  	}
    62  	return nil
    63  }
    64  
    65  func (a *Agent) stopping(error) error { return nil }
    66  
    67  // The function is only needed to satisfy the services.OneIteration
    68  // signature: there's no case when the service stops on its own:
    69  // it's better to serve outdated rules than to not serve at all.
    70  func (a *Agent) loadRulesNoError(ctx context.Context) error {
    71  	util.Recover(func() { a.loadRules(ctx) })
    72  	return nil
    73  }
    74  
    75  func (a *Agent) loadRules(ctx context.Context) {
    76  	rules, err := a.store.LoadRules(ctx)
    77  	switch {
    78  	case err == nil:
    79  	case errors.Is(err, ErrRulesNotFound):
    80  		_ = level.Warn(a.logger).Log("msg", "placement rules not found")
    81  		rules = &adaptive_placementpb.PlacementRules{CreatedAt: time.Now().UnixNano()}
    82  	default:
    83  		_ = level.Error(a.logger).Log("msg", "failed to load placement rules", "err", err)
    84  		return
    85  	}
    86  	a.metrics.lag.Set(max(0, time.Since(time.Unix(0, rules.CreatedAt)).Seconds()))
    87  	if a.rules != nil {
    88  		if rules.CreatedAt < a.rules.CreatedAt {
    89  			_ = level.Warn(a.logger).Log(
    90  				"msg", "placement rules are outdated",
    91  				"discovered", time.Unix(0, rules.CreatedAt),
    92  				"loaded", time.Unix(0, a.rules.CreatedAt),
    93  			)
    94  			return
    95  		}
    96  	}
    97  	_ = level.Debug(a.logger).Log(
    98  		"msg", "loading placement rules",
    99  		"created_at", time.Unix(0, rules.CreatedAt),
   100  	)
   101  	a.placement.Update(rules)
   102  	a.rules = rules
   103  }