201 lines
6.1 KiB
Go
201 lines
6.1 KiB
Go
// Copyright The OpenTelemetry Authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package internal // import "go.opentelemetry.io/collector/exporter/exporterhelper/internal"
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/metric"
|
|
"go.opentelemetry.io/otel/trace"
|
|
"go.uber.org/zap"
|
|
|
|
"go.opentelemetry.io/collector/component"
|
|
"go.opentelemetry.io/collector/exporter"
|
|
"go.opentelemetry.io/collector/exporter/exporterbatcher"
|
|
"go.opentelemetry.io/collector/exporter/exporterqueue"
|
|
"go.opentelemetry.io/collector/exporter/internal"
|
|
"go.opentelemetry.io/collector/exporter/internal/queue"
|
|
)
|
|
|
|
const defaultQueueSize = 1000
|
|
|
|
// QueueConfig defines configuration for queueing batches before sending to the consumerSender.
|
|
type QueueConfig struct {
|
|
// Enabled indicates whether to not enqueue batches before sending to the consumerSender.
|
|
Enabled bool `mapstructure:"enabled"`
|
|
// NumConsumers is the number of consumers from the queue. Defaults to 10.
|
|
// If batching is enabled, a combined batch cannot contain more requests than the number of consumers.
|
|
// So it's recommended to set higher number of consumers if batching is enabled.
|
|
NumConsumers int `mapstructure:"num_consumers"`
|
|
// QueueSize is the maximum number of batches allowed in queue at a given time.
|
|
QueueSize int `mapstructure:"queue_size"`
|
|
// StorageID if not empty, enables the persistent storage and uses the component specified
|
|
// as a storage extension for the persistent queue
|
|
StorageID *component.ID `mapstructure:"storage"`
|
|
}
|
|
|
|
// NewDefaultQueueConfig returns the default config for QueueConfig.
|
|
func NewDefaultQueueConfig() QueueConfig {
|
|
return QueueConfig{
|
|
Enabled: true,
|
|
NumConsumers: 10,
|
|
// By default, batches are 8192 spans, for a total of up to 8 million spans in the queue
|
|
// This can be estimated at 1-4 GB worth of maximum memory usage
|
|
// This default is probably still too high, and may be adjusted further down in a future release
|
|
QueueSize: defaultQueueSize,
|
|
}
|
|
}
|
|
|
|
// Validate checks if the QueueConfig configuration is valid
|
|
func (qCfg *QueueConfig) Validate() error {
|
|
if !qCfg.Enabled {
|
|
return nil
|
|
}
|
|
|
|
if qCfg.QueueSize <= 0 {
|
|
return errors.New("queue size must be positive")
|
|
}
|
|
|
|
if qCfg.NumConsumers <= 0 {
|
|
return errors.New("number of queue consumers must be positive")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type QueueSender struct {
|
|
BaseRequestSender
|
|
queue exporterqueue.Queue[internal.Request]
|
|
numConsumers int
|
|
traceAttribute attribute.KeyValue
|
|
batcher queue.Batcher
|
|
consumers *queue.Consumers[internal.Request]
|
|
|
|
obsrep *ObsReport
|
|
exporterID component.ID
|
|
logger *zap.Logger
|
|
shutdownFns []component.ShutdownFunc
|
|
}
|
|
|
|
func NewQueueSender(
|
|
q exporterqueue.Queue[internal.Request],
|
|
set exporter.Settings,
|
|
numConsumers int,
|
|
exportFailureMessage string,
|
|
obsrep *ObsReport,
|
|
batcherCfg exporterbatcher.Config,
|
|
) *QueueSender {
|
|
qs := &QueueSender{
|
|
queue: q,
|
|
numConsumers: numConsumers,
|
|
traceAttribute: attribute.String(ExporterKey, set.ID.String()),
|
|
obsrep: obsrep,
|
|
exporterID: set.ID,
|
|
logger: set.Logger,
|
|
}
|
|
|
|
exportFunc := func(ctx context.Context, req internal.Request) error {
|
|
err := qs.NextSender.Send(ctx, req)
|
|
if err != nil {
|
|
set.Logger.Error("Exporting failed. Dropping data."+exportFailureMessage,
|
|
zap.Error(err), zap.Int("dropped_items", req.ItemsCount()))
|
|
}
|
|
return err
|
|
}
|
|
if usePullingBasedExporterQueueBatcher.IsEnabled() {
|
|
qs.batcher, _ = queue.NewBatcher(batcherCfg, q, exportFunc, numConsumers)
|
|
} else {
|
|
qs.consumers = queue.NewQueueConsumers[internal.Request](q, numConsumers, exportFunc)
|
|
}
|
|
return qs
|
|
}
|
|
|
|
// Start is invoked during service startup.
|
|
func (qs *QueueSender) Start(ctx context.Context, host component.Host) error {
|
|
if err := qs.queue.Start(ctx, host); err != nil {
|
|
return err
|
|
}
|
|
|
|
if usePullingBasedExporterQueueBatcher.IsEnabled() {
|
|
if err := qs.batcher.Start(ctx, host); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if err := qs.consumers.Start(ctx, host); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
dataTypeAttr := attribute.String(DataTypeKey, qs.obsrep.Signal.String())
|
|
|
|
reg1, err1 := qs.obsrep.TelemetryBuilder.InitExporterQueueSize(func() int64 { return int64(qs.queue.Size()) },
|
|
metric.WithAttributeSet(attribute.NewSet(qs.traceAttribute, dataTypeAttr)))
|
|
|
|
if reg1 != nil {
|
|
qs.shutdownFns = append(qs.shutdownFns, func(context.Context) error {
|
|
return reg1.Unregister()
|
|
})
|
|
}
|
|
|
|
reg2, err2 := qs.obsrep.TelemetryBuilder.InitExporterQueueCapacity(func() int64 { return int64(qs.queue.Capacity()) },
|
|
metric.WithAttributeSet(attribute.NewSet(qs.traceAttribute)))
|
|
|
|
if reg2 != nil {
|
|
qs.shutdownFns = append(qs.shutdownFns, func(context.Context) error {
|
|
return reg2.Unregister()
|
|
})
|
|
}
|
|
|
|
return errors.Join(err1, err2)
|
|
}
|
|
|
|
// Shutdown is invoked during service shutdown.
|
|
func (qs *QueueSender) Shutdown(ctx context.Context) error {
|
|
// Stop the queue and consumers, this will drain the queue and will call the retry (which is stopped) that will only
|
|
// try once every request.
|
|
|
|
for _, fn := range qs.shutdownFns {
|
|
err := fn(ctx)
|
|
if err != nil {
|
|
qs.logger.Warn("Error while shutting down QueueSender", zap.Error(err))
|
|
}
|
|
}
|
|
qs.shutdownFns = nil
|
|
|
|
if err := qs.queue.Shutdown(ctx); err != nil {
|
|
return err
|
|
}
|
|
if usePullingBasedExporterQueueBatcher.IsEnabled() {
|
|
return qs.batcher.Shutdown(ctx)
|
|
}
|
|
return qs.consumers.Shutdown(ctx)
|
|
}
|
|
|
|
// send implements the requestSender interface. It puts the request in the queue.
|
|
func (qs *QueueSender) Send(ctx context.Context, req internal.Request) error {
|
|
// Prevent cancellation and deadline to propagate to the context stored in the queue.
|
|
// The grpc/http based receivers will cancel the request context after this function returns.
|
|
c := context.WithoutCancel(ctx)
|
|
|
|
span := trace.SpanFromContext(c)
|
|
if err := qs.queue.Offer(c, req); err != nil {
|
|
span.AddEvent("Failed to enqueue item.", trace.WithAttributes(qs.traceAttribute))
|
|
return err
|
|
}
|
|
|
|
span.AddEvent("Enqueued item.", trace.WithAttributes(qs.traceAttribute))
|
|
return nil
|
|
}
|
|
|
|
type MockHost struct {
|
|
component.Host
|
|
Ext map[component.ID]component.Component
|
|
}
|
|
|
|
func (nh *MockHost) GetExtensions() map[component.ID]component.Component {
|
|
return nh.Ext
|
|
}
|