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 }