Skip to content

Commit 179ae0e

Browse files
committed
Merge branch 'main' into rtinianov_teeWorkflows
2 parents 3860740 + 43fabe4 commit 179ae0e

91 files changed

Lines changed: 8321 additions & 513 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.tool-versions

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
golang 1.26.2
22
protoc 29.3
33
protoc-gen-go-grpc 1.3.0
4-
golangci-lint 2.11.4
4+
golangci-lint 2.12.2
55
mockery 2.53.3

go.mod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ require (
4242
github.com/scylladb/go-reflectx v1.0.1
4343
github.com/shopspring/decimal v1.4.0
4444
github.com/smartcontractkit/chain-selectors v1.0.89
45-
github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10
45+
github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260518142424-bacfb6ba4146
4646
github.com/smartcontractkit/chainlink-protos/billing/go v0.0.0-20251024234028-0988426d98f4
4747
github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260520192008-ba2a4dc64333
4848
github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20251002192024-d2ad9222409b
@@ -52,6 +52,7 @@ require (
5252
github.com/smartcontractkit/freeport v0.1.3-0.20250716200817-cb5dfd0e369e
5353
github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7
5454
github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d
55+
github.com/stellar/go-stellar-sdk v0.5.0
5556
github.com/stretchr/testify v1.11.1
5657
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0
5758
go.opentelemetry.io/otel v1.43.0
@@ -138,6 +139,7 @@ require (
138139
github.com/rogpeppe/go-internal v1.14.1 // indirect
139140
github.com/ryanuber/go-glob v1.0.0 // indirect
140141
github.com/sanity-io/litter v1.5.5 // indirect
142+
github.com/stellar/go-xdr v0.0.0-20260312225820-cc2b0611aabf // indirect
141143
github.com/stretchr/objx v0.5.2 // indirect
142144
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
143145
github.com/x448/float16 v0.8.4 // indirect

go.sum

Lines changed: 18 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

observability-lib/grafana/alerts.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,8 @@ func newReduceSettingsOptions(options expr.ExprTypeReduceSettings) cog.Builder[e
124124

125125
func newConditionQuery(options ConditionQuery) *alerting.QueryBuilder {
126126
if options.IntervalMs == nil {
127-
options.IntervalMs = Pointer[float64](1000)
127+
// Recommended value for intervalMs is 30s
128+
options.IntervalMs = Pointer[float64](30 * 1000)
128129
}
129130

130131
if options.MaxDataPoints == nil {
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
package beholder
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"sync"
7+
8+
"go.opentelemetry.io/otel"
9+
"go.opentelemetry.io/otel/attribute"
10+
otelmetric "go.opentelemetry.io/otel/metric"
11+
12+
"github.com/smartcontractkit/chainlink-common/pkg/chipingress"
13+
"github.com/smartcontractkit/chainlink-common/pkg/chipingress/batch"
14+
"github.com/smartcontractkit/chainlink-common/pkg/logger"
15+
"github.com/smartcontractkit/chainlink-common/pkg/services"
16+
)
17+
18+
// ChipIngressBatchEmitterService batches events and sends them via chipingress.Client.PublishBatch.
19+
// It implements the Emitter interface.
20+
type ChipIngressBatchEmitterService struct {
21+
services.Service
22+
eng *services.Engine
23+
24+
batchClient *batch.Client
25+
26+
metricAttrsCache sync.Map // map[string]otelmetric.MeasurementOption
27+
metrics batchEmitterMetrics
28+
}
29+
30+
type batchEmitterMetrics struct {
31+
eventsSent otelmetric.Int64Counter
32+
eventsDropped otelmetric.Int64Counter
33+
}
34+
35+
// NewChipIngressBatchEmitterService creates a batch emitter service backed by the given chipingress client.
36+
func NewChipIngressBatchEmitterService(client chipingress.Client, cfg Config, lggr logger.Logger) (*ChipIngressBatchEmitterService, error) {
37+
if client == nil {
38+
return nil, fmt.Errorf("chip ingress client is nil")
39+
}
40+
41+
defaults := DefaultConfig()
42+
bufferSize := int(cfg.ChipIngressBufferSize)
43+
if bufferSize == 0 {
44+
bufferSize = int(defaults.ChipIngressBufferSize)
45+
}
46+
maxBatchSize := int(cfg.ChipIngressMaxBatchSize)
47+
if maxBatchSize == 0 {
48+
maxBatchSize = int(defaults.ChipIngressMaxBatchSize)
49+
}
50+
maxConcurrentSends := cfg.ChipIngressMaxConcurrentSends
51+
if maxConcurrentSends == 0 {
52+
maxConcurrentSends = defaults.ChipIngressMaxConcurrentSends
53+
}
54+
sendInterval := cfg.ChipIngressSendInterval
55+
if sendInterval == 0 {
56+
sendInterval = defaults.ChipIngressSendInterval
57+
}
58+
sendTimeout := cfg.ChipIngressSendTimeout
59+
if sendTimeout == 0 {
60+
sendTimeout = defaults.ChipIngressSendTimeout
61+
}
62+
drainTimeout := cfg.ChipIngressDrainTimeout
63+
if drainTimeout == 0 {
64+
drainTimeout = defaults.ChipIngressDrainTimeout
65+
}
66+
67+
meter := otel.Meter("beholder/chip_ingress_batch_emitter")
68+
metrics, err := newBatchEmitterMetrics(meter)
69+
if err != nil {
70+
return nil, fmt.Errorf("failed to create batch emitter metrics: %w", err)
71+
}
72+
73+
batchClient, err := batch.NewBatchClient(client,
74+
batch.WithBatchSize(maxBatchSize),
75+
batch.WithMessageBuffer(bufferSize),
76+
batch.WithBatchInterval(sendInterval),
77+
batch.WithMaxPublishTimeout(sendTimeout),
78+
batch.WithShutdownTimeout(drainTimeout),
79+
batch.WithMaxConcurrentSends(maxConcurrentSends),
80+
batch.WithEventClone(false),
81+
)
82+
if err != nil {
83+
return nil, fmt.Errorf("failed to create batch client: %w", err)
84+
}
85+
86+
e := &ChipIngressBatchEmitterService{
87+
batchClient: batchClient,
88+
metrics: metrics,
89+
}
90+
91+
e.Service, e.eng = services.Config{
92+
Name: "ChipIngressBatchEmitterService",
93+
Start: e.start,
94+
Close: e.stop,
95+
}.NewServiceEngine(lggr)
96+
97+
return e, nil
98+
}
99+
100+
func (e *ChipIngressBatchEmitterService) start(_ context.Context) error {
101+
// Do not pass the startup ctx — the services contract forbids retaining it
102+
// after Start returns. Use the engine's lifecycle context so the batcher
103+
// is cancelled when the service shuts down (StopChan closes before stop() runs).
104+
ctx, _ := e.eng.NewCtx()
105+
e.batchClient.Start(ctx)
106+
return nil
107+
}
108+
109+
func (e *ChipIngressBatchEmitterService) stop() error {
110+
e.batchClient.Stop()
111+
return nil
112+
}
113+
114+
// Emit queues an event for batched delivery without blocking.
115+
// Returns an error if the emitter is stopped or the context is cancelled.
116+
// If the buffer is full, the event is silently dropped.
117+
func (e *ChipIngressBatchEmitterService) Emit(ctx context.Context, body []byte, attrKVs ...any) error {
118+
return e.emitInternal(ctx, body, nil, attrKVs...)
119+
}
120+
121+
// EmitWithCallback works like Emit but invokes callback once the event's fate
122+
// is determined (nil on success, non-nil on failure or buffer-full drop).
123+
//
124+
// If EmitWithCallback returns a non-nil error, the callback will NOT be invoked.
125+
// If it returns nil, the callback is guaranteed to fire exactly once.
126+
func (e *ChipIngressBatchEmitterService) EmitWithCallback(ctx context.Context, body []byte, callback func(error), attrKVs ...any) error {
127+
return e.emitInternal(ctx, body, callback, attrKVs...)
128+
}
129+
130+
func (e *ChipIngressBatchEmitterService) emitInternal(ctx context.Context, body []byte, callback func(error), attrKVs ...any) error {
131+
return e.eng.IfStarted(func() error {
132+
domain, entity, err := ExtractSourceAndType(attrKVs...)
133+
if err != nil {
134+
return err
135+
}
136+
137+
attributes := newAttributes(attrKVs...)
138+
139+
event, err := chipingress.NewEvent(domain, entity, body, attributes)
140+
if err != nil {
141+
return fmt.Errorf("failed to create CloudEvent: %w", err)
142+
}
143+
eventPb, err := chipingress.EventToProto(event)
144+
if err != nil {
145+
return fmt.Errorf("failed to convert to proto: %w", err)
146+
}
147+
148+
if err := ctx.Err(); err != nil {
149+
return err
150+
}
151+
152+
metricAttrs := e.metricAttrsFor(domain, entity)
153+
154+
queueErr := e.batchClient.QueueMessage(eventPb, func(sendErr error) {
155+
// The callback fires asynchronously after the batch is sent,
156+
// so the caller's ctx may already be cancelled. Use ctx directly
157+
// for metric recording — OTel Add is non-blocking and tolerates
158+
// cancelled contexts.
159+
if sendErr != nil {
160+
e.metrics.eventsDropped.Add(ctx, 1, metricAttrs)
161+
e.eng.Errorw("failed to emit to chip ingress", "error", sendErr, "domain", domain, "entity", entity)
162+
} else {
163+
e.metrics.eventsSent.Add(ctx, 1, metricAttrs)
164+
}
165+
if callback != nil {
166+
callback(sendErr)
167+
}
168+
})
169+
if queueErr != nil {
170+
e.metrics.eventsDropped.Add(ctx, 1, metricAttrs)
171+
e.eng.Errorw("failed to queue message for chip ingress", "error", queueErr, "domain", domain, "entity", entity)
172+
if callback != nil {
173+
callback(queueErr)
174+
}
175+
}
176+
177+
return nil
178+
})
179+
}
180+
181+
func (e *ChipIngressBatchEmitterService) metricAttrsFor(domain, entity string) otelmetric.MeasurementOption {
182+
key := domain + "\x00" + entity
183+
if v, ok := e.metricAttrsCache.Load(key); ok {
184+
return v.(otelmetric.MeasurementOption)
185+
}
186+
attrs := otelmetric.WithAttributeSet(attribute.NewSet(
187+
attribute.String("domain", domain),
188+
attribute.String("entity", entity),
189+
))
190+
v, _ := e.metricAttrsCache.LoadOrStore(key, attrs)
191+
return v.(otelmetric.MeasurementOption)
192+
}
193+
194+
func newBatchEmitterMetrics(meter otelmetric.Meter) (batchEmitterMetrics, error) {
195+
eventsSent, err := meter.Int64Counter("chip_ingress.events_sent",
196+
otelmetric.WithDescription("Total events successfully sent via PublishBatch"),
197+
otelmetric.WithUnit("{event}"))
198+
if err != nil {
199+
return batchEmitterMetrics{}, err
200+
}
201+
202+
eventsDropped, err := meter.Int64Counter("chip_ingress.events_dropped",
203+
otelmetric.WithDescription("Total events dropped (buffer full or send failure)"),
204+
otelmetric.WithUnit("{event}"))
205+
if err != nil {
206+
return batchEmitterMetrics{}, err
207+
}
208+
209+
return batchEmitterMetrics{
210+
eventsSent: eventsSent,
211+
eventsDropped: eventsDropped,
212+
}, nil
213+
}

0 commit comments

Comments
 (0)