github.com/grafana/synthetic-monitoring-agent@v0.64.2/pkg/collector/collector.go (about)

     1  // Package collector exposes the agent's normal single-execution telemetry
     2  // pipeline to callers that supply a prober. It contains no scheduling,
     3  // persistence, virtual-time execution, or scenario semantics. The context
     4  // deadline remains a real wall-clock deadline; callers generating logical
     5  // history are responsible for resolving their simulated outcome before
     6  // collection.
     7  package collector
     8  
     9  import (
    10  	"context"
    11  	"io"
    12  	"time"
    13  
    14  	logproto "github.com/grafana/loki/pkg/push"
    15  	"github.com/grafana/synthetic-monitoring-agent/internal/model"
    16  	internalprober "github.com/grafana/synthetic-monitoring-agent/internal/prober"
    17  	internallogger "github.com/grafana/synthetic-monitoring-agent/internal/prober/logger"
    18  	"github.com/grafana/synthetic-monitoring-agent/internal/pusher"
    19  	"github.com/grafana/synthetic-monitoring-agent/internal/scraper"
    20  	"github.com/grafana/synthetic-monitoring-agent/internal/telemetry"
    21  	sm "github.com/grafana/synthetic-monitoring-agent/pkg/pb/synthetic_monitoring"
    22  	"github.com/prometheus/client_golang/prometheus"
    23  	"github.com/prometheus/prometheus/prompb"
    24  	"github.com/rs/zerolog"
    25  )
    26  
    27  type TimeSeries = []prompb.TimeSeries
    28  type Streams = []logproto.Stream
    29  
    30  // Logger is the logging surface available to an injected Probe.
    31  type Logger interface {
    32  	Log(keyvals ...any) error
    33  }
    34  
    35  // Probe performs one check execution and registers its metrics and logs.
    36  // It matches the agent's prober contract without exposing internal packages.
    37  type Probe interface {
    38  	Name() string
    39  	Probe(ctx context.Context, target string, registry *prometheus.Registry, logger Logger) (bool, float64)
    40  }
    41  
    42  // Option configures a Collector.
    43  type Option func(*options)
    44  
    45  type options struct {
    46  	labelMode sm.LabelMode
    47  }
    48  
    49  // WithLabelMode configures how user-defined check and probe labels are shaped.
    50  // Collectors default to LABEL_MODE_PREFIXED, matching the protobuf default used
    51  // by the agent.
    52  func WithLabelMode(mode sm.LabelMode) Option {
    53  	return func(opts *options) {
    54  		opts.labelMode = mode
    55  	}
    56  }
    57  
    58  // Collector runs an injected Probe through the same metric and log
    59  // transformation pipeline used by the agent scraper, without publishing.
    60  type Collector struct {
    61  	scraper *scraper.Scraper
    62  	cancel  context.CancelFunc
    63  }
    64  
    65  // New builds a Collector for one API-level check and probe.
    66  func New(ctx context.Context, check sm.Check, probe sm.Probe, supplied Probe, collectorOptions ...Option) (*Collector, error) {
    67  	var modelCheck model.Check
    68  	if err := modelCheck.FromSM(check); err != nil {
    69  		return nil, err
    70  	}
    71  
    72  	opts := options{labelMode: sm.LabelMode_LABEL_MODE_PREFIXED}
    73  	for _, option := range collectorOptions {
    74  		option(&opts)
    75  	}
    76  
    77  	collectorCtx, cancel := context.WithCancel(ctx)
    78  
    79  	s, err := scraper.NewWithOpts(collectorCtx, modelCheck, scraper.ScraperOpts{
    80  		Probe:                 probe,
    81  		Publisher:             noopPublisher{},
    82  		Logger:                zerolog.New(io.Discard),
    83  		Metrics:               noopMetrics{},
    84  		ProbeFactory:          probeFactory{probe: supplied},
    85  		LabelsLimiter:         noopLabelsLimiter{},
    86  		Telemeter:             noopTelemeter{},
    87  		CostAttributionLabels: noopTenantCals{},
    88  		LabellingMode:         staticLabellingMode{mode: opts.labelMode},
    89  	})
    90  	if err != nil {
    91  		cancel()
    92  		return nil, err
    93  	}
    94  
    95  	return &Collector{scraper: s, cancel: cancel}, nil
    96  }
    97  
    98  // Close releases the resources associated with the Collector. It is safe to
    99  // call Close more than once.
   100  func (c *Collector) Close() {
   101  	c.cancel()
   102  }
   103  
   104  // Collect runs one execution whose metrics and logs are stamped at logical
   105  // event time t. It does not make the supplied Probe consume logical elapsed
   106  // time. A failed probe returns its telemetry alongside a non-nil error; a fatal
   107  // collection error returns no telemetry.
   108  func (c *Collector) Collect(ctx context.Context, t time.Time) (TimeSeries, Streams, error) {
   109  	ts, streams, _, _, err := c.scraper.CollectData(ctx, t.UTC())
   110  	return ts, streams, err
   111  }
   112  
   113  type probeAdapter struct {
   114  	probe Probe
   115  }
   116  
   117  func (p probeAdapter) Name() string { return p.probe.Name() }
   118  
   119  // Probe adapts the internal prober contract to the public Probe. The internal
   120  // logger is passed straight through: internallogger.Logger satisfies the public
   121  // collector.Logger (both expose Log(...any) error), and the internal execution
   122  // ID is dropped because the public Probe does not surface it.
   123  func (p probeAdapter) Probe(ctx context.Context, target string, registry *prometheus.Registry, logger internallogger.Logger, _ string) (bool, float64) {
   124  	return p.probe.Probe(ctx, target, registry, logger)
   125  }
   126  
   127  type probeFactory struct {
   128  	probe Probe
   129  }
   130  
   131  func (f probeFactory) New(_ context.Context, _ zerolog.Logger, check model.Check) (internalprober.Prober, string, error) {
   132  	// probeFactory and probeAdapter share the same shape; staticcheck (S1016)
   133  	// prefers the direct conversion over a field-by-field struct literal.
   134  	return probeAdapter(f), check.Target, nil
   135  }
   136  
   137  type noopPublisher struct{}
   138  
   139  func (noopPublisher) Publish(pusher.Payload) {}
   140  
   141  type noopMetrics struct{}
   142  
   143  func (noopMetrics) AddScrape()         {}
   144  func (noopMetrics) AddCheckError()     {}
   145  func (noopMetrics) AddCollectorError() {}
   146  
   147  type noopLabelsLimiter struct{}
   148  
   149  // The unscheduled collector does not enforce tenant label budgets; 128 is a
   150  // permissive constant chosen to clear the scraper's guard, not a real limit.
   151  
   152  func (noopLabelsLimiter) MetricLabels(context.Context, model.GlobalID) (int, error) {
   153  	return 128, nil
   154  }
   155  
   156  func (noopLabelsLimiter) LogLabels(context.Context, model.GlobalID) (int, error) {
   157  	return 128, nil
   158  }
   159  
   160  type noopTelemeter struct{}
   161  
   162  func (noopTelemeter) AddExecution(telemetry.Execution) {}
   163  
   164  type noopTenantCals struct{}
   165  
   166  func (noopTenantCals) CostAttributionLabels(context.Context, model.GlobalID) ([]string, error) {
   167  	return nil, nil
   168  }
   169  
   170  type staticLabellingMode struct {
   171  	mode sm.LabelMode
   172  }
   173  
   174  func (m staticLabellingMode) ForTenant(context.Context, model.GlobalID) (sm.LabelMode, error) {
   175  	return m.mode, nil
   176  }