github.com/grafana/synthetic-monitoring-agent@v0.64.2/internal/pusher/v1/pusher.go (about) 1 package v1 2 3 import ( 4 "context" 5 "fmt" 6 "net/http" 7 "strconv" 8 "sync" 9 "time" 10 11 "github.com/prometheus/client_golang/prometheus" 12 "github.com/prometheus/prometheus/prompb" 13 "github.com/rs/zerolog" 14 15 logproto "github.com/grafana/loki/pkg/push" 16 "github.com/grafana/synthetic-monitoring-agent/internal/model" 17 "github.com/grafana/synthetic-monitoring-agent/internal/pkg/loki" 18 "github.com/grafana/synthetic-monitoring-agent/internal/pkg/prom" 19 "github.com/grafana/synthetic-monitoring-agent/internal/pusher" 20 sm "github.com/grafana/synthetic-monitoring-agent/pkg/pb/synthetic_monitoring" 21 ) 22 23 const ( 24 defaultBufferCapacity = 10 * 1024 25 Name = "v1" 26 ) 27 28 var bufPool = sync.Pool{ 29 New: func() any { 30 buf := make([]byte, 0, defaultBufferCapacity) 31 return &buf 32 }, 33 } 34 35 type remoteTarget struct { 36 Events *prom.Client 37 Metrics *prom.Client 38 } 39 40 type publisherImpl struct { 41 ctx context.Context 42 tenantManager pusher.TenantProvider 43 logger zerolog.Logger 44 clientsMutex sync.Mutex 45 clients map[model.GlobalID]*remoteTarget 46 metrics pusher.Metrics 47 } 48 49 var _ pusher.Publisher = &publisherImpl{} 50 51 func NewPublisher(ctx context.Context, tm pusher.TenantProvider, logger zerolog.Logger, promRegisterer prometheus.Registerer) pusher.Publisher { 52 return &publisherImpl{ 53 ctx: ctx, 54 tenantManager: tm, 55 clients: make(map[model.GlobalID]*remoteTarget), 56 logger: logger, 57 metrics: pusher.NewMetrics(promRegisterer), 58 } 59 } 60 61 func (p *publisherImpl) Publish(payload pusher.Payload) { 62 go p.publish(p.ctx, payload) 63 } 64 65 func (p *publisherImpl) publish(ctx context.Context, payload pusher.Payload) { 66 var ( 67 tenantID = payload.Tenant() 68 69 // The above tenant ID is potentially a global ID. This is valid 70 // for using internally but in logs and metrics we want to publish 71 // the region and local tenant ID. 72 localID, regionID = model.GetLocalAndRegionIDs(tenantID) 73 regionStr = strconv.FormatInt(int64(regionID), 10) 74 tenantStr = strconv.FormatInt(localID, 10) 75 76 newClient = false 77 logger = p.logger.With().Int("region", regionID).Int64("tenant", localID).Logger() 78 ) 79 80 streams := payload.Streams() 81 metrics := payload.Metrics() 82 83 for retry := 2; retry > 0; retry-- { 84 client, err := p.getClient(ctx, tenantID, newClient) 85 if err != nil { 86 logger.Error().Err(err).Msg("get client failed") 87 88 if len(streams) > 0 { 89 p.metrics.FailedCounter.WithLabelValues(regionStr, tenantStr, pusher.LabelValueLogs, pusher.LabelValueClient).Inc() 90 } 91 92 if len(metrics) > 0 { 93 p.metrics.FailedCounter.WithLabelValues(regionStr, tenantStr, pusher.LabelValueMetrics, pusher.LabelValueClient).Inc() 94 } 95 96 return 97 } 98 99 if len(streams) > 0 { 100 if n, err := p.pushEvents(ctx, client.Events, streams); err != nil { 101 httpStatusCode, hasStatusCode := prom.GetHttpStatusCode(err) 102 logger.Error().Err(err).Int("status", httpStatusCode).Msg("publish events") 103 p.metrics.ErrorCounter.WithLabelValues(regionStr, tenantStr, pusher.LabelValueLogs, strconv.Itoa(httpStatusCode)).Inc() 104 105 if hasStatusCode && httpStatusCode == http.StatusUnauthorized { 106 // Retry to get a new client, credentials might be stale. 107 newClient = true 108 continue 109 } 110 } else { 111 p.metrics.PushCounter.WithLabelValues(regionStr, tenantStr, pusher.LabelValueLogs).Inc() 112 p.metrics.BytesOut.WithLabelValues(regionStr, tenantStr, pusher.LabelValueLogs).Add(float64(n)) 113 114 streams = nil 115 } 116 } 117 118 if len(metrics) > 0 { 119 if n, err := p.pushMetrics(ctx, client.Metrics, metrics); err != nil { 120 httpStatusCode, hasStatusCode := prom.GetHttpStatusCode(err) 121 logger.Error().Err(err).Int("status", httpStatusCode).Msg("publish metrics") 122 p.metrics.ErrorCounter.WithLabelValues(regionStr, tenantStr, pusher.LabelValueMetrics, strconv.Itoa(httpStatusCode)).Inc() 123 124 if hasStatusCode && httpStatusCode == http.StatusUnauthorized { 125 // Retry to get a new client, credentials might be stale. 126 newClient = true 127 continue 128 } 129 } else { 130 p.metrics.PushCounter.WithLabelValues(regionStr, tenantStr, pusher.LabelValueMetrics).Inc() 131 p.metrics.BytesOut.WithLabelValues(regionStr, tenantStr, pusher.LabelValueMetrics).Add(float64(n)) 132 metrics = nil 133 } 134 } 135 136 if len(streams) == 0 && len(metrics) == 0 { 137 // if we make it here we have sent everything we could send, we are done. 138 return 139 } 140 } 141 142 // if we are here, we retried and failed 143 if len(streams) > 0 { 144 p.metrics.FailedCounter.WithLabelValues(regionStr, tenantStr, pusher.LabelValueLogs, pusher.LabelValueRetryExhausted).Inc() 145 } 146 147 if len(metrics) > 0 { 148 p.metrics.FailedCounter.WithLabelValues(regionStr, tenantStr, pusher.LabelValueMetrics, pusher.LabelValueRetryExhausted).Inc() 149 } 150 151 logger.Warn().Msg("failed to push payload") 152 } 153 154 func (p *publisherImpl) pushEvents(ctx context.Context, client *prom.Client, streams []logproto.Stream) (int, error) { 155 buf := bufPool.Get().(*[]byte) 156 defer bufPool.Put(buf) 157 158 ctx, cancel := context.WithTimeout(ctx, 10*time.Second) 159 defer cancel() 160 161 if err := loki.SendStreamsWithBackoff(ctx, client, streams, buf); err != nil { 162 return 0, fmt.Errorf("sending events: %w", err) 163 } 164 165 return len(*buf), nil 166 } 167 168 func (p *publisherImpl) pushMetrics(ctx context.Context, client *prom.Client, metrics []prompb.TimeSeries) (int, error) { 169 buf := bufPool.Get().(*[]byte) 170 defer bufPool.Put(buf) 171 172 ctx, cancel := context.WithTimeout(ctx, 10*time.Second) 173 defer cancel() 174 175 if err := prom.SendSamplesWithBackoff(ctx, client, metrics, buf); err != nil { 176 return 0, fmt.Errorf("sending timeseries: %w", err) 177 } 178 179 return len(*buf), nil 180 } 181 182 func (p *publisherImpl) getClient(ctx context.Context, tenantId model.GlobalID, newClient bool) (*remoteTarget, error) { 183 var ( 184 client *remoteTarget 185 found bool 186 ) 187 188 localID, regionID := model.GetLocalAndRegionIDs(tenantId) 189 190 p.clientsMutex.Lock() 191 if newClient { 192 p.logger.Info().Int("regionId", regionID).Int64("tenantId", localID).Msg("removing tenant from cache") 193 delete(p.clients, tenantId) 194 } else { 195 client, found = p.clients[tenantId] 196 } 197 p.clientsMutex.Unlock() 198 199 if found { 200 return client, nil 201 } 202 203 p.logger.Info().Int("regionId", regionID).Int64("tenantId", localID).Msg("fetching tenant credentials") 204 205 req := sm.TenantInfo{ 206 Id: int64(tenantId), 207 } 208 209 tenant, err := p.tenantManager.GetTenant(ctx, &req) 210 if err != nil { 211 return nil, err 212 } 213 214 return p.updateClient(tenant) 215 } 216 217 func (p *publisherImpl) updateClient(tenant *sm.Tenant) (*remoteTarget, error) { 218 mClientCfg, err := pusher.ClientFromRemoteInfo(tenant.MetricsRemote) 219 if err != nil { 220 return nil, fmt.Errorf("creating metrics client configuration: %w", err) 221 } 222 223 localID, regionID := model.GetLocalAndRegionIDs(model.GlobalID(tenant.Id)) 224 225 regionStr := strconv.FormatInt(int64(regionID), 10) 226 tenantStr := strconv.FormatInt(localID, 10) 227 228 mClient, err := prom.NewClient(tenant.MetricsRemote.Name, mClientCfg, func(c float64) { 229 p.metrics.RetriesCounter.WithLabelValues(regionStr, tenantStr, pusher.LabelValueMetrics).Add(c) 230 }) 231 if err != nil { 232 return nil, fmt.Errorf("creating metrics client: %w", err) 233 } 234 235 eClientCfg, err := pusher.ClientFromRemoteInfo(tenant.EventsRemote) 236 if err != nil { 237 return nil, fmt.Errorf("creating events client configuration: %w", err) 238 } 239 240 eClient, err := prom.NewClient(tenant.EventsRemote.Name, eClientCfg, func(c float64) { 241 p.metrics.RetriesCounter.WithLabelValues(regionStr, tenantStr, pusher.LabelValueLogs).Add(c) 242 }) 243 if err != nil { 244 return nil, fmt.Errorf("creating events client: %w", err) 245 } 246 247 clients := &remoteTarget{ 248 Metrics: mClient, 249 Events: eClient, 250 } 251 252 p.clientsMutex.Lock() 253 p.clients[model.GlobalID(tenant.Id)] = clients 254 p.clientsMutex.Unlock() 255 p.logger.Debug().Int("regionId", regionID).Int64("tenantId", localID).Int64("stackId", tenant.StackId).Msg("updated client") 256 257 return clients, nil 258 }