// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package groupbyattrsprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/groupbyattrsprocessor"
import (
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/pdata/xpdata/xhash"
)
type tracesGroup struct {
traces ptrace.Traces
resourceHashes [][16]byte
}
func newTracesGroup() *tracesGroup {
return &tracesGroup{traces: ptrace.NewTraces()}
}
// findOrCreateResource searches for a Resource with matching attributes and returns it. If nothing is found, it is being created
func (tg *tracesGroup) findOrCreateResourceSpans(originResource pcommon.Resource, requiredAttributes pcommon.Map) ptrace.ResourceSpans {
referenceResource := buildReferenceResource(originResource, requiredAttributes)
referenceResourceHash := xhash.MapHash(referenceResource.Attributes())
rss := tg.traces.ResourceSpans()
for i := 0; i < rss.Len(); i++ {
if tg.resourceHashes[i] == referenceResourceHash {
return rss.At(i)
}
}
rs := tg.traces.ResourceSpans().AppendEmpty()
referenceResource.MoveTo(rs.Resource())
tg.resourceHashes = append(tg.resourceHashes, referenceResourceHash)
return rs
}
type metricsGroup struct {
metrics pmetric.Metrics
resourceHashes [][16]byte
}
func newMetricsGroup() *metricsGroup {
return &metricsGroup{metrics: pmetric.NewMetrics()}
}
// findOrCreateResourceMetrics searches for a Resource with matching attributes and returns it. If nothing is found, it is being created
func (mg *metricsGroup) findOrCreateResourceMetrics(originResource pcommon.Resource, requiredAttributes pcommon.Map) pmetric.ResourceMetrics {
referenceResource := buildReferenceResource(originResource, requiredAttributes)
referenceResourceHash := xhash.MapHash(referenceResource.Attributes())
rms := mg.metrics.ResourceMetrics()
for i := 0; i < rms.Len(); i++ {
if mg.resourceHashes[i] == referenceResourceHash {
return rms.At(i)
}
}
rm := mg.metrics.ResourceMetrics().AppendEmpty()
referenceResource.MoveTo(rm.Resource())
mg.resourceHashes = append(mg.resourceHashes, referenceResourceHash)
return rm
}
type logsGroup struct {
logs plog.Logs
resourceHashes [][16]byte
}
// newLogsGroup returns new logsGroup with predefined capacity
func newLogsGroup() *logsGroup {
return &logsGroup{logs: plog.NewLogs()}
}
// findOrCreateResourceLogs searches for a Resource with matching attributes and returns it. If nothing is found, it is being created
func (lg *logsGroup) findOrCreateResourceLogs(originResource pcommon.Resource, requiredAttributes pcommon.Map) plog.ResourceLogs {
referenceResource := buildReferenceResource(originResource, requiredAttributes)
referenceResourceHash := xhash.MapHash(referenceResource.Attributes())
rls := lg.logs.ResourceLogs()
for i := 0; i < rls.Len(); i++ {
if lg.resourceHashes[i] == referenceResourceHash {
return rls.At(i)
}
}
rl := lg.logs.ResourceLogs().AppendEmpty()
referenceResource.MoveTo(rl.Resource())
lg.resourceHashes = append(lg.resourceHashes, referenceResourceHash)
return rl
}
func instrumentationLibrariesEqual(il1, il2 pcommon.InstrumentationScope) bool {
return il1.Name() == il2.Name() && il1.Version() == il2.Version()
}
// matchingScopeSpans searches for a ptrace.ScopeSpans instance matching
// given InstrumentationScope. If nothing is found, it creates a new one
func matchingScopeSpans(rl ptrace.ResourceSpans, library pcommon.InstrumentationScope) ptrace.ScopeSpans {
ilss := rl.ScopeSpans()
for i := 0; i < ilss.Len(); i++ {
ils := ilss.At(i)
if instrumentationLibrariesEqual(ils.Scope(), library) {
return ils
}
}
ils := ilss.AppendEmpty()
library.CopyTo(ils.Scope())
return ils
}
// matchingScopeLogs searches for a plog.ScopeLogs instance matching
// given InstrumentationScope. If nothing is found, it creates a new one
func matchingScopeLogs(rl plog.ResourceLogs, library pcommon.InstrumentationScope) plog.ScopeLogs {
ills := rl.ScopeLogs()
for i := 0; i < ills.Len(); i++ {
sl := ills.At(i)
if instrumentationLibrariesEqual(sl.Scope(), library) {
return sl
}
}
sl := ills.AppendEmpty()
library.CopyTo(sl.Scope())
return sl
}
// matchingScopeMetrics searches for a pmetric.ScopeMetrics instance matching
// given InstrumentationScope. If nothing is found, it creates a new one
func matchingScopeMetrics(rm pmetric.ResourceMetrics, library pcommon.InstrumentationScope) pmetric.ScopeMetrics {
ilms := rm.ScopeMetrics()
for i := 0; i < ilms.Len(); i++ {
ilm := ilms.At(i)
if instrumentationLibrariesEqual(ilm.Scope(), library) {
return ilm
}
}
ilm := ilms.AppendEmpty()
library.CopyTo(ilm.Scope())
return ilm
}
// buildReferenceResource returns a new resource that we'll be looking for in existing Resources
// as a merge of the Attributes of the original Resource with the requested Attributes.
func buildReferenceResource(originResource pcommon.Resource, requiredAttributes pcommon.Map) pcommon.Resource {
referenceResource := pcommon.NewResource()
originResource.Attributes().CopyTo(referenceResource.Attributes())
for k, v := range requiredAttributes.All() {
v.CopyTo(referenceResource.Attributes().PutEmpty(k))
}
return referenceResource
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package groupbyattrsprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/groupbyattrsprocessor"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/processorhelper"
"go.uber.org/zap"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/groupbyattrsprocessor/internal/metadata"
)
var consumerCapabilities = consumer.Capabilities{MutatesData: true}
// NewFactory returns a new factory for the Filter processor.
func NewFactory() processor.Factory {
return processor.NewFactory(
metadata.Type,
createDefaultConfig,
processor.WithTraces(createTracesProcessor, metadata.TracesStability),
processor.WithLogs(createLogsProcessor, metadata.LogsStability),
processor.WithMetrics(createMetricsProcessor, metadata.MetricsStability),
)
}
// createDefaultConfig creates the default configuration for the processor.
func createDefaultConfig() component.Config {
return &Config{
GroupByKeys: []string{},
}
}
func createGroupByAttrsProcessor(set processor.Settings, attributes []string) (*groupByAttrsProcessor, error) {
var nonEmptyAttributes []string
presentAttributes := make(map[string]struct{})
for _, str := range attributes {
if str != "" {
_, isPresent := presentAttributes[str]
if isPresent {
set.Logger.Warn("A grouping key is already present", zap.String("key", str))
} else {
nonEmptyAttributes = append(nonEmptyAttributes, str)
presentAttributes[str] = struct{}{}
}
}
}
telemetryBuilder, err := metadata.NewTelemetryBuilder(set.TelemetrySettings)
if err != nil {
return nil, err
}
return &groupByAttrsProcessor{logger: set.Logger, groupByKeys: nonEmptyAttributes, telemetryBuilder: telemetryBuilder}, nil
}
// createTracesProcessor creates a trace processor based on this config.
func createTracesProcessor(
ctx context.Context,
set processor.Settings,
cfg component.Config,
nextConsumer consumer.Traces,
) (processor.Traces, error) {
oCfg := cfg.(*Config)
gap, err := createGroupByAttrsProcessor(set, oCfg.GroupByKeys)
if err != nil {
return nil, err
}
return processorhelper.NewTraces(
ctx,
set,
cfg,
nextConsumer,
gap.processTraces,
processorhelper.WithCapabilities(consumerCapabilities),
)
}
// createLogsProcessor creates a logs processor based on this config.
func createLogsProcessor(
ctx context.Context,
set processor.Settings,
cfg component.Config,
nextConsumer consumer.Logs,
) (processor.Logs, error) {
oCfg := cfg.(*Config)
gap, err := createGroupByAttrsProcessor(set, oCfg.GroupByKeys)
if err != nil {
return nil, err
}
return processorhelper.NewLogs(
ctx,
set,
cfg,
nextConsumer,
gap.processLogs,
processorhelper.WithCapabilities(consumerCapabilities),
)
}
// createMetricsProcessor creates a metrics processor based on this config.
func createMetricsProcessor(
ctx context.Context,
set processor.Settings,
cfg component.Config,
nextConsumer consumer.Metrics,
) (processor.Metrics, error) {
oCfg := cfg.(*Config)
gap, err := createGroupByAttrsProcessor(set, oCfg.GroupByKeys)
if err != nil {
return nil, err
}
return processorhelper.NewMetrics(
ctx,
set,
cfg,
nextConsumer,
gap.processMetrics,
processorhelper.WithCapabilities(consumerCapabilities),
)
}
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"errors"
"sync"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/trace"
)
func Meter(settings component.TelemetrySettings) metric.Meter {
return settings.MeterProvider.Meter("github.com/open-telemetry/opentelemetry-collector-contrib/processor/groupbyattrsprocessor")
}
func Tracer(settings component.TelemetrySettings) trace.Tracer {
return settings.TracerProvider.Tracer("github.com/open-telemetry/opentelemetry-collector-contrib/processor/groupbyattrsprocessor")
}
// TelemetryBuilder provides an interface for components to report telemetry
// as defined in metadata and user config.
type TelemetryBuilder struct {
meter metric.Meter
mu sync.Mutex
registrations []metric.Registration
ProcessorGroupbyattrsLogGroups metric.Int64Histogram
ProcessorGroupbyattrsMetricGroups metric.Int64Histogram
ProcessorGroupbyattrsNumGroupedLogs metric.Int64Counter
ProcessorGroupbyattrsNumGroupedMetrics metric.Int64Counter
ProcessorGroupbyattrsNumGroupedSpans metric.Int64Counter
ProcessorGroupbyattrsNumNonGroupedLogs metric.Int64Counter
ProcessorGroupbyattrsNumNonGroupedMetrics metric.Int64Counter
ProcessorGroupbyattrsNumNonGroupedSpans metric.Int64Counter
ProcessorGroupbyattrsSpanGroups metric.Int64Histogram
}
// TelemetryBuilderOption applies changes to default builder.
type TelemetryBuilderOption interface {
apply(*TelemetryBuilder)
}
type telemetryBuilderOptionFunc func(mb *TelemetryBuilder)
func (tbof telemetryBuilderOptionFunc) apply(mb *TelemetryBuilder) {
tbof(mb)
}
// Shutdown unregister all registered callbacks for async instruments.
func (builder *TelemetryBuilder) Shutdown() {
builder.mu.Lock()
defer builder.mu.Unlock()
for _, reg := range builder.registrations {
reg.Unregister()
}
}
// NewTelemetryBuilder provides a struct with methods to update all internal telemetry
// for a component
func NewTelemetryBuilder(settings component.TelemetrySettings, options ...TelemetryBuilderOption) (*TelemetryBuilder, error) {
builder := TelemetryBuilder{}
for _, op := range options {
op.apply(&builder)
}
builder.meter = Meter(settings)
var err, errs error
builder.ProcessorGroupbyattrsLogGroups, err = builder.meter.Int64Histogram(
"otelcol_processor_groupbyattrs_log_groups",
metric.WithDescription("Distribution of groups extracted for logs [Development]"),
metric.WithUnit("1"),
)
errs = errors.Join(errs, err)
builder.ProcessorGroupbyattrsMetricGroups, err = builder.meter.Int64Histogram(
"otelcol_processor_groupbyattrs_metric_groups",
metric.WithDescription("Distribution of groups extracted for metrics [Development]"),
metric.WithUnit("1"),
)
errs = errors.Join(errs, err)
builder.ProcessorGroupbyattrsNumGroupedLogs, err = builder.meter.Int64Counter(
"otelcol_processor_groupbyattrs_num_grouped_logs",
metric.WithDescription("Number of logs that had attributes grouped [Development]"),
metric.WithUnit("1"),
)
errs = errors.Join(errs, err)
builder.ProcessorGroupbyattrsNumGroupedMetrics, err = builder.meter.Int64Counter(
"otelcol_processor_groupbyattrs_num_grouped_metrics",
metric.WithDescription("Number of metrics that had attributes grouped [Development]"),
metric.WithUnit("1"),
)
errs = errors.Join(errs, err)
builder.ProcessorGroupbyattrsNumGroupedSpans, err = builder.meter.Int64Counter(
"otelcol_processor_groupbyattrs_num_grouped_spans",
metric.WithDescription("Number of spans that had attributes grouped [Development]"),
metric.WithUnit("1"),
)
errs = errors.Join(errs, err)
builder.ProcessorGroupbyattrsNumNonGroupedLogs, err = builder.meter.Int64Counter(
"otelcol_processor_groupbyattrs_num_non_grouped_logs",
metric.WithDescription("Number of logs that did not have attributes grouped [Development]"),
metric.WithUnit("1"),
)
errs = errors.Join(errs, err)
builder.ProcessorGroupbyattrsNumNonGroupedMetrics, err = builder.meter.Int64Counter(
"otelcol_processor_groupbyattrs_num_non_grouped_metrics",
metric.WithDescription("Number of metrics that did not have attributes grouped [Development]"),
metric.WithUnit("1"),
)
errs = errors.Join(errs, err)
builder.ProcessorGroupbyattrsNumNonGroupedSpans, err = builder.meter.Int64Counter(
"otelcol_processor_groupbyattrs_num_non_grouped_spans",
metric.WithDescription("Number of spans that did not have attributes grouped [Development]"),
metric.WithUnit("1"),
)
errs = errors.Join(errs, err)
builder.ProcessorGroupbyattrsSpanGroups, err = builder.meter.Int64Histogram(
"otelcol_processor_groupbyattrs_span_groups",
metric.WithDescription("Distribution of groups extracted for spans [Development]"),
metric.WithUnit("1"),
)
errs = errors.Join(errs, err)
return &builder, errs
}
// Code generated by mdatagen. DO NOT EDIT.
package metadatatest
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/processortest"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
)
func NewSettings(tt *componenttest.Telemetry) processor.Settings {
set := processortest.NewNopSettings(processortest.NopType)
set.ID = component.NewID(component.MustNewType("groupbyattrs"))
set.TelemetrySettings = tt.NewTelemetrySettings()
return set
}
func AssertEqualProcessorGroupbyattrsLogGroups(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.HistogramDataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_processor_groupbyattrs_log_groups",
Description: "Distribution of groups extracted for logs [Development]",
Unit: "1",
Data: metricdata.Histogram[int64]{
Temporality: metricdata.CumulativeTemporality,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_processor_groupbyattrs_log_groups")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualProcessorGroupbyattrsMetricGroups(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.HistogramDataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_processor_groupbyattrs_metric_groups",
Description: "Distribution of groups extracted for metrics [Development]",
Unit: "1",
Data: metricdata.Histogram[int64]{
Temporality: metricdata.CumulativeTemporality,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_processor_groupbyattrs_metric_groups")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualProcessorGroupbyattrsNumGroupedLogs(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_processor_groupbyattrs_num_grouped_logs",
Description: "Number of logs that had attributes grouped [Development]",
Unit: "1",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_processor_groupbyattrs_num_grouped_logs")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualProcessorGroupbyattrsNumGroupedMetrics(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_processor_groupbyattrs_num_grouped_metrics",
Description: "Number of metrics that had attributes grouped [Development]",
Unit: "1",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_processor_groupbyattrs_num_grouped_metrics")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualProcessorGroupbyattrsNumGroupedSpans(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_processor_groupbyattrs_num_grouped_spans",
Description: "Number of spans that had attributes grouped [Development]",
Unit: "1",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_processor_groupbyattrs_num_grouped_spans")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualProcessorGroupbyattrsNumNonGroupedLogs(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_processor_groupbyattrs_num_non_grouped_logs",
Description: "Number of logs that did not have attributes grouped [Development]",
Unit: "1",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_processor_groupbyattrs_num_non_grouped_logs")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualProcessorGroupbyattrsNumNonGroupedMetrics(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_processor_groupbyattrs_num_non_grouped_metrics",
Description: "Number of metrics that did not have attributes grouped [Development]",
Unit: "1",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_processor_groupbyattrs_num_non_grouped_metrics")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualProcessorGroupbyattrsNumNonGroupedSpans(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_processor_groupbyattrs_num_non_grouped_spans",
Description: "Number of spans that did not have attributes grouped [Development]",
Unit: "1",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_processor_groupbyattrs_num_non_grouped_spans")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualProcessorGroupbyattrsSpanGroups(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.HistogramDataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_processor_groupbyattrs_span_groups",
Description: "Distribution of groups extracted for spans [Development]",
Unit: "1",
Data: metricdata.Histogram[int64]{
Temporality: metricdata.CumulativeTemporality,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_processor_groupbyattrs_span_groups")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package groupbyattrsprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/groupbyattrsprocessor"
import (
"context"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.uber.org/zap"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/groupbyattrsprocessor/internal/metadata"
)
type groupByAttrsProcessor struct {
logger *zap.Logger
groupByKeys []string
telemetryBuilder *metadata.TelemetryBuilder
}
// ProcessTraces process traces and groups traces by attribute.
func (gap *groupByAttrsProcessor) processTraces(ctx context.Context, td ptrace.Traces) (ptrace.Traces, error) {
rss := td.ResourceSpans()
tg := newTracesGroup()
for i := 0; i < rss.Len(); i++ {
rs := rss.At(i)
ilss := rs.ScopeSpans()
for j := 0; j < ilss.Len(); j++ {
ils := ilss.At(j)
for k := 0; k < ils.Spans().Len(); k++ {
span := ils.Spans().At(k)
toBeGrouped, requiredAttributes := gap.extractGroupingAttributes(span.Attributes())
if toBeGrouped {
gap.telemetryBuilder.ProcessorGroupbyattrsNumGroupedSpans.Add(ctx, 1)
// Some attributes are going to be moved from span to resource level,
// so we can delete those on the record level
deleteAttributes(requiredAttributes, span.Attributes())
} else {
gap.telemetryBuilder.ProcessorGroupbyattrsNumNonGroupedSpans.Add(ctx, 1)
}
// Lets combine the base resource attributes + the extracted (grouped) attributes
// and keep them in the grouping entry
groupedResourceSpans := tg.findOrCreateResourceSpans(rs.Resource(), requiredAttributes)
sp := matchingScopeSpans(groupedResourceSpans, ils.Scope()).Spans().AppendEmpty()
span.CopyTo(sp)
}
}
}
// Copy the grouped data into output
gap.telemetryBuilder.ProcessorGroupbyattrsSpanGroups.Record(ctx, int64(tg.traces.ResourceSpans().Len()))
return tg.traces, nil
}
func (gap *groupByAttrsProcessor) processLogs(ctx context.Context, ld plog.Logs) (plog.Logs, error) {
rl := ld.ResourceLogs()
lg := newLogsGroup()
for i := 0; i < rl.Len(); i++ {
ls := rl.At(i)
ills := ls.ScopeLogs()
for j := 0; j < ills.Len(); j++ {
sl := ills.At(j)
for k := 0; k < sl.LogRecords().Len(); k++ {
log := sl.LogRecords().At(k)
toBeGrouped, requiredAttributes := gap.extractGroupingAttributes(log.Attributes())
if toBeGrouped {
gap.telemetryBuilder.ProcessorGroupbyattrsNumGroupedLogs.Add(ctx, 1)
// Some attributes are going to be moved from log record to resource level,
// so we can delete those on the record level
deleteAttributes(requiredAttributes, log.Attributes())
} else {
gap.telemetryBuilder.ProcessorGroupbyattrsNumNonGroupedLogs.Add(ctx, 1)
}
// Lets combine the base resource attributes + the extracted (grouped) attributes
// and keep them in the grouping entry
groupedResourceLogs := lg.findOrCreateResourceLogs(ls.Resource(), requiredAttributes)
lr := matchingScopeLogs(groupedResourceLogs, sl.Scope()).LogRecords().AppendEmpty()
log.CopyTo(lr)
}
}
}
// Copy the grouped data into output
gap.telemetryBuilder.ProcessorGroupbyattrsLogGroups.Record(ctx, int64(lg.logs.ResourceLogs().Len()))
return lg.logs, nil
}
func (gap *groupByAttrsProcessor) processMetrics(ctx context.Context, md pmetric.Metrics) (pmetric.Metrics, error) {
rms := md.ResourceMetrics()
mg := newMetricsGroup()
for i := 0; i < rms.Len(); i++ {
rm := rms.At(i)
ilms := rm.ScopeMetrics()
for j := 0; j < ilms.Len(); j++ {
ilm := ilms.At(j)
for k := 0; k < ilm.Metrics().Len(); k++ {
metric := ilm.Metrics().At(k)
//exhaustive:enforce
switch metric.Type() {
case pmetric.MetricTypeGauge:
for pointIndex := 0; pointIndex < metric.Gauge().DataPoints().Len(); pointIndex++ {
dataPoint := metric.Gauge().DataPoints().At(pointIndex)
groupedMetric := gap.getGroupedMetricsFromAttributes(ctx, mg, rm, ilm, metric, dataPoint.Attributes())
dataPoint.CopyTo(groupedMetric.Gauge().DataPoints().AppendEmpty())
}
case pmetric.MetricTypeSum:
for pointIndex := 0; pointIndex < metric.Sum().DataPoints().Len(); pointIndex++ {
dataPoint := metric.Sum().DataPoints().At(pointIndex)
groupedMetric := gap.getGroupedMetricsFromAttributes(ctx, mg, rm, ilm, metric, dataPoint.Attributes())
dataPoint.CopyTo(groupedMetric.Sum().DataPoints().AppendEmpty())
}
case pmetric.MetricTypeSummary:
for pointIndex := 0; pointIndex < metric.Summary().DataPoints().Len(); pointIndex++ {
dataPoint := metric.Summary().DataPoints().At(pointIndex)
groupedMetric := gap.getGroupedMetricsFromAttributes(ctx, mg, rm, ilm, metric, dataPoint.Attributes())
dataPoint.CopyTo(groupedMetric.Summary().DataPoints().AppendEmpty())
}
case pmetric.MetricTypeHistogram:
for pointIndex := 0; pointIndex < metric.Histogram().DataPoints().Len(); pointIndex++ {
dataPoint := metric.Histogram().DataPoints().At(pointIndex)
groupedMetric := gap.getGroupedMetricsFromAttributes(ctx, mg, rm, ilm, metric, dataPoint.Attributes())
dataPoint.CopyTo(groupedMetric.Histogram().DataPoints().AppendEmpty())
}
case pmetric.MetricTypeExponentialHistogram:
for pointIndex := 0; pointIndex < metric.ExponentialHistogram().DataPoints().Len(); pointIndex++ {
dataPoint := metric.ExponentialHistogram().DataPoints().At(pointIndex)
groupedMetric := gap.getGroupedMetricsFromAttributes(ctx, mg, rm, ilm, metric, dataPoint.Attributes())
dataPoint.CopyTo(groupedMetric.ExponentialHistogram().DataPoints().AppendEmpty())
}
case pmetric.MetricTypeEmpty:
}
}
}
}
gap.telemetryBuilder.ProcessorGroupbyattrsMetricGroups.Record(ctx, int64(mg.metrics.ResourceMetrics().Len()))
return mg.metrics, nil
}
func deleteAttributes(attrsForRemoval, targetAttrs pcommon.Map) {
for key := range attrsForRemoval.All() {
targetAttrs.Remove(key)
}
}
// extractGroupingAttributes extracts the keys and values of the specified Attributes
// that match with the attributes keys that is used for grouping
// Returns:
// - whether any attribute matched (true) or none (false)
// - the extracted AttributeMap of matching keys and their corresponding values
func (gap *groupByAttrsProcessor) extractGroupingAttributes(attrMap pcommon.Map) (bool, pcommon.Map) {
groupingAttributes := pcommon.NewMap()
foundMatch := false
for _, attrKey := range gap.groupByKeys {
attrVal, found := attrMap.Get(attrKey)
if found {
attrVal.CopyTo(groupingAttributes.PutEmpty(attrKey))
foundMatch = true
}
}
return foundMatch, groupingAttributes
}
// Searches for metric with same name in the specified InstrumentationLibrary and returns it. If nothing is found, create it.
func getMetricInInstrumentationLibrary(ilm pmetric.ScopeMetrics, searchedMetric pmetric.Metric) pmetric.Metric {
// Loop through all metrics and try to find the one that matches with the one we search for
// (name and type)
for i := 0; i < ilm.Metrics().Len(); i++ {
metric := ilm.Metrics().At(i)
if metric.Name() == searchedMetric.Name() && metric.Type() == searchedMetric.Type() {
return metric
}
}
// We're here, which means that we haven't found our metric, so we need to create a new one, with the same name and type
metric := ilm.Metrics().AppendEmpty()
metric.SetDescription(searchedMetric.Description())
metric.SetName(searchedMetric.Name())
metric.SetUnit(searchedMetric.Unit())
searchedMetric.Metadata().CopyTo(metric.Metadata())
// Move other special type specific values
//exhaustive:enforce
switch searchedMetric.Type() {
case pmetric.MetricTypeHistogram:
metric.SetEmptyHistogram().SetAggregationTemporality(searchedMetric.Histogram().AggregationTemporality())
case pmetric.MetricTypeExponentialHistogram:
metric.SetEmptyExponentialHistogram().SetAggregationTemporality(searchedMetric.ExponentialHistogram().AggregationTemporality())
case pmetric.MetricTypeSum:
metric.SetEmptySum().SetAggregationTemporality(searchedMetric.Sum().AggregationTemporality())
metric.Sum().SetIsMonotonic(searchedMetric.Sum().IsMonotonic())
case pmetric.MetricTypeGauge:
metric.SetEmptyGauge()
case pmetric.MetricTypeSummary:
metric.SetEmptySummary()
case pmetric.MetricTypeEmpty:
}
return metric
}
// Returns the Metric in the appropriate Resource matching with the specified Attributes
func (gap *groupByAttrsProcessor) getGroupedMetricsFromAttributes(
ctx context.Context,
mg *metricsGroup,
originResourceMetrics pmetric.ResourceMetrics,
ilm pmetric.ScopeMetrics,
metric pmetric.Metric,
attributes pcommon.Map,
) pmetric.Metric {
toBeGrouped, requiredAttributes := gap.extractGroupingAttributes(attributes)
if toBeGrouped {
gap.telemetryBuilder.ProcessorGroupbyattrsNumGroupedMetrics.Add(ctx, 1)
// These attributes are going to be moved from datapoint to resource level,
// so we can delete those on the datapoint
deleteAttributes(requiredAttributes, attributes)
} else {
gap.telemetryBuilder.ProcessorGroupbyattrsNumNonGroupedMetrics.Add(ctx, 1)
}
// Get the ResourceMetrics matching with these attributes
groupedResourceMetrics := mg.findOrCreateResourceMetrics(originResourceMetrics.Resource(), requiredAttributes)
// Get the corresponding instrumentation library
groupedInstrumentationLibrary := matchingScopeMetrics(groupedResourceMetrics, ilm.Scope())
// Return the metric in this resource
return getMetricInInstrumentationLibrary(groupedInstrumentationLibrary, metric)
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package logdedupprocessor provides a processor that counts logs as metrics.
package logdedupprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/logdedupprocessor"
import (
"errors"
"fmt"
"strings"
"time"
"go.opentelemetry.io/collector/component"
)
// TimestampMode defines how the timestamp of the emitted log record is set.
type TimestampMode string
const (
// TimestampModeAggregated sets the log record timestamp to when the aggregated log was emitted.
TimestampModeAggregated TimestampMode = "aggregated"
// TimestampModePreserved preserves the log record timestamp from the first log received in the aggregation window.
TimestampModePreserved TimestampMode = "preserved"
)
// Config defaults
const (
// defaultInterval is the default export interval.
defaultInterval = 10 * time.Second
// defaultLogCountAttribute is the default log count attribute
defaultLogCountAttribute = "log_count"
// defaultTimezone is the default timezone
defaultTimezone = "UTC"
// defaultTimestampMode is the default timestamp mode
defaultTimestampMode = TimestampModeAggregated
// bodyField is the name of the body field
bodyField = "body"
// attributeField is the name of the attribute field
attributeField = "attributes"
)
// Config errors
var (
errInvalidLogCountAttribute = errors.New("log_count_attribute must be set")
errInvalidInterval = errors.New("interval must be greater than 0")
errCannotExcludeBody = errors.New("cannot exclude the entire body")
errCannotIncludeBody = errors.New("cannot include the entire body")
errInvalidTimestampMode = errors.New("timestamp_mode must be one of 'aggregated' or 'preserved'")
)
// Config is the config of the processor.
type Config struct {
LogCountAttribute string `mapstructure:"log_count_attribute"`
Interval time.Duration `mapstructure:"interval"`
Timezone string `mapstructure:"timezone"`
ExcludeFields []string `mapstructure:"exclude_fields"`
IncludeFields []string `mapstructure:"include_fields"`
Conditions []string `mapstructure:"conditions"`
TimestampMode TimestampMode `mapstructure:"timestamp_mode"`
MetadataKeys []string `mapstructure:"metadata_keys"`
// MetadataCardinalityLimit limits the number of unique metadata combinations
// tracked simultaneously. 0 (default) means unbounded.
MetadataCardinalityLimit uint32 `mapstructure:"metadata_cardinality_limit"`
}
// createDefaultConfig returns the default config for the processor.
func createDefaultConfig() component.Config {
return &Config{
LogCountAttribute: defaultLogCountAttribute,
Interval: defaultInterval,
Timezone: defaultTimezone,
TimestampMode: defaultTimestampMode,
ExcludeFields: []string{},
IncludeFields: []string{},
Conditions: []string{},
MetadataKeys: []string{},
MetadataCardinalityLimit: 0,
}
}
// Validate validates the configuration
func (c Config) Validate() error {
if c.Interval <= 0 {
return errInvalidInterval
}
if c.LogCountAttribute == "" {
return errInvalidLogCountAttribute
}
_, err := time.LoadLocation(c.Timezone)
if err != nil {
return fmt.Errorf("timezone is invalid: %w", err)
}
if len(c.ExcludeFields) > 0 && len(c.IncludeFields) > 0 {
return errors.New("cannot define both exclude_fields and include_fields")
}
err = c.validateExcludeFields()
if err != nil {
return err
}
err = c.validateIncludeFields()
if err != nil {
return err
}
err = c.validateMetadataKeys()
if err != nil {
return err
}
switch c.TimestampMode {
case TimestampModeAggregated, TimestampModePreserved:
default:
return errInvalidTimestampMode
}
return nil
}
// validateExcludeFields validates that all the exclude fields
func (c Config) validateExcludeFields() error {
knownExcludeFields := make(map[string]struct{})
for _, field := range c.ExcludeFields {
// Special check to make sure the entire body is not excluded
if field == bodyField {
return errCannotExcludeBody
}
// Split and ensure the field starts with `body` or `attributes`
parts := strings.Split(field, fieldDelimiter)
if parts[0] != bodyField && parts[0] != attributeField {
return fmt.Errorf("an excludefield must start with %s or %s", bodyField, attributeField)
}
// If a field is valid make sure we haven't already seen it
if _, ok := knownExcludeFields[field]; ok {
return fmt.Errorf("duplicate exclude_field %s", field)
}
knownExcludeFields[field] = struct{}{}
}
return nil
}
// validateMetadataKeys validates that metadata_keys has no duplicates (case-insensitive).
func (c Config) validateMetadataKeys() error {
seen := make(map[string]struct{}, len(c.MetadataKeys))
for _, k := range c.MetadataKeys {
lk := strings.ToLower(k)
if _, ok := seen[lk]; ok {
return fmt.Errorf("duplicate metadata_key %q", lk)
}
seen[lk] = struct{}{}
}
return nil
}
// validateIncludeFields validates that all the exclude fields
func (c Config) validateIncludeFields() error {
knownFields := make(map[string]struct{})
for _, field := range c.IncludeFields {
// Special check to make sure the entire body is not included
if field == bodyField {
return errCannotIncludeBody
}
// Split and ensure the field starts with `body` or `attributes`
parts := strings.Split(field, fieldDelimiter)
if parts[0] != bodyField && parts[0] != attributeField {
return fmt.Errorf("an include_fields must start with %s or %s", bodyField, attributeField)
}
// If a field is valid make sure we haven't already seen it
if _, ok := knownFields[field]; ok {
return fmt.Errorf("duplicate include_fields %s", field)
}
knownFields[field] = struct{}{}
}
return nil
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package logdedupprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/logdedupprocessor"
import (
"context"
"time"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/xpdata/xhash"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/logdedupprocessor/internal/metadata"
)
// Attribute names added to each emitted log record.
const (
firstObservedTSAttr = "first_observed_timestamp"
lastObservedTSAttr = "last_observed_timestamp"
firstEventTSAttr = "first_event_timestamp"
lastEventTSAttr = "last_event_timestamp"
numAddedAttributesAggregated = 5 // log_count, first_observed_timestamp, last_observed_timestamp, first_event_timestamp, last_event_timestamp
numAddedAttributesPreserved = 4 // log_count, first_observed_timestamp, last_observed_timestamp, last_event_timestamp
)
// timeNow can be reassigned for testing
var timeNow = time.Now
// logAggregator tracks the number of times a specific logRecord has been seen.
type logAggregator struct {
resources map[uint64]*resourceAggregator
logCountAttribute string
timezone *time.Location
telemetryBuilder *metadata.TelemetryBuilder
dedupFields []string
timestampMode TimestampMode
}
// newLogAggregator creates a new LogCounter.
func newLogAggregator(logCountAttribute string, timezone *time.Location, telemetryBuilder *metadata.TelemetryBuilder, dedupFields []string, timestampMode TimestampMode) *logAggregator {
return &logAggregator{
resources: make(map[uint64]*resourceAggregator),
logCountAttribute: logCountAttribute,
timezone: timezone,
telemetryBuilder: telemetryBuilder,
dedupFields: dedupFields,
timestampMode: timestampMode,
}
}
// Export exports the counter as a Logs
func (l *logAggregator) Export(ctx context.Context) plog.Logs {
logs := plog.NewLogs()
for _, resourceAggregator := range l.resources {
rl := logs.ResourceLogs().AppendEmpty()
resourceAggregator.resource.CopyTo(rl.Resource())
for _, scopeAggregator := range resourceAggregator.scopeCounters {
sl := rl.ScopeLogs().AppendEmpty()
scopeAggregator.scope.CopyTo(sl.Scope())
for _, logAggregator := range scopeAggregator.logCounters {
// Record aggregated logs records
l.telemetryBuilder.DedupProcessorAggregatedLogs.Record(ctx, logAggregator.count)
lr := sl.LogRecords().AppendEmpty()
logAggregator.logRecord.CopyTo(lr)
// Set ObservedTimestamp to when the first record in this window was observed by the processor.
lr.SetObservedTimestamp(pcommon.NewTimestampFromTime(logAggregator.firstObservedTimestamp))
// Capture the original event timestamp from the first record. It may be unset (zero).
firstEventTimestamp := lr.Timestamp()
// In aggregated mode (the default), overwrite the event Timestamp with the batch export time
// and include first_event_timestamp. In preserved mode, the original event Timestamp is
// retained and first_event_timestamp is omitted as it is redundant. If the first record has
// no Timestamp, preserved mode falls back to the export time.
numAttrs := numAddedAttributesPreserved
if l.timestampMode != TimestampModePreserved {
lr.SetTimestamp(pcommon.NewTimestampFromTime(timeNow()))
numAttrs = numAddedAttributesAggregated
} else if firstEventTimestamp == 0 {
lr.SetTimestamp(pcommon.NewTimestampFromTime(timeNow()))
}
// Add attributes for log count, first/last observed timestamps, and first/last event timestamps
lr.Attributes().EnsureCapacity(lr.Attributes().Len() + numAttrs)
lr.Attributes().PutInt(l.logCountAttribute, logAggregator.count)
firstTimestampStr := logAggregator.firstObservedTimestamp.In(l.timezone).Format(time.RFC3339)
lr.Attributes().PutStr(firstObservedTSAttr, firstTimestampStr)
lastTimestampStr := logAggregator.lastObservedTimestamp.In(l.timezone).Format(time.RFC3339)
lr.Attributes().PutStr(lastObservedTSAttr, lastTimestampStr)
// Event timestamp attributes are omitted when the source Timestamp is unset.
if l.timestampMode != TimestampModePreserved && firstEventTimestamp != 0 {
firstEventTimestampStr := firstEventTimestamp.AsTime().In(l.timezone).Format(time.RFC3339)
lr.Attributes().PutStr(firstEventTSAttr, firstEventTimestampStr)
}
if logAggregator.lastEventTimestamp != 0 {
lastEventTimestampStr := logAggregator.lastEventTimestamp.AsTime().In(l.timezone).Format(time.RFC3339)
lr.Attributes().PutStr(lastEventTSAttr, lastEventTimestampStr)
}
}
}
}
return logs
}
// Add adds the logRecord to the resource aggregator that is identified by the resource attributes
func (l *logAggregator) Add(resource pcommon.Resource, scope pcommon.InstrumentationScope, logRecord plog.LogRecord) {
key := getResourceKey(resource)
resourceAggregator, ok := l.resources[key]
if !ok {
resourceAggregator = newResourceAggregator(resource, l.dedupFields)
l.resources[key] = resourceAggregator
}
resourceAggregator.Add(scope, logRecord)
}
// Reset resets the counter.
func (l *logAggregator) Reset() {
l.resources = make(map[uint64]*resourceAggregator)
}
// resourceAggregator dimensions the counter by resource.
type resourceAggregator struct {
resource pcommon.Resource
scopeCounters map[uint64]*scopeAggregator
dedupFields []string
}
// newResourceAggregator creates a new ResourceCounter.
func newResourceAggregator(resource pcommon.Resource, dedupFields []string) *resourceAggregator {
cloneResource := pcommon.NewResource()
resource.CopyTo(cloneResource)
return &resourceAggregator{
resource: cloneResource,
scopeCounters: make(map[uint64]*scopeAggregator),
dedupFields: dedupFields,
}
}
// Add increments the counter that the logRecord matches.
func (r *resourceAggregator) Add(scope pcommon.InstrumentationScope, logRecord plog.LogRecord) {
key := getScopeKey(scope)
scopeAggregator, ok := r.scopeCounters[key]
if !ok {
scopeAggregator = newScopeAggregator(scope, r.dedupFields)
r.scopeCounters[key] = scopeAggregator
}
scopeAggregator.Add(logRecord)
}
// scopeAggregator dimensions the counter by scope.
type scopeAggregator struct {
scope pcommon.InstrumentationScope
logCounters map[uint64]*logCounter
dedupFields []string
}
// newScopeAggregator creates a new ScopeCounter.
func newScopeAggregator(scope pcommon.InstrumentationScope, dedupFields []string) *scopeAggregator {
cloneScope := pcommon.NewInstrumentationScope()
scope.CopyTo(cloneScope)
return &scopeAggregator{
scope: cloneScope,
logCounters: make(map[uint64]*logCounter),
dedupFields: dedupFields,
}
}
// Add increments the counter that the logRecord matches.
func (s *scopeAggregator) Add(logRecord plog.LogRecord) {
key := getLogKey(logRecord, s.dedupFields)
lc, ok := s.logCounters[key]
if !ok {
lc = newLogCounter(logRecord)
s.logCounters[key] = lc
} else {
lc.lastEventTimestamp = logRecord.Timestamp()
}
lc.Increment()
}
// logCounter is a counter for a log record.
type logCounter struct {
logRecord plog.LogRecord
firstObservedTimestamp time.Time
lastObservedTimestamp time.Time
lastEventTimestamp pcommon.Timestamp
count int64
}
// newLogCounter creates a new AttributeCounter.
func newLogCounter(logRecord plog.LogRecord) *logCounter {
// Since we always remove the logRecord if we got to this point, we can move it instead of copying.
movedLogRecord := plog.NewLogRecord()
logRecord.MoveTo(movedLogRecord)
return &logCounter{
logRecord: movedLogRecord,
count: 0,
firstObservedTimestamp: timeNow().UTC(),
lastObservedTimestamp: timeNow().UTC(),
lastEventTimestamp: movedLogRecord.Timestamp(),
}
}
// Increment increments the counter.
func (a *logCounter) Increment() {
a.lastObservedTimestamp = timeNow().UTC()
a.count++
}
// getResourceKey creates a unique hash for the resource to use as a map key
func getResourceKey(resource pcommon.Resource) uint64 {
return xhash.Hash64(
xhash.WithMap(resource.Attributes()),
)
}
// getScopeKey creates a unique hash for the scope to use as a map key
func getScopeKey(scope pcommon.InstrumentationScope) uint64 {
return xhash.Hash64(
xhash.WithMap(scope.Attributes()),
xhash.WithString(scope.Name()),
xhash.WithString(scope.Version()),
)
}
// getLogKey creates a unique hash for the log record to use as a map key.
// If dedupFields is non-empty, it is used to determine the fields whose values are hashed.
// If no dedupFields are found in the log record, all fields are hashed.
func getLogKey(logRecord plog.LogRecord, dedupFields []string) uint64 {
if len(dedupFields) > 0 {
var opts []xhash.HashOption
for _, field := range dedupFields {
parts := splitField(field)
if m, ok := getMap(logRecord, parts[0]); ok {
if value, ok := getKeyValue(m, parts[1:]); ok {
opts = append(opts, xhash.WithString(value.AsString()))
}
}
}
if len(opts) > 0 {
return xhash.Hash64(opts...)
}
}
return xhash.Hash64(
xhash.WithMap(logRecord.Attributes()),
xhash.WithValue(logRecord.Body()),
xhash.WithString(logRecord.SeverityNumber().String()),
xhash.WithString(logRecord.SeverityText()),
)
}
func getMap(logRecord plog.LogRecord, leadingPart string) (pcommon.Map, bool) {
switch leadingPart {
case bodyField:
if logRecord.Body().Type() == pcommon.ValueTypeMap {
return logRecord.Body().Map(), true
}
case attributeField:
return logRecord.Attributes(), true
}
var m pcommon.Map
return m, false
}
func getKeyValue(valueMap pcommon.Map, keyParts []string) (pcommon.Value, bool) {
nextKeyPart, remainingParts := keyParts[0], keyParts[1:]
// Look for the value associated with the next key part.
// If we don't find it then return
value, ok := valueMap.Get(nextKeyPart)
if !ok {
return pcommon.NewValueEmpty(), false
}
// No more key parts that means we have found the value
if len(remainingParts) == 0 {
return valueMap.Get(nextKeyPart)
}
// If the value is a map then recurse through with the remaining parts
if value.Type() == pcommon.ValueTypeMap {
return getKeyValue(value.Map(), remainingParts)
}
return pcommon.NewValueEmpty(), false
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package logdedupprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/logdedupprocessor"
import (
"context"
"fmt"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/xprocessor"
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/filterottl"
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/logdedupprocessor/internal/metadata"
)
// NewFactory creates a new factory for the processor.
func NewFactory() processor.Factory {
return xprocessor.NewFactory(
metadata.Type,
createDefaultConfig,
xprocessor.WithLogs(createLogsProcessor, metadata.LogsStability),
xprocessor.WithDeprecatedTypeAlias(metadata.DeprecatedType),
)
}
// createLogsProcessor creates a log processor.
func createLogsProcessor(_ context.Context, settings processor.Settings, cfg component.Config, consumer consumer.Logs) (processor.Logs, error) {
processorCfg, ok := cfg.(*Config)
if !ok {
return nil, fmt.Errorf("invalid config type: %+v", cfg)
}
if err := processorCfg.Validate(); err != nil {
return nil, err
}
processor, err := newProcessor(processorCfg, consumer, settings)
if err != nil {
return nil, fmt.Errorf("error creating processor: %w", err)
}
if len(processorCfg.Conditions) == 0 {
processor.conditions = nil
} else {
conditions, err := filterottl.NewBoolExprForLogWithPathContextNames(
processorCfg.Conditions,
filterottl.StandardLogFuncs(),
ottl.PropagateError,
settings.TelemetrySettings,
)
if err != nil {
return nil, fmt.Errorf("invalid condition: %w", err)
}
processor.conditions = conditions
}
return processor, nil
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package logdedupprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/logdedupprocessor"
import (
"fmt"
"strings"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
)
const (
// fieldDelimiter is the delimiter used to split a field key into its parts.
fieldDelimiter = "."
// fieldEscapeKeyReplacement is the string used to temporarily replace escaped delimiters while splitting a field key.
fieldEscapeKeyReplacement = "{TEMP_REPLACE}"
)
// fieldRemover handles removing excluded fields from log records
type fieldRemover struct {
fields []*field
}
// field represents a field and it's compound key to match on
type field struct {
keyParts []string
}
// newFieldRemover creates a new field remover based on the passed in field keys
func newFieldRemover(fieldKeys []string) *fieldRemover {
fe := &fieldRemover{
fields: make([]*field, 0, len(fieldKeys)),
}
for _, f := range fieldKeys {
fe.fields = append(fe.fields, &field{
keyParts: splitField(f),
})
}
return fe
}
// RemoveFields removes any body or attribute fields that match in the log record
func (fe *fieldRemover) RemoveFields(logRecord plog.LogRecord) {
for _, field := range fe.fields {
field.removeField(logRecord)
}
}
// removeField removes the field from the log record if it exists
func (f *field) removeField(logRecord plog.LogRecord) {
firstPart, remainingParts := f.keyParts[0], f.keyParts[1:]
switch firstPart {
case bodyField:
// If body is a map then recurse through to remove the field
if logRecord.Body().Type() == pcommon.ValueTypeMap {
removeFieldFromMap(logRecord.Body().Map(), remainingParts)
}
case attributeField:
// Remove all attributes
if len(remainingParts) == 0 {
logRecord.Attributes().Clear()
return
}
// Recurse through map and remove fields
removeFieldFromMap(logRecord.Attributes(), remainingParts)
}
}
// removeFieldFromMap recurses through the map and removes the field if it's found.
func removeFieldFromMap(valueMap pcommon.Map, keyParts []string) {
nextKeyPart, remainingParts := keyParts[0], keyParts[1:]
// Look for the value associated with the next key part.
// If we don't find it then return
value, ok := valueMap.Get(nextKeyPart)
if !ok {
return
}
// No more key parts that means we have found the value and remove it
if len(remainingParts) == 0 {
valueMap.Remove(nextKeyPart)
return
}
// If the value is a map then recurse through with the remaining parts
if value.Type() == pcommon.ValueTypeMap {
removeFieldFromMap(value.Map(), remainingParts)
}
}
// splitField splits a field key into its parts.
// It replaces escaped delimiters with the full delimiter after splitting.
func splitField(fieldKey string) []string {
escapedKey := strings.ReplaceAll(fieldKey, fmt.Sprintf("\\%s", fieldDelimiter), fieldEscapeKeyReplacement)
keyParts := strings.Split(escapedKey, fieldDelimiter)
// Replace the temporarily escaped delimiters with the actual delimiter.
for i := range keyParts {
keyParts[i] = strings.ReplaceAll(keyParts[i], fieldEscapeKeyReplacement, fieldDelimiter)
}
return keyParts
}
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"errors"
"sync"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/trace"
)
func Meter(settings component.TelemetrySettings) metric.Meter {
return settings.MeterProvider.Meter("github.com/open-telemetry/opentelemetry-collector-contrib/processor/logdedupprocessor")
}
func Tracer(settings component.TelemetrySettings) trace.Tracer {
return settings.TracerProvider.Tracer("github.com/open-telemetry/opentelemetry-collector-contrib/processor/logdedupprocessor")
}
// TelemetryBuilder provides an interface for components to report telemetry
// as defined in metadata and user config.
type TelemetryBuilder struct {
meter metric.Meter
mu sync.Mutex
registrations []metric.Registration
DedupProcessorAggregatedLogs metric.Int64Histogram
}
// TelemetryBuilderOption applies changes to default builder.
type TelemetryBuilderOption interface {
apply(*TelemetryBuilder)
}
type telemetryBuilderOptionFunc func(mb *TelemetryBuilder)
func (tbof telemetryBuilderOptionFunc) apply(mb *TelemetryBuilder) {
tbof(mb)
}
// Shutdown unregister all registered callbacks for async instruments.
func (builder *TelemetryBuilder) Shutdown() {
builder.mu.Lock()
defer builder.mu.Unlock()
for _, reg := range builder.registrations {
reg.Unregister()
}
}
// NewTelemetryBuilder provides a struct with methods to update all internal telemetry
// for a component
func NewTelemetryBuilder(settings component.TelemetrySettings, options ...TelemetryBuilderOption) (*TelemetryBuilder, error) {
builder := TelemetryBuilder{}
for _, op := range options {
op.apply(&builder)
}
builder.meter = Meter(settings)
var err, errs error
builder.DedupProcessorAggregatedLogs, err = builder.meter.Int64Histogram(
"otelcol_dedup_processor_aggregated_logs",
metric.WithDescription("Number of log records that were aggregated together. [Development]"),
metric.WithUnit("{records}"),
)
errs = errors.Join(errs, err)
return &builder, errs
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package logdedupprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/logdedupprocessor"
import (
"context"
"errors"
"fmt"
"sort"
"strings"
"sync"
"time"
"go.opentelemetry.io/collector/client"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/otel/attribute"
"go.uber.org/zap"
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottllog"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/logdedupprocessor/internal/metadata"
)
// shardedAggregator is the common interface for aggregating logs, either as a
// single bucket or as multiple buckets keyed by metadata combination.
type shardedAggregator interface {
add(ctx context.Context, logRecord plog.LogRecord, scope pcommon.InstrumentationScope, resource pcommon.Resource) error
flush(ctx context.Context, nextConsumer consumer.Logs, logger *zap.Logger)
}
// singleShardAggregator is used when no metadata_keys are configured.
// It wraps a single logAggregator with no runtime overhead compared to the original behavior.
type singleShardAggregator struct {
aggregator *logAggregator
}
func (s *singleShardAggregator) add(_ context.Context, logRecord plog.LogRecord, scope pcommon.InstrumentationScope, resource pcommon.Resource) error {
s.aggregator.Add(resource, scope, logRecord)
return nil
}
func (s *singleShardAggregator) flush(ctx context.Context, nextConsumer consumer.Logs, logger *zap.Logger) {
logs := s.aggregator.Export(ctx)
if logs.LogRecordCount() > 0 {
if err := nextConsumer.ConsumeLogs(ctx, logs); err != nil {
logger.Error("failed to consume logs", zap.Error(err))
}
s.aggregator.Reset()
}
}
// aggregatorShard holds a logAggregator and the client metadata for one metadata combination.
type aggregatorShard struct {
aggregator *logAggregator
clientInfo client.Info
}
// multiShardAggregator is used when metadata_keys are configured.
// It maintains one aggregatorShard per unique combination of metadata values.
type multiShardAggregator struct {
metadataKeys []string
metadataCardinalityLimit int
// Fields below are passed through to newLogAggregator for on-demand shard creation.
logCountAttribute string
timezone *time.Location
telemetryBuilder *metadata.TelemetryBuilder
includeFields []string
timestampMode TimestampMode
shards map[attribute.Set]*aggregatorShard
// lock protects the shards map during concurrent lookups and creation.
lock sync.Mutex
}
func (m *multiShardAggregator) add(ctx context.Context, logRecord plog.LogRecord, scope pcommon.InstrumentationScope, resource pcommon.Resource) error {
info := client.FromContext(ctx)
attrs := make([]attribute.KeyValue, 0, len(m.metadataKeys))
for _, k := range m.metadataKeys {
vs := info.Metadata.Get(k)
if len(vs) == 1 {
attrs = append(attrs, attribute.String(k, vs[0]))
} else {
attrs = append(attrs, attribute.StringSlice(k, vs))
}
}
aset := attribute.NewSet(attrs...)
shard, err := m.getOrCreateShard(info, aset)
if err != nil {
return err
}
shard.aggregator.Add(resource, scope, logRecord)
return nil
}
func (m *multiShardAggregator) getOrCreateShard(info client.Info, aset attribute.Set) (*aggregatorShard, error) {
m.lock.Lock()
defer m.lock.Unlock()
shard, ok := m.shards[aset]
if ok {
return shard, nil
}
if m.metadataCardinalityLimit > 0 && len(m.shards) >= m.metadataCardinalityLimit {
return nil, consumererror.NewPermanent(errors.New("too many batcher metadata-value combinations"))
}
md := make(map[string][]string, len(m.metadataKeys))
for _, k := range m.metadataKeys {
md[k] = info.Metadata.Get(k)
}
shard = &aggregatorShard{
aggregator: newLogAggregator(m.logCountAttribute, m.timezone, m.telemetryBuilder, m.includeFields, m.timestampMode),
clientInfo: client.Info{
Metadata: client.NewMetadata(md),
},
}
m.shards[aset] = shard
return shard, nil
}
func (m *multiShardAggregator) flush(_ context.Context, nextConsumer consumer.Logs, logger *zap.Logger) {
m.lock.Lock()
shards := make([]*aggregatorShard, 0, len(m.shards))
for _, s := range m.shards {
shards = append(shards, s)
}
m.lock.Unlock()
for _, shard := range shards {
exportCtx := client.NewContext(context.Background(), shard.clientInfo)
logs := shard.aggregator.Export(exportCtx)
if logs.LogRecordCount() > 0 {
if err := nextConsumer.ConsumeLogs(exportCtx, logs); err != nil {
logger.Error("failed to consume logs", zap.Error(err))
}
shard.aggregator.Reset()
}
}
}
// logDedupProcessor is a logDedupProcessor that counts duplicate instances of logs.
type logDedupProcessor struct {
emitInterval time.Duration
conditions *ottl.ConditionSequence[*ottllog.TransformContext]
aggregator shardedAggregator
remover *fieldRemover
nextConsumer consumer.Logs
logger *zap.Logger
cancel context.CancelFunc
wg sync.WaitGroup
mux sync.Mutex
}
func newProcessor(cfg *Config, nextConsumer consumer.Logs, settings processor.Settings) (*logDedupProcessor, error) {
telemetryBuilder, err := metadata.NewTelemetryBuilder(settings.TelemetrySettings)
if err != nil {
return nil, fmt.Errorf("failed to create telemetry builder: %w", err)
}
// This should not happen due to config validation but we check anyways.
timezone, err := time.LoadLocation(cfg.Timezone)
if err != nil {
return nil, fmt.Errorf("invalid timezone: %w", err)
}
// Normalize metadata keys to lowercase. HTTP/2 headers are case-insensitive,
// but attribute.Set key lookup is case-sensitive — normalizing avoids
// duplicate shards for the same logical key.
metadataKeys := make([]string, len(cfg.MetadataKeys))
for i, k := range cfg.MetadataKeys {
metadataKeys[i] = strings.ToLower(k)
}
sort.Strings(metadataKeys)
var agg shardedAggregator
if len(metadataKeys) == 0 {
agg = &singleShardAggregator{
aggregator: newLogAggregator(cfg.LogCountAttribute, timezone, telemetryBuilder, cfg.IncludeFields, cfg.TimestampMode),
}
} else {
if cfg.MetadataCardinalityLimit == 0 {
settings.Logger.Warn("metadata_keys is set but metadata_cardinality_limit is 0; the number of metadata combinations is unbounded, which may lead to unbounded memory growth. Set metadata_cardinality_limit to a positive value to cap it.",
zap.Strings("metadata_keys", metadataKeys),
)
}
agg = &multiShardAggregator{
metadataKeys: metadataKeys,
metadataCardinalityLimit: int(cfg.MetadataCardinalityLimit),
logCountAttribute: cfg.LogCountAttribute,
timezone: timezone,
telemetryBuilder: telemetryBuilder,
includeFields: cfg.IncludeFields,
timestampMode: cfg.TimestampMode,
shards: make(map[attribute.Set]*aggregatorShard),
}
}
return &logDedupProcessor{
emitInterval: cfg.Interval,
aggregator: agg,
remover: newFieldRemover(cfg.ExcludeFields),
nextConsumer: nextConsumer,
logger: settings.Logger,
}, nil
}
// Start starts the processor.
func (p *logDedupProcessor) Start(_ context.Context, _ component.Host) error {
ctx, cancel := context.WithCancel(context.Background())
p.cancel = cancel
p.wg.Add(1)
go p.handleExportInterval(ctx)
return nil
}
// Capabilities returns the consumer's capabilities.
func (*logDedupProcessor) Capabilities() consumer.Capabilities {
return consumer.Capabilities{MutatesData: true}
}
// Shutdown stops the processor.
func (p *logDedupProcessor) Shutdown(context.Context) error {
if p.cancel != nil {
// Call cancel to stop the export interval goroutine and wait for it to finish.
p.cancel()
p.wg.Wait()
}
return nil
}
// ConsumeLogs processes the logs.
func (p *logDedupProcessor) ConsumeLogs(ctx context.Context, pl plog.Logs) error {
p.mux.Lock()
defer p.mux.Unlock()
var aggregateErr error
pl.ResourceLogs().RemoveIf(func(rl plog.ResourceLogs) bool {
resource := rl.Resource()
rl.ScopeLogs().RemoveIf(func(sl plog.ScopeLogs) bool {
scope := sl.Scope()
logs := sl.LogRecords()
logs.RemoveIf(func(logRecord plog.LogRecord) bool {
if p.conditions == nil {
if err := p.aggregateLog(ctx, logRecord, scope, resource); err != nil {
aggregateErr = err
return false
}
return true
}
logCtx := ottllog.NewTransformContext(rl, sl, logRecord)
defer logCtx.Close()
logMatch, err := p.conditions.Eval(ctx, logCtx)
if err != nil {
p.logger.Error("error matching conditions", zap.Error(err))
return false
}
if !logMatch {
return false
}
if err := p.aggregateLog(ctx, logRecord, scope, resource); err != nil {
aggregateErr = err
return false
}
return true
})
return sl.LogRecords().Len() == 0
})
return rl.ScopeLogs().Len() == 0
})
// immediately consume any logs that didn't match any conditions
if pl.LogRecordCount() > 0 {
err := p.nextConsumer.ConsumeLogs(ctx, pl)
if err != nil {
p.logger.Error("failed to consume logs", zap.Error(err))
}
}
return aggregateErr
}
func (p *logDedupProcessor) aggregateLog(ctx context.Context, logRecord plog.LogRecord, scope pcommon.InstrumentationScope, resource pcommon.Resource) error {
p.remover.RemoveFields(logRecord)
return p.aggregator.add(ctx, logRecord, scope, resource)
}
// handleExportInterval sends metrics at the configured interval.
func (p *logDedupProcessor) handleExportInterval(ctx context.Context) {
defer p.wg.Done()
ticker := time.NewTicker(p.emitInterval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
// Export any remaining logs with a live context so downstream does not reject them.
p.exportLogs(context.WithoutCancel(ctx))
if err := ctx.Err(); err != context.Canceled {
p.logger.Error("context error", zap.Error(err))
}
return
case <-ticker.C:
p.exportLogs(ctx)
}
}
}
// exportLogs exports the logs to the next consumer.
func (p *logDedupProcessor) exportLogs(ctx context.Context) {
p.mux.Lock()
defer p.mux.Unlock()
p.aggregator.flush(ctx, p.nextConsumer, p.logger)
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package probabilisticsamplerprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/probabilisticsamplerprocessor"
import (
"errors"
"fmt"
"math"
"go.opentelemetry.io/collector/component"
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/sampling"
)
type AttributeSource string
const (
traceIDAttributeSource = AttributeSource("traceID")
recordAttributeSource = AttributeSource("record")
defaultAttributeSource = traceIDAttributeSource
)
var validAttributeSource = map[AttributeSource]bool{
traceIDAttributeSource: true,
recordAttributeSource: true,
}
// Config has the configuration guiding the sampler processor.
type Config struct {
// SamplingPercentage is the percentage rate at which traces or logs are going to be sampled. Defaults to zero, i.e.: no sample.
// Values greater or equal 100 are treated as "sample all traces/logs".
SamplingPercentage float32 `mapstructure:"sampling_percentage"`
// HashSeed allows one to configure the hashing seed. This is important in scenarios where multiple layers of collectors
// have different sampling rates: if they use the same seed all passing one layer may pass the other even if they have
// different sampling rates, configuring different seeds avoids that.
HashSeed uint32 `mapstructure:"hash_seed"`
// Mode selects the sampling behavior. Supported values:
//
// - "hash_seed": the legacy behavior of this processor.
// Using an FNV hash combined with the HashSeed value, this
// sampler performs a non-consistent probabilistic
// downsampling. The number of spans output is expected to
// equal SamplingPercentage (as a ratio) times the number of
// spans inpout, assuming good behavior from FNV and good
// entropy in the hashed attributes or TraceID.
//
// - "equalizing": Using an OTel-specified consistent sampling
// mechanism, this sampler selectively reduces the effective
// sampling probability of arriving spans. This can be
// useful to select a small fraction of complete traces from
// a stream with mixed sampling rates. The rate of spans
// passing through depends on how much sampling has already
// been applied. If an arriving span was head sampled at
// the same probability it passes through. If the span
// arrives with lower probability, a warning is logged
// because it means this sampler is configured with too
// large a sampling probability to ensure complete traces.
//
// - "proportional": Using an OTel-specified consistent sampling
// mechanism, this sampler reduces the effective sampling
// probability of each span by `SamplingProbability`.
Mode SamplerMode `mapstructure:"mode"`
// FailClosed indicates to not sample data (the processor will
// fail "closed") in case of error, such as failure to parse
// the tracestate field or missing the randomness attribute.
//
// By default, failure cases are sampled (the processor is
// fails "open"). Sampling priority-based decisions are made after
// FailClosed is processed, making it possible to sample
// despite errors using priority.
FailClosed bool `mapstructure:"fail_closed"`
// SamplingPrecision is how many hex digits of sampling
// threshold will be encoded, from 1 up to 14. Default is 4.
// 0 is treated as full precision.
SamplingPrecision int `mapstructure:"sampling_precision"`
///////
// Logs only fields below.
// AttributeSource (logs only) defines where to look for the attribute in from_attribute. The allowed values are
// `traceID` or `record`. Default is `traceID`.
AttributeSource AttributeSource `mapstructure:"attribute_source"`
// FromAttribute (logs only) The optional name of a log record attribute used for sampling purposes, such as a
// unique log record ID. The value of the attribute is only used if the trace ID is absent or if `attribute_source` is set to `record`.
FromAttribute string `mapstructure:"from_attribute"`
// SamplingPriority (logs only) enables using a log record attribute as the sampling priority of the log record.
SamplingPriority string `mapstructure:"sampling_priority"`
}
var _ component.Config = (*Config)(nil)
// Validate checks if the processor configuration is valid
func (cfg *Config) Validate() error {
pct := float64(cfg.SamplingPercentage)
if math.IsInf(pct, 0) || math.IsNaN(pct) {
return fmt.Errorf("sampling rate is invalid: %f%%", cfg.SamplingPercentage)
}
ratio := pct / 100.0
switch {
case ratio < 0:
return fmt.Errorf("sampling rate is negative: %f%%", cfg.SamplingPercentage)
case ratio == 0:
// Special case
case ratio < sampling.MinSamplingProbability:
// Too-small case
return fmt.Errorf("sampling rate is too small: %g%%", cfg.SamplingPercentage)
default:
// Note that ratio > 1 is specifically allowed by the README, taken to mean 100%
}
if cfg.AttributeSource != "" && !validAttributeSource[cfg.AttributeSource] {
return fmt.Errorf("invalid attribute source: %v. Expected: %v or %v", cfg.AttributeSource, traceIDAttributeSource, recordAttributeSource)
}
if cfg.SamplingPrecision == 0 {
return errors.New("invalid sampling precision: 0")
} else if cfg.SamplingPrecision > sampling.NumHexDigits {
return fmt.Errorf("sampling precision is too great, should be <= 14: %d", cfg.SamplingPrecision)
}
return nil
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:generate make mdatagen
package probabilisticsamplerprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/probabilisticsamplerprocessor"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/processor"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/probabilisticsamplerprocessor/internal/metadata"
)
// The default precision is 4 hex digits, slightly more the original
// component logic's 14-bits of precision.
const defaultPrecision = 4
// NewFactory returns a new factory for the Probabilistic sampler processor.
func NewFactory() processor.Factory {
return processor.NewFactory(
metadata.Type,
createDefaultConfig,
processor.WithTraces(createTracesProcessor, metadata.TracesStability),
processor.WithLogs(createLogsProcessor, metadata.LogsStability),
)
}
func createDefaultConfig() component.Config {
return &Config{
AttributeSource: defaultAttributeSource,
FailClosed: true,
Mode: modeUnset,
SamplingPrecision: defaultPrecision,
}
}
// createTracesProcessor creates a trace processor based on this config.
func createTracesProcessor(
ctx context.Context,
set processor.Settings,
cfg component.Config,
nextConsumer consumer.Traces,
) (processor.Traces, error) {
return newTracesProcessor(ctx, set, cfg.(*Config), nextConsumer)
}
// createLogsProcessor creates a log processor based on this config.
func createLogsProcessor(
ctx context.Context,
set processor.Settings,
cfg component.Config,
nextConsumer consumer.Logs,
) (processor.Logs, error) {
return newLogsProcessor(ctx, set, nextConsumer, cfg.(*Config))
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package probabilisticsamplerprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/probabilisticsamplerprocessor"
import (
"encoding/binary"
"hash/fnv"
)
// computeHash creates a hash using the FNV-1a algorithm
func computeHash(b []byte, seed uint32) uint32 {
hash := fnv.New32a()
// the implementation fnv.Write() does not return an error, see hash/fnv/fnv.go
_, _ = hash.Write(i32tob(seed))
_, _ = hash.Write(b)
return hash.Sum32()
}
// i32tob converts a seed to a byte array to be used as part of fnv.Write()
func i32tob(val uint32) []byte {
r := make([]byte, 4)
binary.LittleEndian.PutUint32(r, val)
return r
}
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"errors"
"sync"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/trace"
)
func Meter(settings component.TelemetrySettings) metric.Meter {
return settings.MeterProvider.Meter("github.com/open-telemetry/opentelemetry-collector-contrib/processor/probabilisticsamplerprocessor")
}
func Tracer(settings component.TelemetrySettings) trace.Tracer {
return settings.TracerProvider.Tracer("github.com/open-telemetry/opentelemetry-collector-contrib/processor/probabilisticsamplerprocessor")
}
// TelemetryBuilder provides an interface for components to report telemetry
// as defined in metadata and user config.
type TelemetryBuilder struct {
meter metric.Meter
mu sync.Mutex
registrations []metric.Registration
ProcessorProbabilisticSamplerCountLogsSampled metric.Int64Counter
ProcessorProbabilisticSamplerCountSpansProcessed metric.Int64Counter
ProcessorProbabilisticSamplerCountTracesSampled metric.Int64Counter
}
// TelemetryBuilderOption applies changes to default builder.
type TelemetryBuilderOption interface {
apply(*TelemetryBuilder)
}
type telemetryBuilderOptionFunc func(mb *TelemetryBuilder)
func (tbof telemetryBuilderOptionFunc) apply(mb *TelemetryBuilder) {
tbof(mb)
}
// Shutdown unregister all registered callbacks for async instruments.
func (builder *TelemetryBuilder) Shutdown() {
builder.mu.Lock()
defer builder.mu.Unlock()
for _, reg := range builder.registrations {
reg.Unregister()
}
}
// NewTelemetryBuilder provides a struct with methods to update all internal telemetry
// for a component
func NewTelemetryBuilder(settings component.TelemetrySettings, options ...TelemetryBuilderOption) (*TelemetryBuilder, error) {
builder := TelemetryBuilder{}
for _, op := range options {
op.apply(&builder)
}
builder.meter = Meter(settings)
var err, errs error
builder.ProcessorProbabilisticSamplerCountLogsSampled, err = builder.meter.Int64Counter(
"otelcol_processor_probabilistic_sampler_count_logs_sampled",
metric.WithDescription("Count of logs that were sampled or not [Development]"),
metric.WithUnit("1"),
)
errs = errors.Join(errs, err)
builder.ProcessorProbabilisticSamplerCountSpansProcessed, err = builder.meter.Int64Counter(
"otelcol_processor_probabilistic_sampler_count_spans_processed",
metric.WithDescription("Count of spans that were sampled or not [Development]"),
metric.WithUnit("1"),
)
errs = errors.Join(errs, err)
builder.ProcessorProbabilisticSamplerCountTracesSampled, err = builder.meter.Int64Counter(
"otelcol_processor_probabilistic_sampler_count_traces_sampled",
metric.WithDescription("[DEPRECATED] Count of spans that were sampled or not [Deprecated]"),
metric.WithUnit("1"),
)
errs = errors.Join(errs, err)
return &builder, errs
}
// Code generated by mdatagen. DO NOT EDIT.
package metadatatest
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/processortest"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
)
func NewSettings(tt *componenttest.Telemetry) processor.Settings {
set := processortest.NewNopSettings(processortest.NopType)
set.ID = component.NewID(component.MustNewType("probabilistic_sampler"))
set.TelemetrySettings = tt.NewTelemetrySettings()
return set
}
func AssertEqualProcessorProbabilisticSamplerCountLogsSampled(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_processor_probabilistic_sampler_count_logs_sampled",
Description: "Count of logs that were sampled or not [Development]",
Unit: "1",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_processor_probabilistic_sampler_count_logs_sampled")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualProcessorProbabilisticSamplerCountSpansProcessed(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_processor_probabilistic_sampler_count_spans_processed",
Description: "Count of spans that were sampled or not [Development]",
Unit: "1",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_processor_probabilistic_sampler_count_spans_processed")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
func AssertEqualProcessorProbabilisticSamplerCountTracesSampled(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) {
want := metricdata.Metrics{
Name: "otelcol_processor_probabilistic_sampler_count_traces_sampled",
Description: "[DEPRECATED] Count of spans that were sampled or not [Deprecated]",
Unit: "1",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: dps,
},
}
got, err := tt.GetMetric("otelcol_processor_probabilistic_sampler_count_traces_sampled")
require.NoError(t, err)
metricdatatest.AssertEqual(t, want, got, opts...)
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package probabilisticsamplerprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/probabilisticsamplerprocessor"
import (
"context"
"errors"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/processorhelper"
"go.uber.org/zap"
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/sampling"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/probabilisticsamplerprocessor/internal/metadata"
)
type logsProcessor struct {
sampler dataSampler
samplingPriority string
precision int
failClosed bool
logger *zap.Logger
telemetryBuilder *metadata.TelemetryBuilder
}
type recordCarrier struct {
record plog.LogRecord
parsed struct {
tvalue string
threshold sampling.Threshold
rvalue string
randomness sampling.Randomness
}
}
var _ samplingCarrier = &recordCarrier{}
func (rc *recordCarrier) get(key string) string {
val, ok := rc.record.Attributes().Get(key)
if !ok || val.Type() != pcommon.ValueTypeStr {
return ""
}
return val.Str()
}
func newLogRecordCarrier(l plog.LogRecord) (samplingCarrier, error) {
var ret error
carrier := &recordCarrier{
record: l,
}
if tvalue := carrier.get("sampling.threshold"); tvalue != "" {
th, err := sampling.TValueToThreshold(tvalue)
if err != nil {
ret = errors.Join(err, ret)
} else {
carrier.parsed.tvalue = tvalue
carrier.parsed.threshold = th
}
}
if rvalue := carrier.get("sampling.randomness"); rvalue != "" {
rnd, err := sampling.RValueToRandomness(rvalue)
if err != nil {
ret = errors.Join(err, ret)
} else {
carrier.parsed.rvalue = rvalue
carrier.parsed.randomness = rnd
}
}
return carrier, ret
}
func (rc *recordCarrier) threshold() (sampling.Threshold, bool) {
return rc.parsed.threshold, rc.parsed.tvalue != ""
}
func (rc *recordCarrier) explicitRandomness() (randomnessNamer, bool) {
if rc.parsed.rvalue == "" {
return newMissingRandomnessMethod(), false
}
return newSamplingRandomnessMethod(rc.parsed.randomness), true
}
func (rc *recordCarrier) updateThreshold(th sampling.Threshold) error {
exist, has := rc.threshold()
if has && sampling.ThresholdLessThan(th, exist) {
return sampling.ErrInconsistentSampling
}
rc.record.Attributes().PutStr("sampling.threshold", th.TValue())
return nil
}
func (rc *recordCarrier) setExplicitRandomness(rnd randomnessNamer) {
rc.parsed.randomness = rnd.randomness()
rc.parsed.rvalue = rnd.randomness().RValue()
rc.record.Attributes().PutStr("sampling.randomness", rnd.randomness().RValue())
}
func (rc *recordCarrier) clearThreshold() {
rc.parsed.threshold = sampling.NeverSampleThreshold
rc.parsed.tvalue = ""
rc.record.Attributes().Remove("sampling.threshold")
}
func (*recordCarrier) reserialize() error {
return nil
}
func (*neverSampler) randomnessFromLogRecord(logRec plog.LogRecord) (randomnessNamer, samplingCarrier, error) {
// We return a fake randomness value, since it will not be used.
// This avoids a consistency check error for missing randomness.
lrc, err := newLogRecordCarrier(logRec)
return newSamplingPriorityMethod(sampling.AllProbabilitiesRandomness), lrc, err
}
// randomnessFromLogRecord (hashingSampler) uses a hash function over
// the TraceID or logs attribute source.
func (th *hashingSampler) randomnessFromLogRecord(logRec plog.LogRecord) (randomnessNamer, samplingCarrier, error) {
rnd := newMissingRandomnessMethod()
lrc, err := newLogRecordCarrier(logRec)
if th.logsTraceIDEnabled {
value := logRec.TraceID()
if !value.IsEmpty() {
rnd = newTraceIDHashingMethod(randomnessFromBytes(value[:], th.hashSeed))
}
}
if isMissing(rnd) && th.logsRandomnessSourceAttribute != "" {
if value, ok := logRec.Attributes().Get(th.logsRandomnessSourceAttribute); ok {
by := getBytesFromValue(value)
if len(by) > 0 {
rnd = newAttributeHashingMethod(
th.logsRandomnessSourceAttribute,
randomnessFromBytes(by, th.hashSeed),
)
}
}
}
if err != nil {
// The sampling.randomness or sampling.threshold attributes
// had a parse error, in this case.
lrc = nil
} else if _, hasRnd := lrc.explicitRandomness(); hasRnd {
// If the log record contains a randomness value, do not update.
err = ErrRandomnessInUse
lrc = nil
} else if _, hasTh := lrc.threshold(); hasTh {
// If the log record contains a threshold value, do not update.
err = ErrThresholdInUse
lrc = nil
} else if !isMissing(rnd) {
// When no sampling information is already present and we have
// calculated new randomness, add it to the record.
lrc.setExplicitRandomness(rnd)
}
return rnd, lrc, err
}
// randomnessFromLogRecord (hashingSampler) uses OTEP 235 semantic
// conventions basing its decision only on the TraceID.
func (*consistentTracestateCommon) randomnessFromLogRecord(logRec plog.LogRecord) (randomnessNamer, samplingCarrier, error) {
lrc, err := newLogRecordCarrier(logRec)
rnd := newMissingRandomnessMethod()
if err != nil {
// Parse error in sampling.randomness or sampling.threshold
lrc = nil
} else if rv, hasRnd := lrc.explicitRandomness(); hasRnd {
rnd = rv
} else if tid := logRec.TraceID(); !tid.IsEmpty() {
rnd = newTraceIDW3CSpecMethod(sampling.TraceIDToRandomness(tid))
}
return rnd, lrc, err
}
// newLogsProcessor returns a processor.LogsProcessor that will perform head sampling according to the given
// configuration.
func newLogsProcessor(ctx context.Context, set processor.Settings, nextConsumer consumer.Logs, cfg *Config) (processor.Logs, error) {
telemetryBuilder, err := metadata.NewTelemetryBuilder(set.TelemetrySettings)
if err != nil {
return nil, err
}
lsp := &logsProcessor{
sampler: makeSampler(cfg, true),
samplingPriority: cfg.SamplingPriority,
precision: cfg.SamplingPrecision,
failClosed: cfg.FailClosed,
logger: set.Logger,
telemetryBuilder: telemetryBuilder,
}
return processorhelper.NewLogs(
ctx,
set,
cfg,
nextConsumer,
lsp.processLogs,
processorhelper.WithCapabilities(consumer.Capabilities{MutatesData: true}),
)
}
func (lsp *logsProcessor) processLogs(ctx context.Context, logsData plog.Logs) (plog.Logs, error) {
logsData.ResourceLogs().RemoveIf(func(rl plog.ResourceLogs) bool {
rl.ScopeLogs().RemoveIf(func(ill plog.ScopeLogs) bool {
ill.LogRecords().RemoveIf(func(l plog.LogRecord) bool {
return !commonShouldSampleLogic(
ctx,
l,
lsp.sampler,
lsp.failClosed,
lsp.sampler.randomnessFromLogRecord,
lsp.priorityFunc,
"logs sampler",
lsp.logger,
lsp.telemetryBuilder.ProcessorProbabilisticSamplerCountLogsSampled,
)
})
// Filter out empty ScopeLogs
return ill.LogRecords().Len() == 0
})
// Filter out empty ResourceLogs
return rl.ScopeLogs().Len() == 0
})
if logsData.ResourceLogs().Len() == 0 {
return logsData, processorhelper.ErrSkipProcessingData
}
return logsData, nil
}
func (lsp *logsProcessor) priorityFunc(logRec plog.LogRecord, rnd randomnessNamer, threshold sampling.Threshold) (randomnessNamer, sampling.Threshold) {
// Note: in logs, unlike traces, the sampling priority
// attribute is interpreted as a request to be sampled.
if lsp.samplingPriority != "" {
priorityThreshold, has := lsp.logRecordToPriorityThreshold(logRec)
if has {
threshold = priorityThreshold
rnd = newSamplingPriorityMethod(rnd.randomness()) // override policy name
}
}
return rnd, threshold
}
func (lsp *logsProcessor) logRecordToPriorityThreshold(logRec plog.LogRecord) (sampling.Threshold, bool) {
if localPriority, ok := logRec.Attributes().Get(lsp.samplingPriority); ok {
// Potentially raise the sampling probability to minProb
minProb := 0.0
switch localPriority.Type() {
case pcommon.ValueTypeDouble:
minProb = localPriority.Double() / 100.0
case pcommon.ValueTypeInt:
minProb = float64(localPriority.Int()) / 100.0
}
if minProb != 0 {
if th, err := sampling.ProbabilityToThresholdWithPrecision(minProb, lsp.precision); err == nil {
// The record has supplied a valid alternative sampling probability
return th, true
}
}
}
return sampling.NeverSampleThreshold, false
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package probabilisticsamplerprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/probabilisticsamplerprocessor"
import (
"context"
"errors"
"fmt"
"strconv"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
"go.uber.org/zap"
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/sampling"
)
const (
// These four can happen at runtime and be returned by
// randomnessFromXXX()
ErrInconsistentArrivingTValue samplerError = "inconsistent arriving threshold: item should not have been sampled"
ErrMissingRandomness samplerError = "missing randomness"
ErrRandomnessInUse samplerError = "item has sampling randomness, equalizing or proportional mode recommended"
ErrThresholdInUse samplerError = "item has sampling threshold, equalizing or proportional mode recommended"
)
const (
// Hashing method: The constants below help translate user friendly percentages
// to numbers direct used in sampling.
numHashBucketsLg2 = 14
numHashBuckets = 0x4000 // Using a power of 2 to avoid division.
bitMaskHashBuckets = numHashBuckets - 1
percentageScaleFactor = numHashBuckets / 100.0
)
// samplerErrors are conditions reported by the sampler that are somewhat
// ordinary and should log as info-level.
type samplerError string
var _ error = samplerError("")
func (s samplerError) Error() string {
return string(s)
}
// SamplerMode determines which of several modes is used for the
// sampling decision.
type SamplerMode string
const (
// HashSeed applies the hash/fnv hash function originally used in this component.
HashSeed SamplerMode = "hash_seed"
// Equalizing uses OpenTelemetry consistent probability
// sampling information (OTEP 235), applies an absolute
// threshold to equalize incoming sampling probabilities.
Equalizing SamplerMode = "equalizing"
// Proportional uses OpenTelemetry consistent probability
// sampling information (OTEP 235), multiplies incoming
// sampling probabilities.
Proportional SamplerMode = "proportional"
// defaultHashSeed is applied when the mode is unset.
defaultMode SamplerMode = HashSeed
// modeUnset indicates the user has not configured the mode.
modeUnset SamplerMode = ""
)
type randomnessNamer interface {
randomness() sampling.Randomness
policyName() string
}
type randomnessMethod sampling.Randomness
func (rm randomnessMethod) randomness() sampling.Randomness {
return sampling.Randomness(rm)
}
type (
traceIDHashingMethod struct{ randomnessMethod }
traceIDW3CSpecMethod struct{ randomnessMethod }
samplingRandomnessMethod struct{ randomnessMethod }
samplingPriorityMethod struct{ randomnessMethod }
)
type missingRandomnessMethod struct{}
func (missingRandomnessMethod) randomness() sampling.Randomness {
return sampling.AllProbabilitiesRandomness
}
func (missingRandomnessMethod) policyName() string {
return "missing_randomness"
}
type attributeHashingMethod struct {
randomnessMethod
attribute string
}
func (am attributeHashingMethod) policyName() string {
return am.attribute
}
func (traceIDHashingMethod) policyName() string {
return "trace_id_hash"
}
func (samplingRandomnessMethod) policyName() string {
return "sampling_randomness"
}
func (traceIDW3CSpecMethod) policyName() string {
return "trace_id_w3c"
}
func (samplingPriorityMethod) policyName() string {
return "sampling_priority"
}
var (
_ randomnessNamer = missingRandomnessMethod{}
_ randomnessNamer = traceIDHashingMethod{}
_ randomnessNamer = traceIDW3CSpecMethod{}
_ randomnessNamer = samplingRandomnessMethod{}
_ randomnessNamer = samplingPriorityMethod{}
)
func newMissingRandomnessMethod() randomnessNamer {
return missingRandomnessMethod{}
}
func isMissing(rnd randomnessNamer) bool {
_, ok := rnd.(missingRandomnessMethod)
return ok
}
func newSamplingRandomnessMethod(rnd sampling.Randomness) randomnessNamer {
return samplingRandomnessMethod{randomnessMethod(rnd)}
}
func newTraceIDW3CSpecMethod(rnd sampling.Randomness) randomnessNamer {
return traceIDW3CSpecMethod{randomnessMethod(rnd)}
}
func newTraceIDHashingMethod(rnd sampling.Randomness) randomnessNamer {
return traceIDHashingMethod{randomnessMethod(rnd)}
}
func newSamplingPriorityMethod(rnd sampling.Randomness) randomnessNamer {
return samplingPriorityMethod{randomnessMethod(rnd)}
}
func newAttributeHashingMethod(attribute string, rnd sampling.Randomness) randomnessNamer {
return attributeHashingMethod{
randomnessMethod: randomnessMethod(rnd),
attribute: attribute,
}
}
// samplingCarrier conveys information about the underlying data item
// (whether span or log record) through the sampling decision.
type samplingCarrier interface {
// explicitRandomness returns a randomness value and a boolean
// indicating whether the item had sampling randomness
// explicitly set.
explicitRandomness() (randomnessNamer, bool)
// setExplicitRandomness updates the item with the signal-specific
// encoding for an explicit randomness value.
setExplicitRandomness(randomnessNamer)
// clearThreshold unsets a sampling threshold, which is used to
// clear information that breaks the expected sampling invariants
// described in OTEP 235.
clearThreshold()
// threshold returns a sampling threshold and a boolean
// indicating whether the item had sampling threshold
// explicitly set.
threshold() (sampling.Threshold, bool)
// updateThreshold modifies the sampling threshold. This
// returns an error if the updated sampling threshold has a
// lower adjusted account; the only permissible updates raise
// adjusted count (i.e., reduce sampling probability).
updateThreshold(sampling.Threshold) error
// reserialize re-encodes the updated sampling information
// into the item, if necessary. For Spans, this re-encodes
// the tracestate. This is a no-op for logs records.
reserialize() error
}
// dataSampler implements the logic of a sampling mode.
type dataSampler interface {
// decide reports the result based on a probabilistic decision.
decide(carrier samplingCarrier) sampling.Threshold
// randomnessFromSpan extracts randomness and returns a carrier specific to traces data.
randomnessFromSpan(s ptrace.Span) (randomness randomnessNamer, carrier samplingCarrier, err error)
// randomnessFromLogRecord extracts randomness and returns a carrier specific to logs data.
randomnessFromLogRecord(s plog.LogRecord) (randomness randomnessNamer, carrier samplingCarrier, err error)
}
func (sm *SamplerMode) UnmarshalText(in []byte) error {
switch mode := SamplerMode(in); mode {
case HashSeed,
Equalizing,
Proportional,
modeUnset:
*sm = mode
return nil
default:
return fmt.Errorf("unsupported sampler mode %q", mode)
}
}
// hashingSampler is the original hash-based calculation. It is an
// equalizing sampler with randomness calculation that matches the
// original implementation. This hash-based implementation is limited
// to 14 bits of precision.
type hashingSampler struct {
hashSeed uint32
tvalueThreshold sampling.Threshold
// Logs only: name of attribute to obtain randomness
logsRandomnessSourceAttribute string
// Logs only: whether traceID is being used
logsTraceIDEnabled bool
}
func (th *hashingSampler) decide(_ samplingCarrier) sampling.Threshold {
return th.tvalueThreshold
}
// consistentTracestateCommon contains the common aspects of the
// Proportional and Equalizing sampler modes. These samplers sample
// using the TraceID and do not support use of logs source attribute.
type consistentTracestateCommon struct{}
// neverSampler always decides false.
type neverSampler struct{}
func (*neverSampler) decide(_ samplingCarrier) sampling.Threshold {
return sampling.NeverSampleThreshold
}
// equalizingSampler raises thresholds up to a fixed value.
type equalizingSampler struct {
// TraceID-randomness-based calculation
tvalueThreshold sampling.Threshold
consistentTracestateCommon
}
func (te *equalizingSampler) decide(carrier samplingCarrier) sampling.Threshold {
if tv, has := carrier.threshold(); has && sampling.ThresholdLessThan(te.tvalueThreshold, tv) {
return tv
}
return te.tvalueThreshold
}
// proportionalSampler raises thresholds relative to incoming value.
type proportionalSampler struct {
// ratio in the range [2**-56, 1]
ratio float64
// precision is the precision in number of hex digits
precision int
consistentTracestateCommon
}
func (tp *proportionalSampler) decide(carrier samplingCarrier) sampling.Threshold {
incoming := 1.0
if tv, has := carrier.threshold(); has {
incoming = tv.Probability()
}
// There is a potential here for the product probability to
// underflow, which is checked here.
threshold, err := sampling.ProbabilityToThresholdWithPrecision(incoming*tp.ratio, tp.precision)
// Check the only known error condition.
if errors.Is(err, sampling.ErrProbabilityRange) {
// Considered valid, a case where the sampling probability
// has fallen below the minimum supported value and simply
// becomes unsampled.
return sampling.NeverSampleThreshold
}
return threshold
}
func getBytesFromValue(value pcommon.Value) []byte {
if value.Type() == pcommon.ValueTypeBytes {
return value.Bytes().AsRaw()
}
return []byte(value.AsString())
}
func randomnessFromBytes(b []byte, hashSeed uint32) sampling.Randomness {
hashed32 := computeHash(b, hashSeed)
hashed := uint64(hashed32 & bitMaskHashBuckets)
// Ordinarily, hashed is compared against an acceptance
// threshold i.e., sampled when hashed < scaledSamplerate,
// which has the form R < T with T in [1, 2^14] and
// R in [0, 2^14-1].
//
// Here, modify R to R' and T to T', so that the sampling
// equation has identical form to the specification, i.e., T'
// <= R', using:
//
// T' = numHashBuckets-T
// R' = numHashBuckets-1-R
//
// As a result, R' has the correct most-significant 14 bits to
// use in an R-value.
rprime14 := numHashBuckets - 1 - hashed
// There are 18 unused bits from the FNV hash function.
unused18 := uint64(hashed32 >> (32 - numHashBucketsLg2))
mixed28 := unused18 ^ (unused18 << 10)
// The 56 bit quantity here consists of, most- to least-significant:
// - 14 bits: R' = numHashBuckets - 1 - hashed
// - 28 bits: mixture of unused 18 bits
// - 14 bits: original `hashed`.
rnd56 := (rprime14 << 42) | (mixed28 << 14) | hashed
// Note: by construction:
// - OTel samplers make the same probabilistic decision with this r-value,
// - only 14 out of 56 bits are used in the sampling decision,
// - there are only 32 actual random bits.
rnd, _ := sampling.UnsignedToRandomness(rnd56)
return rnd
}
func consistencyCheck(rnd randomnessNamer, carrier samplingCarrier) error {
// Without randomness, do not check the threshold.
if isMissing(rnd) {
return ErrMissingRandomness
}
// When the carrier is nil, it means there was trouble parsing the
// tracestate or trace-related attributes. In this case, skip the
// consistency check.
if carrier == nil {
return nil
}
// Consistency check: if the TraceID is out of range, the
// TValue is a lie. If inconsistent, clear it and return an error.
if tv, has := carrier.threshold(); has {
if !tv.ShouldSample(rnd.randomness()) {
// In case we fail open, the threshold is cleared as
// recommended in the OTel spec.
carrier.clearThreshold()
return ErrInconsistentArrivingTValue
}
}
return nil
}
// makeSample constructs a sampler. There are no errors, as the only
// potential error, out-of-range probability, is corrected automatically
// according to the README, which allows percents >100 to equal 100%.
//
// Extending this logic, we round very small probabilities up to the
// minimum supported value(s) which varies according to sampler mode.
func makeSampler(cfg *Config, isLogs bool) dataSampler {
// README allows percents >100 to equal 100%.
pct := cfg.SamplingPercentage
if pct > 100 {
pct = 100
}
mode := cfg.Mode
if mode == modeUnset {
// Reasons to choose the legacy behavior include:
// (a) having set the hash seed
// (b) logs signal w/o trace ID source
if cfg.HashSeed != 0 || (isLogs && cfg.AttributeSource != traceIDAttributeSource) {
mode = HashSeed
} else {
mode = defaultMode
}
}
if pct == 0 {
return &neverSampler{}
}
// Note: Convert to float64 before dividing by 100, otherwise loss of precision.
// If the probability is too small, round it up to the minimum.
ratio := float64(pct) / 100
// Like the pct > 100 test above, but for values too small to
// express in 14 bits of precision.
if ratio < sampling.MinSamplingProbability {
ratio = sampling.MinSamplingProbability
}
switch mode {
case Equalizing:
// The error case below is ignored, we have rounded the probability so
// that it is in-range
threshold, _ := sampling.ProbabilityToThresholdWithPrecision(ratio, cfg.SamplingPrecision)
return &equalizingSampler{
tvalueThreshold: threshold,
}
case Proportional:
return &proportionalSampler{
ratio: ratio,
precision: cfg.SamplingPrecision,
}
default: // i.e., HashSeed
// Note: the original hash function used in this code
// is preserved to ensure consistency across updates.
//
// uint32(pct * percentageScaleFactor)
//
// (a) carried out the multiplication in 32-bit precision
// (b) rounded to zero instead of nearest.
scaledSamplerate := uint32(pct * percentageScaleFactor)
if scaledSamplerate == 0 {
return &neverSampler{}
}
// Convert the accept threshold to a reject threshold,
// then shift it into 56-bit value.
reject := numHashBuckets - scaledSamplerate
reject56 := uint64(reject) << 42
threshold, _ := sampling.UnsignedToThreshold(reject56)
return &hashingSampler{
tvalueThreshold: threshold,
hashSeed: cfg.HashSeed,
// Logs specific:
logsTraceIDEnabled: cfg.AttributeSource == traceIDAttributeSource,
logsRandomnessSourceAttribute: cfg.FromAttribute,
}
}
}
// randFunc returns randomness (w/ named policy), a carrier, and the error.
type randFunc[T any] func(T) (randomnessNamer, samplingCarrier, error)
// priorityFunc makes changes resulting from sampling priority.
type priorityFunc[T any] func(T, randomnessNamer, sampling.Threshold) (randomnessNamer, sampling.Threshold)
// commonShouldSampleLogic implements sampling on a per-item basis
// independent of the signal type, as embodied in the functional
// parameters:
func commonShouldSampleLogic[T any](
ctx context.Context,
item T,
sampler dataSampler,
failClosed bool,
randFunc randFunc[T],
priorityFunc priorityFunc[T],
description string,
logger *zap.Logger,
counters ...metric.Int64Counter,
) bool {
rnd, carrier, err := randFunc(item)
if err == nil {
err = consistencyCheck(rnd, carrier)
}
var threshold sampling.Threshold
if err != nil {
if _, ok := errors.AsType[samplerError](err); ok {
logger.Debug(description, zap.Error(err))
} else {
logger.Info(description, zap.Error(err))
}
if failClosed {
threshold = sampling.NeverSampleThreshold
} else {
threshold = sampling.AlwaysSampleThreshold
}
} else {
threshold = sampler.decide(carrier)
}
rnd, threshold = priorityFunc(item, rnd, threshold)
sampled := threshold.ShouldSample(rnd.randomness())
if sampled && carrier != nil {
// Note: updateThreshold limits loss of adjusted count, by
// preventing the threshold from being lowered, only allowing
// probability to fall and never to rise.
if err := carrier.updateThreshold(threshold); err != nil {
if errors.Is(err, sampling.ErrInconsistentSampling) {
// This is working-as-intended. You can't lower
// the threshold, it's illogical.
logger.Debug(description, zap.Error(err))
} else {
logger.Info(description, zap.Error(err))
}
}
if err := carrier.reserialize(); err != nil {
logger.Info(description, zap.Error(err))
}
}
for _, counter := range counters {
counter.Add(ctx, 1, metric.WithAttributes(
attribute.String("policy", rnd.policyName()),
attribute.String("sampled", strconv.FormatBool(sampled)),
))
}
return sampled
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package probabilisticsamplerprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/probabilisticsamplerprocessor"
import (
"context"
"strconv"
"strings"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/processorhelper"
"go.uber.org/zap"
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/sampling"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/probabilisticsamplerprocessor/internal/metadata"
)
// samplingPriority has the semantic result of parsing the "sampling.priority"
// attribute per OpenTracing semantic conventions.
type samplingPriority int
const (
// deferDecision means that the decision if a span will be "sampled" (ie.:
// forwarded by the collector) is made by hashing the trace ID according
// to the configured sampling rate.
deferDecision samplingPriority = iota
// mustSampleSpan indicates that the span had a "sampling.priority" attribute
// greater than zero and it is going to be sampled, ie.: forwarded by the
// collector.
mustSampleSpan
// doNotSampleSpan indicates that the span had a "sampling.priority" attribute
// equal zero and it is NOT going to be sampled, ie.: it won't be forwarded
// by the collector.
doNotSampleSpan
)
type traceProcessor struct {
sampler dataSampler
failClosed bool
logger *zap.Logger
telemetryBuilder *metadata.TelemetryBuilder
}
// tracestateCarrier conveys information about sampled spans between
// the call to parse incoming randomness/threshold and the call to
// decide.
type tracestateCarrier struct {
span ptrace.Span
sampling.W3CTraceState
}
var _ samplingCarrier = &tracestateCarrier{}
func newTracestateCarrier(s ptrace.Span) (samplingCarrier, error) {
var err error
tsc := &tracestateCarrier{
span: s,
}
tsc.W3CTraceState, err = sampling.NewW3CTraceState(s.TraceState().AsRaw())
return tsc, err
}
func (tc *tracestateCarrier) threshold() (sampling.Threshold, bool) {
return tc.W3CTraceState.OTelValue().TValueThreshold()
}
func (tc *tracestateCarrier) explicitRandomness() (randomnessNamer, bool) {
rnd, ok := tc.W3CTraceState.OTelValue().RValueRandomness()
if !ok {
return newMissingRandomnessMethod(), false
}
return newSamplingRandomnessMethod(rnd), true
}
func (tc *tracestateCarrier) updateThreshold(th sampling.Threshold) error {
return tc.W3CTraceState.OTelValue().UpdateTValueWithSampling(th)
}
func (tc *tracestateCarrier) setExplicitRandomness(rnd randomnessNamer) {
tc.W3CTraceState.OTelValue().SetRValue(rnd.randomness())
}
func (tc *tracestateCarrier) clearThreshold() {
tc.W3CTraceState.OTelValue().ClearTValue()
}
func (tc *tracestateCarrier) reserialize() error {
var w strings.Builder
err := tc.Serialize(&w)
if err == nil {
tc.span.TraceState().FromRaw(w.String())
}
return err
}
// newTracesProcessor returns a processor.TracesProcessor that will
// perform intermediate span sampling according to the given
// configuration.
func newTracesProcessor(ctx context.Context, set processor.Settings, cfg *Config, nextConsumer consumer.Traces) (processor.Traces, error) {
telemetryBuilder, err := metadata.NewTelemetryBuilder(set.TelemetrySettings)
if err != nil {
return nil, err
}
tp := &traceProcessor{
sampler: makeSampler(cfg, false),
failClosed: cfg.FailClosed,
logger: set.Logger,
telemetryBuilder: telemetryBuilder,
}
return processorhelper.NewTraces(
ctx,
set,
cfg,
nextConsumer,
tp.processTraces,
processorhelper.WithCapabilities(consumer.Capabilities{MutatesData: true}),
)
}
func (th *hashingSampler) randomnessFromSpan(s ptrace.Span) (randomnessNamer, samplingCarrier, error) {
tid := s.TraceID()
tsc, err := newTracestateCarrier(s)
rnd := newMissingRandomnessMethod()
if !tid.IsEmpty() {
rnd = newTraceIDHashingMethod(randomnessFromBytes(tid[:], th.hashSeed))
}
// If the tracestate contains a proper R-value or T-value, we
// have to leave it alone. The user should not be using this
// sampler mode if they are using specified forms of consistent
// sampling in OTel.
if err != nil {
return rnd, nil, err
} else if _, has := tsc.explicitRandomness(); has {
err = ErrRandomnessInUse
tsc = nil
} else if _, has := tsc.threshold(); has {
err = ErrThresholdInUse
tsc = nil
} else {
// When no sampling information is present, add a
// Randomness value.
tsc.setExplicitRandomness(rnd)
}
return rnd, tsc, err
}
func (*consistentTracestateCommon) randomnessFromSpan(s ptrace.Span) (randomnessNamer, samplingCarrier, error) {
rnd := newMissingRandomnessMethod()
tsc, err := newTracestateCarrier(s)
if err != nil {
tsc = nil
} else if rv, has := tsc.explicitRandomness(); has {
// When the tracestate is OK and has r-value, use it.
rnd = rv
} else if !s.TraceID().IsEmpty() {
rnd = newTraceIDW3CSpecMethod(sampling.TraceIDToRandomness(s.TraceID()))
}
return rnd, tsc, err
}
func (*neverSampler) randomnessFromSpan(span ptrace.Span) (randomnessNamer, samplingCarrier, error) {
// We return a fake randomness value, since it will not be used.
// This avoids a consistency check error for missing randomness.
tsc, err := newTracestateCarrier(span)
return newSamplingPriorityMethod(sampling.AllProbabilitiesRandomness), tsc, err
}
func (tp *traceProcessor) processTraces(ctx context.Context, td ptrace.Traces) (ptrace.Traces, error) {
td.ResourceSpans().RemoveIf(func(rs ptrace.ResourceSpans) bool {
rs.ScopeSpans().RemoveIf(func(ils ptrace.ScopeSpans) bool {
ils.Spans().RemoveIf(func(s ptrace.Span) bool {
return !commonShouldSampleLogic(
ctx,
s,
tp.sampler,
tp.failClosed,
tp.sampler.randomnessFromSpan,
tp.priorityFunc,
"traces sampler",
tp.logger,
tp.telemetryBuilder.ProcessorProbabilisticSamplerCountTracesSampled,
tp.telemetryBuilder.ProcessorProbabilisticSamplerCountSpansProcessed,
)
})
// Filter out empty ScopeMetrics
return ils.Spans().Len() == 0
})
// Filter out empty ResourceMetrics
return rs.ScopeSpans().Len() == 0
})
if td.ResourceSpans().Len() == 0 {
return td, processorhelper.ErrSkipProcessingData
}
return td, nil
}
func (*traceProcessor) priorityFunc(s ptrace.Span, rnd randomnessNamer, threshold sampling.Threshold) (randomnessNamer, sampling.Threshold) {
switch parseSpanSamplingPriority(s) {
case doNotSampleSpan:
// OpenTracing mentions this as a "hint". We take a stronger
// approach and do not sample the span since some may use it to
// remove specific spans from traces.
threshold = sampling.NeverSampleThreshold
rnd = newSamplingPriorityMethod(rnd.randomness()) // override policy name
case mustSampleSpan:
threshold = sampling.AlwaysSampleThreshold
rnd = newSamplingPriorityMethod(rnd.randomness()) // override policy name
case deferDecision:
// Note that the logs processor has very different logic here,
// but that in tracing the priority can only force to never or
// always.
}
return rnd, threshold
}
// parseSpanSamplingPriority checks if the span has the "sampling.priority" tag to
// decide if the span should be sampled or not. The usage of the tag follows the
// OpenTracing semantic tags:
// https://github.com/opentracing/specification/blob/main/semantic_conventions.md#span-tags-table
func parseSpanSamplingPriority(span ptrace.Span) samplingPriority {
attribMap := span.Attributes()
if attribMap.Len() <= 0 {
return deferDecision
}
samplingPriorityAttrib, ok := attribMap.Get("sampling.priority")
if !ok {
return deferDecision
}
// By default defer the decision.
decision := deferDecision
// Try check for different types since there are various client libraries
// using different conventions regarding "sampling.priority". Besides the
// client libraries it is also possible that the type was lost in translation
// between different formats.
switch samplingPriorityAttrib.Type() {
case pcommon.ValueTypeInt:
value := samplingPriorityAttrib.Int()
if value == 0 {
decision = doNotSampleSpan
} else if value > 0 {
decision = mustSampleSpan
}
case pcommon.ValueTypeDouble:
value := samplingPriorityAttrib.Double()
if value == 0.0 {
decision = doNotSampleSpan
} else if value > 0.0 {
decision = mustSampleSpan
}
case pcommon.ValueTypeStr:
attribVal := samplingPriorityAttrib.Str()
if value, err := strconv.ParseFloat(attribVal, 64); err == nil {
if value == 0.0 {
decision = doNotSampleSpan
} else if value > 0.0 {
decision = mustSampleSpan
}
}
}
return decision
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sumologicprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/sumologicprocessor"
import (
"fmt"
"strings"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
)
// aggregateAttributesProcessor
type aggregateAttributesProcessor struct {
aggregations []*aggregation
}
type aggregation struct {
attribute string
prefixes []string
}
func newAggregateAttributesProcessor(config []AggregationPair) *aggregateAttributesProcessor {
aggregations := []*aggregation{}
for i := range config {
pair := &aggregation{
attribute: config[i].Attribute,
prefixes: config[i].Prefixes,
}
aggregations = append(aggregations, pair)
}
return &aggregateAttributesProcessor{aggregations: aggregations}
}
func (proc *aggregateAttributesProcessor) processLogs(logs plog.Logs) error {
for i := 0; i < logs.ResourceLogs().Len(); i++ {
resourceLogs := logs.ResourceLogs().At(i)
err := proc.processAttributes(resourceLogs.Resource().Attributes())
if err != nil {
return err
}
for j := 0; j < resourceLogs.ScopeLogs().Len(); j++ {
scopeLogs := resourceLogs.ScopeLogs().At(j)
for k := 0; k < scopeLogs.LogRecords().Len(); k++ {
err := proc.processAttributes(scopeLogs.LogRecords().At(k).Attributes())
if err != nil {
return err
}
}
}
}
return nil
}
func (proc *aggregateAttributesProcessor) processMetrics(metrics pmetric.Metrics) error {
for i := 0; i < metrics.ResourceMetrics().Len(); i++ {
resourceMetrics := metrics.ResourceMetrics().At(i)
err := proc.processAttributes(resourceMetrics.Resource().Attributes())
if err != nil {
return err
}
for j := 0; j < resourceMetrics.ScopeMetrics().Len(); j++ {
scopeMetrics := resourceMetrics.ScopeMetrics().At(j)
for k := 0; k < scopeMetrics.Metrics().Len(); k++ {
err := processMetricLevelAttributes(proc, scopeMetrics.Metrics().At(k))
if err != nil {
return err
}
}
}
}
return nil
}
func (proc *aggregateAttributesProcessor) processTraces(traces ptrace.Traces) error {
for i := 0; i < traces.ResourceSpans().Len(); i++ {
resourceSpans := traces.ResourceSpans().At(i)
err := proc.processAttributes(resourceSpans.Resource().Attributes())
if err != nil {
return err
}
for j := 0; j < resourceSpans.ScopeSpans().Len(); j++ {
scopeSpans := resourceSpans.ScopeSpans().At(j)
for k := 0; k < scopeSpans.Spans().Len(); k++ {
err := proc.processAttributes(scopeSpans.Spans().At(k).Attributes())
if err != nil {
return err
}
}
}
}
return nil
}
func (proc *aggregateAttributesProcessor) isEnabled() bool {
return len(proc.aggregations) != 0
}
func (*aggregateAttributesProcessor) ConfigPropertyName() string {
return "aggregate_attributes"
}
func (proc *aggregateAttributesProcessor) processAttributes(attributes pcommon.Map) error {
for i := 0; i < len(proc.aggregations); i++ {
curr := proc.aggregations[i]
names := []string{}
attrs := []pcommon.Value{}
for j := 0; j < len(curr.prefixes); j++ {
prefix := curr.prefixes[j]
// Create a new map. Unused keys will be added here,
// so we can check them against other prefixes.
newMap := pcommon.NewMap()
newMap.EnsureCapacity(attributes.Len())
for key, value := range attributes.All() {
trimmedKey, ok := strings.CutPrefix(key, prefix)
if ok {
// TODO: Potential name conflict to resolve, eg.:
// pod_* matches pod_foo
// pod2_* matches pod2_foo
// both will be renamed to foo
// ref: https://github.com/SumoLogic/sumologic-otel-collector/issues/1263
names = append(names, trimmedKey)
val := pcommon.NewValueEmpty()
value.CopyTo(val)
attrs = append(attrs, val)
} else {
value.CopyTo(newMap.PutEmpty(key))
}
}
newMap.CopyTo(attributes)
}
if len(names) != len(attrs) {
return fmt.Errorf(
"internal error: number of values does not equal the number of keys; len(keys) = %d, len(values) = %d",
len(names),
len(attrs),
)
}
// Add a new attribute only if there's anything that should be put under it.
if len(names) > 0 {
aggregated := attributes.PutEmptyMap(curr.attribute)
for j := 0; j < len(names); j++ {
attrs[j].CopyTo(aggregated.PutEmpty(names[j]))
}
}
}
return nil
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sumologicprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/sumologicprocessor"
import (
"fmt"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/pmetric"
)
// This file contains some common functionalities for subprocessors that modify attributes (represented by pcommon.Map)
type attributesProcessor interface {
processAttributes(pcommon.Map) error
}
func processMetricLevelAttributes(proc attributesProcessor, metric pmetric.Metric) error {
switch metric.Type() {
case pmetric.MetricTypeEmpty:
return nil
case pmetric.MetricTypeSum:
dp := metric.Sum().DataPoints()
for i := 0; i < dp.Len(); i++ {
err := proc.processAttributes(dp.At(i).Attributes())
if err != nil {
return err
}
}
return nil
case pmetric.MetricTypeGauge:
dp := metric.Gauge().DataPoints()
for i := 0; i < dp.Len(); i++ {
err := proc.processAttributes(dp.At(i).Attributes())
if err != nil {
return err
}
}
return nil
case pmetric.MetricTypeHistogram:
dp := metric.Histogram().DataPoints()
for i := 0; i < dp.Len(); i++ {
err := proc.processAttributes(dp.At(i).Attributes())
if err != nil {
return err
}
}
return nil
case pmetric.MetricTypeExponentialHistogram:
dp := metric.ExponentialHistogram().DataPoints()
for i := 0; i < dp.Len(); i++ {
err := proc.processAttributes(dp.At(i).Attributes())
if err != nil {
return err
}
}
return nil
case pmetric.MetricTypeSummary:
dp := metric.Summary().DataPoints()
for i := 0; i < dp.Len(); i++ {
err := proc.processAttributes(dp.At(i).Attributes())
if err != nil {
return err
}
}
return nil
}
return fmt.Errorf("unknown metric type: %s", metric.Type().String())
}
func mapToPcommonMap(m map[string]pcommon.Value) pcommon.Map {
attrs := pcommon.NewMap()
for k, v := range m {
v.CopyTo(attrs.PutEmpty(k))
}
return attrs
}
func mapToPcommonValue(m map[string]pcommon.Value) pcommon.Value {
attrs := pcommon.NewValueMap()
for k, v := range m {
v.CopyTo(attrs.Map().PutEmpty(k))
}
return attrs
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sumologicprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/sumologicprocessor"
import (
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
conventions "go.opentelemetry.io/otel/semconv/v1.43.0"
)
// cloudNamespaceProcessor adds the `cloud.namespace` resource attribute to logs, metrics and traces.
type cloudNamespaceProcessor struct {
addCloudNamespace bool
}
const (
cloudNamespaceAttributeName = "cloud.namespace"
cloudNamespaceAwsEc2 = "aws/ec2"
cloudNamespaceAwsEcs = "ecs"
cloudNamespaceAwsBeanstalk = "ElasticBeanstalk"
)
func newCloudNamespaceProcessor(addCloudNamespace bool) *cloudNamespaceProcessor {
return &cloudNamespaceProcessor{
addCloudNamespace: addCloudNamespace,
}
}
func (*cloudNamespaceProcessor) processLogs(logs plog.Logs) error {
for i := 0; i < logs.ResourceLogs().Len(); i++ {
addCloudNamespaceAttribute(logs.ResourceLogs().At(i).Resource().Attributes())
}
return nil
}
func (*cloudNamespaceProcessor) processMetrics(metrics pmetric.Metrics) error {
for i := 0; i < metrics.ResourceMetrics().Len(); i++ {
addCloudNamespaceAttribute(metrics.ResourceMetrics().At(i).Resource().Attributes())
}
return nil
}
func (*cloudNamespaceProcessor) processTraces(traces ptrace.Traces) error {
for i := 0; i < traces.ResourceSpans().Len(); i++ {
addCloudNamespaceAttribute(traces.ResourceSpans().At(i).Resource().Attributes())
}
return nil
}
func (proc *cloudNamespaceProcessor) isEnabled() bool {
return proc.addCloudNamespace
}
func (*cloudNamespaceProcessor) ConfigPropertyName() string {
return "add_cloud_namespace"
}
// addCloudNamespaceAttribute adds the `cloud.namespace` attribute
// to a collection of attributes that already contains a `cloud.platform` attribute.
// It does not add the `cloud.namespace` attribute for all `cloud.platform` values,
// but only for a few specific ones - namely AWS EC2, AWS ECS, and AWS Elastic Beanstalk.
func addCloudNamespaceAttribute(attributes pcommon.Map) {
cloudPlatformAttributeValue, found := attributes.Get(string(conventions.CloudPlatformKey))
if !found {
return
}
switch cloudPlatformAttributeValue.Str() {
case conventions.CloudPlatformAWSEC2.Value.AsString():
attributes.PutStr(cloudNamespaceAttributeName, cloudNamespaceAwsEc2)
case conventions.CloudPlatformAWSECS.Value.AsString():
attributes.PutStr(cloudNamespaceAttributeName, cloudNamespaceAwsEcs)
case conventions.CloudPlatformAWSElasticBeanstalk.Value.AsString():
attributes.PutStr(cloudNamespaceAttributeName, cloudNamespaceAwsBeanstalk)
}
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sumologicprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/sumologicprocessor"
import (
"go.opentelemetry.io/collector/component"
)
type Config struct {
AddCloudNamespace bool `mapstructure:"add_cloud_namespace"`
TranslateAttributes bool `mapstructure:"translate_attributes"`
TranslateTelegrafAttributes bool `mapstructure:"translate_telegraf_attributes"`
NestAttributes NestingProcessorConfig `mapstructure:"nest_attributes"`
AggregateAttributes []AggregationPair `mapstructure:"aggregate_attributes"`
LogFieldsAttributes LogFieldAttributesConfig `mapstructure:"field_attributes"`
TranslateDockerMetrics bool `mapstructure:"translate_docker_metrics"`
}
type AggregationPair struct {
Attribute string `mapstructure:"attribute"`
Prefixes []string `mapstructure:"prefixes"`
}
const (
defaultAddCloudNamespace = true
defaultTranslateAttributes = true
defaultTranslateTelegrafAttributes = true
defaultTranslateDockerMetrics = false
// Nesting processor default config
defaultNestingEnabled = false
defaultNestingSeparator = "."
defaultNestingSquashSingleValues = false
defaultAddSeverityNumberAttribute = false
defaultAddSeverityTextAttribute = false
defaultAddSpanIDAttribute = false
defaultAddTraceIDAttribute = false
)
var _ component.Config = (*Config)(nil)
func defaultNestingInclude() []string {
return []string{}
}
func defaultNestingExclude() []string {
return []string{}
}
func defaultAggregateAttributes() []AggregationPair {
return []AggregationPair{}
}
func createDefaultConfig() component.Config {
return &Config{
AddCloudNamespace: defaultAddCloudNamespace,
TranslateAttributes: defaultTranslateAttributes,
TranslateTelegrafAttributes: defaultTranslateTelegrafAttributes,
NestAttributes: NestingProcessorConfig{
Separator: defaultNestingSeparator,
Enabled: defaultNestingEnabled,
Include: defaultNestingInclude(),
Exclude: defaultNestingExclude(),
SquashSingleValues: defaultNestingSquashSingleValues,
},
AggregateAttributes: defaultAggregateAttributes(),
LogFieldsAttributes: LogFieldAttributesConfig{
SeverityNumberAttribute: &LogFieldAttribute{defaultAddSeverityNumberAttribute, SeverityNumberAttributeName},
SeverityTextAttribute: &LogFieldAttribute{defaultAddSeverityTextAttribute, SeverityTextAttributeName},
SpanIDAttribute: &LogFieldAttribute{defaultAddSpanIDAttribute, SpanIDAttributeName},
TraceIDAttribute: &LogFieldAttribute{defaultAddTraceIDAttribute, TraceIDAttributeName},
},
TranslateDockerMetrics: defaultTranslateDockerMetrics,
}
}
// Validate config
func (*Config) Validate() error {
return nil
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//
//go:generate make mdatagen
package sumologicprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/sumologicprocessor"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/collector/processor/processorhelper"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/sumologicprocessor/internal/metadata"
)
var processorCapabilities = consumer.Capabilities{MutatesData: true}
// NewFactory returns a new factory for the processor.
func NewFactory() processor.Factory {
return processor.NewFactory(
metadata.Type,
createDefaultConfig,
processor.WithTraces(createTracesProcessor, metadata.TracesStability),
processor.WithMetrics(createMetricsProcessor, metadata.MetricsStability),
processor.WithLogs(createLogsProcessor, metadata.LogsStability),
)
}
func createLogsProcessor(
ctx context.Context,
set processor.Settings,
cfg component.Config,
nextConsumer consumer.Logs,
) (processor.Logs, error) {
processor := newsumologicProcessor(set, cfg.(*Config))
return processorhelper.NewLogs(
ctx,
set,
cfg,
nextConsumer,
processor.processLogs,
processorhelper.WithCapabilities(processorCapabilities),
processorhelper.WithStart(processor.start),
processorhelper.WithShutdown(processor.shutdown),
)
}
func createMetricsProcessor(
ctx context.Context,
set processor.Settings,
cfg component.Config,
nextConsumer consumer.Metrics,
) (processor.Metrics, error) {
processor := newsumologicProcessor(set, cfg.(*Config))
return processorhelper.NewMetrics(
ctx,
set,
cfg,
nextConsumer,
processor.processMetrics,
processorhelper.WithCapabilities(processorCapabilities),
processorhelper.WithStart(processor.start),
processorhelper.WithShutdown(processor.shutdown),
)
}
func createTracesProcessor(
ctx context.Context,
set processor.Settings,
cfg component.Config,
nextConsumer consumer.Traces,
) (processor.Traces, error) {
processor := newsumologicProcessor(set, cfg.(*Config))
return processorhelper.NewTraces(
ctx,
set,
cfg,
nextConsumer,
processor.processTraces,
processorhelper.WithCapabilities(processorCapabilities),
processorhelper.WithStart(processor.start),
processorhelper.WithShutdown(processor.shutdown),
)
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sumologicprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/sumologicprocessor"
import (
"encoding/hex"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
)
const (
SeverityNumberAttributeName = "loglevel"
SeverityTextAttributeName = "severitytext"
SpanIDAttributeName = "spanid"
TraceIDAttributeName = "traceid"
)
type LogFieldAttribute struct {
Enabled bool `mapstructure:"enabled"`
Name string `mapstructure:"name"`
}
type LogFieldAttributesConfig struct {
SeverityNumberAttribute *LogFieldAttribute `mapstructure:"severity_number"`
SeverityTextAttribute *LogFieldAttribute `mapstructure:"severity_text"`
SpanIDAttribute *LogFieldAttribute `mapstructure:"span_id"`
TraceIDAttribute *LogFieldAttribute `mapstructure:"trace_id"`
}
// spanIDToHexOrEmptyString returns a hex string from SpanID.
// An empty string is returned, if SpanID is empty.
func spanIDToHexOrEmptyString(id pcommon.SpanID) string {
if id.IsEmpty() {
return ""
}
return hex.EncodeToString(id[:])
}
// traceIDToHexOrEmptyString returns a hex string from TraceID.
// An empty string is returned, if TraceID is empty.
func traceIDToHexOrEmptyString(id pcommon.TraceID) string {
if id.IsEmpty() {
return ""
}
return hex.EncodeToString(id[:])
}
var severityNumberToLevel = map[string]string{
plog.SeverityNumberUnspecified.String(): "UNSPECIFIED",
plog.SeverityNumberTrace.String(): "TRACE",
plog.SeverityNumberTrace2.String(): "TRACE2",
plog.SeverityNumberTrace3.String(): "TRACE3",
plog.SeverityNumberTrace4.String(): "TRACE4",
plog.SeverityNumberDebug.String(): "DEBUG",
plog.SeverityNumberDebug2.String(): "DEBUG2",
plog.SeverityNumberDebug3.String(): "DEBUG3",
plog.SeverityNumberDebug4.String(): "DEBUG4",
plog.SeverityNumberInfo.String(): "INFO",
plog.SeverityNumberInfo2.String(): "INFO2",
plog.SeverityNumberInfo3.String(): "INFO3",
plog.SeverityNumberInfo4.String(): "INFO4",
plog.SeverityNumberWarn.String(): "WARN",
plog.SeverityNumberWarn2.String(): "WARN2",
plog.SeverityNumberWarn3.String(): "WARN3",
plog.SeverityNumberWarn4.String(): "WARN4",
plog.SeverityNumberError.String(): "ERROR",
plog.SeverityNumberError2.String(): "ERROR2",
plog.SeverityNumberError3.String(): "ERROR3",
plog.SeverityNumberError4.String(): "ERROR4",
plog.SeverityNumberFatal.String(): "FATAL",
plog.SeverityNumberFatal2.String(): "FATAL2",
plog.SeverityNumberFatal3.String(): "FATAL3",
plog.SeverityNumberFatal4.String(): "FATAL4",
}
// logFieldsConversionProcessor converts specific log entries to attributes which leads to presenting them as fields
// in the backend
type logFieldsConversionProcessor struct {
LogFieldsAttributes *LogFieldAttributesConfig
}
func newLogFieldConversionProcessor(logFieldsAttributes *LogFieldAttributesConfig) *logFieldsConversionProcessor {
return &logFieldsConversionProcessor{
logFieldsAttributes,
}
}
func (proc *logFieldsConversionProcessor) addAttributes(log plog.LogRecord) {
if log.SeverityNumber() != plog.SeverityNumberUnspecified {
if _, found := log.Attributes().Get(SeverityNumberAttributeName); !found &&
proc.LogFieldsAttributes.SeverityNumberAttribute.Enabled {
level := severityNumberToLevel[log.SeverityNumber().String()]
log.Attributes().PutStr(proc.LogFieldsAttributes.SeverityNumberAttribute.Name, level)
}
}
if _, found := log.Attributes().Get(SeverityTextAttributeName); !found &&
proc.LogFieldsAttributes.SeverityTextAttribute.Enabled {
log.Attributes().PutStr(proc.LogFieldsAttributes.SeverityTextAttribute.Name, log.SeverityText())
}
if _, found := log.Attributes().Get(SpanIDAttributeName); !found &&
proc.LogFieldsAttributes.SpanIDAttribute.Enabled {
log.Attributes().PutStr(proc.LogFieldsAttributes.SpanIDAttribute.Name, spanIDToHexOrEmptyString(log.SpanID()))
}
if _, found := log.Attributes().Get(TraceIDAttributeName); !found &&
proc.LogFieldsAttributes.TraceIDAttribute.Enabled {
log.Attributes().PutStr(proc.LogFieldsAttributes.TraceIDAttribute.Name, traceIDToHexOrEmptyString(log.TraceID()))
}
}
func (proc *logFieldsConversionProcessor) processLogs(logs plog.Logs) error {
if !proc.isEnabled() {
return nil
}
rls := logs.ResourceLogs()
for i := 0; i < rls.Len(); i++ {
ills := rls.At(i).ScopeLogs()
for j := 0; j < ills.Len(); j++ {
logs := ills.At(j).LogRecords()
for k := 0; k < logs.Len(); k++ {
proc.addAttributes(logs.At(k))
}
}
}
return nil
}
func (*logFieldsConversionProcessor) processMetrics(pmetric.Metrics) error {
// No-op. Metrics should not be translated.
return nil
}
func (*logFieldsConversionProcessor) processTraces(ptrace.Traces) error {
// No-op. Traces should not be translated.
return nil
}
func (proc *logFieldsConversionProcessor) isEnabled() bool {
return proc.LogFieldsAttributes.SeverityNumberAttribute.Enabled ||
proc.LogFieldsAttributes.SeverityTextAttribute.Enabled ||
proc.LogFieldsAttributes.SpanIDAttribute.Enabled ||
proc.LogFieldsAttributes.TraceIDAttribute.Enabled
}
func (*logFieldsConversionProcessor) ConfigPropertyName() string {
return "field_attributes"
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sumologicprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/sumologicprocessor"
import (
"strings"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
)
type NestingProcessorConfig struct {
Separator string `mapstructure:"separator"`
Enabled bool `mapstructure:"enabled"`
Include []string `mapstructure:"include"`
Exclude []string `mapstructure:"exclude"`
SquashSingleValues bool `mapstructure:"squash_single_values"`
}
type nestingProcessor struct {
separator string
enabled bool
allowlist []string
denylist []string
squashSingleValues bool
}
func newNestingProcessor(config *NestingProcessorConfig) *nestingProcessor {
proc := &nestingProcessor{
separator: config.Separator,
enabled: config.Enabled,
allowlist: config.Include,
denylist: config.Exclude,
squashSingleValues: config.SquashSingleValues,
}
return proc
}
func (proc *nestingProcessor) processLogs(logs plog.Logs) error {
if !proc.enabled {
return nil
}
for i := 0; i < logs.ResourceLogs().Len(); i++ {
rl := logs.ResourceLogs().At(i)
if err := proc.processAttributes(rl.Resource().Attributes()); err != nil {
return err
}
for j := 0; j < rl.ScopeLogs().Len(); j++ {
logsRecord := rl.ScopeLogs().At(j).LogRecords()
for k := 0; k < logsRecord.Len(); k++ {
if err := proc.processAttributes(logsRecord.At(k).Attributes()); err != nil {
return err
}
}
}
}
return nil
}
func (proc *nestingProcessor) processMetrics(metrics pmetric.Metrics) error {
if !proc.enabled {
return nil
}
for i := 0; i < metrics.ResourceMetrics().Len(); i++ {
rm := metrics.ResourceMetrics().At(i)
if err := proc.processAttributes(rm.Resource().Attributes()); err != nil {
return err
}
for j := 0; j < rm.ScopeMetrics().Len(); j++ {
metricsSlice := rm.ScopeMetrics().At(j).Metrics()
for k := 0; k < metricsSlice.Len(); k++ {
if err := processMetricLevelAttributes(proc, metricsSlice.At(k)); err != nil {
return err
}
}
}
}
return nil
}
func (proc *nestingProcessor) processTraces(traces ptrace.Traces) error {
if !proc.enabled {
return nil
}
for i := 0; i < traces.ResourceSpans().Len(); i++ {
rs := traces.ResourceSpans().At(i)
if err := proc.processAttributes(rs.Resource().Attributes()); err != nil {
return err
}
for j := 0; j < rs.ScopeSpans().Len(); j++ {
spans := rs.ScopeSpans().At(j).Spans()
for k := 0; k < spans.Len(); k++ {
if err := proc.processAttributes(spans.At(k).Attributes()); err != nil {
return err
}
}
}
}
return nil
}
func (proc *nestingProcessor) processAttributes(attributes pcommon.Map) error {
newMap := pcommon.NewMap()
for k, v := range attributes.All() {
// If key is not on allow list or is on deny list, skip translating it.
if !proc.shouldTranslateKey(k) {
v.CopyTo(newMap.PutEmpty(k))
continue
}
keys := strings.Split(k, proc.separator)
if len(keys) == 0 {
// Split returns empty slice only if both string and separator are empty
// set map[""] = v and return
newVal := newMap.PutEmpty(k)
v.CopyTo(newVal)
continue
}
prevValue := pcommon.NewValueMap()
nextMap := prevValue.Map()
newMap.CopyTo(nextMap)
for i := range keys {
if prevValue.Type() != pcommon.ValueTypeMap {
// If previous value was not a map, change it into a map.
// The former value will be set under the key "".
tempMap := pcommon.NewValueMap()
prevValue.CopyTo(tempMap.Map().PutEmpty(""))
tempMap.CopyTo(prevValue)
}
newValue, ok := prevValue.Map().Get(keys[i])
if ok {
prevValue = newValue
} else {
if i == len(keys)-1 {
// If we're checking the last key, insert empty value, to which v will be copied.
prevValue = prevValue.Map().PutEmpty(keys[i])
} else {
// If we're not checking the last key, put a map.
prevValue = prevValue.Map().PutEmpty(keys[i])
prevValue.SetEmptyMap()
}
}
}
if prevValue.Type() == pcommon.ValueTypeMap {
// Now check the value we want to copy. If it is a map, we should merge both maps.
// Else, just place the value under the key "".
if v.Type() == pcommon.ValueTypeMap {
for k, val := range v.Map().All() {
val.CopyTo(prevValue.Map().PutEmpty(k))
}
} else {
v.CopyTo(prevValue.Map().PutEmpty(""))
}
} else {
v.CopyTo(prevValue)
}
nextMap.CopyTo(newMap)
}
if proc.squashSingleValues {
newMap = proc.squash(newMap)
}
newMap.CopyTo(attributes)
return nil
}
// Checks if given key fulfills the following conditions:
// - has a prefix that exists in the allowlist (if it's not empty)
// - does not have a prefix that exists in the denylist
func (proc *nestingProcessor) shouldTranslateKey(k string) bool {
if len(proc.allowlist) > 0 {
isOk := false
for i := 0; i < len(proc.allowlist); i++ {
if strings.HasPrefix(k, proc.allowlist[i]) {
isOk = true
break
}
}
if !isOk {
return false
}
}
if len(proc.denylist) > 0 {
for i := 0; i < len(proc.denylist); i++ {
if strings.HasPrefix(k, proc.denylist[i]) {
return false
}
}
}
return true
}
// Squashes maps that have single values, eg. map {"a": {"b": {"c": "C", "d": "D"}}}}
// gets squashes into {"a.b": {"c": "C", "d": "D"}}}
func (proc *nestingProcessor) squash(attributes pcommon.Map) pcommon.Map {
newMap := pcommon.NewValueMap()
attributes.CopyTo(newMap.Map())
key := proc.squashAttribute(newMap)
if key != "" {
retMap := pcommon.NewMap()
newMap.Map().CopyTo(retMap.PutEmptyMap(key))
return retMap
}
return newMap.Map()
}
// A function that squashes keys in a value.
// If this value contained a map with one element, it gets squished and its key gets returned.
//
// If this value contained a map with many elements, this function is called on these elements,
// and the key gets replaced if needed, "" is returned.
//
// Else, nothing happens and "" is returned.
func (proc *nestingProcessor) squashAttribute(value pcommon.Value) string {
if value.Type() != pcommon.ValueTypeMap {
return ""
}
m := value.Map()
if m.Len() == 1 {
// If the map contains only one key-value pair, squash it.
key := ""
val := pcommon.NewValueEmpty()
// This will iterate only over one value (the only one)
for k, v := range m.All() {
keySuffix := proc.squashAttribute(v)
key = proc.squashKey(k, keySuffix)
val = v
}
val.CopyTo(value)
return key
}
// This map doesn't get squashed, but its content might have keys replaced.
newMap := pcommon.NewMap()
for k, v := range m.All() {
keySuffix := proc.squashAttribute(v)
// If "" was returned, the value was not a one-element map and did not get squashed.
if keySuffix == "" {
v.CopyTo(newMap.PutEmpty(k))
} else {
v.CopyTo(newMap.PutEmpty(proc.squashKey(k, keySuffix)))
}
}
newMap.CopyTo(value.Map())
return ""
}
func (proc *nestingProcessor) squashKey(key, keySuffix string) string {
if keySuffix == "" {
return key
}
return key + proc.separator + keySuffix
}
func (proc *nestingProcessor) isEnabled() bool {
return proc.enabled
}
func (*nestingProcessor) ConfigPropertyName() string {
return "nest_attributes"
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sumologicprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/sumologicprocessor"
import (
"context"
"fmt"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/processor"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
type sumologicSubprocessor interface {
processLogs(plog.Logs) error
processMetrics(pmetric.Metrics) error
processTraces(ptrace.Traces) error
isEnabled() bool
ConfigPropertyName() string
}
type sumologicProcessor struct {
logger *zap.Logger
subprocessors []sumologicSubprocessor
}
func newsumologicProcessor(set processor.Settings, config *Config) *sumologicProcessor {
cloudNamespaceProcessor := newCloudNamespaceProcessor(config.AddCloudNamespace)
translateAttributesProcessor := newTranslateAttributesProcessor(config.TranslateAttributes)
translateTelegrafMetricsProcessor := newTranslateTelegrafMetricsProcessor(config.TranslateTelegrafAttributes)
nestingProcessor := newNestingProcessor(&config.NestAttributes)
aggregateAttributesProcessor := newAggregateAttributesProcessor(config.AggregateAttributes)
logFieldsConversionProcessor := newLogFieldConversionProcessor(&config.LogFieldsAttributes)
translateDockerMetricsProcessor := newTranslateDockerMetricsProcessor(config.TranslateDockerMetrics)
processors := []sumologicSubprocessor{
cloudNamespaceProcessor,
translateAttributesProcessor,
translateTelegrafMetricsProcessor,
nestingProcessor,
aggregateAttributesProcessor,
logFieldsConversionProcessor,
translateDockerMetricsProcessor,
}
processor := &sumologicProcessor{
logger: set.Logger,
subprocessors: processors,
}
return processor
}
func (processor *sumologicProcessor) start(_ context.Context, _ component.Host) error {
enabledSubprocessors := []zapcore.Field{}
for _, proc := range processor.subprocessors {
enabledSubprocessors = append(enabledSubprocessors, zap.Bool(proc.ConfigPropertyName(), proc.isEnabled()))
}
processor.logger.Info("Sumo Logic Processor has started.", enabledSubprocessors...)
return nil
}
func (processor *sumologicProcessor) shutdown(_ context.Context) error {
processor.logger.Info("Sumo Logic Processor has shut down.")
return nil
}
func (processor *sumologicProcessor) processLogs(_ context.Context, logs plog.Logs) (plog.Logs, error) {
for _, subprocessor := range processor.subprocessors {
if err := subprocessor.processLogs(logs); err != nil {
return logs, fmt.Errorf("failed to process logs for property %s: %w", subprocessor.ConfigPropertyName(), err)
}
}
return logs, nil
}
func (processor *sumologicProcessor) processMetrics(_ context.Context, metrics pmetric.Metrics) (pmetric.Metrics, error) {
for _, subprocessor := range processor.subprocessors {
if err := subprocessor.processMetrics(metrics); err != nil {
return metrics, fmt.Errorf("failed to process metrics for property %s: %w", subprocessor.ConfigPropertyName(), err)
}
}
return metrics, nil
}
func (processor *sumologicProcessor) processTraces(_ context.Context, traces ptrace.Traces) (ptrace.Traces, error) {
for _, subprocessor := range processor.subprocessors {
if err := subprocessor.processTraces(traces); err != nil {
return traces, fmt.Errorf("failed to process traces for property %s: %w", subprocessor.ConfigPropertyName(), err)
}
}
return traces, nil
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sumologicprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/sumologicprocessor"
import (
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
)
// translateAttributesProcessor translates attribute names from OpenTelemetry to Sumo Logic convention
type translateAttributesProcessor struct {
shouldTranslate bool
}
// attributeTranslations maps OpenTelemetry attribute names to Sumo Logic attribute names
var attributeTranslations = map[string]string{
"cloud.account.id": "AccountId",
"cloud.availability_zone": "AvailabilityZone",
"cloud.platform": "aws_service",
"cloud.region": "Region",
"host.id": "InstanceId",
"host.name": "host",
"host.type": "InstanceType",
"k8s.cluster.name": "Cluster",
"k8s.container.name": "container",
"k8s.daemonset.name": "daemonset",
"k8s.deployment.name": "deployment",
"k8s.namespace.name": "namespace",
"k8s.node.name": "node",
"k8s.service.name": "service",
"k8s.pod.hostname": "host",
"k8s.pod.name": "pod",
"k8s.pod.uid": "pod_id",
"k8s.replicaset.name": "replicaset",
"k8s.statefulset.name": "statefulset",
"service.name": "service",
"log.file.path_resolved": "_sourceName",
}
func newTranslateAttributesProcessor(shouldTranslate bool) *translateAttributesProcessor {
return &translateAttributesProcessor{
shouldTranslate: shouldTranslate,
}
}
func (proc *translateAttributesProcessor) processLogs(logs plog.Logs) error {
if !proc.shouldTranslate {
return nil
}
for i := 0; i < logs.ResourceLogs().Len(); i++ {
translateAttributes(logs.ResourceLogs().At(i).Resource().Attributes())
}
return nil
}
func (proc *translateAttributesProcessor) processMetrics(metrics pmetric.Metrics) error {
if !proc.shouldTranslate {
return nil
}
for i := 0; i < metrics.ResourceMetrics().Len(); i++ {
translateAttributes(metrics.ResourceMetrics().At(i).Resource().Attributes())
}
return nil
}
func (*translateAttributesProcessor) processTraces(ptrace.Traces) error {
// No-op. Traces should not be translated.
return nil
}
func (proc *translateAttributesProcessor) isEnabled() bool {
return proc.shouldTranslate
}
func (*translateAttributesProcessor) ConfigPropertyName() string {
return "translate_attributes"
}
func translateAttributes(attributes pcommon.Map) {
result := pcommon.NewMap()
result.EnsureCapacity(attributes.Len())
for otKey, value := range attributes.All() {
if sumoKey, ok := attributeTranslations[otKey]; ok {
// Only insert if it doesn't exist yet to prevent overwriting.
// We have to do it this way since the final return value is not
// ready yet to rely on .Insert() not overwriting.
if _, exists := attributes.Get(sumoKey); !exists {
if _, ok := result.Get(sumoKey); !ok {
value.CopyTo(result.PutEmpty(sumoKey))
}
} else {
if _, ok := result.Get(otKey); !ok {
value.CopyTo(result.PutEmpty(otKey))
}
}
} else {
if _, ok := result.Get(otKey); !ok {
value.CopyTo(result.PutEmpty(otKey))
}
}
}
result.CopyTo(attributes)
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sumologicprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/sumologicprocessor"
import (
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
)
// translateTelegrafMetricsProcessor translates metric names from OpenTelemetry to Sumo Logic convention
type translateDockerMetricsProcessor struct {
shouldTranslate bool
}
// metricsTranslations maps Telegraf metric names to corresponding names in Sumo Logic convention
var dockerMetricsTranslations = map[string]string{
"container.cpu.percent": "cpu_percentage",
"container.cpu.usage.system": "system_cpu_usage",
"container.cpu.usage.percpu": "cpu_usage.percpu_usage",
"container.cpu.usage.total": "cpu_usage.total_usage",
"container.cpu.usage.kernelmode": "cpu_usage.usage_in_kernelmode",
"container.cpu.usage.usermode": "cpu_usage.usage_in_usermode",
"container.cpu.throttling_data.periods": "throttling_data.periods",
"container.cpu.throttling_data.throttled_periods": "throttling_data.throttled_periods",
"container.cpu.throttling_data.throttled_time": "throttling_data.throttled_time",
"container.memory.usage.limit": "limit",
"container.memory.usage.max": "max_usage",
"container.memory.percent": "memory_percentage",
"container.memory.usage.total": "usage",
"container.memory.active_anon": "stats.active_anon",
"container.memory.active_file": "stats.active_file",
"container.memory.cache": "stats.cache",
"container.memory.hierarchical_memory_limit": "stats.hierarchical_memory_limit",
"container.memory.inactive_anon": "stats.inactive_anon",
"container.memory.inactive_file": "stats.inactive_file",
"container.memory.mapped_file": "stats.mapped_file",
"container.memory.pgfault": "stats.pgfault",
"container.memory.pgmajfault": "stats.pgmajfault",
"container.memory.pgpgin": "stats.pgpgin",
"container.memory.pgpgout": "stats.pgpgout",
"container.memory.rss": "stats.rss",
"container.memory.rss_huge": "stats.rss_huge",
"container.memory.unevictable": "stats.unevictable",
"container.memory.writeback": "stats.writeback",
"container.memory.total_active_anon": "stats.total_active_anon",
"container.memory.total_active_file": "stats.total_active_file",
"container.memory.total_cache": "stats.total_cache",
"container.memory.total_inactive_anon": "stats.total_inactive_anon",
"container.memory.total_mapped_file": "stats.total_mapped_file",
"container.memory.total_pgfault": "stats.total_pgfault",
"container.memory.total_pgmajfault": "stats.total_pgmajfault",
"container.memory.total_pgpgin": "stats.total_pgpgin",
"container.memory.total_pgpgout": "stats.total_pgpgout",
"container.memory.total_rss": "stats.total_rss",
"container.memory.total_rss_huge": "stats.total_rss_huge",
"container.memory.total_unevictable": "stats.total_unevictable",
"container.memory.total_writeback": "stats.total_writeback",
"container.blockio.io_merged_recursive": "io_merged_recursive",
"container.blockio.io_queued_recursive": "io_queue_recursive",
"container.blockio.io_service_bytes_recursive": "io_service_bytes_recursive",
"container.blockio.io_service_time_recursive": "io_service_time_recursive",
"container.blockio.io_serviced_recursive": "io_serviced_recursive",
"container.blockio.io_time_recursive": "io_time_recursive",
"container.blockio.io_wait_time_recursive": "io_wait_time_recursive",
"container.blockio.sectors_recursive": "sectors_recursive",
}
var dockerResourceAttributeTranslations = map[string]string{
"container.id": "container.FullID",
"container.image.name": "container.ImageName",
"container.name": "container.Name",
}
func newTranslateDockerMetricsProcessor(shouldTranslate bool) *translateDockerMetricsProcessor {
return &translateDockerMetricsProcessor{
shouldTranslate: shouldTranslate,
}
}
func (*translateDockerMetricsProcessor) processLogs(plog.Logs) error {
// No-op, this subprocessor doesn't process logs.
return nil
}
func (proc *translateDockerMetricsProcessor) processMetrics(metrics pmetric.Metrics) error {
if !proc.shouldTranslate {
return nil
}
for i := 0; i < metrics.ResourceMetrics().Len(); i++ {
rm := metrics.ResourceMetrics().At(i)
translateDockerResourceAttributes(rm.Resource().Attributes())
for j := 0; j < rm.ScopeMetrics().Len(); j++ {
metricsSlice := rm.ScopeMetrics().At(j).Metrics()
for k := 0; k < metricsSlice.Len(); k++ {
translateDockerMetric(metricsSlice.At(k))
}
}
}
return nil
}
func (*translateDockerMetricsProcessor) processTraces(ptrace.Traces) error {
// No-op, this subprocessor doesn't process traces.
return nil
}
func (proc *translateDockerMetricsProcessor) isEnabled() bool {
return proc.shouldTranslate
}
func (*translateDockerMetricsProcessor) ConfigPropertyName() string {
return "translate_docker_metrics"
}
func translateDockerMetric(m pmetric.Metric) {
name, exists := dockerMetricsTranslations[m.Name()]
if exists {
m.SetName(name)
}
}
func translateDockerResourceAttributes(attributes pcommon.Map) {
result := pcommon.NewMap()
result.EnsureCapacity(attributes.Len())
for otKey, value := range attributes.All() {
if sumoKey, ok := dockerResourceAttributeTranslations[otKey]; ok {
// Only insert if it doesn't exist yet to prevent overwriting.
// We have to do it this way since the final return value is not
// ready yet to rely on .Insert() not overwriting.
if _, exists := attributes.Get(sumoKey); !exists {
if _, ok := result.Get(sumoKey); !ok {
value.CopyTo(result.PutEmpty(sumoKey))
}
} else {
if _, ok := result.Get(otKey); !ok {
value.CopyTo(result.PutEmpty(otKey))
}
}
} else {
if _, ok := result.Get(otKey); !ok {
value.CopyTo(result.PutEmpty(otKey))
}
}
}
result.CopyTo(attributes)
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sumologicprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/sumologicprocessor"
import (
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
)
// translateTelegrafMetricsProcessor translates metric names from OpenTelemetry to Sumo Logic convention
type translateTelegrafMetricsProcessor struct {
shouldTranslate bool
}
// metricsTranslations maps Telegraf metric names to corresponding names in Sumo Logic convention
var metricsTranslations = map[string]string{
// CPU metrics
"cpu_usage_active": "CPU_Total",
"cpu_usage_idle": "CPU_Idle",
"cpu_usage_iowait": "CPU_IOWait",
"cpu_usage_irq": "CPU_Irq",
"cpu_usage_nice": "CPU_Nice",
"cpu_usage_softirq": "CPU_SoftIrq",
"cpu_usage_steal": "CPU_Stolen",
"cpu_usage_System": "CPU_Sys",
"cpu_usage_user": "CPU_User",
"system_load1": "CPU_LoadAvg_1min",
"system_load5": "CPU_LoadAvg_5min",
"system_load15": "CPU_LoadAvg_15min",
// Disk metrics
"disk_used": "Disk_Used",
"disk_used_percent": "Disk_UsedPercent",
"disk_inodes_free": "Disk_InodesAvailable",
// Disk IO metrics
"diskio_reads": "Disk_Reads",
"diskio_read_bytes": "Disk_ReadBytes",
"diskio_writes": "Disk_Writes",
"diskio_write_bytes": "Disk_WriteBytes",
// Memory metrics
"mem_total": "Mem_Total",
"mem_free": "Mem_free",
"mem_available": "Mem_ActualFree",
"mem_used": "Mem_ActualUsed",
"mem_used_percent": "Mem_UsedPercent",
"mem_available_percent": "Mem_FreePercent",
// Procstat metrics
"procstat_num_threads": "Proc_Threads",
"procstat_memory_vms": "Proc_VMSize",
"procstat_memory_rss": "Proc_RSSize",
"procstat_cpu_usage": "Proc_CPU",
"procstat_major_faults": "Proc_MajorFaults",
"procstat_minor_faults": "Proc_MinorFaults",
// Net metrics
"net_bytes_sent": "Net_OutBytes",
"net_bytes_recv": "Net_InBytes",
"net_packets_sent": "Net_OutPackets",
"net_packets_recv": "Net_InPackets",
// Netstat metrics
"netstat_tcp_close": "TCP_Close",
"netstat_tcp_close_wait": "TCP_CloseWait",
"netstat_tcp_closing": "TCP_Closing",
"netstat_tcp_established": "TCP_Established",
"netstat_tcp_listen": "TCP_Listen",
"netstat_tcp_time_wait": "TCP_TimeWait",
}
func newTranslateTelegrafMetricsProcessor(shouldTranslate bool) *translateTelegrafMetricsProcessor {
return &translateTelegrafMetricsProcessor{
shouldTranslate: shouldTranslate,
}
}
func (*translateTelegrafMetricsProcessor) processLogs(plog.Logs) error {
// No-op, this subprocessor doesn't process logs.
return nil
}
func (proc *translateTelegrafMetricsProcessor) processMetrics(metrics pmetric.Metrics) error {
if !proc.shouldTranslate {
return nil
}
for i := 0; i < metrics.ResourceMetrics().Len(); i++ {
rm := metrics.ResourceMetrics().At(i)
for j := 0; j < rm.ScopeMetrics().Len(); j++ {
metricsSlice := rm.ScopeMetrics().At(j).Metrics()
for k := 0; k < metricsSlice.Len(); k++ {
translateTelegrafMetric(metricsSlice.At(k))
}
}
}
return nil
}
func (*translateTelegrafMetricsProcessor) processTraces(ptrace.Traces) error {
// No-op, this subprocessor doesn't process traces.
return nil
}
func (proc *translateTelegrafMetricsProcessor) isEnabled() bool {
return proc.shouldTranslate
}
func (*translateTelegrafMetricsProcessor) ConfigPropertyName() string {
return "translate_telegraf_attributes"
}
func translateTelegrafMetric(m pmetric.Metric) {
name, exists := metricsTranslations[m.Name()]
if exists {
m.SetName(name)
}
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package tailsamplingprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor"
import (
"go.opentelemetry.io/collector/component"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/sampling"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/pkg/samplingpolicy"
)
func getNewAndPolicy(settings component.TelemetrySettings, config *AndCfg, policyExtensions map[string]samplingpolicy.Extension) (samplingpolicy.Evaluator, error) {
subPolicyEvaluators := make([]samplingpolicy.Evaluator, len(config.SubPolicyCfg))
for i := range config.SubPolicyCfg {
policyCfg := &config.SubPolicyCfg[i]
policy, err := getAndSubPolicyEvaluator(settings, policyCfg, policyExtensions)
if err != nil {
return nil, err
}
subPolicyEvaluators[i] = policy
}
return sampling.NewAnd(settings.Logger, subPolicyEvaluators), nil
}
// Return instance of and sub-policy
func getAndSubPolicyEvaluator(settings component.TelemetrySettings, cfg *AndSubPolicyCfg, policyExtensions map[string]samplingpolicy.Extension) (samplingpolicy.Evaluator, error) {
if cfg.Type == Not {
return getNewNotPolicy(settings, &cfg.NotCfg, policyExtensions)
}
return getSharedPolicyEvaluator(settings, &cfg.sharedPolicyCfg, policyExtensions)
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package cache // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/cache"
import (
"encoding/binary"
lru "github.com/hashicorp/golang-lru/v2"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// lruDecisionCache implements Cache as a simple LRU cache.
// It holds trace IDs that had sampling decisions made on them.
// It does not specify the type of sampling decision that was made, only that
// a decision was made for an ID. You need separate DecisionCaches for caching
// sampled and not sampled trace IDs.
type lruDecisionCache struct {
cache *lru.Cache[uint64, DecisionMetadata]
}
var _ Cache = (*lruDecisionCache)(nil)
// NewLRUDecisionCache returns a new lruDecisionCache.
// The size parameter indicates the amount of keys the cache will hold before it
// starts evicting the least recently used key.
func NewLRUDecisionCache(size int) (Cache, error) {
c, err := lru.New[uint64, DecisionMetadata](size)
if err != nil {
return nil, err
}
return &lruDecisionCache{cache: c}, nil
}
func (c *lruDecisionCache) Get(id pcommon.TraceID) (DecisionMetadata, bool) {
return c.cache.Get(rightHalfTraceID(id))
}
func (c *lruDecisionCache) Put(id pcommon.TraceID, metadata DecisionMetadata) {
_ = c.cache.Add(rightHalfTraceID(id), metadata)
}
func rightHalfTraceID(id pcommon.TraceID) uint64 {
return binary.LittleEndian.Uint64(id[8:])
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package cache // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/cache"
import "go.opentelemetry.io/collector/pdata/pcommon"
type nopDecisionCache struct{}
var _ Cache = (*nopDecisionCache)(nil)
func NewNopDecisionCache() Cache {
return &nopDecisionCache{}
}
func (*nopDecisionCache) Get(pcommon.TraceID) (DecisionMetadata, bool) {
return DecisionMetadata{}, false
}
func (*nopDecisionCache) Put(_ pcommon.TraceID, _ DecisionMetadata) {
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package tailsamplingprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor"
import (
"go.opentelemetry.io/collector/component"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/sampling"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/telemetry"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/pkg/samplingpolicy"
)
func getNewCompositePolicy(settings component.TelemetrySettings, config *CompositeCfg, policyExtensions map[string]samplingpolicy.Extension) (samplingpolicy.Evaluator, error) {
subPolicyEvalParams := make([]sampling.SubPolicyEvalParams, len(config.SubPolicyCfg))
rateAllocationsMap := getRateAllocationMap(config)
for i := range config.SubPolicyCfg {
policyCfg := &config.SubPolicyCfg[i]
policy, err := getCompositeSubPolicyEvaluator(settings, policyCfg, policyExtensions)
if err != nil {
return nil, err
}
evalParams := sampling.SubPolicyEvalParams{
Evaluator: policy,
MaxSpansPerSecond: int64(rateAllocationsMap[policyCfg.Name]),
Name: policyCfg.Name,
}
subPolicyEvalParams[i] = evalParams
}
return sampling.NewComposite(settings.Logger, config.MaxTotalSpansPerSecond, subPolicyEvalParams, sampling.MonotonicClock{}, telemetry.IsRecordPolicyEnabled()), nil
}
// Apply rate allocations to the sub-policies
func getRateAllocationMap(config *CompositeCfg) map[string]float64 {
rateAllocationsMap := make(map[string]float64, len(config.SubPolicyCfg))
maxTotalSPS := float64(config.MaxTotalSpansPerSecond)
// Default SPS determined by equally diving number of sub policies
defaultSPS := maxTotalSPS / float64(len(config.SubPolicyCfg))
percentByPolicy := make(map[string]int64, len(config.RateAllocation))
for _, rAlloc := range config.RateAllocation {
percentByPolicy[rAlloc.Policy] = rAlloc.Percent
}
// Iterate over every configured sub-policy, not just the ones listed in
// RateAllocation, so a sub-policy left out of RateAllocation still gets
// its equal-share default instead of silently falling back to a zero
// value MaxSpansPerSecond (which permanently blocks it from sampling).
for i := range config.SubPolicyCfg {
subPolicy := &config.SubPolicyCfg[i]
if percent, ok := percentByPolicy[subPolicy.Name]; ok && percent > 0 {
rateAllocationsMap[subPolicy.Name] = (float64(percent) / 100) * maxTotalSPS
} else {
rateAllocationsMap[subPolicy.Name] = defaultSPS
}
}
return rateAllocationsMap
}
// Return instance of composite sub-policy
func getCompositeSubPolicyEvaluator(settings component.TelemetrySettings, cfg *CompositeSubPolicyCfg, policyExtensions map[string]samplingpolicy.Extension) (samplingpolicy.Evaluator, error) {
switch cfg.Type {
case And:
return getNewAndPolicy(settings, &cfg.AndCfg, policyExtensions)
default:
return getSharedPolicyEvaluator(settings, &cfg.sharedPolicyCfg, policyExtensions)
}
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package tailsamplingprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor"
import (
"errors"
"fmt"
"time"
"go.opentelemetry.io/collector/component"
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/tailstorageextension"
)
// PolicyType indicates the type of sampling policy.
type PolicyType string
const (
// AlwaysSample samples all traces, typically used for debugging.
AlwaysSample PolicyType = "always_sample"
// Latency sample traces that are longer than a given threshold.
Latency PolicyType = "latency"
// NumericAttribute sample traces that have a given numeric attribute in a specified
// range, e.g.: attribute "http.status_code" >= 399 and <= 999.
NumericAttribute PolicyType = "numeric_attribute"
// Probabilistic samples a given percentage of traces.
Probabilistic PolicyType = "probabilistic"
// StatusCode sample traces that have a given status code.
StatusCode PolicyType = "status_code"
// StringAttribute sample traces that an attribute, of type string, matching
// one of the listed values.
StringAttribute PolicyType = "string_attribute"
// RateLimiting allows all traces until the specified limits are satisfied.
RateLimiting PolicyType = "rate_limiting"
// Composite allows defining a composite policy, combining the other policies in one
Composite PolicyType = "composite"
// And allows defining a And policy, combining the other policies in one
And PolicyType = "and"
// Not allows defining a Not policy, returning the opposite of the decision of a wrapped policy
Not PolicyType = "not"
// Drop allows defining a Drop policy, combining one or more policies to drop traces.
Drop PolicyType = "drop"
// SpanCount sample traces that are have more spans per Trace than a given threshold.
SpanCount PolicyType = "span_count"
// TraceState sample traces with specified values by the given key
TraceState PolicyType = "trace_state"
// BooleanAttribute sample traces having an attribute, of type bool, that matches
// the specified boolean value [true|false].
BooleanAttribute PolicyType = "boolean_attribute"
// OTTLCondition sample traces which match user provided OpenTelemetry Transformation Language
// conditions.
OTTLCondition PolicyType = "ottl_condition"
// BytesLimiting allows all traces until the specified byte limits are satisfied.
BytesLimiting PolicyType = "bytes_limiting"
// TraceFlags sample traces which have specific trace flags set.
TraceFlags PolicyType = "trace_flags"
)
const (
// samplingStrategyTraceComplete keeps the current tail-sampling behavior:
// accumulate spans and decide on full trace data after decision timing.
samplingStrategyTraceComplete samplingStrategy = "trace-complete"
// samplingStrategySpanIngest evaluates each incoming span batch on ingest.
// Non-terminal outcomes remain pending until cleanup finalization.
samplingStrategySpanIngest samplingStrategy = "span-ingest"
)
type samplingStrategy string
// sharedPolicyCfg holds the common configuration to all policies that are used in derivative policy configurations
// such as the and & composite policies.
type sharedPolicyCfg struct {
// Name given to the instance of the policy to make easy to identify it in metrics and logs.
Name string `mapstructure:"name"`
// Type of the policy this will be used to match the proper configuration of the policy.
Type PolicyType `mapstructure:"type"`
// Configs for latency filter sampling policy evaluator.
LatencyCfg LatencyCfg `mapstructure:"latency"`
// Configs for numeric attribute filter sampling policy evaluator.
NumericAttributeCfg NumericAttributeCfg `mapstructure:"numeric_attribute"`
// Configs for probabilistic sampling policy evaluator.
ProbabilisticCfg ProbabilisticCfg `mapstructure:"probabilistic"`
// Configs for status code filter sampling policy evaluator.
StatusCodeCfg StatusCodeCfg `mapstructure:"status_code"`
// Configs for string attribute filter sampling policy evaluator.
StringAttributeCfg StringAttributeCfg `mapstructure:"string_attribute"`
// Configs for rate limiting filter sampling policy evaluator.
RateLimitingCfg RateLimitingCfg `mapstructure:"rate_limiting"`
// Configs for bytes limiting filter sampling policy evaluator.
BytesLimitingCfg BytesLimitingCfg `mapstructure:"bytes_limiting"`
// Configs for span count filter sampling policy evaluator.
SpanCountCfg SpanCountCfg `mapstructure:"span_count"`
// Configs for defining trace_state policy
TraceStateCfg TraceStateCfg `mapstructure:"trace_state"`
// Configs for boolean attribute filter sampling policy evaluator.
BooleanAttributeCfg BooleanAttributeCfg `mapstructure:"boolean_attribute"`
// Configs for OTTL condition filter sampling policy evaluator
OTTLConditionCfg OTTLConditionCfg `mapstructure:"ottl_condition"`
// Configs for any extensions that are used.
ExtensionCfg map[string]map[string]any `mapstructure:",remain"`
}
// CompositeSubPolicyCfg holds the common configuration to all policies under composite policy.
type CompositeSubPolicyCfg struct {
sharedPolicyCfg `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct
// Configs for and policy evaluator.
AndCfg AndCfg `mapstructure:"and"`
}
// AndSubPolicyCfg holds the common configuration to all policies under and policy.
type AndSubPolicyCfg struct {
sharedPolicyCfg `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct
// Configs for defining not policy under and policy.
NotCfg NotCfg `mapstructure:"not"`
}
// NotSubPolicyCfg holds the common configuration to the policy under the not policy.
type NotSubPolicyCfg struct {
sharedPolicyCfg `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct
}
// TraceStateCfg holds the common configuration for trace states.
type TraceStateCfg struct {
// Tag that the filter is going to be matching against.
Key string `mapstructure:"key"`
// Values indicate the set of values to use when matching against trace_state values.
Values []string `mapstructure:"values"`
}
// AndCfg holds the common configuration to all and policies.
type AndCfg struct {
SubPolicyCfg []AndSubPolicyCfg `mapstructure:"and_sub_policy"`
// prevent unkeyed literal initialization
_ struct{}
}
// NotCfg holds the configuration for the not policy.
type NotCfg struct {
SubPolicy NotSubPolicyCfg `mapstructure:"not_sub_policy"`
}
// DropCfg holds the common configuration to all policies under drop policy.
type DropCfg struct {
SubPolicyCfg []AndSubPolicyCfg `mapstructure:"drop_sub_policy"`
// prevent unkeyed literal initialization
_ struct{}
}
// CompositeCfg holds the configurable settings to create a composite
// sampling policy evaluator.
type CompositeCfg struct {
MaxTotalSpansPerSecond int64 `mapstructure:"max_total_spans_per_second"`
PolicyOrder []string `mapstructure:"policy_order"`
SubPolicyCfg []CompositeSubPolicyCfg `mapstructure:"composite_sub_policy"`
RateAllocation []RateAllocationCfg `mapstructure:"rate_allocation"`
}
// RateAllocationCfg used within composite policy
type RateAllocationCfg struct {
Policy string `mapstructure:"policy"`
Percent int64 `mapstructure:"percent"`
// prevent unkeyed literal initialization
_ struct{}
}
// PolicyCfg holds the common configuration to all policies.
type PolicyCfg struct {
sharedPolicyCfg `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct
// Configs for defining composite policy
CompositeCfg CompositeCfg `mapstructure:"composite"`
// Configs for defining and policy
AndCfg AndCfg `mapstructure:"and"`
// Configs for defining not policy
NotCfg NotCfg `mapstructure:"not"`
// Configs for defining drop policy
DropCfg DropCfg `mapstructure:"drop"`
}
// LatencyCfg holds the configurable settings to create a latency filter sampling policy
// evaluator
type LatencyCfg struct {
// Lower bound in milliseconds. Retaining original name for compatibility
ThresholdMs int64 `mapstructure:"threshold_ms"`
// Upper bound in milliseconds.
UpperThresholdMs int64 `mapstructure:"upper_threshold_ms"`
// prevent unkeyed literal initialization
_ struct{}
}
// NumericAttributeCfg holds the configurable settings to create a numeric attribute filter
// sampling policy evaluator.
type NumericAttributeCfg struct {
// Tag that the filter is going to be matching against.
Key string `mapstructure:"key"`
// MinValue is the minimum value of the attribute to be considered a match.
MinValue int64 `mapstructure:"min_value"`
// MaxValue is the maximum value of the attribute to be considered a match.
MaxValue int64 `mapstructure:"max_value"`
// InvertMatch indicates that values must not match against attribute values.
// If InvertMatch is true and Values is equal to '123', all other values will be sampled except '123'.
// Also, if the specified Key does not match any resource or span attributes, data will be sampled.
InvertMatch bool `mapstructure:"invert_match"`
}
// ProbabilisticCfg holds the configurable settings to create a probabilistic
// sampling policy evaluator.
type ProbabilisticCfg struct {
// HashSalt allows one to configure the hashing salts. This is important in scenarios where multiple layers of collectors
// have different sampling rates: if they use the same salt all passing one layer may pass the other even if they have
// different sampling rates, configuring different salts avoids that.
HashSalt string `mapstructure:"hash_salt"`
// SamplingPercentage is the percentage rate at which traces are going to be sampled. Defaults to zero, i.e.: no sample.
// Values greater or equal 100 are treated as "sample all traces".
SamplingPercentage float64 `mapstructure:"sampling_percentage"`
// prevent unkeyed literal initialization
_ struct{}
}
// StatusCodeCfg holds the configurable settings to create a status code filter sampling
// policy evaluator.
type StatusCodeCfg struct {
StatusCodes []string `mapstructure:"status_codes"`
// prevent unkeyed literal initialization
_ struct{}
}
// StringAttributeCfg holds the configurable settings to create a string attribute filter
// sampling policy evaluator.
type StringAttributeCfg struct {
// Tag that the filter is going to be matching against.
Key string `mapstructure:"key"`
// Values indicate the set of values or regular expressions to use when matching against attribute values.
// StringAttribute Policy will apply exact value match on Values unless EnabledRegexMatching is true.
Values []string `mapstructure:"values"`
// EnabledRegexMatching determines whether match attribute values by regexp string.
EnabledRegexMatching bool `mapstructure:"enabled_regex_matching"`
// CacheMaxSize is the maximum number of attribute entries of LRU Cache that stores the matched result
// from the regular expressions defined in Values.
// CacheMaxSize will not be used if EnabledRegexMatching is set to false.
CacheMaxSize int `mapstructure:"cache_max_size"`
// InvertMatch indicates that values or regular expressions must not match against attribute values.
// If InvertMatch is true and Values is equal to 'acme', all other values will be sampled except 'acme'.
// Also, if the specified Key does not match on any resource or span attributes, data will be sampled.
InvertMatch bool `mapstructure:"invert_match"`
}
// RateLimitingCfg holds the configurable settings to create a rate limiting
// sampling policy evaluator.
type RateLimitingCfg struct {
// SpansPerSecond sets the limit on the maximum number of spans that can be processed each second.
SpansPerSecond int64 `mapstructure:"spans_per_second"`
// BurstCapacity sets the maximum burst capacity in spans. If not specified, defaults to 2x SpansPerSecond.
// This allows for short bursts of traffic above the sustained rate. It also acts as a
// limit for individual trace span counts, a single trace with more spans than the burst size will not pass.
BurstCapacity int64 `mapstructure:"burst_capacity"`
// prevent unkeyed literal initialization
_ struct{}
}
// BytesLimitingCfg holds the configurable settings to create a bytes limiting
// sampling policy evaluator using a token bucket algorithm.
type BytesLimitingCfg struct {
// BytesPerSecond sets the limit on the maximum number of bytes that can be processed each second.
BytesPerSecond int64 `mapstructure:"bytes_per_second"`
// BurstCapacity sets the maximum burst capacity in bytes. If not specified, defaults to 2x BytesPerSecond.
// This allows for short bursts of traffic above the sustained rate. It also acts as a
// limit for individual trace sizes, a single trace larger than the burst size will not pass.
BurstCapacity int64 `mapstructure:"burst_capacity"`
}
// SpanCountCfg holds the configurable settings to create a Span Count filter sampling
// policy evaluator
type SpanCountCfg struct {
// Minimum number of spans in a Trace
MinSpans int32 `mapstructure:"min_spans"`
MaxSpans int32 `mapstructure:"max_spans"`
// prevent unkeyed literal initialization
_ struct{}
}
// BooleanAttributeCfg holds the configurable settings to create a boolean attribute filter
// sampling policy evaluator.
type BooleanAttributeCfg struct {
// Tag that the filter is going to be matching against.
Key string `mapstructure:"key"`
// Value indicate the bool value, either true or false to use when matching against attribute values.
// BooleanAttribute Policy will apply exact value match on Value
Value bool `mapstructure:"value"`
// InvertMatch indicates that values must not match against attribute values.
// If InvertMatch is true and Values is equal to 'true', all other values will be sampled except 'true'.
// Also, if the specified Key does not match any resource or span attributes, data will be sampled.
InvertMatch bool `mapstructure:"invert_match"`
}
// OTTLConditionCfg holds the configurable setting to create a OTTL condition filter
// sampling policy evaluator.
type OTTLConditionCfg struct {
ErrorMode ottl.ErrorMode `mapstructure:"error_mode"`
SpanConditions []string `mapstructure:"span"`
SpanEventConditions []string `mapstructure:"spanevent"`
// prevent unkeyed literal initialization
_ struct{}
}
type DecisionCacheConfig struct {
// SampledCacheSize specifies the size of the cache that holds the sampled trace IDs.
// This value will be the maximum amount of trace IDs that the cache can hold before overwriting previous IDs.
// For effective use, this value should be at least an order of magnitude greater than Config.NumTraces.
// If left as default 0, a no-op DecisionCache will be used.
SampledCacheSize int `mapstructure:"sampled_cache_size"`
// NonSampledCacheSize specifies the size of the cache that holds the non-sampled trace IDs.
// This value will be the maximum amount of trace IDs that the cache can hold before overwriting previous IDs.
// For effective use, this value should be at least an order of magnitude greater than Config.NumTraces.
// If left as default 0, a no-op DecisionCache will be used.
NonSampledCacheSize int `mapstructure:"non_sampled_cache_size"`
// prevent unkeyed literal initialization
_ struct{}
}
// Config holds the configuration for tail-based sampling.
type Config struct {
// DecisionWait is the time before timer handling for a trace.
// When sampling_strategy is "trace-complete", this controls decision timing.
// When sampling_strategy is "span-ingest", this controls pending cleanup finalization timing.
DecisionWait time.Duration `mapstructure:"decision_wait"`
// DecisionWaitAfterRootReceived adds root-span-based acceleration for timer handling.
// When sampling_strategy is "trace-complete", this can make decisions earlier.
// When sampling_strategy is "span-ingest", this can finalize pending traces earlier on cleanup.
DecisionWaitAfterRootReceived time.Duration `mapstructure:"decision_wait_after_root_received"`
// NumTraces is the number of traces kept on memory. Typically most of the data
// of a trace is released after a sampling decision is taken.
NumTraces uint64 `mapstructure:"num_traces"`
// BlockOnOverflow determines the behavior when the component's NumTraces limit is reached.
// If true, the component will wait for space; otherwise, old traces will be evicted to make space.
BlockOnOverflow bool `mapstructure:"block_on_overflow"`
// ExpectedNewTracesPerSec sets the expected number of new traces sending to the tail sampling processor
// per second. This helps with allocating data structures with closer to actual usage size.
ExpectedNewTracesPerSec uint64 `mapstructure:"expected_new_traces_per_sec"`
// PolicyCfgs sets the tail-based sampling policy which makes a sampling decision
// for a given trace when requested.
PolicyCfgs []PolicyCfg `mapstructure:"policies"`
// DecisionCache holds configuration for the decision cache(s)
DecisionCache DecisionCacheConfig `mapstructure:"decision_cache"`
// TailStorageID specifies an optional tail storage extension to use for buffering spans.
// If not set, in-memory tail storage is used.
// It is behind feature gate `processor.tailsamplingprocessor.tailstorageextension`.
TailStorageID *component.ID `mapstructure:"tail_storage"`
// Options allows for additional configuration of the tail-based sampling processor in code.
Options []Option `mapstructure:"-"`
// Make decision as soon as a policy matches
SampleOnFirstMatch bool `mapstructure:"sample_on_first_match"`
// SamplingStrategy controls how/when sampling decisions are made.
// "trace-complete" (default) evaluates accumulated trace data on timer handling.
// "span-ingest" evaluates each incoming batch on ingest; terminal outcomes
// finalize immediately, and non-terminal traces are finalized on cleanup.
SamplingStrategy samplingStrategy `mapstructure:"sampling_strategy"`
// DropPendingTracesOnShutdown will drop all traces that are part of batches that have not yet reached the decision
// wait when the processor is shutdown.
DropPendingTracesOnShutdown bool `mapstructure:"drop_pending_traces_on_shutdown"`
// MaximumTraceSizeBytes is the largest size of a trace a decision will be made for.
// If the trace size exceeds this it will be dropped before the decision period to keep memory more predictable.
// A 0 value disables dropping large traces early.
MaximumTraceSizeBytes uint64 `mapstructure:"maximum_trace_size_bytes"`
// NumShards controls the number of parallel goroutine loops processing
// traces. Each shard runs an independent event loop with its own trace
// storage and decision batcher. Traces are routed to shards by a hash of
// the trace ID, ensuring all spans for a given trace are processed by
// the same shard. Higher values reduce contention between trace
// ingestion and sampling decision evaluation under high load. NumTraces,
// ExpectedNewTracesPerSec, decision cache sizes, and per-second rate
// limits in policies (rate_limiting, bytes_limiting, and composite
// max_total_spans_per_second) are divided evenly across shards so
// aggregate behavior matches the configured values. Limiter burst
// capacities are not divided so single large traces stay admissible
// regardless of the shard count. Must not exceed 256,
// and values greater than 1 are not supported with tail_storage.
// Defaults to 1 (single event loop, original behavior).
NumShards uint32 `mapstructure:"num_shards"`
}
// maxNumShards bounds num_shards to catch configuration mistakes: each shard
// materializes a full event loop with its own goroutine, storage and batcher,
// so values beyond CPU-count scale only add overhead.
const maxNumShards = 256
func (cfg *Config) Validate() error {
switch cfg.SamplingStrategy {
case samplingStrategyTraceComplete, samplingStrategySpanIngest:
// valid sampling strategies
default:
return fmt.Errorf(
"invalid sampling_strategy %q, expected one of %q or %q",
cfg.SamplingStrategy,
samplingStrategyTraceComplete,
samplingStrategySpanIngest,
)
}
if cfg.NumShards > maxNumShards {
return fmt.Errorf("num_shards (%d) must not exceed %d", cfg.NumShards, maxNumShards)
}
// The TailStorage contract makes the caller responsible for serializing
// access, which multiple shard event loops cannot guarantee for a shared
// extension instance. Sharding support belongs in the storage layer.
if cfg.NumShards > 1 && cfg.TailStorageID != nil {
return errors.New("num_shards greater than 1 is not supported with tail_storage")
}
if cfg.TailStorageID != nil && !tailstorageextension.IsFeatureGateEnabled() {
return fmt.Errorf(
"'tail_storage' requires the %q feature gate to be enabled, use --feature-gates=+%s",
tailstorageextension.FeatureGateID,
tailstorageextension.FeatureGateID,
)
}
return nil
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package tailsamplingprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor"
import (
"go.opentelemetry.io/collector/component"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/sampling"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/pkg/samplingpolicy"
)
func getNewDropPolicy(settings component.TelemetrySettings, config *DropCfg, policyExtensions map[string]samplingpolicy.Extension) (samplingpolicy.Evaluator, error) {
subPolicyEvaluators := make([]samplingpolicy.Evaluator, len(config.SubPolicyCfg))
for i := range config.SubPolicyCfg {
policyCfg := &config.SubPolicyCfg[i]
policy, err := getDropSubPolicyEvaluator(settings, policyCfg, policyExtensions)
if err != nil {
return nil, err
}
subPolicyEvaluators[i] = policy
}
return sampling.NewDrop(settings.Logger, subPolicyEvaluators), nil
}
// Return instance of and sub-policy
func getDropSubPolicyEvaluator(settings component.TelemetrySettings, cfg *AndSubPolicyCfg, policyExtensions map[string]samplingpolicy.Extension) (samplingpolicy.Evaluator, error) {
if cfg.Type == Not {
return getNewNotPolicy(settings, &cfg.NotCfg, policyExtensions)
}
return getSharedPolicyEvaluator(settings, &cfg.sharedPolicyCfg, policyExtensions)
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:generate make mdatagen
package tailsamplingprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor"
import (
"context"
"time"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/processor"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/metadata"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/telemetry"
)
// NewFactory returns a new factory for the Tail Sampling processor.
func NewFactory() processor.Factory {
return processor.NewFactory(
metadata.Type,
createDefaultConfig,
processor.WithTraces(createTracesProcessor, metadata.TracesStability),
)
}
func createDefaultConfig() component.Config {
return &Config{
DecisionWait: 30 * time.Second,
NumTraces: 50000,
NumShards: 1,
SampleOnFirstMatch: false,
SamplingStrategy: samplingStrategyTraceComplete,
}
}
func createTracesProcessor(
ctx context.Context,
params processor.Settings,
cfg component.Config,
nextConsumer consumer.Traces,
) (processor.Traces, error) {
tCfg := cfg.(*Config)
if telemetry.IsRecordPolicyEnabled() {
tCfg.Options = append(tCfg.Options, withRecordPolicy())
}
if telemetry.IsUseTracestateEnabled() {
tCfg.Options = append(tCfg.Options, withUseTracestate())
}
return newTracesProcessor(ctx, params, nextConsumer, *tCfg)
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package idbatcher defines a pipeline of fixed size in which the
// elements are batches of ids.
package idbatcher // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/idbatcher"
import (
"errors"
"math"
"sync"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// ErrInvalidNumBatches occurs when an invalid number of batches is specified.
var ErrInvalidNumBatches = errors.New("invalid number of batches, it must be greater than zero")
// Batch is the type of batches held by the Batcher. It uses a set in order to merge batches efficiently.
type Batch map[pcommon.TraceID]struct{}
// Batcher behaves like a pipeline of batches that has a fixed number of batches in the pipe
// and a new batch being built outside of the pipe. Items can be concurrently added to the batch
// currently being built. When the batch being built is closed, the oldest batch in the pipe
// is pushed out so the one just closed can be put on the end of the pipe (this is done as an
// atomic operation). The caller is in control of when a batch is completed and a new one should
// be started.
type Batcher interface {
// AddToCurrentBatch puts the given id on the batch being currently built. The client is in charge
// of limiting the growth of the current batch if appropriate for its scenario. It can
// either call CloseCurrentAndTakeFirstBatch earlier or stop adding new items depending on what is
// required by the scenario.
AddToCurrentBatch(id pcommon.TraceID) uint64
// MoveToEarlierBatch tries to move the trace from the current batch to a
// batch that is only a few batches from now. If the current batch will be
// processed before the proposed batch it will do nothing. Returns the
// batch that the trace will now be a part of (which may stay the same).
MoveToEarlierBatch(id pcommon.TraceID, traceCurrentBatch, batchesFromNow uint64) uint64
// RemoveFromBatch will remove the trace from the given batch.
// If the batch is not in the range of batches then it is a noop.
RemoveFromBatch(id pcommon.TraceID, batch uint64)
// CloseCurrentAndTakeFirstBatch takes the batch at the front of the pipe, and moves the current
// batch to the end of the pipe, creating a new batch to receive new items. This operation should
// be atomic.
// It returns the batch that was in front of the pipe and a boolean that if true indicates that
// there are more batches to be retrieved.
CloseCurrentAndTakeFirstBatch() (Batch, bool)
// Stop informs that no more items are going to be batched and the pipeline can be read until it
// is empty. After this method is called attempts to enqueue new items will panic.
Stop()
}
var _ Batcher = (*batcher)(nil)
type batcher struct {
takeID uint64
batches []Batch
// mux protects any batch storing/moving ids.
mux sync.Mutex
currentBatch Batch
newBatchesInitialCapacity uint64
lastBatchID uint64
stopped bool
}
// New creates a Batcher that will hold numBatches in its pipeline, having a channel with
// batchChannelSize to receive new items. New batches will be created with capacity set to
// newBatchesInitialCapacity.
func New(numBatches, newBatchesInitialCapacity uint64) (Batcher, error) {
if numBatches < 1 {
return nil, ErrInvalidNumBatches
}
if newBatchesInitialCapacity == 0 {
// Always allocate a small map rather than sending a size hint of 0.
// As the batcher runs it will allocate based on previous batch sizes.
newBatchesInitialCapacity = 10
}
batcher := &batcher{
batches: make([]Batch, numBatches),
currentBatch: make(Batch, newBatchesInitialCapacity),
newBatchesInitialCapacity: newBatchesInitialCapacity,
lastBatchID: math.MaxUint64,
}
return batcher, nil
}
func (b *batcher) AddToCurrentBatch(id pcommon.TraceID) uint64 {
b.mux.Lock()
defer b.mux.Unlock()
b.currentBatch[id] = struct{}{}
return b.takeID + uint64(len(b.batches))
}
func (b *batcher) MoveToEarlierBatch(id pcommon.TraceID, traceCurrentBatch, batchesFromNow uint64) uint64 {
b.mux.Lock()
defer b.mux.Unlock()
proposedBatch := b.takeID + batchesFromNow
// Only move the batch if it is earlier.
if proposedBatch >= traceCurrentBatch {
return traceCurrentBatch
}
// Check if the trace's batch is the batch currently being added to.
currentBatchID := b.takeID + uint64(len(b.batches))
if traceCurrentBatch == currentBatchID {
delete(b.currentBatch, id)
} else {
currentIdx := traceCurrentBatch % uint64(len(b.batches))
delete(b.batches[currentIdx], id)
}
proposedIdx := proposedBatch % uint64(len(b.batches))
if b.batches[proposedIdx] == nil {
b.batches[proposedIdx] = make(Batch, b.newBatchesInitialCapacity)
}
b.batches[proposedIdx][id] = struct{}{}
return proposedBatch
}
func (b *batcher) RemoveFromBatch(id pcommon.TraceID, batch uint64) {
b.mux.Lock()
defer b.mux.Unlock()
currentBatchID := b.takeID + uint64(len(b.batches))
if batch == currentBatchID {
delete(b.currentBatch, id)
} else if batch >= b.takeID && batch < currentBatchID {
delete(b.batches[batch%uint64(len(b.batches))], id)
}
// Nothing to remove if we are outside of the batch range.
}
func (b *batcher) CloseCurrentAndTakeFirstBatch() (Batch, bool) {
b.mux.Lock()
defer b.mux.Unlock()
if b.takeID < b.lastBatchID {
takeIdx := b.takeID % uint64(len(b.batches))
readBatch := b.batches[takeIdx]
if !b.stopped {
nextBatch := make(Batch, max(b.newBatchesInitialCapacity, uint64(len(readBatch))))
b.batches[takeIdx] = b.currentBatch
b.currentBatch = nextBatch
}
b.takeID++
return readBatch, true
}
readBatch := b.currentBatch
b.currentBatch = nil
return readBatch, false
}
func (b *batcher) Stop() {
b.mux.Lock()
defer b.mux.Unlock()
b.stopped = true
b.lastBatchID = b.takeID + uint64(len(b.batches))
}
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"errors"
"sync"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/trace"
)
func Meter(settings component.TelemetrySettings) metric.Meter {
return settings.MeterProvider.Meter("github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor")
}
func Tracer(settings component.TelemetrySettings) trace.Tracer {
return settings.TracerProvider.Tracer("github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor")
}
// TelemetryBuilder provides an interface for components to report telemetry
// as defined in metadata and user config.
type TelemetryBuilder struct {
meter metric.Meter
mu sync.Mutex
registrations []metric.Registration
ProcessorTailSamplingCountBytesSampled metric.Int64Counter
ProcessorTailSamplingCountSpansSampled metric.Int64Counter
ProcessorTailSamplingCountSpansWithUnparseableTracestate metric.Int64Counter
ProcessorTailSamplingCountTracesSampled metric.Int64Counter
ProcessorTailSamplingEarlyReleasesFromCacheDecision metric.Int64Counter
ProcessorTailSamplingGlobalCountTracesSampled metric.Int64Counter
ProcessorTailSamplingNewTraceIDReceived metric.Int64Counter
ProcessorTailSamplingSamplingDecisionTimerLatency metric.Int64Histogram
ProcessorTailSamplingSamplingLateSpanAge metric.Int64Histogram
ProcessorTailSamplingSamplingPolicyEvaluationError metric.Int64Counter
ProcessorTailSamplingSamplingPolicyExecutionCount metric.Int64Counter
ProcessorTailSamplingSamplingPolicyExecutionTimeSum metric.Int64Counter
ProcessorTailSamplingSamplingTraceDroppedTooEarly metric.Int64Counter
ProcessorTailSamplingSamplingTraceRemovalAge metric.Int64Histogram
ProcessorTailSamplingSamplingTracesOnMemory metric.Int64Gauge
ProcessorTailSamplingTracesDroppedTooLarge metric.Int64Counter
}
// TelemetryBuilderOption applies changes to default builder.
type TelemetryBuilderOption interface {
apply(*TelemetryBuilder)
}
type telemetryBuilderOptionFunc func(mb *TelemetryBuilder)
func (tbof telemetryBuilderOptionFunc) apply(mb *TelemetryBuilder) {
tbof(mb)
}
// Shutdown unregister all registered callbacks for async instruments.
func (builder *TelemetryBuilder) Shutdown() {
builder.mu.Lock()
defer builder.mu.Unlock()
for _, reg := range builder.registrations {
reg.Unregister()
}
}
// NewTelemetryBuilder provides a struct with methods to update all internal telemetry
// for a component
func NewTelemetryBuilder(settings component.TelemetrySettings, options ...TelemetryBuilderOption) (*TelemetryBuilder, error) {
builder := TelemetryBuilder{}
for _, op := range options {
op.apply(&builder)
}
builder.meter = Meter(settings)
var err, errs error
builder.ProcessorTailSamplingCountBytesSampled, err = builder.meter.Int64Counter(
"otelcol_processor_tail_sampling_count_bytes_sampled",
metric.WithDescription("Count of bytes that were sampled or not per sampling policy [Development]"),
metric.WithUnit("By"),
)
errs = errors.Join(errs, err)
builder.ProcessorTailSamplingCountSpansSampled, err = builder.meter.Int64Counter(
"otelcol_processor_tail_sampling_count_spans_sampled",
metric.WithDescription("Count of spans that were sampled or not per sampling policy [Development]"),
metric.WithUnit("{spans}"),
)
errs = errors.Join(errs, err)
builder.ProcessorTailSamplingCountSpansWithUnparseableTracestate, err = builder.meter.Int64Counter(
"otelcol_processor_tail_sampling_count_spans_with_unparseable_tracestate",
metric.WithDescription("Count of spans skipped while rewriting the effective `th` because their tracestate could not be parsed [Development]"),
metric.WithUnit("{spans}"),
)
errs = errors.Join(errs, err)
builder.ProcessorTailSamplingCountTracesSampled, err = builder.meter.Int64Counter(
"otelcol_processor_tail_sampling_count_traces_sampled",
metric.WithDescription("Count of traces that were sampled or not per sampling policy [Development]"),
metric.WithUnit("{traces}"),
)
errs = errors.Join(errs, err)
builder.ProcessorTailSamplingEarlyReleasesFromCacheDecision, err = builder.meter.Int64Counter(
"otelcol_processor_tail_sampling_early_releases_from_cache_decision",
metric.WithDescription("Number of spans that were able to be immediately released due to a decision cache hit. [Development]"),
metric.WithUnit("{spans}"),
)
errs = errors.Join(errs, err)
builder.ProcessorTailSamplingGlobalCountTracesSampled, err = builder.meter.Int64Counter(
"otelcol_processor_tail_sampling_global_count_traces_sampled",
metric.WithDescription("Global count of traces that were sampled or not by at least one policy [Development]"),
metric.WithUnit("{traces}"),
)
errs = errors.Join(errs, err)
builder.ProcessorTailSamplingNewTraceIDReceived, err = builder.meter.Int64Counter(
"otelcol_processor_tail_sampling_new_trace_id_received",
metric.WithDescription("Counts the arrival of new traces [Development]"),
metric.WithUnit("{traces}"),
)
errs = errors.Join(errs, err)
builder.ProcessorTailSamplingSamplingDecisionTimerLatency, err = builder.meter.Int64Histogram(
"otelcol_processor_tail_sampling_sampling_decision_timer_latency",
metric.WithDescription("Latency (in milliseconds) of each run of the sampling decision timer [Development]"),
metric.WithUnit("ms"),
metric.WithExplicitBucketBoundaries([]float64{1, 2, 5, 10, 25, 50, 75, 100, 150, 200, 300, 400, 500, 750, 1000, 2000, 3000, 4000, 5000, 10000, 20000, 30000, 50000}...),
)
errs = errors.Join(errs, err)
builder.ProcessorTailSamplingSamplingLateSpanAge, err = builder.meter.Int64Histogram(
"otelcol_processor_tail_sampling_sampling_late_span_age",
metric.WithDescription("Time (in seconds) from the sampling decision was taken and the arrival of a late span [Development]"),
metric.WithUnit("s"),
)
errs = errors.Join(errs, err)
builder.ProcessorTailSamplingSamplingPolicyEvaluationError, err = builder.meter.Int64Counter(
"otelcol_processor_tail_sampling_sampling_policy_evaluation_error",
metric.WithDescription("Count of sampling policy evaluation errors [Development]"),
metric.WithUnit("{errors}"),
)
errs = errors.Join(errs, err)
builder.ProcessorTailSamplingSamplingPolicyExecutionCount, err = builder.meter.Int64Counter(
"otelcol_processor_tail_sampling_sampling_policy_execution_count",
metric.WithDescription("Total number of executions of a specific sampling policy [Development]"),
metric.WithUnit("{executions}"),
)
errs = errors.Join(errs, err)
builder.ProcessorTailSamplingSamplingPolicyExecutionTimeSum, err = builder.meter.Int64Counter(
"otelcol_processor_tail_sampling_sampling_policy_execution_time_sum",
metric.WithDescription("Total time spent (in microseconds) executing a specific sampling policy [Development]"),
metric.WithUnit("us"),
)
errs = errors.Join(errs, err)
builder.ProcessorTailSamplingSamplingTraceDroppedTooEarly, err = builder.meter.Int64Counter(
"otelcol_processor_tail_sampling_sampling_trace_dropped_too_early",
metric.WithDescription("Count of traces that needed to be dropped before the configured wait time [Development]"),
metric.WithUnit("{traces}"),
)
errs = errors.Join(errs, err)
builder.ProcessorTailSamplingSamplingTraceRemovalAge, err = builder.meter.Int64Histogram(
"otelcol_processor_tail_sampling_sampling_trace_removal_age",
metric.WithDescription("Time (in seconds) from arrival of a new trace until its removal from memory [Development]"),
metric.WithUnit("s"),
)
errs = errors.Join(errs, err)
builder.ProcessorTailSamplingSamplingTracesOnMemory, err = builder.meter.Int64Gauge(
"otelcol_processor_tail_sampling_sampling_traces_on_memory",
metric.WithDescription("Tracks the number of traces current on memory [Development]"),
metric.WithUnit("{traces}"),
)
errs = errors.Join(errs, err)
builder.ProcessorTailSamplingTracesDroppedTooLarge, err = builder.meter.Int64Counter(
"otelcol_processor_tail_sampling_traces_dropped_too_large",
metric.WithDescription("Count of traces that were dropped because they were too large [Development]"),
metric.WithUnit("{traces}"),
)
errs = errors.Join(errs, err)
return &builder, errs
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sampling // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/sampling"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.uber.org/zap"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/pkg/samplingpolicy"
)
type alwaysSample struct {
logger *zap.Logger
}
var _ samplingpolicy.Evaluator = (*alwaysSample)(nil)
// NewAlwaysSample creates a policy evaluator the samples all traces.
func NewAlwaysSample(settings component.TelemetrySettings) samplingpolicy.Evaluator {
return &alwaysSample{
logger: settings.Logger,
}
}
// Evaluate looks at the trace data and returns a corresponding SamplingDecision.
func (as *alwaysSample) Evaluate(context.Context, pcommon.TraceID, *samplingpolicy.TraceData) (samplingpolicy.Decision, error) {
as.logger.Debug("Evaluating spans in always-sample filter")
return samplingpolicy.Sampled, nil
}
func (*alwaysSample) IsStateful() bool {
return false
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sampling // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/sampling"
import (
"context"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.uber.org/zap"
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/sampling"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/pkg/samplingpolicy"
)
type And struct {
// the subpolicy evaluators
subpolicies []samplingpolicy.ThresholdEvaluator
logger *zap.Logger
}
var (
_ samplingpolicy.Evaluator = (*And)(nil)
_ samplingpolicy.ThresholdEvaluator = (*And)(nil)
)
func NewAnd(
logger *zap.Logger,
subpolicies []samplingpolicy.Evaluator,
) samplingpolicy.Evaluator {
wrapped := make([]samplingpolicy.ThresholdEvaluator, len(subpolicies))
for i, sub := range subpolicies {
wrapped[i] = samplingpolicy.AsThresholdEvaluator(sub)
}
and := &And{
subpolicies: wrapped,
logger: logger,
}
// Only advertise batch support when a sub-policy actually needs it
// (e.g. a nested rate_limiting), so plain `and` policies are not
// pulled into the processor's batch pre-pass.
for _, sub := range wrapped {
if _, ok := sub.(BatchEvaluator); ok {
return &batchAnd{And: and}
}
}
return and
}
// Evaluate looks at the trace data and returns a corresponding SamplingDecision.
func (c *And) Evaluate(ctx context.Context, traceID pcommon.TraceID, trace *samplingpolicy.TraceData) (samplingpolicy.Decision, error) {
d, _, err := c.EvaluateWithThreshold(ctx, traceID, trace)
return d, err
}
// EvaluateWithThreshold returns Sampled iff all sub-policies return
// Sampled. The reported threshold is the most aggressive (largest)
// threshold across sub-policies, because a trace passes AND iff it
// passes the most aggressive sub-policy.
func (c *And) EvaluateWithThreshold(ctx context.Context, traceID pcommon.TraceID, trace *samplingpolicy.TraceData) (samplingpolicy.Decision, sampling.Threshold, error) {
threshold := sampling.AlwaysSampleThreshold
for _, sub := range c.subpolicies {
d, subTh, err := sub.EvaluateWithThreshold(ctx, traceID, trace)
if err != nil {
return samplingpolicy.Unspecified, sampling.AlwaysSampleThreshold, err
}
//nolint:staticcheck // SA1019: Use of inverted decisions until they are fully removed.
if d == samplingpolicy.NotSampled || d == samplingpolicy.InvertNotSampled {
return samplingpolicy.NotSampled, sampling.AlwaysSampleThreshold, nil
}
if sampling.ThresholdGreater(subTh, threshold) {
threshold = subTh
}
}
return samplingpolicy.Sampled, threshold, nil
}
func (c *And) IsStateful() bool {
for _, sub := range c.subpolicies {
if sub.IsStateful() {
return true
}
}
return false
}
// batchAnd is an And that contains at least one batch sub-policy (e.g. a
// rate_limiting policy). It only passes traces that match the non-batch
// sub-policies to the BatchEvaluator.
type batchAnd struct {
*And
}
var (
_ samplingpolicy.Evaluator = (*batchAnd)(nil)
_ BatchEvaluator = (*batchAnd)(nil)
)
// CalculateThreshold narrows the batch to traces that pass every non-batch
// sub-policy and forwards that subset to each nested batch sub-policy.
func (c *batchAnd) CalculateThreshold(ctx context.Context, batch []*samplingpolicy.TraceData) {
candidates := make([]*samplingpolicy.TraceData, 0, len(batch))
for _, td := range batch {
if c.candidate(ctx, td) {
candidates = append(candidates, td)
}
}
for _, sub := range c.subpolicies {
if be, ok := sub.(BatchEvaluator); ok {
be.CalculateThreshold(ctx, candidates)
}
}
}
// candidate reports whether a trace passes every non-batch sub-policy, and
// is therefore eligible to be considered by the nested batch sub-policy.
func (c *batchAnd) candidate(ctx context.Context, td *samplingpolicy.TraceData) bool {
id := traceIDOf(td)
for _, sub := range c.subpolicies {
if _, ok := sub.(BatchEvaluator); ok {
// Decided by the batch pass below, not part of the filter.
continue
}
d, err := sub.Evaluate(ctx, id, td)
if err != nil {
// Mirror And: a failing sub-policy means the trace is not
// sampled, so it is not a candidate for the budget.
return false
}
//nolint:staticcheck // SA1019: Use of inverted decisions until they are fully removed.
if d == samplingpolicy.NotSampled || d == samplingpolicy.InvertNotSampled {
return false
}
}
return true
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sampling // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/sampling"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.uber.org/zap"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/pkg/samplingpolicy"
)
type booleanAttributeFilter struct {
key string
value bool
logger *zap.Logger
invertMatch bool
}
var _ samplingpolicy.Evaluator = (*booleanAttributeFilter)(nil)
// NewBooleanAttributeFilter creates a policy evaluator that samples all traces with
// the given attribute that match the supplied boolean value.
func NewBooleanAttributeFilter(settings component.TelemetrySettings, key string, value, invertMatch bool) samplingpolicy.Evaluator {
return &booleanAttributeFilter{
key: key,
value: value,
logger: settings.Logger,
invertMatch: invertMatch,
}
}
// Evaluate looks at the trace data and returns a corresponding SamplingDecision.
func (baf *booleanAttributeFilter) Evaluate(_ context.Context, _ pcommon.TraceID, trace *samplingpolicy.TraceData) (samplingpolicy.Decision, error) {
batches := trace.ReceivedBatches
if baf.invertMatch {
return invertHasResourceOrSpanWithCondition(
batches,
func(resource pcommon.Resource) bool {
if v, ok := resource.Attributes().Get(baf.key); ok {
value := v.Bool()
return value != baf.value
}
return true
},
func(span ptrace.Span) bool {
if v, ok := span.Attributes().Get(baf.key); ok {
value := v.Bool()
return value != baf.value
}
return true
},
), nil
}
return hasResourceOrSpanWithCondition(
batches,
func(resource pcommon.Resource) bool {
if v, ok := resource.Attributes().Get(baf.key); ok {
value := v.Bool()
return value == baf.value
}
return false
},
func(span ptrace.Span) bool {
if v, ok := span.Attributes().Get(baf.key); ok {
value := v.Bool()
return value == baf.value
}
return false
},
), nil
}
func (*booleanAttributeFilter) IsStateful() bool {
return false
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sampling // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/sampling"
import (
"context"
"sort"
"time"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.uber.org/zap"
"golang.org/x/time/rate"
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/sampling"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/pkg/samplingpolicy"
)
// BatchEvaluator is implemented by policies whose threshold depends on the
// whole group of traces eligible for a decision at once, rather than a
// single trace in isolation. rate_limiting is the canonical example: to
// report a consistent threshold it must sort the group by randomness and
// cut where the span budget runs out.
type BatchEvaluator interface {
samplingpolicy.ThresholdEvaluator
// CalculateThreshold calculates and stores the threshold to be used for the passed in batch.
CalculateThreshold(ctx context.Context, batch []*samplingpolicy.TraceData)
}
// traceCostFunc reports how much of a limiter's budget a single trace
// consumes: spans for rate_limiting, protobuf-marshaled bytes for
// bytes_limiting.
type traceCostFunc func(*samplingpolicy.TraceData) int64
// traceSpanCount reports the number of spans in a trace, the unit the
// rate_limiting policy budgets in. bytes_limiting uses calculateTraceSize.
func traceSpanCount(trace *samplingpolicy.TraceData) int64 {
return trace.SpanCount
}
// budgetLimiter is a token-bucket sampling algorithm that calculates a tracestate threshold cutoff for a batch of traces.
type budgetLimiter struct {
limiter *rate.Limiter
// cost reports the tokens a single trace consumes from the bucket.
cost traceCostFunc
logger *zap.Logger
threshold sampling.Threshold
// thresholdCalculated controls if we have calculated the threshold and
// therefore should use it in EvaluateWithThreshold. If the threshold was
// never calculated then the budgetLimiter is used in a component that does
// not support tracestate yet.
thresholdCalculated bool
}
type budgetItem struct {
randomness sampling.Randomness
cost int64
}
// newBudgetLimiter builds a limiter that refills tokensPerSecond tokens per
// second into a bucket holding at most burstCapacity, measuring each trace
// with cost.
func newBudgetLimiter(settings component.TelemetrySettings, tokensPerSecond, burstCapacity int64, cost traceCostFunc) *budgetLimiter {
return &budgetLimiter{
limiter: rate.NewLimiter(rate.Limit(tokensPerSecond), int(burstCapacity)),
cost: cost,
logger: settings.Logger,
}
}
var (
_ samplingpolicy.Evaluator = (*budgetLimiter)(nil)
_ samplingpolicy.ThresholdEvaluator = (*budgetLimiter)(nil)
_ BatchEvaluator = (*budgetLimiter)(nil)
)
// CalculateThreshold sets the threshold at the point where the
// highest-randomness traces in batch, exhaust the currently available budget.
func (b *budgetLimiter) CalculateThreshold(_ context.Context, batch []*samplingpolicy.TraceData) {
b.thresholdCalculated = true
now := time.Now()
budget := int64(b.limiter.TokensAt(now))
burst := int64(b.limiter.Burst())
items := make([]budgetItem, 0, len(batch))
for _, td := range batch {
cost := b.cost(td)
if cost > burst {
// Can never fit even a fully-refilled bucket; excluding it here
// keeps it from starving traces that do fit (see EvaluateWithThreshold).
continue
}
items = append(items, budgetItem{
randomness: resolveRandomness(traceIDOf(td), td.ReceivedBatches),
cost: cost,
})
}
// Sort by randomness descending: consistent sampling keeps the
// traces with the highest randomness (threshold <= randomness), so
// spending the budget from the top yields a single clean threshold.
sort.SliceStable(items, func(i, j int) bool {
return items[i].randomness.Unsigned() > items[j].randomness.Unsigned()
})
var cumulative int64
kept := len(items)
for i, it := range items {
if cumulative+it.cost > budget {
kept = i
break
}
cumulative += it.cost
}
switch {
case kept == len(items):
b.threshold = sampling.AlwaysSampleThreshold
case kept == 0:
b.threshold = sampling.NeverSampleThreshold
default:
// A threshold can't separate traces with equal randomness, so if the
// first rejected trace ties the last kept one, drop the whole tied
// group -- keeps the invariant exact (kept: th <= R, dropped: th > R)
// even though 56-bit ties are vanishingly unlikely.
firstRejected := items[kept].randomness.Unsigned()
for kept > 0 && items[kept-1].randomness.Unsigned() == firstRejected {
kept--
cumulative -= items[kept].cost
}
if kept == 0 {
b.threshold = sampling.NeverSampleThreshold
} else {
// Set the threshold to the priority (randomness) of the first
// trace that causes the budget to be exceeded. See (Ting,
// "Adaptive Threshold Sampling," §3.1: arxiv.org/abs/1708.04970).
// Note that if the per second budget is not double the maximum
// trace cost then future extrapolation can break down. The +1 is
// because we sample on threshold <= randomness so we need to move
// the position by 1.
b.threshold, _ = sampling.UnsignedToThreshold(firstRejected + 1)
}
}
if cumulative > 0 {
// Update the limiter here as we have now decided which traces will be
// sampled. This avoids ordering being necessary when calling
// EvaluateWithThreshold alongside other policies in an and.
b.limiter.AllowN(now, int(cumulative))
}
}
// Evaluate looks at the trace data and returns a corresponding SamplingDecision.
func (b *budgetLimiter) Evaluate(ctx context.Context, id pcommon.TraceID, trace *samplingpolicy.TraceData) (samplingpolicy.Decision, error) {
d, _, err := b.EvaluateWithThreshold(ctx, id, trace)
return d, err
}
// EvaluateWithThreshold compares this trace's own randomness against the
// threshold CalculateThreshold last computed.
//
// A policy that has never does not calculate the threshold falls back to live
// per-trace token bucket admission.
func (b *budgetLimiter) EvaluateWithThreshold(_ context.Context, id pcommon.TraceID, trace *samplingpolicy.TraceData) (samplingpolicy.Decision, sampling.Threshold, error) {
cost := b.cost(trace)
if cost > int64(b.limiter.Burst()) {
// Never fits, regardless of randomness or threshold: CalculateThreshold
// excludes it from the batch, so the reported threshold says nothing
// about it either way.
return samplingpolicy.NotSampled, sampling.AlwaysSampleThreshold, nil
}
if !b.thresholdCalculated {
if b.limiter.AllowN(time.Now(), int(cost)) {
return samplingpolicy.Sampled, sampling.AlwaysSampleThreshold, nil
}
return samplingpolicy.NotSampled, sampling.AlwaysSampleThreshold, nil
}
randomness := resolveRandomness(id, trace.ReceivedBatches)
if b.threshold.ShouldSample(randomness) {
return samplingpolicy.Sampled, b.threshold, nil
}
return samplingpolicy.NotSampled, sampling.AlwaysSampleThreshold, nil
}
func (*budgetLimiter) IsStateful() bool {
return true
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sampling // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/sampling"
import (
"context"
"time"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/ptrace"
"golang.org/x/time/rate"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/metadata"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/pkg/samplingpolicy"
)
type bytesLimiting struct {
// Rate limiter using golang.org/x/time/rate for efficient token bucket implementation
limiter *rate.Limiter
}
var _ samplingpolicy.Evaluator = (*bytesLimiting)(nil)
// NewBytesLimiting creates a policy evaluator that samples traces based on byte limit per second using a token bucket algorithm.
// The bucket capacity defaults to 2x the bytes per second to allow for reasonable burst traffic.
func NewBytesLimiting(settings component.TelemetrySettings, bytesPerSecond int64) samplingpolicy.Evaluator {
return NewBytesLimitingWithBurstCapacity(settings, bytesPerSecond, bytesPerSecond*2)
}
// NewBytesLimitingWithBurstCapacity creates a policy evaluator with custom burst capacity.
// Uses golang.org/x/time/rate.Limiter for efficient, thread-safe token bucket implementation.
func NewBytesLimitingWithBurstCapacity(settings component.TelemetrySettings, bytesPerSecond, burstCapacity int64) samplingpolicy.Evaluator {
if metadata.ProcessorTailsamplingprocessorUsetracestateFeatureGate.IsEnabled() {
return newBudgetLimiter(settings, bytesPerSecond, burstCapacity, calculateTraceSize)
}
// Create rate limiter with specified rate and burst capacity
// rate.Limit is tokens per second (bytes per second in our case)
// burst capacity is the maximum number of tokens (bytes) that can be consumed in a single request
limiter := rate.NewLimiter(rate.Limit(bytesPerSecond), int(burstCapacity))
return &bytesLimiting{
limiter: limiter,
}
}
// Evaluate looks at the trace data and returns a corresponding SamplingDecision based on token bucket algorithm.
// Uses golang.org/x/time/rate.Limiter.AllowN() for efficient, thread-safe token consumption.
func (b *bytesLimiting) Evaluate(_ context.Context, _ pcommon.TraceID, trace *samplingpolicy.TraceData) (samplingpolicy.Decision, error) {
// Calculate the size of the trace in bytes
traceSize := calculateTraceSize(trace)
// Use AllowN to check if we can consume 'traceSize' tokens
// AllowN returns true if the limiter allows the event and false otherwise
// The limiter automatically handles token bucket refill and thread safety
if b.limiter.AllowN(time.Now(), int(traceSize)) {
return samplingpolicy.Sampled, nil
}
return samplingpolicy.NotSampled, nil
}
func (*bytesLimiting) IsStateful() bool {
return true
}
// calculateTraceSize calculates the accurate protobuf marshaled size of a trace in bytes
// using the OpenTelemetry Collector's built-in ProtoMarshaler.TracesSize() method
func calculateTraceSize(trace *samplingpolicy.TraceData) int64 {
// Use the OpenTelemetry Collector's built-in method for accurate size calculation
// This gives us the exact protobuf marshaled size
marshaler := &ptrace.ProtoMarshaler{}
return int64(marshaler.TracesSize(trace.ReceivedBatches))
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sampling // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/sampling"
import (
"context"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.uber.org/zap"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/pkg/samplingpolicy"
)
type subpolicy struct {
// the subpolicy evaluator
evaluator samplingpolicy.Evaluator
// spans per second allocated to each subpolicy
allocatedSPS int64
// spans per second that each subpolicy sampled in this period
sampledSPS int64
name string
}
// Composite evaluator and its internal data
type Composite struct {
// the subpolicy evaluators
subpolicies []*subpolicy
// maximum total spans per second that must be sampled
maxTotalSPS int64
// current unix timestamp second
currentSecond int64
// The time provider (can be different from clock for testing purposes)
timeProvider TimeProvider
logger *zap.Logger
recordSubPolicy bool
}
var _ samplingpolicy.Evaluator = (*Composite)(nil)
// SubPolicyEvalParams defines the evaluator and max rate for a sub-policy
type SubPolicyEvalParams struct {
Evaluator samplingpolicy.Evaluator
MaxSpansPerSecond int64
Name string
}
// NewComposite creates a policy evaluator that samples all subpolicies.
func NewComposite(
logger *zap.Logger,
maxTotalSpansPerSecond int64,
subPolicyParams []SubPolicyEvalParams,
timeProvider TimeProvider,
recordSubPolicy bool,
) samplingpolicy.Evaluator {
var subpolicies []*subpolicy
for i := range subPolicyParams {
sub := &subpolicy{}
sub.evaluator = subPolicyParams[i].Evaluator
sub.allocatedSPS = subPolicyParams[i].MaxSpansPerSecond
sub.name = subPolicyParams[i].Name
// We are just starting, so there is no previous input, set it to 0
sub.sampledSPS = 0
subpolicies = append(subpolicies, sub)
}
return &Composite{
maxTotalSPS: maxTotalSpansPerSecond,
subpolicies: subpolicies,
timeProvider: timeProvider,
logger: logger,
recordSubPolicy: recordSubPolicy,
}
}
// Evaluate looks at the trace data and returns a corresponding SamplingDecision.
func (c *Composite) Evaluate(ctx context.Context, traceID pcommon.TraceID, trace *samplingpolicy.TraceData) (samplingpolicy.Decision, error) {
// Rate limiting works by counting spans that are sampled during each 1 second
// time period. Until the total number of spans during a particular second
// exceeds the allocated number of spans-per-second the traces are sampled,
// once the limit is exceeded the traces are no longer sampled. The counter
// restarts at the beginning of each second.
// Current counters and rate limits are kept separately for each subpolicy.
currSecond := c.timeProvider.getCurSecond()
if c.currentSecond != currSecond {
// This is a new second
c.currentSecond = currSecond
// Reset counters
for i := range c.subpolicies {
c.subpolicies[i].sampledSPS = 0
}
}
for _, sub := range c.subpolicies {
decision, err := sub.evaluator.Evaluate(ctx, traceID, trace)
if err != nil {
return samplingpolicy.Unspecified, err
}
//nolint:staticcheck // SA1019: Use of inverted decisions until they are fully removed.
if decision == samplingpolicy.Sampled || decision == samplingpolicy.InvertSampled {
// The subpolicy made a decision to Sample. Now we need to make our decision.
// Calculate resulting SPS counter if we decide to sample this trace
spansInSecondIfSampled := sub.sampledSPS + trace.SpanCount
// Check if the rate will be within the allocated bandwidth.
if spansInSecondIfSampled <= sub.allocatedSPS && spansInSecondIfSampled <= c.maxTotalSPS {
sub.sampledSPS = spansInSecondIfSampled
// Let the sampling happen
if c.recordSubPolicy {
SetAttrOnScopeSpans(trace.ReceivedBatches, "tailsampling.composite_policy", sub.name)
}
return samplingpolicy.Sampled, nil
}
// We exceeded the rate limit. Don't sample this trace.
// Note that we will continue evaluating new incoming traces against
// allocated SPS, we do not update sub.sampledSPS here in order to give
// chance to another smaller trace to be accepted later.
return samplingpolicy.NotSampled, nil
}
}
return samplingpolicy.NotSampled, nil
}
func (c *Composite) IsStateful() bool {
for _, sub := range c.subpolicies {
if sub.evaluator.IsStateful() {
return true
}
}
return false
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sampling // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/sampling"
import (
"context"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.uber.org/zap"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/pkg/samplingpolicy"
)
type Drop struct {
// the subpolicy evaluators
subpolicies []samplingpolicy.Evaluator
logger *zap.Logger
}
func NewDrop(
logger *zap.Logger,
subpolicies []samplingpolicy.Evaluator,
) samplingpolicy.Evaluator {
return &Drop{
subpolicies: subpolicies,
logger: logger,
}
}
// Evaluate looks at the trace data and returns a corresponding SamplingDecision.
func (c *Drop) Evaluate(ctx context.Context, traceID pcommon.TraceID, trace *samplingpolicy.TraceData) (samplingpolicy.Decision, error) {
// The policy iterates over all sub-policies and returns Dropped if all
// sub-policies returned a Sampled Decision. If any subpolicy returns
// NotSampled, it returns NotSampled Decision.
for _, sub := range c.subpolicies {
decision, err := sub.Evaluate(ctx, traceID, trace)
if err != nil {
return samplingpolicy.Unspecified, err
}
//nolint:staticcheck // SA1019: Use of inverted decisions until they are fully removed.
if decision == samplingpolicy.NotSampled || decision == samplingpolicy.InvertNotSampled {
return samplingpolicy.NotSampled, nil
}
}
return samplingpolicy.Dropped, nil
}
func (c *Drop) IsStateful() bool {
for _, sub := range c.subpolicies {
if sub.IsStateful() {
return true
}
}
return false
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sampling // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/sampling"
import (
"context"
"time"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.uber.org/zap"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/pkg/samplingpolicy"
)
type latency struct {
logger *zap.Logger
thresholdMs int64
upperThresholdMs int64
}
var _ samplingpolicy.Evaluator = (*latency)(nil)
// NewLatency creates a policy evaluator sampling traces with a duration greater than a configured threshold
func NewLatency(settings component.TelemetrySettings, thresholdMs, upperThresholdMs int64) samplingpolicy.Evaluator {
return &latency{
logger: settings.Logger,
thresholdMs: thresholdMs,
upperThresholdMs: upperThresholdMs,
}
}
// Evaluate looks at the trace data and returns a corresponding SamplingDecision.
func (l *latency) Evaluate(_ context.Context, _ pcommon.TraceID, traceData *samplingpolicy.TraceData) (samplingpolicy.Decision, error) {
l.logger.Debug("Evaluating spans in latency filter")
batches := traceData.ReceivedBatches
var minTime pcommon.Timestamp
var maxTime pcommon.Timestamp
return hasSpanWithCondition(batches, func(span ptrace.Span) bool {
if minTime == 0 || span.StartTimestamp() < minTime {
minTime = span.StartTimestamp()
}
if maxTime == 0 || span.EndTimestamp() > maxTime {
maxTime = span.EndTimestamp()
}
// Timestamps are already nanoseconds since epoch. Subtracting them
// and dividing by a millisecond is the same truncation as
// time.Duration.Milliseconds, without converting each value to time.Time.
ms := int64(maxTime-minTime) / int64(time.Millisecond)
if l.upperThresholdMs == 0 {
return ms > l.thresholdMs
}
return l.thresholdMs < ms && ms <= l.upperThresholdMs
}), nil
}
// IsStateful determines if an evaluator can be used for ingest time decisions.
// In the case of a latency evaluator that is only possible if no upper
// threshold is set as a trace can always get longer in duration and become
// NotSampled.
func (l *latency) IsStateful() bool {
return l.upperThresholdMs > 0
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sampling // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/sampling"
import (
"context"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.uber.org/zap"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/pkg/samplingpolicy"
)
type not struct {
logger *zap.Logger
subPolicyEvaluator samplingpolicy.Evaluator
}
var _ samplingpolicy.Evaluator = (*not)(nil)
// NewNot creates a new not policy evaluator returns the opposite of the decision of the wrapped policy.
func NewNot(logger *zap.Logger, subPolicyEvaluator samplingpolicy.Evaluator) samplingpolicy.Evaluator {
return ¬{
logger: logger,
subPolicyEvaluator: subPolicyEvaluator,
}
}
// Evaluate looks at the trace data and returns a corresponding SamplingResult.
// The not policy return the opposite of the decision of the wrapped policy.
func (n *not) Evaluate(ctx context.Context, traceID pcommon.TraceID, trace *samplingpolicy.TraceData) (samplingpolicy.Decision, error) {
decision, err := n.subPolicyEvaluator.Evaluate(ctx, traceID, trace)
if err != nil {
n.logger.Debug("Evaluation error from sub-policy", zap.Error(err))
return decision, err
}
// Return the opposite of the decision
switch decision {
case samplingpolicy.Sampled:
return samplingpolicy.NotSampled, nil
case samplingpolicy.NotSampled:
return samplingpolicy.Sampled, nil
default:
// For any other decision types (Unspecified, Pending, Error), just let them bubble up.
return decision, nil
}
}
func (n *not) IsStateful() bool {
return n.subPolicyEvaluator.IsStateful()
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sampling // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/sampling"
import (
"context"
"math"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.uber.org/zap"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/pkg/samplingpolicy"
)
type numericAttributeFilter struct {
key string
minValue *int64
maxValue *int64
logger *zap.Logger
invertMatch bool
}
var _ samplingpolicy.Evaluator = (*numericAttributeFilter)(nil)
// NewNumericAttributeFilter creates a policy evaluator that samples all traces with
// the given attribute in the given numeric range. If minValue is nil, it will use math.MinInt64.
// If maxValue is nil, it will use math.MaxInt64. At least one of minValue or maxValue must be set.
func NewNumericAttributeFilter(settings component.TelemetrySettings, key string, minValue, maxValue *int64, invertMatch bool) samplingpolicy.Evaluator {
if minValue == nil && maxValue == nil {
settings.Logger.Error("At least one of minValue or maxValue must be set")
return nil
}
return &numericAttributeFilter{
key: key,
minValue: minValue,
maxValue: maxValue,
logger: settings.Logger,
invertMatch: invertMatch,
}
}
// Evaluate looks at the trace data and returns a corresponding SamplingDecision.
func (naf *numericAttributeFilter) Evaluate(_ context.Context, _ pcommon.TraceID, trace *samplingpolicy.TraceData) (samplingpolicy.Decision, error) {
batches := trace.ReceivedBatches
// Get the effective min/max values
minVal := int64(math.MinInt64)
if naf.minValue != nil {
minVal = *naf.minValue
}
maxVal := int64(math.MaxInt64)
if naf.maxValue != nil {
maxVal = *naf.maxValue
}
if naf.invertMatch {
return invertHasResourceOrSpanWithCondition(
batches,
func(resource pcommon.Resource) bool {
if v, ok := resource.Attributes().Get(naf.key); ok {
value := v.Int()
if value >= minVal && value <= maxVal {
return false
}
}
return true
},
func(span ptrace.Span) bool {
if v, ok := span.Attributes().Get(naf.key); ok {
value := v.Int()
if value >= minVal && value <= maxVal {
return false
}
}
return true
},
), nil
}
return hasResourceOrSpanWithCondition(
batches,
func(resource pcommon.Resource) bool {
if v, ok := resource.Attributes().Get(naf.key); ok {
value := v.Int()
if value >= minVal && value <= maxVal {
return true
}
}
return false
},
func(span ptrace.Span) bool {
if v, ok := span.Attributes().Get(naf.key); ok {
value := v.Int()
if value >= minVal && value <= maxVal {
return true
}
}
return false
},
), nil
}
func (*numericAttributeFilter) IsStateful() bool {
return false
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sampling // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/sampling"
import (
"context"
"errors"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.uber.org/zap"
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/filterottl"
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottlspan"
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottlspanevent"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/pkg/samplingpolicy"
)
type ottlConditionFilter struct {
sampleSpanExpr *ottl.ConditionSequence[*ottlspan.TransformContext]
sampleSpanEventExpr *ottl.ConditionSequence[*ottlspanevent.TransformContext]
errorMode ottl.ErrorMode
logger *zap.Logger
}
var _ samplingpolicy.Evaluator = (*ottlConditionFilter)(nil)
// NewOTTLConditionFilter looks at the trace data and returns a corresponding SamplingDecision.
func NewOTTLConditionFilter(settings component.TelemetrySettings, spanConditions, spanEventConditions []string, errMode ottl.ErrorMode) (samplingpolicy.Evaluator, error) {
filter := &ottlConditionFilter{
errorMode: errMode,
logger: settings.Logger,
}
var err error
if len(spanConditions) == 0 && len(spanEventConditions) == 0 {
return nil, errors.New("expected at least one OTTL condition to filter on")
}
if len(spanConditions) > 0 {
if filter.sampleSpanExpr, err = filterottl.NewBoolExprForSpanWithPathContextNames(spanConditions, filterottl.StandardSpanFuncs(), errMode, settings); err != nil {
return nil, err
}
}
if len(spanEventConditions) > 0 {
if filter.sampleSpanEventExpr, err = filterottl.NewBoolExprForSpanEventWithPathContextNames(spanEventConditions, filterottl.StandardSpanEventFuncs(), errMode, settings); err != nil {
return nil, err
}
}
return filter, nil
}
func (ocf *ottlConditionFilter) Evaluate(ctx context.Context, traceID pcommon.TraceID, trace *samplingpolicy.TraceData) (samplingpolicy.Decision, error) {
if ocf.logger.Core().Enabled(zap.DebugLevel) {
ocf.logger.Debug("Evaluating with OTTL conditions filter", zap.String("traceID", traceID.String()))
}
if ocf.sampleSpanExpr == nil && ocf.sampleSpanEventExpr == nil {
return samplingpolicy.NotSampled, nil
}
batches := trace.ReceivedBatches
for i := 0; i < batches.ResourceSpans().Len(); i++ {
rs := batches.ResourceSpans().At(i)
for j := 0; j < rs.ScopeSpans().Len(); j++ {
ss := rs.ScopeSpans().At(j)
for k := 0; k < ss.Spans().Len(); k++ {
span := ss.Spans().At(k)
var (
ok bool
err error
)
// Now we reach span level and begin evaluation with parsed expr.
// The evaluation will break when:
// 1. error happened.
// 2. "Sampled" decision made.
// Otherwise, it will keep evaluating and finally exit with "NotSampled" decision.
// Span evaluation
if ocf.sampleSpanExpr != nil {
tCtx := ottlspan.NewTransformContext(rs, ss, span)
ok, err = ocf.sampleSpanExpr.Eval(ctx, tCtx)
tCtx.Close()
if err != nil {
return samplingpolicy.Error, err
}
if ok {
return samplingpolicy.Sampled, nil
}
}
// Span event evaluation
if ocf.sampleSpanEventExpr != nil {
spanEvents := span.Events()
for l := 0; l < spanEvents.Len(); l++ {
tCtx := ottlspanevent.NewTransformContext(rs, ss, span, spanEvents.At(l))
ok, err = ocf.sampleSpanEventExpr.Eval(ctx, tCtx)
tCtx.Close()
if err != nil {
return samplingpolicy.Error, err
}
if ok {
return samplingpolicy.Sampled, nil
}
}
}
}
}
}
return samplingpolicy.NotSampled, nil
}
func (*ottlConditionFilter) IsStateful() bool {
return false
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sampling // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/sampling"
import (
"context"
"hash/fnv"
"math"
"math/big"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.uber.org/zap"
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/sampling"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/metadata"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/pkg/samplingpolicy"
)
const (
defaultHashSalt = "default-hash-seed"
)
type probabilisticSampler struct {
logger *zap.Logger
threshold uint64
hashSalt string
useTraceState bool
tracestateThreshold sampling.Threshold
}
var (
_ samplingpolicy.Evaluator = (*probabilisticSampler)(nil)
_ samplingpolicy.ThresholdEvaluator = (*probabilisticSampler)(nil)
)
// NewProbabilisticSampler creates a policy evaluator that samples a percentage of
// traces.
func NewProbabilisticSampler(settings component.TelemetrySettings, hashSalt string, samplingPercentage float64) samplingpolicy.Evaluator {
if hashSalt == "" {
hashSalt = defaultHashSalt
}
ratio := samplingPercentage / 100
otelThreshold, err := sampling.ProbabilityToThreshold(ratio)
if err != nil {
// ProbabilityToThreshold accepts [MinSamplingProbability, 1].
// Anything outside collapses to the nearest extreme: ratios at
// or above 1 always sample, ratios below MinSamplingProbability
// (including 0 and negatives) never sample.
if ratio >= 1 {
otelThreshold = sampling.AlwaysSampleThreshold
} else {
otelThreshold = sampling.NeverSampleThreshold
}
}
return &probabilisticSampler{
logger: settings.Logger,
// calculate threshold once
threshold: calculateThreshold(samplingPercentage / 100),
hashSalt: hashSalt,
useTraceState: metadata.ProcessorTailsamplingprocessorUsetracestateFeatureGate.IsEnabled(),
tracestateThreshold: otelThreshold,
}
}
// Evaluate looks at the trace data and returns a corresponding SamplingDecision.
func (s *probabilisticSampler) Evaluate(ctx context.Context, traceID pcommon.TraceID, trace *samplingpolicy.TraceData) (samplingpolicy.Decision, error) {
d, _, err := s.EvaluateWithThreshold(ctx, traceID, trace)
return d, err
}
// EvaluateWithThreshold makes the sampling decision and reports the
// effective OpenTelemetry threshold the policy would advertise on
// outgoing tracestate.
//
// When the usetracestate feature gate is enabled and the trace
// carries OpenTelemetry sampling information in its W3C tracestate
// (any "ot" section with rv and/or th), the decision uses an
// explicit "rv" if present, otherwise it derives randomness from the
// TraceID per the W3C Trace Context spec, and compares that against
// the configured threshold. Otherwise it falls back to the legacy
// FNV trace ID hash.
func (s *probabilisticSampler) EvaluateWithThreshold(_ context.Context, traceID pcommon.TraceID, trace *samplingpolicy.TraceData) (samplingpolicy.Decision, sampling.Threshold, error) {
s.logger.Debug("Evaluating spans in probabilistic filter")
if s.useTraceState {
if scan := scanOTelTracestate(trace.ReceivedBatches); scan.hasSamplingInfo {
rnd := scan.randomness
if !scan.hasRandomness {
rnd = sampling.TraceIDToRandomness(traceID)
}
if s.tracestateThreshold.ShouldSample(rnd) {
return samplingpolicy.Sampled, s.tracestateThreshold, nil
}
return samplingpolicy.NotSampled, s.tracestateThreshold, nil
}
}
if hashTraceID(s.hashSalt, traceID[:]) <= s.threshold {
// Report the configured OTel-equivalent threshold so
// downstream samplers can compute adjusted counts.
return samplingpolicy.Sampled, s.tracestateThreshold, nil
}
return samplingpolicy.NotSampled, s.tracestateThreshold, nil
}
func (*probabilisticSampler) IsStateful() bool {
return false
}
type tracestateScan struct {
// randomness is the first explicit `rv` found across any span's
// tracestate "ot" section (spec says all spans of a trace
// should share the same rv).
randomness sampling.Randomness
hasRandomness bool
// hasSamplingInfo is true if any span carries OTel sampling
// information (rv and/or th). When false, the sampler falls
// back to the legacy FNV trace ID hash.
hasSamplingInfo bool
}
// resolveRandomness returns the sampling randomness for a trace: the
// explicit `rv` from its W3C tracestate if present, otherwise the value
// derived from the trace ID per the W3C Trace Context spec. This is the
// same resolution the probabilistic policy uses, so policies that sort
// or threshold on randomness stay consistent with one another.
func resolveRandomness(id pcommon.TraceID, td ptrace.Traces) sampling.Randomness {
if scan := scanOTelTracestate(td); scan.hasRandomness {
return scan.randomness
}
return sampling.TraceIDToRandomness(id)
}
// traceIDOf returns the trace ID carried by td's spans, or the zero ID if
// it has none. All spans in a trace share the same trace ID, so the first
// span found is authoritative.
func traceIDOf(td *samplingpolicy.TraceData) pcommon.TraceID {
for _, rs := range td.ReceivedBatches.ResourceSpans().All() {
for _, ss := range rs.ScopeSpans().All() {
if spans := ss.Spans(); spans.Len() > 0 {
return spans.At(0).TraceID()
}
}
}
return pcommon.TraceID{}
}
// scanOTelTracestate finds the first OTel `rv` across the trace and
// notes whether any span carries OTel sampling info at all (rv or
// th). It short-circuits as soon as both are known.
func scanOTelTracestate(td ptrace.Traces) tracestateScan {
var result tracestateScan
for _, rs := range td.ResourceSpans().All() {
for _, ss := range rs.ScopeSpans().All() {
for _, span := range ss.Spans().All() {
ts, err := sampling.NewW3CTraceState(span.TraceState().AsRaw())
if err != nil {
continue
}
otts := ts.OTelValue()
if !result.hasRandomness {
if rv, ok := otts.RValueRandomness(); ok {
result.randomness = rv
result.hasRandomness = true
result.hasSamplingInfo = true
}
}
if !result.hasSamplingInfo {
if _, ok := otts.TValueThreshold(); ok {
result.hasSamplingInfo = true
}
}
if result.hasRandomness {
return result
}
}
}
}
return result
}
// calculateThreshold converts a ratio into a value between 0 and MaxUint64
func calculateThreshold(ratio float64) uint64 {
// Use big.Float and big.Int to calculate threshold because directly convert
// math.MaxUint64 to float64 will cause digits/bits to be cut off if the converted value
// doesn't fit into bits that are used to store digits for float64 in Golang
boundary := new(big.Float).SetInt(new(big.Int).SetUint64(math.MaxUint64))
res, _ := boundary.Mul(boundary, big.NewFloat(ratio)).Uint64()
return res
}
// hashTraceID creates a hash using the FNV-1a algorithm.
func hashTraceID(salt string, b []byte) uint64 {
hasher := fnv.New64a()
// the implementation fnv.Write() never returns an error, see hash/fnv/fnv.go
_, _ = hasher.Write([]byte(salt))
_, _ = hasher.Write(b)
return hasher.Sum64()
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sampling // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/sampling"
import (
"context"
"time"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.uber.org/zap"
"golang.org/x/time/rate"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/metadata"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/pkg/samplingpolicy"
)
type rateLimiting struct {
// Rate limiter using golang.org/x/time/rate for efficient token bucket implementation.
limiter *rate.Limiter
logger *zap.Logger
}
var _ samplingpolicy.Evaluator = (*rateLimiting)(nil)
// NewRateLimiting creates a policy evaluator that samples traces based on a span limit per second using a token bucket algorithm.
// The bucket capacity defaults to 2x the spans per second to allow for reasonable burst traffic.
func NewRateLimiting(settings component.TelemetrySettings, spansPerSecond int64) samplingpolicy.Evaluator {
return NewRateLimitingWithBurstCapacity(settings, spansPerSecond, spansPerSecond*2)
}
// NewRateLimitingWithBurstCapacity creates a rate limiting policy evaluator with a configurable
// burst capacity, using a token bucket algorithm. Tokens (spans) refill continuously at
// spansPerSecond and the bucket holds at most burstCapacity tokens. A single trace whose span
// count exceeds the burst capacity will not pass.
func NewRateLimitingWithBurstCapacity(settings component.TelemetrySettings, spansPerSecond, burstCapacity int64) samplingpolicy.Evaluator {
if metadata.ProcessorTailsamplingprocessorUsetracestateFeatureGate.IsEnabled() {
return newBudgetLimiter(settings, spansPerSecond, burstCapacity, traceSpanCount)
}
return &rateLimiting{
limiter: rate.NewLimiter(rate.Limit(spansPerSecond), int(burstCapacity)),
logger: settings.Logger,
}
}
// Evaluate looks at the trace data and returns a corresponding SamplingDecision based on token bucket consumption.
func (r *rateLimiting) Evaluate(_ context.Context, _ pcommon.TraceID, trace *samplingpolicy.TraceData) (samplingpolicy.Decision, error) {
r.logger.Debug("Evaluating spans in rate-limiting filter")
if r.limiter.AllowN(time.Now(), int(trace.SpanCount)) {
return samplingpolicy.Sampled, nil
}
return samplingpolicy.NotSampled, nil
}
func (*rateLimiting) IsStateful() bool {
return true
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sampling // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/sampling"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.uber.org/zap"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/pkg/samplingpolicy"
)
type spanCount struct {
logger *zap.Logger
minSpans int64
maxSpans int64
}
var _ samplingpolicy.Evaluator = (*spanCount)(nil)
// NewSpanCount creates a policy evaluator sampling traces with more than one span per trace
func NewSpanCount(settings component.TelemetrySettings, minSpans, maxSpans int32) samplingpolicy.Evaluator {
return &spanCount{
logger: settings.Logger,
minSpans: int64(minSpans),
maxSpans: int64(maxSpans),
}
}
// Evaluate looks at the trace data and returns a corresponding SamplingDecision.
func (c *spanCount) Evaluate(_ context.Context, _ pcommon.TraceID, traceData *samplingpolicy.TraceData) (samplingpolicy.Decision, error) {
c.logger.Debug("Evaluating spans counts in filter")
spanCount := traceData.SpanCount
switch {
case c.maxSpans == 0 && spanCount >= c.minSpans:
return samplingpolicy.Sampled, nil
case spanCount >= c.minSpans && spanCount <= c.maxSpans:
return samplingpolicy.Sampled, nil
default:
return samplingpolicy.NotSampled, nil
}
}
// IsStateful determines if an evaluator can be used for ingest time decisions.
// In the case of a span count evaluator that is only possible if no max span
// count is set as a trace can always receive more spans and become NotSampled.
func (c *spanCount) IsStateful() bool {
return c.maxSpans > 0
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sampling // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/sampling"
import (
"context"
"errors"
"fmt"
"slices"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.uber.org/zap"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/pkg/samplingpolicy"
)
type statusCodeFilter struct {
logger *zap.Logger
statusCodes []ptrace.StatusCode
}
var _ samplingpolicy.Evaluator = (*statusCodeFilter)(nil)
// NewStatusCodeFilter creates a policy evaluator that samples all traces with
// a given status code.
func NewStatusCodeFilter(settings component.TelemetrySettings, statusCodeString []string) (samplingpolicy.Evaluator, error) {
if len(statusCodeString) == 0 {
return nil, errors.New("expected at least one status code to filter on")
}
statusCodes := make([]ptrace.StatusCode, len(statusCodeString))
for i := range statusCodeString {
switch statusCodeString[i] {
case "OK":
statusCodes[i] = ptrace.StatusCodeOk
case "ERROR":
statusCodes[i] = ptrace.StatusCodeError
case "UNSET":
statusCodes[i] = ptrace.StatusCodeUnset
default:
return nil, fmt.Errorf("unknown status code %q, supported: OK, ERROR, UNSET", statusCodeString[i])
}
}
return &statusCodeFilter{
logger: settings.Logger,
statusCodes: statusCodes,
}, nil
}
// Evaluate looks at the trace data and returns a corresponding SamplingDecision.
func (r *statusCodeFilter) Evaluate(_ context.Context, _ pcommon.TraceID, trace *samplingpolicy.TraceData) (samplingpolicy.Decision, error) {
r.logger.Debug("Evaluating spans in status code filter")
batches := trace.ReceivedBatches
return hasSpanWithCondition(batches, func(span ptrace.Span) bool {
return slices.Contains(r.statusCodes, span.Status().Code())
}), nil
}
func (*statusCodeFilter) IsStateful() bool {
return false
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sampling // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/sampling"
import (
"context"
"fmt"
"regexp"
"github.com/golang/groupcache/lru"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.uber.org/zap"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/pkg/samplingpolicy"
)
const defaultCacheSize = 128
type stringAttributeFilter struct {
key string
logger *zap.Logger
// matcher defines the func to match the attribute values in strict string
// or in regular expression
matcher func(string) bool
invertMatch bool
}
type regexStrSetting struct {
matchedAttrs *lru.Cache
filterList []*regexp.Regexp
}
var _ samplingpolicy.Evaluator = (*stringAttributeFilter)(nil)
// NewStringAttributeFilter creates a policy evaluator that samples all traces with
// the given attribute in the given numeric range.
func NewStringAttributeFilter(settings component.TelemetrySettings, key string, values []string, regexMatchEnabled bool, evictSize int, invertMatch bool) (samplingpolicy.Evaluator, error) {
// initialize regex filter rules and LRU cache for matched results
if regexMatchEnabled {
if evictSize <= 0 {
evictSize = defaultCacheSize
}
filterList, err := addFilters(values)
if err != nil {
return nil, err
}
regexStrSetting := ®exStrSetting{
matchedAttrs: lru.New(evictSize),
filterList: filterList,
}
return &stringAttributeFilter{
key: key,
logger: settings.Logger,
// matcher returns true if the given string matches the regex rules defined in string attribute filters
matcher: func(toMatch string) bool {
if v, ok := regexStrSetting.matchedAttrs.Get(toMatch); ok {
return v.(bool)
}
for _, r := range regexStrSetting.filterList {
if r.MatchString(toMatch) {
regexStrSetting.matchedAttrs.Add(toMatch, true)
return true
}
}
regexStrSetting.matchedAttrs.Add(toMatch, false)
return false
},
invertMatch: invertMatch,
}, nil
}
// initialize the exact value map
valuesMap := make(map[string]struct{})
for _, value := range values {
if value != "" {
valuesMap[value] = struct{}{}
}
}
return &stringAttributeFilter{
key: key,
logger: settings.Logger,
// matcher returns true if the given string matches any of the string attribute filters
matcher: func(toMatch string) bool {
_, matched := valuesMap[toMatch]
return matched
},
invertMatch: invertMatch,
}, nil
}
// Evaluate looks at the trace data and returns a corresponding SamplingDecision.
// The SamplingDecision is made by comparing the attribute values with the matching values,
// which might be static strings or regular expressions.
func (saf *stringAttributeFilter) Evaluate(_ context.Context, _ pcommon.TraceID, trace *samplingpolicy.TraceData) (samplingpolicy.Decision, error) {
saf.logger.Debug("Evaluating spans in string-tag filter")
batches := trace.ReceivedBatches
if saf.invertMatch {
// Invert Match returns true by default, except when key and value are matched
return invertHasResourceOrSpanWithCondition(
batches,
func(resource pcommon.Resource) bool {
if v, ok := resource.Attributes().Get(saf.key); ok {
if ok := saf.matcher(v.Str()); ok {
return false
}
}
return true
},
func(span ptrace.Span) bool {
if v, ok := span.Attributes().Get(saf.key); ok {
truncatableStr := v.Str()
if truncatableStr != "" {
if ok := saf.matcher(v.Str()); ok {
return false
}
}
}
return true
},
), nil
}
return hasResourceOrSpanWithCondition(
batches,
func(resource pcommon.Resource) bool {
if v, ok := resource.Attributes().Get(saf.key); ok {
if ok := saf.matcher(v.Str()); ok {
return true
}
}
return false
},
func(span ptrace.Span) bool {
if v, ok := span.Attributes().Get(saf.key); ok {
truncatableStr := v.Str()
if truncatableStr != "" {
if ok := saf.matcher(v.Str()); ok {
return true
}
}
}
return false
},
), nil
}
func (*stringAttributeFilter) IsStateful() bool {
return false
}
// addFilters compiles all the given filters and stores them as regexes.
// All regexes are automatically anchored to enforce full string matches.
func addFilters(exprs []string) ([]*regexp.Regexp, error) {
list := make([]*regexp.Regexp, 0, len(exprs))
for _, entry := range exprs {
rule, err := regexp.Compile(entry)
if err != nil {
return nil, fmt.Errorf("invalid regex `%s`: %w", entry, err)
}
list = append(list, rule)
}
return list, nil
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sampling // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/sampling"
import (
"time"
)
// TimeProvider allows to get current Unix second
type TimeProvider interface {
getCurSecond() int64
}
// MonotonicClock provides monotonic real clock-based current Unix second.
// Use it when creating a NewComposite which should measure sample rates
// against a realtime clock (this is almost always what you want to do,
// the exception is usually only automated testing where you may want
// to have fake clocks).
type MonotonicClock struct{}
func (MonotonicClock) getCurSecond() int64 {
return time.Now().Unix()
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sampling // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/sampling"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/pkg/samplingpolicy"
)
type traceFlags struct {
logger *zap.Logger
}
var _ samplingpolicy.Evaluator = (*traceFlags)(nil)
// NewTraceFlags creates a policy evaluator that samples all traces with the sampled flag set in the trace flags.
func NewTraceFlags(settings component.TelemetrySettings) samplingpolicy.Evaluator {
return &traceFlags{
logger: settings.Logger,
}
}
// Evaluate looks at the trace data and returns a corresponding SamplingDecision.
// Spans are sampled if any span in the trace has the sampled flag set in the trace flags.
func (tf *traceFlags) Evaluate(_ context.Context, _ pcommon.TraceID, td *samplingpolicy.TraceData) (samplingpolicy.Decision, error) {
tf.logger.Debug("Evaluating spans in trace-flags filter")
return hasSpanWithCondition(td.ReceivedBatches, func(span ptrace.Span) bool {
// Get first 8 bits for trace flags byte from span flags, then bit mask for sampled flag.
return (byte(span.Flags()) & byte(trace.FlagsSampled)) != 0
}), nil
}
func (*traceFlags) IsStateful() bool {
return false
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sampling // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/sampling"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/ptrace"
tracesdk "go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/pkg/samplingpolicy"
)
type traceStateFilter struct {
key string
logger *zap.Logger
matcher func(string) bool
}
var _ samplingpolicy.Evaluator = (*traceStateFilter)(nil)
// NewTraceStateFilter creates a policy evaluator that samples all traces with
// the given value by the specific key in the trace_state.
func NewTraceStateFilter(settings component.TelemetrySettings, key string, values []string) samplingpolicy.Evaluator {
// initialize the exact value map
valuesMap := make(map[string]struct{})
for _, value := range values {
// the key-value pair("=" will take one character) in trace_state can't exceed 256 characters
if value != "" && len(key)+len(value) < 256 {
valuesMap[value] = struct{}{}
}
}
return &traceStateFilter{
key: key,
logger: settings.Logger,
matcher: func(toMatch string) bool {
_, matched := valuesMap[toMatch]
return matched
},
}
}
// Evaluate looks at the trace data and returns a corresponding SamplingDecision.
func (tsf *traceStateFilter) Evaluate(_ context.Context, _ pcommon.TraceID, trace *samplingpolicy.TraceData) (samplingpolicy.Decision, error) {
batches := trace.ReceivedBatches
return hasSpanWithCondition(batches, func(span ptrace.Span) bool {
traceState, err := tracesdk.ParseTraceState(span.TraceState().AsRaw())
if err != nil {
return false
}
if ok := tsf.matcher(traceState.Get(tsf.key)); ok {
return true
}
return false
}), nil
}
func (*traceStateFilter) IsStateful() bool {
return false
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package sampling // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/sampling"
import (
"context"
"strings"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/otel/metric"
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/sampling"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/pkg/samplingpolicy"
)
// hasResourceOrSpanWithCondition iterates through all the resources and instrumentation library spans until any
// callback returns true.
func hasResourceOrSpanWithCondition(
td ptrace.Traces,
shouldSampleResource func(resource pcommon.Resource) bool,
shouldSampleSpan func(span ptrace.Span) bool,
) samplingpolicy.Decision {
for i := 0; i < td.ResourceSpans().Len(); i++ {
rs := td.ResourceSpans().At(i)
resource := rs.Resource()
if shouldSampleResource(resource) {
return samplingpolicy.Sampled
}
if hasInstrumentationLibrarySpanWithCondition(rs.ScopeSpans(), shouldSampleSpan, false) {
return samplingpolicy.Sampled
}
}
return samplingpolicy.NotSampled
}
// invertHasResourceOrSpanWithCondition iterates through all the resources and instrumentation library spans until any
// callback returns false.
func invertHasResourceOrSpanWithCondition(
td ptrace.Traces,
shouldSampleResource func(resource pcommon.Resource) bool,
shouldSampleSpan func(span ptrace.Span) bool,
) samplingpolicy.Decision {
for i := 0; i < td.ResourceSpans().Len(); i++ {
rs := td.ResourceSpans().At(i)
resource := rs.Resource()
if !shouldSampleResource(resource) {
return samplingpolicy.NotSampled
}
if !hasInstrumentationLibrarySpanWithCondition(rs.ScopeSpans(), shouldSampleSpan, true) {
return samplingpolicy.NotSampled
}
}
return samplingpolicy.Sampled
}
// hasSpanWithCondition iterates through all the instrumentation library spans until any callback returns true.
func hasSpanWithCondition(td ptrace.Traces, shouldSample func(span ptrace.Span) bool) samplingpolicy.Decision {
for i := 0; i < td.ResourceSpans().Len(); i++ {
rs := td.ResourceSpans().At(i)
if hasInstrumentationLibrarySpanWithCondition(rs.ScopeSpans(), shouldSample, false) {
return samplingpolicy.Sampled
}
}
return samplingpolicy.NotSampled
}
func hasInstrumentationLibrarySpanWithCondition(ilss ptrace.ScopeSpansSlice, check func(span ptrace.Span) bool, invert bool) bool {
for i := 0; i < ilss.Len(); i++ {
ils := ilss.At(i)
for j := 0; j < ils.Spans().Len(); j++ {
span := ils.Spans().At(j)
if r := check(span); r != invert {
return r
}
}
}
return invert
}
func SetAttrOnScopeSpans(data ptrace.Traces, attrName, attrKey string) {
rs := data.ResourceSpans()
for i := 0; i < rs.Len(); i++ {
rss := rs.At(i)
for j := 0; j < rss.ScopeSpans().Len(); j++ {
ss := rss.ScopeSpans().At(j)
ss.Scope().Attributes().PutStr(attrName, attrKey)
}
}
}
func SetBoolAttrOnScopeSpans(data ptrace.Traces, attrName string, attrValue bool) {
rs := data.ResourceSpans()
for i := 0; i < rs.Len(); i++ {
rss := rs.At(i)
for j := 0; j < rss.ScopeSpans().Len(); j++ {
ss := rss.ScopeSpans().At(j)
ss.Scope().Attributes().PutBool(attrName, attrValue)
}
}
}
// WriteEffectiveThreshold rewrites the OpenTelemetry tracestate `th`
// field on every span in td to encode the given effective sampling
// threshold. Spans whose existing th is already stricter are left
// unchanged (UpdateTValueWithSampling refuses to lower probability).
// Spans with unparseable tracestate are counted via
// unparseableTracestate and skipped.
func WriteEffectiveThreshold(ctx context.Context, td ptrace.Traces, th sampling.Threshold, unparseableTracestate metric.Int64Counter) {
for _, rs := range td.ResourceSpans().All() {
for _, ss := range rs.ScopeSpans().All() {
for _, span := range ss.Spans().All() {
ts, err := sampling.NewW3CTraceState(span.TraceState().AsRaw())
if err != nil {
unparseableTracestate.Add(ctx, 1)
continue
}
if err := ts.OTelValue().UpdateTValueWithSampling(th); err != nil {
// UpdateTValueWithSampling only returns
// ErrInconsistentSampling: the existing
// threshold is stricter and the spec forbids
// lowering it. Leave the span as-is.
continue
}
var w strings.Builder
// Serialize writes to a strings.Builder, which never
// returns an error.
_ = ts.Serialize(&w)
span.TraceState().FromRaw(w.String())
}
}
}
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package tailstorageextension // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/tailstorageextension"
import (
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/metadata"
)
var FeatureGateID = metadata.ProcessorTailsamplingprocessorTailstorageextensionFeatureGate.ID()
func IsFeatureGateEnabled() bool {
return metadata.ProcessorTailsamplingprocessorTailstorageextensionFeatureGate.IsEnabled()
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package tailstorageextension // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/tailstorageextension"
import (
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/ptrace"
)
var _ TailStorage = (*inMemoryTailStorage)(nil)
type inMemoryTailStorage struct {
idToSpans map[pcommon.TraceID]ptrace.Traces
}
func NewInMemoryTailStorage() TailStorage {
return &inMemoryTailStorage{
idToSpans: make(map[pcommon.TraceID]ptrace.Traces),
}
}
func (s *inMemoryTailStorage) Append(traceID pcommon.TraceID, traceTd ptrace.Traces) error {
td, ok := s.idToSpans[traceID]
if !ok {
td = ptrace.NewTraces()
s.idToSpans[traceID] = td
}
for _, rss := range traceTd.ResourceSpans().All() {
rss.MoveTo(td.ResourceSpans().AppendEmpty())
}
return nil
}
func (s *inMemoryTailStorage) Take(traceID pcommon.TraceID) (ptrace.Traces, error) {
td, ok := s.idToSpans[traceID]
if ok {
delete(s.idToSpans, traceID)
return td, nil
}
return ptrace.NewTraces(), nil
}
func (s *inMemoryTailStorage) Delete(traceID pcommon.TraceID) error {
delete(s.idToSpans, traceID)
return nil
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package telemetry // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/telemetry"
import (
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/metadata"
)
func IsMetricStatCountSpansSampledEnabled() bool {
return metadata.ProcessorTailsamplingprocessorMetricstatcountspanssampledFeatureGate.IsEnabled()
}
func IsMetricStatCountBytesSampledEnabled() bool {
return metadata.ProcessorTailsamplingprocessorMetricstatcountbytessampledFeatureGate.IsEnabled()
}
func IsRecordPolicyEnabled() bool {
return metadata.ProcessorTailsamplingprocessorRecordpolicyFeatureGate.IsEnabled()
}
func IsUseTracestateEnabled() bool {
return metadata.ProcessorTailsamplingprocessorUsetracestateFeatureGate.IsEnabled()
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package telemetry // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/telemetry"
import (
"go.opentelemetry.io/otel/attribute"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/pkg/samplingpolicy"
)
// DecisionAttributes holds pre-allocated attribute.KeyValue for all decision types.
// It provides O(1) access to pre-computed attributes using array indexing instead of map lookups,
// eliminating runtime allocations during trace processing. The struct is immutable after construction
// and safe for concurrent access.
type DecisionAttributes struct {
key attribute.Key // The attribute key used for all decisions
attrs [samplingpolicy.NumDecisions]attribute.KeyValue // Array indexed by Decision
unknownAttr attribute.KeyValue // Returned for invalid decisions
}
// NewDecisionAttributes creates a DecisionAttributes instance with pre-computed KeyValue pairs
// for all decision types defined in samplingpolicy.Decision.
// Parameters:
// - attrKey: The attribute key to use for all decision attributes (e.g., "decision.final", "policy.decision")
//
// Example:
//
// spanDecisionAttrs := NewDecisionAttributes(attribute.Key("decision.final"))
// attr := spanDecisionAttrs.Get(samplingpolicy.Sampled) // Returns pre-allocated KeyValue
func NewDecisionAttributes(attrKey attribute.Key) *DecisionAttributes {
da := &DecisionAttributes{
key: attrKey,
}
// Pre-compute all decision attributes
da.attrs[samplingpolicy.Unspecified] = attrKey.String(samplingpolicy.Unspecified.String())
da.attrs[samplingpolicy.Pending] = attrKey.String(samplingpolicy.Pending.String())
da.attrs[samplingpolicy.Sampled] = attrKey.String(samplingpolicy.Sampled.String())
da.attrs[samplingpolicy.NotSampled] = attrKey.String(samplingpolicy.NotSampled.String())
da.attrs[samplingpolicy.Dropped] = attrKey.String(samplingpolicy.Dropped.String())
da.attrs[samplingpolicy.Error] = attrKey.String(samplingpolicy.Error.String())
//nolint:staticcheck // InvertSampled is deprecated but we need to support existing decision values
da.attrs[samplingpolicy.InvertSampled] = attrKey.String(samplingpolicy.InvertSampled.String())
//nolint:staticcheck // InvertNotSampled is deprecated but we need to support existing decision values
da.attrs[samplingpolicy.InvertNotSampled] = attrKey.String(samplingpolicy.InvertNotSampled.String())
// Pre-compute the unknown attribute for out-of-bounds decisions
da.unknownAttr = attrKey.String("missing.preallocation")
return da
}
// Get returns the pre-allocated KeyValue for the given decision.
func (d *DecisionAttributes) Get(decision samplingpolicy.Decision) attribute.KeyValue {
if decision < 0 || decision >= samplingpolicy.NumDecisions {
return d.unknownAttr
}
return d.attrs[decision]
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package tailsamplingprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor"
import (
"go.opentelemetry.io/collector/component"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/sampling"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/pkg/samplingpolicy"
)
func getNewNotPolicy(settings component.TelemetrySettings, config *NotCfg, policyExtensions map[string]samplingpolicy.Extension) (samplingpolicy.Evaluator, error) {
subPolicyEvaluator, err := getNotSubPolicyEvaluator(settings, &config.SubPolicy, policyExtensions)
if err != nil {
return nil, err
}
return sampling.NewNot(settings.Logger, subPolicyEvaluator), nil
}
// Return instance of not sub-policy
func getNotSubPolicyEvaluator(settings component.TelemetrySettings, cfg *NotSubPolicyCfg, policyExtensions map[string]samplingpolicy.Extension) (samplingpolicy.Evaluator, error) {
return getSharedPolicyEvaluator(settings, &cfg.sharedPolicyCfg, policyExtensions)
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package samplingpolicy // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/pkg/samplingpolicy"
import (
"context"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/ptrace"
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/sampling"
)
// TraceData stores the sampling related trace data.
type TraceData struct {
// SpanCount track the number of spans on the trace.
SpanCount int64
// SizeBytes is how many bytes we have accumulated for the trace.
SizeBytes uint64
// ReceivedBatches stores all the batches received for the trace.
ReceivedBatches ptrace.Traces
}
// Decision gives the status of sampling decision.
type Decision int32
const (
// Unspecified indicates that the status of the decision was not set yet.
Unspecified Decision = iota
// Pending indicates that the policy was not evaluated yet.
Pending
// Sampled is used to indicate that the decision was already taken
// to sample the data.
Sampled
// NotSampled is used to indicate that the decision was already taken
// to not sample the data.
NotSampled
// Dropped is used to indicate that a trace should be dropped regardless of
// all other decisions.
Dropped
// Error is used to indicate that policy evaluation was not succeeded.
Error
// InvertSampled is used on the invert match flow and indicates to sample
// the data.
//
// Deprecated: Drop policies should be used instead of invert decisions.
InvertSampled
// InvertNotSampled is used on the invert match flow and indicates to not
// sample the data.
//
// Deprecated: Drop policies should be used instead of invert decisions.
InvertNotSampled
// NumDecisions is not a sampling decision. It must remain the last iota
// so arrays indexed by Decision stay correctly sized when values are added.
NumDecisions
)
// String returns a string representation of the Decision.
func (d Decision) String() string {
switch d {
case Unspecified:
return "unspecified"
case Pending:
return "pending"
case Sampled:
return "sampled"
case NotSampled:
return "not_sampled"
case Dropped:
return "dropped"
case Error:
return "error"
case InvertSampled:
return "invert_sampled"
case InvertNotSampled:
return "invert_not_sampled"
default:
return "unknown"
}
}
// Evaluator implements a tail-based sampling policy evaluator,
// which makes a sampling decision for a given trace when requested.
type Evaluator interface {
// Evaluate looks at the trace data and returns a corresponding SamplingDecision.
Evaluate(ctx context.Context, traceID pcommon.TraceID, trace *TraceData) (Decision, error)
// IsStateful reports whether decisions can depend on prior evaluations/state.
IsStateful() bool
}
// ThresholdEvaluator is implemented by policies that can report the
// effective OpenTelemetry sampling threshold they would advertise on
// outgoing tracestate. Implementing this is optional: policies that
// do not report a threshold are treated by the processor as Sampled
// with sampling.AlwaysSampleThreshold (i.e., always-sample for
// matching items, dominates downstream min(th) reduction).
type ThresholdEvaluator interface {
Evaluator
// EvaluateWithThreshold returns the policy's Decision along with
// the effective Threshold. The Threshold is only meaningful when
// Decision is Sampled.
EvaluateWithThreshold(ctx context.Context, traceID pcommon.TraceID, trace *TraceData) (Decision, sampling.Threshold, error)
}
type Extension interface {
NewEvaluator(policyName string, cfg map[string]any) (Evaluator, error)
}
// AsThresholdEvaluator returns e as a ThresholdEvaluator. If e
// already implements ThresholdEvaluator it is returned unchanged;
// otherwise e is wrapped in an adapter that reports
// sampling.AlwaysSampleThreshold (i.e., "sampled with no
// quantifiable threshold") whenever Evaluate returns Sampled. This
// lets callers query an effective sampling threshold from any
// Evaluator without per-call type assertions.
func AsThresholdEvaluator(e Evaluator) ThresholdEvaluator {
if te, ok := e.(ThresholdEvaluator); ok {
return te
}
return &decisionAdapter{Evaluator: e}
}
// decisionAdapter wraps a plain Evaluator so it satisfies
// ThresholdEvaluator.
type decisionAdapter struct {
Evaluator
}
var _ ThresholdEvaluator = (*decisionAdapter)(nil)
func (a *decisionAdapter) EvaluateWithThreshold(ctx context.Context, traceID pcommon.TraceID, trace *TraceData) (Decision, sampling.Threshold, error) {
d, err := a.Evaluate(ctx, traceID, trace)
return d, sampling.AlwaysSampleThreshold, err
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package tailsamplingprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor"
import (
"container/list"
"context"
"errors"
"fmt"
"math"
"runtime"
"slices"
"sync/atomic"
"time"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
pkgsampling "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/sampling"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/cache"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/idbatcher"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/metadata"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/sampling"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/tailstorageextension"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/telemetry"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/pkg/samplingpolicy"
)
// policy combines a sampling policy evaluator with the destinations to be
// used for that policy.
type policy struct {
// name used to identify this policy instance.
name string
// evaluator that decides if a trace is sampled or not by this policy instance.
evaluator samplingpolicy.ThresholdEvaluator
// attribute to use in the telemetry to denote the policy.
attribute metric.MeasurementOption
// isDrop indicates this is a drop policy.
isDrop bool
}
// TraceData is a wrapper around the publically used samplingpolicy.TraceData
// that tracks information related to the decision making process but not
// needed by any sampler implementations.
type TraceData struct {
samplingpolicy.TraceData
FinalDecision samplingpolicy.Decision
FinalThreshold pkgsampling.Threshold
PolicyName string
arrivalTime time.Time
decisionTime time.Time
deleteElement *list.Element
batchID uint64
}
// DecisionHook is called when a sampling decision is made for a trace. When
// the processor is configured with num_shards greater than 1, hooks are
// invoked concurrently from multiple shard goroutines and must be safe for
// concurrent use.
type DecisionHook func(ctx context.Context, id pcommon.TraceID, td *TraceData)
type tailSamplingSpanProcessor struct {
ctx context.Context
set processor.Settings
telemetry *metadata.TelemetryBuilder
logger *zap.Logger
tracer trace.Tracer
deleteTraceQueue *list.List
nextConsumer consumer.Traces
policies []*policy
idToTrace map[pcommon.TraceID]*TraceData
tailStorage tailstorageextension.TailStorage
tickerFrequency time.Duration
decisionBatcher idbatcher.Batcher
sampledIDCache cache.Cache
nonSampledIDCache cache.Cache
recordPolicy bool
useTracestate bool
sampleOnFirstMatch bool
blockOnOverflow bool
maxTraceSizeBytes uint64
cfg Config
host component.Host
// tracesOnMemory is shared across all shards of the same processor; see
// shardShared for the rationale.
tracesOnMemory *atomic.Int64
sampledHooks []DecisionHook
nonSampledHooks []DecisionHook
newPolicyChan chan newPolicyCmd
newTraceSizeChan chan uint64
workChan chan []traceBatch
doneChan chan struct{}
// tickChan triggers ticks and responds on the provided channel when the tick is complete.
// this is used in tests to produce deterministic ticks.
tickChan chan chan struct{}
}
func newTracesProcessor(ctx context.Context, set processor.Settings, nextConsumer consumer.Traces, cfg Config) (processor.Traces, error) {
if err := cfg.Validate(); err != nil {
return nil, err
}
return newShardedTracesProcessor(ctx, set, nextConsumer, cfg)
}
// newShardProcessor creates a single shard: an independent event loop with
// its own trace storage and decision batcher. Shards are created and routed
// to by the shardedProcessor returned from newTracesProcessor.
func newShardProcessor(ctx context.Context, set processor.Settings, nextConsumer consumer.Traces, cfg Config, shared shardShared) (*tailSamplingSpanProcessor, error) {
var err error
nopCache := cache.NewNopDecisionCache()
sampledDecisions := nopCache
nonSampledDecisions := nopCache
if cfg.DecisionCache.SampledCacheSize > 0 {
sampledDecisions, err = cache.NewLRUDecisionCache(cfg.DecisionCache.SampledCacheSize)
if err != nil {
return nil, err
}
}
if cfg.DecisionCache.NonSampledCacheSize > 0 {
nonSampledDecisions, err = cache.NewLRUDecisionCache(cfg.DecisionCache.NonSampledCacheSize)
if err != nil {
return nil, err
}
}
tsp := &tailSamplingSpanProcessor{
ctx: ctx,
cfg: cfg,
set: set,
telemetry: shared.telemetry,
tracer: shared.tracer,
nextConsumer: nextConsumer,
sampledIDCache: sampledDecisions,
nonSampledIDCache: nonSampledDecisions,
logger: set.Logger,
idToTrace: make(map[pcommon.TraceID]*TraceData),
deleteTraceQueue: list.New(),
sampleOnFirstMatch: cfg.SampleOnFirstMatch,
blockOnOverflow: cfg.BlockOnOverflow,
maxTraceSizeBytes: cfg.MaximumTraceSizeBytes,
tracesOnMemory: shared.tracesOnMemory,
// Similar to the id batcher, allow a batch per CPU to be buffered before blocking ConsumeTraces.
workChan: make(chan []traceBatch, runtime.NumCPU()),
// We need to buffer one new policy command/size update so that external callers can
// queue up new policies without blocking on the ticker.
newPolicyChan: make(chan newPolicyCmd, 1),
newTraceSizeChan: make(chan uint64, 1),
}
for _, opt := range cfg.Options {
opt(tsp)
}
if tsp.tickerFrequency == 0 {
tsp.tickerFrequency = time.Second
}
return tsp, nil
}
// Start is invoked during service startup.
func (tsp *tailSamplingSpanProcessor) Start(_ context.Context, host component.Host) error {
tsp.host = host
if tsp.sampleOnFirstMatch && tsp.useTracestate {
tsp.logger.Warn("sample_on_first_match is enabled together with the tracestate feature gate; " +
"the reported sampling threshold is only correct if no policy after an earlier match " +
"could ever report a smaller (less strict) threshold for the same trace, since " +
"sample_on_first_match stops evaluating policies at the first match")
}
if tsp.cfg.TailStorageID != nil {
tailStorageExt, err := tailStorageExtension(host, *tsp.cfg.TailStorageID)
if err != nil {
return err
}
tsp.tailStorage = tailStorageExt
} else {
tsp.tailStorage = tailstorageextension.NewInMemoryTailStorage()
}
policies, err := tsp.loadSamplingPolicies(host, tsp.cfg.PolicyCfgs)
if err != nil {
return err
}
// If the policies are not set, set them. This is only for testing purposes,
// so that withPolicies can inject custom policies.
if tsp.policies == nil {
tsp.policies = policies
}
if tsp.decisionBatcher == nil {
// this will start a goroutine in the background, so we run it only if everything went
// well in creating the policies, and only when the processor starts.
numDecisionBatches := math.Max(1, tsp.cfg.DecisionWait.Seconds())
idBatcher, err := idbatcher.New(uint64(numDecisionBatches), tsp.cfg.ExpectedNewTracesPerSec)
if err != nil {
return err
}
tsp.decisionBatcher = idBatcher
}
tsp.doneChan = make(chan struct{})
go tsp.loop()
return nil
}
func (tsp *tailSamplingSpanProcessor) SetSamplingPolicy(cfgs []PolicyCfg) {
policies, err := tsp.loadSamplingPolicies(tsp.host, cfgs)
if err != nil {
tsp.logger.Error("Failed to load sampling policies", zap.Error(err))
return
}
tsp.newPolicyChan <- newPolicyCmd{policies: policies}
}
func (tsp *tailSamplingSpanProcessor) loadSamplingPolicies(host component.Host, cfgs []PolicyCfg) ([]*policy, error) {
telemetrySettings := tsp.set.TelemetrySettings
componentID := tsp.set.ID.Name()
cLen := len(cfgs)
policies := make([]*policy, 0, cLen)
dropPolicies := make([]*policy, 0, cLen)
policyNames := make(map[string]struct{}, cLen)
for i := range cfgs {
cfg := cfgs[i]
if cfg.Name == "" {
return nil, errors.New("policy name cannot be empty")
}
if _, exists := policyNames[cfg.Name]; exists {
return nil, fmt.Errorf("duplicate policy name %q", cfg.Name)
}
policyNames[cfg.Name] = struct{}{}
eval, err := getPolicyEvaluator(telemetrySettings, &cfg, extensions(host))
if err != nil {
return nil, fmt.Errorf("failed to create policy evaluator for %q: %w", cfg.Name, err)
}
if tsp.cfg.SamplingStrategy == samplingStrategySpanIngest && eval.IsStateful() {
return nil, fmt.Errorf("sampling_strategy %q requires all policies to be stateless, but policy %q is stateful", tsp.cfg.SamplingStrategy, cfg.Name)
}
uniquePolicyName := cfg.Name
if componentID != "" {
uniquePolicyName = fmt.Sprintf("%s.%s", componentID, cfg.Name)
}
p := &policy{
name: cfg.Name,
evaluator: samplingpolicy.AsThresholdEvaluator(eval),
attribute: metric.WithAttributes(attribute.String("policy", uniquePolicyName)),
isDrop: cfg.Type == Drop,
}
if p.isDrop {
dropPolicies = append(dropPolicies, p)
} else {
policies = append(policies, p)
}
}
// Dropped decision takes precedence over all others, therefore we evaluate them first.
return slices.Concat(dropPolicies, policies), nil
}
func (tsp *tailSamplingSpanProcessor) SetMaximumTraceSizeBytes(size uint64) {
tsp.newTraceSizeChan <- size
}
// traceBatch contains all spans from a single batch for a single trace.
type traceBatch struct {
id pcommon.TraceID
hasRoot bool
rss ptrace.ResourceSpans
spanCount int64
}
type newPolicyCmd struct {
policies []*policy
}
var (
attrDecisionSampled = metric.WithAttributes(attribute.String("sampled", "true"), attribute.String("decision", "sampled"))
attrDecisionNotSampled = metric.WithAttributes(attribute.String("sampled", "false"), attribute.String("decision", "not_sampled"))
attrDecisionDropped = metric.WithAttributes(attribute.String("sampled", "false"), attribute.String("decision", "dropped"))
decisionToAttributes = map[samplingpolicy.Decision]metric.MeasurementOption{
samplingpolicy.Sampled: attrDecisionSampled,
samplingpolicy.NotSampled: attrDecisionNotSampled,
//nolint:staticcheck // SA1019: Use of inverted decisions until they are fully removed.
samplingpolicy.InvertNotSampled: attrDecisionNotSampled,
//nolint:staticcheck // SA1019: Use of inverted decisions until they are fully removed.
samplingpolicy.InvertSampled: attrDecisionSampled,
samplingpolicy.Dropped: attrDecisionDropped,
}
attrSampledTrue = metric.WithAttributes(attribute.String("sampled", "true"))
attrSampledFalse = metric.WithAttributes(attribute.String("sampled", "false"))
)
type Option func(*tailSamplingSpanProcessor)
// WithSampledDecisionCache sets the cache which the processor uses to store recently sampled trace IDs.
// When num_shards is greater than 1 the same cache instance is shared by all
// shards and must be safe for concurrent use.
func WithSampledDecisionCache(c cache.Cache) Option {
return func(tsp *tailSamplingSpanProcessor) {
tsp.sampledIDCache = c
}
}
// WithNonSampledDecisionCache sets the cache which the processor uses to store recently non-sampled trace IDs.
// When num_shards is greater than 1 the same cache instance is shared by all
// shards and must be safe for concurrent use.
func WithNonSampledDecisionCache(c cache.Cache) Option {
return func(tsp *tailSamplingSpanProcessor) {
tsp.nonSampledIDCache = c
}
}
// WithSampledHooks sets hooks to be called when a trace is sampled.
func WithSampledHooks(hooks ...DecisionHook) Option {
return func(tsp *tailSamplingSpanProcessor) {
tsp.sampledHooks = hooks
}
}
// WithNonSampledHooks sets hooks to be called when a trace is not sampled.
func WithNonSampledHooks(hooks ...DecisionHook) Option {
return func(tsp *tailSamplingSpanProcessor) {
tsp.nonSampledHooks = hooks
}
}
func withRecordPolicy() Option {
return func(tsp *tailSamplingSpanProcessor) {
tsp.recordPolicy = true
}
}
func withUseTracestate() Option {
return func(tsp *tailSamplingSpanProcessor) {
tsp.useTracestate = true
}
}
func getPolicyEvaluator(settings component.TelemetrySettings, cfg *PolicyCfg, policyExtensions map[string]samplingpolicy.Extension) (samplingpolicy.Evaluator, error) {
switch cfg.Type {
case Composite:
return getNewCompositePolicy(settings, &cfg.CompositeCfg, policyExtensions)
case And:
return getNewAndPolicy(settings, &cfg.AndCfg, policyExtensions)
case Not:
return getNewNotPolicy(settings, &cfg.NotCfg, policyExtensions)
case Drop:
return getNewDropPolicy(settings, &cfg.DropCfg, policyExtensions)
default:
return getSharedPolicyEvaluator(settings, &cfg.sharedPolicyCfg, policyExtensions)
}
}
func getSharedPolicyEvaluator(settings component.TelemetrySettings, cfg *sharedPolicyCfg, policyExtensions map[string]samplingpolicy.Extension) (samplingpolicy.Evaluator, error) {
settings.Logger = settings.Logger.With(zap.Any("policy", cfg.Type))
switch cfg.Type {
case AlwaysSample:
return sampling.NewAlwaysSample(settings), nil
case Latency:
lfCfg := cfg.LatencyCfg
return sampling.NewLatency(settings, lfCfg.ThresholdMs, lfCfg.UpperThresholdMs), nil
case NumericAttribute:
nafCfg := cfg.NumericAttributeCfg
var minValuePtr, maxValuePtr *int64
if nafCfg.MinValue != 0 {
minValuePtr = &nafCfg.MinValue
}
if nafCfg.MaxValue != 0 {
maxValuePtr = &nafCfg.MaxValue
}
return sampling.NewNumericAttributeFilter(settings, nafCfg.Key, minValuePtr, maxValuePtr, nafCfg.InvertMatch), nil
case Probabilistic:
pCfg := cfg.ProbabilisticCfg
return sampling.NewProbabilisticSampler(settings, pCfg.HashSalt, pCfg.SamplingPercentage), nil
case StringAttribute:
safCfg := cfg.StringAttributeCfg
return sampling.NewStringAttributeFilter(settings, safCfg.Key, safCfg.Values, safCfg.EnabledRegexMatching, safCfg.CacheMaxSize, safCfg.InvertMatch)
case StatusCode:
scfCfg := cfg.StatusCodeCfg
return sampling.NewStatusCodeFilter(settings, scfCfg.StatusCodes)
case RateLimiting:
rlfCfg := cfg.RateLimitingCfg
if rlfCfg.BurstCapacity > 0 {
return sampling.NewRateLimitingWithBurstCapacity(settings, rlfCfg.SpansPerSecond, rlfCfg.BurstCapacity), nil
}
return sampling.NewRateLimiting(settings, rlfCfg.SpansPerSecond), nil
case BytesLimiting:
blfCfg := cfg.BytesLimitingCfg
if blfCfg.BurstCapacity > 0 {
return sampling.NewBytesLimitingWithBurstCapacity(settings, blfCfg.BytesPerSecond, blfCfg.BurstCapacity), nil
}
return sampling.NewBytesLimiting(settings, blfCfg.BytesPerSecond), nil
case SpanCount:
spCfg := cfg.SpanCountCfg
return sampling.NewSpanCount(settings, spCfg.MinSpans, spCfg.MaxSpans), nil
case TraceState:
tsfCfg := cfg.TraceStateCfg
return sampling.NewTraceStateFilter(settings, tsfCfg.Key, tsfCfg.Values), nil
case BooleanAttribute:
bafCfg := cfg.BooleanAttributeCfg
return sampling.NewBooleanAttributeFilter(settings, bafCfg.Key, bafCfg.Value, bafCfg.InvertMatch), nil
case OTTLCondition:
ottlfCfg := cfg.OTTLConditionCfg
return sampling.NewOTTLConditionFilter(settings, ottlfCfg.SpanConditions, ottlfCfg.SpanEventConditions, ottlfCfg.ErrorMode)
case TraceFlags:
return sampling.NewTraceFlags(settings), nil
default:
t := string(cfg.Type)
extension, ok := policyExtensions[t]
if ok {
evaluator, err := extension.NewEvaluator(cfg.Name, cfg.ExtensionCfg[t])
if err != nil {
return nil, fmt.Errorf("unable to load extension %s: %w", t, err)
}
return evaluator, nil
}
return nil, fmt.Errorf("unknown sampling policy type %s", cfg.Type)
}
}
type policyDecisionMetrics struct {
tracesSampled int
spansSampled int64
bytesSampled int64
}
type policyEvaluationMetrics struct {
idNotFoundOnMapCount, evaluateErrorCount, decisionSampled, decisionNotSampled, decisionDropped int64
tracesSampledByPolicyDecision []map[samplingpolicy.Decision]policyDecisionMetrics
cumulativeExecutionTime []perPolicyExecutionTime
}
// perPolicyExecutionTime is a struct for holding the cumulative execution time
// and number of executions of a policy. This is an optimization to avoid
// instrumentation overhead in the decision making loop.
type perPolicyExecutionTime struct {
executionTime time.Duration
executionCount int64
}
func newPolicyEvaluationMetrics(numPolicies int) *policyEvaluationMetrics {
tracesSampledByPolicyDecision := make([]map[samplingpolicy.Decision]policyDecisionMetrics, numPolicies)
for i := range tracesSampledByPolicyDecision {
tracesSampledByPolicyDecision[i] = make(map[samplingpolicy.Decision]policyDecisionMetrics)
}
return &policyEvaluationMetrics{
tracesSampledByPolicyDecision: tracesSampledByPolicyDecision,
cumulativeExecutionTime: make([]perPolicyExecutionTime, numPolicies),
}
}
func (m *policyEvaluationMetrics) addDecision(policyIndex int, decision samplingpolicy.Decision, spansSampled, bytesSampled int64) {
stats := m.tracesSampledByPolicyDecision[policyIndex][decision]
stats.tracesSampled++
stats.spansSampled += spansSampled
stats.bytesSampled += bytesSampled
m.tracesSampledByPolicyDecision[policyIndex][decision] = stats
}
func (m *policyEvaluationMetrics) addDecisionTime(policyIndex int, decisionTime time.Duration) {
perPolicyExecutionTime := m.cumulativeExecutionTime[policyIndex]
perPolicyExecutionTime.executionTime += decisionTime
perPolicyExecutionTime.executionCount++
m.cumulativeExecutionTime[policyIndex] = perPolicyExecutionTime
}
func (tsp *tailSamplingSpanProcessor) recordPerPolicyEvaluationMetrics(metrics *policyEvaluationMetrics) {
for i, p := range tsp.policies {
for decision, stats := range metrics.tracesSampledByPolicyDecision[i] {
tsp.telemetry.ProcessorTailSamplingCountTracesSampled.Add(tsp.ctx, int64(stats.tracesSampled), p.attribute, decisionToAttributes[decision])
if telemetry.IsMetricStatCountSpansSampledEnabled() {
tsp.telemetry.ProcessorTailSamplingCountSpansSampled.Add(tsp.ctx, stats.spansSampled, p.attribute, decisionToAttributes[decision])
}
if telemetry.IsMetricStatCountBytesSampledEnabled() {
tsp.telemetry.ProcessorTailSamplingCountBytesSampled.Add(tsp.ctx, stats.bytesSampled, p.attribute, decisionToAttributes[decision])
}
}
tsp.telemetry.ProcessorTailSamplingSamplingPolicyExecutionTimeSum.Add(tsp.ctx, metrics.cumulativeExecutionTime[i].executionTime.Microseconds(), p.attribute)
tsp.telemetry.ProcessorTailSamplingSamplingPolicyExecutionCount.Add(tsp.ctx, metrics.cumulativeExecutionTime[i].executionCount, p.attribute)
}
}
func (tsp *tailSamplingSpanProcessor) recordImmediateDecisionMetrics(decision samplingpolicy.Decision, metrics *policyEvaluationMetrics, evaluationLatency time.Duration) {
tsp.telemetry.ProcessorTailSamplingSamplingDecisionTimerLatency.Record(tsp.ctx, evaluationLatency.Milliseconds())
tsp.telemetry.ProcessorTailSamplingSamplingPolicyEvaluationError.Add(tsp.ctx, metrics.evaluateErrorCount)
if attrs, ok := decisionToAttributes[decision]; ok {
tsp.telemetry.ProcessorTailSamplingGlobalCountTracesSampled.Add(tsp.ctx, 1, attrs)
}
tsp.recordPerPolicyEvaluationMetrics(metrics)
}
func (tsp *tailSamplingSpanProcessor) loop() {
ticker := time.NewTicker(tsp.tickerFrequency)
defer ticker.Stop()
defer close(tsp.doneChan)
for tsp.iter(ticker.C, tsp.workChan) {
}
}
func (tsp *tailSamplingSpanProcessor) iter(tickChan <-chan time.Time, workChan <-chan []traceBatch) bool {
select {
case <-tsp.ctx.Done():
// If the context is done then we can't send anything anymore so just exit.
return false
case batch, ok := <-workChan:
// No more traces to process as we are shutting down. Clear the queue and make decisions for all batches based on the current data.
if !ok {
// Stop the batcher so that we can read all batches without creating new ones.
tsp.decisionBatcher.Stop()
// Do the best decision we can for any traces we have already ingested unless a user wants to drop them.
if !tsp.cfg.DropPendingTracesOnShutdown {
for tsp.samplingPolicyOnTick() {
}
}
return false
}
// Drain any pending configuration updates before processing the batch.
// Go's select is non-deterministic, so when both workChan and a config
// update channel are ready simultaneously, the workChan case may be
// selected before the config update is applied. By draining config
// channels here (non-blocking), we ensure that calls like
// SetMaximumTraceSizeBytes or SetSamplingPolicy are always reflected
// before the incoming traces are evaluated against them.
select {
case cmd := <-tsp.newPolicyChan:
tsp.policies = cmd.policies
tsp.logger.Debug("New policies loaded", zap.Int("policies.len", len(tsp.policies)))
default:
}
select {
case maxTraceSize := <-tsp.newTraceSizeChan:
tsp.maxTraceSizeBytes = maxTraceSize
tsp.logger.Debug("New maximum trace size loaded", zap.Uint64("size", maxTraceSize))
default:
}
for _, trace := range batch {
// Short circuit if the trace has already been sampled or dropped.
if tsp.processCachedTrace(trace.id, trace.rss, trace.spanCount) {
continue
}
_, ok = tsp.idToTrace[trace.id]
if !ok && uint64(len(tsp.idToTrace)) >= tsp.cfg.NumTraces {
tsp.waitForSpace(tickChan)
}
tsp.processTrace(trace.id, trace.rss, trace.spanCount, trace.hasRoot)
}
case cmd := <-tsp.newPolicyChan:
tsp.policies = cmd.policies
tsp.logger.Debug("New policies loaded", zap.Int("policies.len", len(tsp.policies)))
case maxTraceSize := <-tsp.newTraceSizeChan:
tsp.maxTraceSizeBytes = maxTraceSize
tsp.logger.Debug("New maximum trace size loaded", zap.Uint64("size", maxTraceSize))
case <-tickChan:
tsp.samplingPolicyOnTick()
case tickChan := <-tsp.tickChan:
tsp.samplingPolicyOnTick()
tickChan <- struct{}{}
}
return true
}
// processCachedTrace checks if a given trace has already been sampled (or
// dropped) and forwards the span appropriately. It returns true if the trace
// was cached and false if regular processing must be done.
func (tsp *tailSamplingSpanProcessor) processCachedTrace(traceID pcommon.TraceID, rss ptrace.ResourceSpans, spanCount int64) bool {
if metadata, ok := tsp.sampledIDCache.Get(traceID); ok {
tsp.logger.Debug("Trace ID is in the sampled cache", zap.Stringer("id", traceID))
traceTd := ptrace.NewTraces()
appendToTraces(traceTd, rss)
if tsp.recordPolicy {
if metadata.PolicyName != "" {
sampling.SetAttrOnScopeSpans(traceTd, "tailsampling.policy", metadata.PolicyName)
}
sampling.SetBoolAttrOnScopeSpans(traceTd, "tailsampling.cached_decision", true)
}
if tsp.useTracestate {
sampling.WriteEffectiveThreshold(tsp.ctx, traceTd, metadata.Threshold, tsp.telemetry.ProcessorTailSamplingCountSpansWithUnparseableTracestate)
}
tsp.forwardSpans(tsp.ctx, traceTd)
tsp.telemetry.ProcessorTailSamplingEarlyReleasesFromCacheDecision.
Add(tsp.ctx, spanCount, attrSampledTrue)
return true
}
if _, ok := tsp.nonSampledIDCache.Get(traceID); ok {
tsp.logger.Debug("Trace ID is in the non-sampled cache", zap.Stringer("id", traceID))
tsp.telemetry.ProcessorTailSamplingEarlyReleasesFromCacheDecision.
Add(tsp.ctx, spanCount, attrSampledFalse)
return true
}
return false
}
// waitForSpace blocks until space is available. Depending on configuration,
// this might immediately drop data or wait until the sampler tick frees space.
func (tsp *tailSamplingSpanProcessor) waitForSpace(tickChan <-chan time.Time) {
if tsp.blockOnOverflow {
// Ticks are not guaranteed to drop data, since they may process an
// empty batch. We loop until we have space for a new trace.
for uint64(len(tsp.idToTrace)) >= tsp.cfg.NumTraces {
// Recursively iter with a nil workChan to wait for space.
tsp.iter(tickChan, nil)
}
return
}
front := tsp.deleteTraceQueue.Front()
if front == nil {
// This should be impossible.
tsp.logger.Error("deleteTraceQueue is empty, but we're waiting for space. This is a bug!")
return
}
if !tsp.dropTrace(front.Value.(pcommon.TraceID), time.Now()) {
// Somehow the trace was already removed from idToTrace, but not the
// queue. Drop the element to avoid an infinite loop.
tsp.deleteTraceQueue.Remove(front)
}
}
// evaluateDropPolicies evaluates the drop-policy prefix of tsp.policies
// once and returns the policy that dropped the trace, or nil.
func (tsp *tailSamplingSpanProcessor) evaluateDropPolicies(ctx context.Context, numDropPolicies int, id pcommon.TraceID, td *samplingpolicy.TraceData, metrics *policyEvaluationMetrics) *policy {
for i := range numDropPolicies {
p := tsp.policies[i]
startTime := time.Now()
decision, err := p.evaluator.Evaluate(ctx, id, td)
metrics.addDecisionTime(i, time.Since(startTime))
if err != nil {
metrics.evaluateErrorCount++
tsp.logger.Debug("Sampling policy error", zap.Error(err))
continue
}
metrics.addDecision(i, decision, td.SpanCount, int64(td.SizeBytes))
if decision == samplingpolicy.Dropped {
return p
}
}
return nil
}
// tickCandidate is a trace resolved once per tick: its spans have been taken
// from storage and it is ready for a decision. Batch-aware policies (see
// runBatchPolicies) and the per-trace decision loop in samplingPolicyOnTick
// both work from this same resolved list, so a trace's spans are taken from
// storage exactly once per tick regardless of how many policies look at it.
type tickCandidate struct {
id pcommon.TraceID
trace *TraceData
data *samplingpolicy.TraceData
}
// runBatchPolicies runs any BatchEvaluator policies (e.g. rate_limiting or
// bytes_limiting with the tracestate gate on) over candidates, which must
// already exclude dropped traces. A no-op if no policy is batch-aware.
func (tsp *tailSamplingSpanProcessor) runBatchPolicies(ctx context.Context, candidates []tickCandidate) {
var batchPolicies []sampling.BatchEvaluator
for _, p := range tsp.policies {
if be, ok := p.evaluator.(sampling.BatchEvaluator); ok {
batchPolicies = append(batchPolicies, be)
}
}
if len(batchPolicies) == 0 {
return
}
items := make([]*samplingpolicy.TraceData, len(candidates))
for i, c := range candidates {
items[i] = c.data
}
for _, be := range batchPolicies {
be.CalculateThreshold(ctx, items)
}
}
// samplingPolicyOnTick takes the next batch and process all traces in that batch. Returns if there are more batches in the batcher.
func (tsp *tailSamplingSpanProcessor) samplingPolicyOnTick() bool {
tsp.logger.Debug("Sampling Policy Evaluation ticked")
metrics := newPolicyEvaluationMetrics(len(tsp.policies))
startTime := time.Now()
globalTracesSampledByDecision := make(map[samplingpolicy.Decision]int64)
batch, hasMore := tsp.decisionBatcher.CloseCurrentAndTakeFirstBatch()
batchLen := len(batch)
var span trace.Span
ctx := context.Background()
if batchLen > 0 {
ctx, span = tsp.tracer.Start(ctx, "tailsampling.samplingPolicyOnTick")
defer span.End()
}
// Resolve every trace due a decision this tick, taking its spans from
// storage once. In span-ingest mode, tick is a terminal cleanup path
// instead: finalize any still-pending trace as implicit not sampled
// without policy evaluation, so no candidate is collected for it.
candidates := make([]tickCandidate, 0, batchLen)
for id := range batch {
trace, ok := tsp.idToTrace[id]
if !ok {
metrics.idNotFoundOnMapCount++
continue
}
// A decision was already made, no need to do it again. This happens
// when no decision cache is used and a trace was processed both due to
// a root span trigger and after decision_wait.
if trace.FinalDecision != samplingpolicy.Unspecified {
continue
}
if tsp.cfg.SamplingStrategy == samplingStrategySpanIngest {
trace.decisionTime = time.Now()
trace.FinalDecision = samplingpolicy.NotSampled
globalTracesSampledByDecision[samplingpolicy.NotSampled]++
metrics.decisionNotSampled++
tsp.releaseNotSampledTrace(id, trace)
// releaseNotSampledTrace only evicts the trace from memory and
// storage (via dropTrace) on a decision cache hit; with NopCache
// that never happens, leaking idToTrace, deleteTraceQueue and the
// tailStorage entry. Since the tick is terminal for the trace, drop
// it unconditionally. dropTrace is idempotent, so the eviction
// already performed by releaseNotSampledTrace when a cache is
// configured is harmless.
tsp.dropTrace(id, trace.decisionTime)
continue
}
allSpans, err := tsp.tailStorage.Take(id)
if err != nil {
tsp.logger.Error("Failed to retrieve trace from tail storage", zap.Error(err))
// Take failures are terminal for this trace: storage may already have
// deleted or otherwise invalidated the spilled batches. Keep going
// without eviction would leave idToTrace / deleteTraceQueue stranded.
tsp.dropTrace(id, time.Now())
continue
}
if allSpans.ResourceSpans().Len() == 0 {
metrics.idNotFoundOnMapCount++
continue
}
candidates = append(candidates, tickCandidate{
id: id,
trace: trace,
data: &samplingpolicy.TraceData{
SpanCount: trace.SpanCount,
SizeBytes: trace.SizeBytes,
ReceivedBatches: allSpans,
},
})
}
numDropPolicies := 0
for _, p := range tsp.policies {
if !p.isDrop {
break
}
numDropPolicies++
}
// Resolve drop decisions before runBatchPolicies so dropped traces
// don't affect a batch policy's threshold.
notDropped := make([]tickCandidate, 0, len(candidates))
for _, c := range candidates {
p := tsp.evaluateDropPolicies(ctx, numDropPolicies, c.id, c.data, metrics)
if p == nil {
notDropped = append(notDropped, c)
continue
}
c.trace.decisionTime = time.Now()
metrics.decisionDropped++
globalTracesSampledByDecision[samplingpolicy.Dropped]++
c.trace.ReceivedBatches = c.data.ReceivedBatches
c.trace.FinalDecision = samplingpolicy.Dropped
c.trace.PolicyName = p.name
tsp.releaseNotSampledTrace(c.id, c.trace)
c.trace.ReceivedBatches = ptrace.NewTraces()
}
if tsp.useTracestate {
tsp.runBatchPolicies(ctx, notDropped)
}
for _, c := range notDropped {
c.trace.decisionTime = time.Now()
decision, policyName, threshold := tsp.makeDecision(ctx, numDropPolicies, c.id, c.data, metrics)
globalTracesSampledByDecision[decision]++
// Keep release paths working with tail storage by attaching the
// retrieved batches back to trace state for this decision.
c.trace.ReceivedBatches = c.data.ReceivedBatches
c.trace.FinalDecision = decision
c.trace.FinalThreshold = threshold
c.trace.PolicyName = policyName
if decision == samplingpolicy.Sampled {
tsp.releaseSampledTrace(ctx, c.id, c.trace)
} else {
tsp.releaseNotSampledTrace(c.id, c.trace)
}
// Sampled or not, remove the batches
c.trace.ReceivedBatches = ptrace.NewTraces()
}
tsp.telemetry.ProcessorTailSamplingSamplingDecisionTimerLatency.Record(tsp.ctx, time.Since(startTime).Milliseconds())
tsp.telemetry.ProcessorTailSamplingSamplingTracesOnMemory.Record(tsp.ctx, tsp.tracesOnMemory.Load())
tsp.telemetry.ProcessorTailSamplingSamplingTraceDroppedTooEarly.Add(tsp.ctx, metrics.idNotFoundOnMapCount)
tsp.telemetry.ProcessorTailSamplingSamplingPolicyEvaluationError.Add(tsp.ctx, metrics.evaluateErrorCount)
for decision, count := range globalTracesSampledByDecision {
tsp.telemetry.ProcessorTailSamplingGlobalCountTracesSampled.Add(tsp.ctx, count, decisionToAttributes[decision])
}
tsp.recordPerPolicyEvaluationMetrics(metrics)
if span != nil && span.IsRecording() {
span.SetAttributes(
attribute.Int("batch.size", batchLen),
attribute.Int("policy.count", len(tsp.policies)),
attribute.Int64("policy.errors", metrics.evaluateErrorCount),
attribute.Int64("decisions.sampled", metrics.decisionSampled),
attribute.Int64("decisions.not_sampled", metrics.decisionNotSampled),
attribute.Int64("decisions.dropped", metrics.decisionDropped),
)
}
if metrics.evaluateErrorCount > 0 && span != nil && span.IsRecording() {
span.SetStatus(codes.Error, fmt.Sprintf("%d policy evaluation errors", metrics.evaluateErrorCount))
}
tsp.logger.Debug("Sampling policy evaluation completed",
zap.Int("batch.len", batchLen),
zap.Int64("sampled", metrics.decisionSampled),
zap.Int64("notSampled", metrics.decisionNotSampled),
zap.Int64("dropped", metrics.decisionDropped),
zap.Int64("droppedPriorToEvaluation", metrics.idNotFoundOnMapCount),
zap.Int64("policyEvaluationErrors", metrics.evaluateErrorCount),
)
return hasMore
}
// makeDecision evaluates tsp.policies[numDropPolicies:], skipping a
// drop-policy prefix the caller already ruled out via evaluateDropPolicies.
func (tsp *tailSamplingSpanProcessor) makeDecision(ctx context.Context, numDropPolicies int, id pcommon.TraceID, traceData *samplingpolicy.TraceData, metrics *policyEvaluationMetrics) (samplingpolicy.Decision, string, pkgsampling.Threshold) {
finalDecision := samplingpolicy.NotSampled
// Decision is a small closed integer set. Indexing by it keeps
// first-policy-wins as a nil check without allocating a map on every trace.
var samplingDecisions [samplingpolicy.NumDecisions]*policy
threshold := pkgsampling.Threshold{}
haveThreshold := false
for i := numDropPolicies; i < len(tsp.policies); i++ {
p := tsp.policies[i]
startTime := time.Now()
decision, th, err := p.evaluator.EvaluateWithThreshold(ctx, id, traceData)
metrics.addDecisionTime(i, time.Since(startTime))
if err != nil {
if samplingDecisions[samplingpolicy.Error] == nil {
samplingDecisions[samplingpolicy.Error] = p
}
metrics.evaluateErrorCount++
tsp.logger.Debug("Sampling policy error", zap.Error(err))
continue
}
metrics.addDecision(i, decision, traceData.SpanCount, int64(traceData.SizeBytes))
// Keep track of which policy was used so that we can understand which
// policy sampled/dropped a span.
if samplingDecisions[decision] == nil {
samplingDecisions[decision] = p
}
if decision == samplingpolicy.Sampled {
if !haveThreshold || pkgsampling.ThresholdLessThan(th, threshold) {
threshold = th
haveThreshold = true
// Ensure we record the policy that will match the span's threshold.
samplingDecisions[samplingpolicy.Sampled] = p
}
}
// Break early if dropped. This can drastically reduce tick/decision latency.
if decision == samplingpolicy.Dropped {
break
}
// If sampleOnFirstMatch is enabled, make decision as soon as a policy matches
if tsp.sampleOnFirstMatch && decision == samplingpolicy.Sampled {
break
}
}
var sampledPolicy *policy
switch {
case samplingDecisions[samplingpolicy.Dropped] != nil: // Dropped takes precedence
finalDecision = samplingpolicy.Dropped
sampledPolicy = samplingDecisions[samplingpolicy.Dropped]
//nolint:staticcheck // SA1019: Use of inverted decisions until they are fully removed.
case samplingDecisions[samplingpolicy.InvertNotSampled] != nil: // Then InvertNotSampled
finalDecision = samplingpolicy.NotSampled
case samplingDecisions[samplingpolicy.Sampled] != nil:
finalDecision = samplingpolicy.Sampled
sampledPolicy = samplingDecisions[samplingpolicy.Sampled]
//nolint:staticcheck // SA1019: Use of inverted decisions until they are fully removed.
case samplingDecisions[samplingpolicy.InvertSampled] != nil && samplingDecisions[samplingpolicy.NotSampled] == nil:
finalDecision = samplingpolicy.Sampled
//nolint:staticcheck // SA1019: Use of inverted decisions until they are fully removed.
sampledPolicy = samplingDecisions[samplingpolicy.InvertSampled]
}
if tsp.recordPolicy && sampledPolicy != nil && finalDecision == samplingpolicy.Sampled {
sampling.SetAttrOnScopeSpans(traceData.ReceivedBatches, "tailsampling.policy", sampledPolicy.name)
}
if finalDecision == samplingpolicy.Sampled && tsp.useTracestate {
sampling.WriteEffectiveThreshold(ctx, traceData.ReceivedBatches, threshold, tsp.telemetry.ProcessorTailSamplingCountSpansWithUnparseableTracestate)
}
switch finalDecision {
case samplingpolicy.Sampled:
metrics.decisionSampled++
case samplingpolicy.NotSampled:
metrics.decisionNotSampled++
case samplingpolicy.Dropped:
metrics.decisionDropped++
}
return finalDecision, getPolicyName(sampledPolicy), threshold
}
// makeDecisionOnSpanIngest is used by span-ingest mode. It only returns
// terminal decisions at ingest time. All other outcomes remain pending.
func (tsp *tailSamplingSpanProcessor) makeDecisionOnSpanIngest(id pcommon.TraceID, trace *samplingpolicy.TraceData, metrics *policyEvaluationMetrics) (samplingpolicy.Decision, string, pkgsampling.Threshold) {
ctx := context.Background()
// Track whether a drop policy didn't match this batch. A drop policy
// returning NotSampled means it didn't match these spans but could still
// match future spans for this trace, so we must defer a Sampled decision
// to avoid forwarding spans that should ultimately be dropped.
dropPolicyPending := false
for i, p := range tsp.policies {
// If we have moved past all drop policies and one is pending there is
// no use wasting work on evaluation when we can't sample the trace.
if !p.isDrop && dropPolicyPending {
break
}
startTime := time.Now()
decision, th, err := p.evaluator.EvaluateWithThreshold(ctx, id, trace)
metrics.addDecisionTime(i, time.Since(startTime))
if err != nil {
metrics.evaluateErrorCount++
tsp.logger.Debug("Sampling policy error", zap.Error(err))
continue
}
metrics.addDecision(i, decision, trace.SpanCount, int64(trace.SizeBytes))
if decision == samplingpolicy.Dropped {
metrics.decisionDropped++
return samplingpolicy.Dropped, p.name, pkgsampling.AlwaysSampleThreshold
}
if p.isDrop {
// Treat all other results as pending as the result could change.
dropPolicyPending = true
continue
}
// Since drop policies are always sorted to the top we know dropPolicyPending is accurate.
if decision == samplingpolicy.Sampled && !dropPolicyPending {
metrics.decisionSampled++
if tsp.recordPolicy {
sampling.SetAttrOnScopeSpans(trace.ReceivedBatches, "tailsampling.policy", p.name)
}
if tsp.useTracestate {
sampling.WriteEffectiveThreshold(ctx, trace.ReceivedBatches, th, tsp.telemetry.ProcessorTailSamplingCountSpansWithUnparseableTracestate)
}
return samplingpolicy.Sampled, p.name, th
}
}
return samplingpolicy.Pending, "", pkgsampling.AlwaysSampleThreshold
}
// splitResourceSpansByTrace moves spans from rss into one ResourceSpans per
// trace ID in one pass, instead of grouping into a slice first. The resource,
// scopes, and their schema URLs are copied, since several traces may share them.
//
// We walk scopes in order and never go back, so lastScope is enough to open
// dest scopes in first-seen order.
func splitResourceSpansByTrace(rss ptrace.ResourceSpans) []traceBatch {
srcScopes := rss.ScopeSpans()
type builder struct {
rs ptrace.ResourceSpans
destScope ptrace.ScopeSpans
lastScope int // Last opened source scope for a dest (-1 until the first span)
count int64
hasRoot bool
}
builders := make(map[pcommon.TraceID]*builder)
for j := 0; j < srcScopes.Len(); j++ {
srcScope := srcScopes.At(j)
spans := srcScope.Spans()
for k := 0; k < spans.Len(); k++ {
span := spans.At(k)
id := span.TraceID()
b, ok := builders[id]
if !ok {
rs := ptrace.NewResourceSpans()
rss.Resource().CopyTo(rs.Resource())
rs.SetSchemaUrl(rss.SchemaUrl())
b = &builder{rs: rs, lastScope: -1}
builders[id] = b
}
if b.lastScope != j {
dest := b.rs.ScopeSpans().AppendEmpty()
srcScope.Scope().CopyTo(dest.Scope())
dest.SetSchemaUrl(srcScope.SchemaUrl())
b.destScope = dest
b.lastScope = j
}
destSpan := b.destScope.Spans().AppendEmpty()
span.MoveTo(destSpan)
b.count++
if destSpan.ParentSpanID().IsEmpty() {
b.hasRoot = true
}
}
}
batches := make([]traceBatch, 0, len(builders))
for id, b := range builders {
batches = append(batches, traceBatch{
id: id,
hasRoot: b.hasRoot,
rss: b.rs,
spanCount: b.count,
})
}
return batches
}
func (tsp *tailSamplingSpanProcessor) processTrace(id pcommon.TraceID, rss ptrace.ResourceSpans, spanCount int64, containsRootSpan bool) {
currTime := time.Now()
var newTraceIDs int64
defer func() {
// Need a closure here to delay evaluation of newTraceIDs.
tsp.telemetry.ProcessorTailSamplingNewTraceIDReceived.Add(tsp.ctx, newTraceIDs)
}()
actualData, ok := tsp.idToTrace[id]
if !ok {
actualData = &TraceData{
arrivalTime: currTime,
TraceData: samplingpolicy.TraceData{
SpanCount: spanCount,
ReceivedBatches: ptrace.NewTraces(),
},
}
tsp.idToTrace[id] = actualData
tsp.tracesOnMemory.Add(1)
newTraceIDs++
actualData.batchID = tsp.decisionBatcher.AddToCurrentBatch(id)
if !tsp.blockOnOverflow {
actualData.deleteElement = tsp.deleteTraceQueue.PushBack(id)
}
} else {
actualData.SpanCount += spanCount
}
if containsRootSpan && tsp.cfg.DecisionWaitAfterRootReceived > 0 {
actualData.batchID = tsp.decisionBatcher.MoveToEarlierBatch(id, actualData.batchID, uint64(tsp.cfg.DecisionWaitAfterRootReceived.Seconds()))
}
finalDecision := actualData.FinalDecision
// ResourceSpansSize walks the proto tree. SizeBytes is only needed to drop
// oversized traces or to record count_bytes_sampled. That metric is behind
// an alpha feature gate (off by default) because proto-sizing every batch
// is expensive, so skip the walk unless a size limit or the gate is on.
if tsp.maxTraceSizeBytes > 0 || telemetry.IsMetricStatCountBytesSampledEnabled() {
var m ptrace.ProtoMarshaler
actualData.SizeBytes += uint64(m.ResourceSpansSize(rss))
}
if finalDecision == samplingpolicy.Unspecified &&
tsp.maxTraceSizeBytes > 0 &&
actualData.SizeBytes > tsp.maxTraceSizeBytes {
tsp.telemetry.ProcessorTailSamplingTracesDroppedTooLarge.Add(tsp.ctx, 1)
actualData.FinalDecision = samplingpolicy.NotSampled
// Since we are not in a normal decision flow when dropping large traces, also be sure to remove it from the batcher.
tsp.decisionBatcher.RemoveFromBatch(id, actualData.batchID)
tsp.releaseNotSampledTrace(id, actualData)
return
}
if finalDecision == samplingpolicy.Unspecified {
if tsp.cfg.SamplingStrategy == samplingStrategySpanIngest {
metrics := newPolicyEvaluationMetrics(len(tsp.policies))
evaluationStart := time.Now()
actualData.decisionTime = evaluationStart
// Build an isolated one-batch trace view for ingest-time evaluation
// using moves to avoid deep-copying span data.
spanIngestTraceData := samplingpolicy.TraceData{
SpanCount: actualData.SpanCount,
SizeBytes: actualData.SizeBytes,
ReceivedBatches: ptrace.NewTraces(),
}
appendToTraces(spanIngestTraceData.ReceivedBatches, rss)
decision, policyName, threshold := tsp.makeDecisionOnSpanIngest(id, &spanIngestTraceData, metrics)
tsp.recordImmediateDecisionMetrics(decision, metrics, time.Since(evaluationStart))
if decision == samplingpolicy.Sampled || decision == samplingpolicy.Dropped {
// Release all accumulated spans (prior pending batches + current batch)
// without writing the current batch to storage first.
merged := ptrace.NewTraces()
allSpans, err := tsp.tailStorage.Take(id)
if err != nil {
tsp.logger.Error("Failed to retrieve trace from tail storage", zap.Error(err))
tsp.dropTrace(id, time.Now())
return
}
appendAllTraces(merged, allSpans)
appendAllTraces(merged, spanIngestTraceData.ReceivedBatches)
actualData.ReceivedBatches = merged
actualData.FinalDecision = decision
actualData.PolicyName = policyName
if decision == samplingpolicy.Sampled {
actualData.FinalThreshold = threshold
tsp.releaseSampledTrace(tsp.ctx, id, actualData)
} else {
tsp.releaseNotSampledTrace(id, actualData)
}
} else {
// Persist current batch for pending traces.
if err := tsp.tailStorage.Append(id, spanIngestTraceData.ReceivedBatches); err != nil {
tsp.logger.Error("Failed to append trace to tail storage", zap.Error(err))
}
}
return
}
// If the final decision hasn't been made, add the new spans to the
// existing trace.
traceTd := ptrace.NewTraces()
appendToTraces(traceTd, rss)
if err := tsp.tailStorage.Append(id, traceTd); err != nil {
tsp.logger.Error("Failed to append trace to tail storage", zap.Error(err))
}
return
}
switch finalDecision {
case samplingpolicy.Sampled:
traceTd := ptrace.NewTraces()
appendToTraces(traceTd, rss)
if tsp.useTracestate {
sampling.WriteEffectiveThreshold(tsp.ctx, traceTd, actualData.FinalThreshold, tsp.telemetry.ProcessorTailSamplingCountSpansWithUnparseableTracestate)
}
tsp.forwardSpans(tsp.ctx, traceTd)
case samplingpolicy.NotSampled:
// TODO: I don't think this is correct? If it isn't sampled shouldn't we just do nothing?
tsp.releaseNotSampledTrace(id, actualData)
default:
tsp.logger.Warn("Unexpected sampling decision", zap.Int("decision", int(finalDecision)))
}
if !actualData.decisionTime.IsZero() {
tsp.telemetry.ProcessorTailSamplingSamplingLateSpanAge.Record(tsp.ctx, int64(time.Since(actualData.decisionTime)/time.Second))
}
}
func extensions(host component.Host) map[string]samplingpolicy.Extension {
if host == nil {
return nil
}
extensions := host.GetExtensions()
scoped := map[string]samplingpolicy.Extension{}
for id, ext := range extensions {
evaluator, ok := ext.(samplingpolicy.Extension)
if ok {
scoped[id.String()] = evaluator
}
}
return scoped
}
func tailStorageExtension(host component.Host, storageID component.ID) (tailstorageextension.TailStorage, error) {
if host == nil {
return nil, errors.New("tail storage extension configured but host is nil")
}
extension, ok := host.GetExtensions()[storageID]
if !ok {
return nil, fmt.Errorf("tail storage extension '%s' not found", storageID)
}
storageExtension, ok := extension.(tailstorageextension.TailStorage)
if !ok {
return nil, fmt.Errorf("non-tail-storage extension '%s' found", storageID)
}
return storageExtension, nil
}
// Shutdown is invoked during service shutdown.
func (tsp *tailSamplingSpanProcessor) Shutdown(context.Context) error {
// All receivers will be shutdown before processors so no sends will be done anymore.
close(tsp.workChan)
if tsp.doneChan != nil {
<-tsp.doneChan
}
return nil
}
// dropTrace removes the trace from all memory locations. Returns true if it was removed and false if not found.
func (tsp *tailSamplingSpanProcessor) dropTrace(traceID pcommon.TraceID, deletionTime time.Time) bool {
trace, ok := tsp.idToTrace[traceID]
if !ok {
tsp.logger.Debug("Attempt to delete trace ID not on table", zap.Stringer("id", traceID))
return false
}
delete(tsp.idToTrace, traceID)
// The trace is gone from idToTrace regardless of whether the storage
// delete below succeeds, so the counter must be decremented here.
tsp.tracesOnMemory.Add(-1)
if err := tsp.tailStorage.Delete(traceID); err != nil {
tsp.logger.Error("Failed to delete trace from tail storage", zap.Error(err))
return false
}
if trace.deleteElement != nil {
tsp.deleteTraceQueue.Remove(trace.deleteElement)
}
tsp.telemetry.ProcessorTailSamplingSamplingTraceRemovalAge.Record(tsp.ctx, int64(deletionTime.Sub(trace.arrivalTime)/time.Second))
return true
}
// forwardSpans sends the trace data to the next consumer. it is different from
// releaseSampledTrace in that it does not modify any tsp state.
func (tsp *tailSamplingSpanProcessor) forwardSpans(ctx context.Context, td ptrace.Traces) {
if err := tsp.nextConsumer.ConsumeTraces(ctx, td); err != nil {
tsp.logger.Warn(
"Error sending spans to destination",
zap.Error(err),
)
}
}
// releaseSampledTrace sends the trace data to the next consumer. It
// additionally adds the trace ID to the cache of sampled trace IDs. If the
// trace ID is cached, it deletes the spans from the internal map.
func (tsp *tailSamplingSpanProcessor) releaseSampledTrace(ctx context.Context, id pcommon.TraceID, td *TraceData) {
for _, hook := range tsp.sampledHooks {
hook(ctx, id, td)
}
tsp.sampledIDCache.Put(id, cache.DecisionMetadata{
PolicyName: td.PolicyName,
Threshold: td.FinalThreshold,
})
tsp.forwardSpans(ctx, td.ReceivedBatches)
_, ok := tsp.sampledIDCache.Get(id)
if ok {
tsp.dropTrace(id, time.Now())
}
}
// releaseNotSampledTrace adds the trace ID to the cache of not sampled trace
// IDs. If the trace ID is cached, it deletes the spans from the internal map.
func (tsp *tailSamplingSpanProcessor) releaseNotSampledTrace(id pcommon.TraceID, td *TraceData) {
for _, hook := range tsp.nonSampledHooks {
hook(context.Background(), id, td)
}
tsp.nonSampledIDCache.Put(id, cache.DecisionMetadata{PolicyName: td.PolicyName})
_, ok := tsp.nonSampledIDCache.Get(id)
if ok {
tsp.dropTrace(id, time.Now())
}
}
func getPolicyName(policy *policy) string {
if policy == nil {
return ""
}
return policy.name
}
func appendToTraces(dest ptrace.Traces, rss ptrace.ResourceSpans) {
rs := dest.ResourceSpans().AppendEmpty()
rss.MoveTo(rs)
}
func appendAllTraces(dest, src ptrace.Traces) {
rs := src.ResourceSpans()
for i := 0; i < rs.Len(); i++ {
appendToTraces(dest, rs.At(i))
}
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package tailsamplingprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor"
import (
"context"
"encoding/binary"
"errors"
"slices"
"sync/atomic"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.opentelemetry.io/collector/processor"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor/internal/metadata"
)
// shardedProcessor wraps N independent tailSamplingSpanProcessor instances,
// routing traces to shards by trace ID. Each shard runs its own event loop
// goroutine, eliminating contention between trace ingestion and sampling
// decision evaluation under high load. It is the single code path for all
// configurations: num_shards of 1 (the default) simply creates one shard.
type shardedProcessor struct {
tracer trace.Tracer
shards []*tailSamplingSpanProcessor
numShards uint32
}
// shardShared holds the dependencies all shards of one processor have in
// common, built once instead of per shard.
type shardShared struct {
telemetry *metadata.TelemetryBuilder
tracer trace.Tracer
// tracesOnMemory counts the traces held in memory across all shards so
// the traces_on_memory gauge reports the processor-wide total instead of
// per-shard values overwriting each other on the same time series.
tracesOnMemory *atomic.Int64
}
func newShardedTracesProcessor(ctx context.Context, set processor.Settings, nextConsumer consumer.Traces, cfg Config) (*shardedProcessor, error) {
numShards := max(1, cfg.NumShards)
shardCfg := cfg
if numShards > 1 {
shardCfg.NumTraces = max(1, cfg.NumTraces/uint64(numShards))
shardCfg.ExpectedNewTracesPerSec = cfg.ExpectedNewTracesPerSec / uint64(numShards)
// Decision caches are keyed on trace_id and each shard only sees ~1/N of
// the trace_id space. Without this division total decision-cache memory
// would scale by num_shards, defeating the memory benefit of sharding.
shardCfg.DecisionCache.SampledCacheSize = divideRate(cfg.DecisionCache.SampledCacheSize, numShards)
shardCfg.DecisionCache.NonSampledCacheSize = divideRate(cfg.DecisionCache.NonSampledCacheSize, numShards)
shardCfg.PolicyCfgs = dividePolicyRates(cfg.PolicyCfgs, numShards)
}
telemetry, err := metadata.NewTelemetryBuilder(set.TelemetrySettings)
if err != nil {
return nil, err
}
shared := shardShared{
telemetry: telemetry,
tracer: metadata.Tracer(set.TelemetrySettings),
tracesOnMemory: new(atomic.Int64),
}
shards := make([]*tailSamplingSpanProcessor, numShards)
for i := range numShards {
p, err := newShardProcessor(ctx, set, nextConsumer, shardCfg, shared)
if err != nil {
return nil, err
}
shards[i] = p
}
return &shardedProcessor{
tracer: shared.tracer,
shards: shards,
numShards: numShards,
}, nil
}
// dividePolicyRates returns a copy of cfgs with per-second rate limits
// divided by numShards. Traces are distributed uniformly across shards by
// trace ID, so each shard enforcing 1/N of a limit keeps the aggregate limit
// close to the configured value. Without this, every shard would enforce the
// full limit and the effective limit would be limit*numShards.
func dividePolicyRates(cfgs []PolicyCfg, numShards uint32) []PolicyCfg {
if numShards <= 1 || len(cfgs) == 0 {
return cfgs
}
out := slices.Clone(cfgs)
for i := range out {
divideSharedPolicyRates(&out[i].sharedPolicyCfg, numShards)
out[i].CompositeCfg = divideCompositeRates(out[i].CompositeCfg, numShards)
out[i].AndCfg.SubPolicyCfg = divideAndSubPolicyRates(out[i].AndCfg.SubPolicyCfg, numShards)
divideSharedPolicyRates(&out[i].NotCfg.SubPolicy.sharedPolicyCfg, numShards)
out[i].DropCfg.SubPolicyCfg = divideAndSubPolicyRates(out[i].DropCfg.SubPolicyCfg, numShards)
}
return out
}
func divideCompositeRates(cfg CompositeCfg, numShards uint32) CompositeCfg {
cfg.MaxTotalSpansPerSecond = divideRate(cfg.MaxTotalSpansPerSecond, numShards)
// RateAllocation is percentage-based and needs no division.
if len(cfg.SubPolicyCfg) > 0 {
subs := slices.Clone(cfg.SubPolicyCfg)
for i := range subs {
divideSharedPolicyRates(&subs[i].sharedPolicyCfg, numShards)
subs[i].AndCfg.SubPolicyCfg = divideAndSubPolicyRates(subs[i].AndCfg.SubPolicyCfg, numShards)
}
cfg.SubPolicyCfg = subs
}
return cfg
}
func divideAndSubPolicyRates(subs []AndSubPolicyCfg, numShards uint32) []AndSubPolicyCfg {
if len(subs) == 0 {
return subs
}
out := slices.Clone(subs)
for i := range out {
divideSharedPolicyRates(&out[i].sharedPolicyCfg, numShards)
}
return out
}
func divideSharedPolicyRates(cfg *sharedPolicyCfg, numShards uint32) {
// Burst capacity is intentionally not divided: besides absorbing short
// spikes, it caps the size of a single trace that can pass the limiter,
// and a trace is always evaluated whole on one shard. An unset burst
// capacity defaults to 2x the per-shard (divided) rate, so pin it to 2x
// the configured rate first to keep the largest admissible trace
// independent of num_shards.
if cfg.RateLimitingCfg.BurstCapacity <= 0 && cfg.RateLimitingCfg.SpansPerSecond > 0 {
cfg.RateLimitingCfg.BurstCapacity = 2 * cfg.RateLimitingCfg.SpansPerSecond
}
if cfg.BytesLimitingCfg.BurstCapacity <= 0 && cfg.BytesLimitingCfg.BytesPerSecond > 0 {
cfg.BytesLimitingCfg.BurstCapacity = 2 * cfg.BytesLimitingCfg.BytesPerSecond
}
cfg.RateLimitingCfg.SpansPerSecond = divideRate(cfg.RateLimitingCfg.SpansPerSecond, numShards)
cfg.BytesLimitingCfg.BytesPerSecond = divideRate(cfg.BytesLimitingCfg.BytesPerSecond, numShards)
}
// divideRate splits a positive limit across numShards, keeping a minimum of
// 1 so a limit smaller than numShards doesn't round down to 0 (which would
// mean "sample nothing" for rates and "disabled" for cache sizes). Zero and
// negative values mean unset/disabled and are preserved.
func divideRate[T int | int64](v T, numShards uint32) T {
if v <= 0 {
return v
}
return max(1, v/T(numShards))
}
func (*shardedProcessor) Capabilities() consumer.Capabilities {
return consumer.Capabilities{MutatesData: true}
}
func (sp *shardedProcessor) Start(ctx context.Context, host component.Host) error {
for _, s := range sp.shards {
if err := s.Start(ctx, host); err != nil {
return err
}
}
return nil
}
func (sp *shardedProcessor) Shutdown(ctx context.Context) error {
var errs []error
for _, s := range sp.shards {
if err := s.Shutdown(ctx); err != nil {
errs = append(errs, err)
}
}
return errors.Join(errs...)
}
func (sp *shardedProcessor) ConsumeTraces(ctx context.Context, td ptrace.Traces) error {
_, span := sp.tracer.Start(ctx, "tailsampling.ConsumeTraces")
defer span.End()
var totalSpans, totalTraces, totalResourceSpans int64
shardBatches := make([][]traceBatch, sp.numShards)
// The split moves spans out of td, so returning an error after it would
// make a retry resend empty spans.
for _, rss := range td.ResourceSpans().All() {
totalResourceSpans++
for _, batch := range splitResourceSpansByTrace(rss) {
shardIdx := sp.traceIDToShard(batch.id)
shardBatches[shardIdx] = append(shardBatches[shardIdx], batch)
totalSpans += batch.spanCount
totalTraces++
}
}
for i, batch := range shardBatches {
if len(batch) > 0 {
sp.shards[i].workChan <- batch
}
}
if span.IsRecording() {
span.SetAttributes(
attribute.Int64("traces.count", totalTraces),
attribute.Int64("spans.count", totalSpans),
attribute.Int64("resource_spans.count", totalResourceSpans),
)
}
return nil
}
func (sp *shardedProcessor) SetSamplingPolicy(cfgs []PolicyCfg) {
cfgs = dividePolicyRates(cfgs, sp.numShards)
for _, s := range sp.shards {
s.SetSamplingPolicy(cfgs)
}
}
func (sp *shardedProcessor) SetMaximumTraceSizeBytes(size uint64) {
for _, s := range sp.shards {
s.SetMaximumTraceSizeBytes(size)
}
}
func (sp *shardedProcessor) traceIDToShard(id pcommon.TraceID) uint32 {
// Mix the full trace ID instead of taking raw bytes modulo numShards:
// real-world trace IDs are not always uniformly random (some SDKs and
// proxies embed timestamps or constants, or zero-pad 64-bit IDs), and a
// raw modulo would concentrate such IDs on few shards, exhausting one
// shard's NumTraces budget while the others sit empty.
h := binary.LittleEndian.Uint64(id[0:8]) ^ binary.LittleEndian.Uint64(id[8:16])
// splitmix64 finalizer.
h ^= h >> 30
h *= 0xbf58476d1ce4e5b9
h ^= h >> 27
h *= 0x94d049bb133111eb
h ^= h >> 31
return uint32(h % uint64(sp.numShards))
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package cloudflarereceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/cloudflarereceiver"
import (
"errors"
"fmt"
"net"
"go.opentelemetry.io/collector/config/configtls"
"go.uber.org/multierr"
)
// Config holds all the parameters to start an HTTP server that can be sent logs from CloudFlare
type Config struct {
Logs LogsConfig `mapstructure:"logs"`
// prevent unkeyed literal initialization
_ struct{}
}
type LogsConfig struct {
Secret string `mapstructure:"secret"`
Endpoint string `mapstructure:"endpoint"`
TLS *configtls.ServerConfig `mapstructure:"tls"`
Attributes map[string]string `mapstructure:"attributes"`
TimestampField string `mapstructure:"timestamp_field"`
TimestampFormat string `mapstructure:"timestamp_format"`
Separator string `mapstructure:"separator"`
// MaxRequestBodySize sets the maximum request body size in bytes. 0 means no limit.
// Default: 20MB
MaxRequestBodySize int64 `mapstructure:"max_request_body_size,omitempty"`
// prevent unkeyed literal initialization
_ struct{}
}
var (
errNoEndpoint = errors.New("an endpoint must be specified")
errNoCert = errors.New("tls was configured, but no cert file was specified")
errNoKey = errors.New("tls was configured, but no key file was specified")
defaultTimestampField = "EdgeStartTimestamp"
defaultTimestampFormat = "rfc3339"
defaultSeparator = "."
)
func (c *Config) Validate() error {
if c.Logs.Endpoint == "" {
return errNoEndpoint
}
if c.Logs.MaxRequestBodySize == 0 {
c.Logs.MaxRequestBodySize = 20 * 1024 * 1024 // 20MB default
}
var errs error
// Validate timestamp_format if provided
if c.Logs.TimestampFormat != "" {
switch c.Logs.TimestampFormat {
case "unix", "unixnano", "rfc3339":
default:
errs = multierr.Append(errs, fmt.Errorf("invalid timestamp_format %q, must be one of: unix, unixnano, rfc3339", c.Logs.TimestampFormat))
}
}
if c.Logs.TLS != nil {
// Missing key
if c.Logs.TLS.KeyFile == "" {
errs = multierr.Append(errs, errNoKey)
}
// Missing cert
if c.Logs.TLS.CertFile == "" {
errs = multierr.Append(errs, errNoCert)
}
}
_, _, err := net.SplitHostPort(c.Logs.Endpoint)
if err != nil {
errs = multierr.Append(errs, fmt.Errorf("failed to split endpoint into 'host:port' pair: %w", err))
}
return errs
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package cloudflarereceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/cloudflarereceiver"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/receiver"
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/cloudflarereceiver/internal/metadata"
)
// NewFactory returns the component factory for the cloudflarereceiver
func NewFactory() receiver.Factory {
return receiver.NewFactory(
metadata.Type,
createDefaultConfig,
receiver.WithLogs(createLogsReceiver, metadata.LogsStability),
)
}
func createLogsReceiver(
_ context.Context,
params receiver.Settings,
rConf component.Config,
consumer consumer.Logs,
) (receiver.Logs, error) {
cfg := rConf.(*Config)
return newLogsReceiver(params, cfg, consumer)
}
func createDefaultConfig() component.Config {
return &Config{
Logs: LogsConfig{
TimestampField: defaultTimestampField,
TimestampFormat: defaultTimestampFormat,
Separator: defaultSeparator,
},
}
}
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/receiver"
)
// LogsBuilder provides an interface for scrapers to report logs while taking care of all the transformations
// required to produce log representation defined in metadata and user config.
type LogsBuilder struct {
logsBuffer plog.Logs
logRecordsBuffer plog.LogRecordSlice
buildInfo component.BuildInfo // contains version information.
}
// LogBuilderOption applies changes to default logs builder.
type LogBuilderOption interface {
apply(*LogsBuilder)
}
func NewLogsBuilder(settings receiver.Settings) *LogsBuilder {
lb := &LogsBuilder{
logsBuffer: plog.NewLogs(),
logRecordsBuffer: plog.NewLogRecordSlice(),
buildInfo: settings.BuildInfo,
}
return lb
}
// ResourceLogsOption applies changes to provided resource logs.
type ResourceLogsOption interface {
apply(plog.ResourceLogs)
}
type resourceLogsOptionFunc func(plog.ResourceLogs)
func (rlof resourceLogsOptionFunc) apply(rl plog.ResourceLogs) {
rlof(rl)
}
// WithLogsResource sets the provided resource on the emitted ResourceLogs.
// It's recommended to use ResourceBuilder to create the resource.
func WithLogsResource(res pcommon.Resource) ResourceLogsOption {
return resourceLogsOptionFunc(func(rl plog.ResourceLogs) {
res.CopyTo(rl.Resource())
})
}
// AppendLogRecord adds a log record to the logs builder.
func (lb *LogsBuilder) AppendLogRecord(lr plog.LogRecord) {
lr.MoveTo(lb.logRecordsBuffer.AppendEmpty())
}
// EmitForResource saves all the generated logs under a new resource and updates the internal state to be ready for
// recording another set of log records as part of another resource. This function can be helpful when one scraper
// needs to emit logs from several resources. Otherwise calling this function is not required,
// just `Emit` function can be called instead.
// Resource attributes should be provided as ResourceLogsOption arguments.
func (lb *LogsBuilder) EmitForResource(options ...ResourceLogsOption) {
rl := plog.NewResourceLogs()
ils := rl.ScopeLogs().AppendEmpty()
ils.Scope().SetName(ScopeName)
ils.Scope().SetVersion(lb.buildInfo.Version)
for _, op := range options {
op.apply(rl)
}
if lb.logRecordsBuffer.Len() > 0 {
lb.logRecordsBuffer.MoveAndAppendTo(ils.LogRecords())
lb.logRecordsBuffer = plog.NewLogRecordSlice()
}
if ils.LogRecords().Len() > 0 {
rl.MoveTo(lb.logsBuffer.ResourceLogs().AppendEmpty())
}
}
// Emit returns all the logs accumulated by the logs builder and updates the internal state to be ready for
// recording another set of logs. This function will be responsible for applying all the transformations required to
// produce logs representation defined in metadata and user config.
func (lb *LogsBuilder) Emit(options ...ResourceLogsOption) plog.Logs {
lb.EmitForResource(options...)
logs := lb.logsBuffer
lb.logsBuffer = plog.NewLogs()
return logs
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package cloudflarereceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/cloudflarereceiver"
import (
"bytes"
"compress/gzip"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"sync"
"time"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componentstatus"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/config/confignet"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
rcvr "go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/receiverhelper"
"go.uber.org/zap"
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal/errorutil"
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/cloudflarereceiver/internal/metadata"
)
type logsReceiver struct {
logger *zap.Logger
cfg *LogsConfig
serverConfig *confighttp.ServerConfig
server *http.Server
consumer consumer.Logs
wg *sync.WaitGroup
telemetrySettings component.TelemetrySettings
id component.ID // ID of the receiver component
obsrecv *receiverhelper.ObsReport
}
const secretHeaderName = "X-CF-Secret"
func newLogsReceiver(params rcvr.Settings, cfg *Config, consumer consumer.Logs) (*logsReceiver, error) {
obsrecv, err := receiverhelper.NewObsReport(receiverhelper.ObsReportSettings{
ReceiverID: params.ID,
Transport: "http",
ReceiverCreateSettings: params,
})
if err != nil {
return nil, err
}
recv := &logsReceiver{
cfg: &cfg.Logs,
consumer: consumer,
logger: params.Logger,
wg: &sync.WaitGroup{},
obsrecv: obsrecv,
telemetrySettings: params.TelemetrySettings,
id: params.ID,
}
serverConfig := confighttp.NewDefaultServerConfig()
serverConfig.NetAddr = confignet.AddrConfig{
Endpoint: recv.cfg.Endpoint,
Transport: "tcp",
}
serverConfig.ReadHeaderTimeout = 20 * time.Second
// confighttp decompresses the body and applies its own limit before
// handleRequest runs. Without this value, it uses its 20MiB default.
serverConfig.MaxRequestBodySize = recv.cfg.MaxRequestBodySize
if tlsConfig := recv.cfg.TLS; tlsConfig != nil {
serverConfig.TLS = configoptional.Some(*tlsConfig)
}
recv.serverConfig = &serverConfig
return recv, nil
}
func (l *logsReceiver) Start(ctx context.Context, host component.Host) error {
return l.startListening(ctx, host)
}
func (l *logsReceiver) Shutdown(ctx context.Context) error {
l.logger.Debug("Shutting down server")
if l.server != nil {
err := l.server.Shutdown(ctx)
if err != nil {
return err
}
}
l.logger.Debug("Waiting for shutdown to complete.")
l.wg.Wait()
return nil
}
func (l *logsReceiver) startListening(ctx context.Context, host component.Host) error {
l.logger.Debug("starting receiver HTTP server")
server, err := l.serverConfig.ToServer(
ctx,
host.GetExtensions(),
l.telemetrySettings,
http.HandlerFunc(l.handleRequest),
)
if err != nil {
return err
}
l.server = server
listener, err := l.serverConfig.ToListener(ctx)
if err != nil {
return err
}
l.wg.Go(func() {
if l.serverConfig.TLS.HasValue() {
tlsConfig := l.serverConfig.TLS.Get()
l.logger.Debug(
"Starting ServeTLS",
zap.String("address", l.serverConfig.NetAddr.Endpoint),
zap.String("certfile", tlsConfig.CertFile),
zap.String("keyfile", tlsConfig.KeyFile),
)
} else {
l.logger.Debug(
"Starting Serve",
zap.String("address", l.serverConfig.NetAddr.Endpoint),
)
}
err := server.Serve(listener)
l.logger.Debug("Serve done")
if !errors.Is(err, http.ErrServerClosed) {
l.logger.Error("Serve failed", zap.Error(err))
componentstatus.ReportStatus(host, componentstatus.NewFatalErrorEvent(err))
}
})
return nil
}
func (l *logsReceiver) handleRequest(rw http.ResponseWriter, req *http.Request) {
if l.cfg.Secret != "" {
secretHeader := req.Header.Get(secretHeaderName)
if secretHeader == "" {
rw.WriteHeader(http.StatusUnauthorized)
l.logger.Debug("Got payload with no Secret when it was specified in config, dropping...")
return
} else if secretHeader != l.cfg.Secret {
rw.WriteHeader(http.StatusUnauthorized)
l.logger.Debug("Got payload with invalid Secret, dropping...")
return
}
}
// Limit request body size
req.Body = http.MaxBytesReader(rw, req.Body, l.cfg.MaxRequestBodySize)
var payload []byte
if req.Header.Get("Content-Encoding") == "gzip" {
reader, err := gzip.NewReader(req.Body)
if err != nil {
rw.WriteHeader(http.StatusUnprocessableEntity)
l.logger.Debug("Got payload with gzip, but failed to read", zap.Error(err))
return
}
defer reader.Close()
// Read the decompressed response body. MaxBytesReader above bounds the
// compressed bytes only, so the decompressed stream needs its own limit:
// a small gzip payload can otherwise expand without bound. Read one byte
// past the limit so exceeding it can be distinguished from meeting it.
var decompressed io.Reader = reader
if l.cfg.MaxRequestBodySize > 0 {
decompressed = io.LimitReader(reader, l.cfg.MaxRequestBodySize+1)
}
payload, err = io.ReadAll(decompressed)
if err != nil {
rw.WriteHeader(http.StatusUnprocessableEntity)
l.logger.Debug("Got payload with gzip, but failed to read", zap.Error(err))
return
}
if l.cfg.MaxRequestBodySize > 0 && int64(len(payload)) > l.cfg.MaxRequestBodySize {
rw.WriteHeader(http.StatusUnprocessableEntity)
l.logger.Debug("Got gzip payload whose decompressed size exceeds max_request_body_size, dropping...",
zap.Int64("max_request_body_size", l.cfg.MaxRequestBodySize),
zap.String("remote", req.RemoteAddr))
return
}
} else {
var err error
payload, err = io.ReadAll(req.Body)
if err != nil {
rw.WriteHeader(http.StatusUnprocessableEntity)
l.logger.Debug("Failed to read alerts payload", zap.Error(err), zap.String("remote", req.RemoteAddr))
return
}
}
if string(payload) == "test" {
l.logger.Info("Received test request from Cloudflare")
rw.WriteHeader(http.StatusOK)
return
}
logs, err := parsePayload(payload)
if err != nil {
rw.WriteHeader(http.StatusUnprocessableEntity)
l.logger.Error("Failed to convert cloudflare request payload to maps", zap.Error(err))
return
}
pLogs := l.processLogs(pcommon.NewTimestampFromTime(time.Now()), logs)
obsCtx := l.obsrecv.StartLogsOp(req.Context())
if err := l.consumer.ConsumeLogs(obsCtx, pLogs); err != nil {
l.obsrecv.EndLogsOp(obsCtx, metadata.Type.String(), pLogs.LogRecordCount(), err)
errorutil.HTTPError(rw, err)
l.logger.Error("Failed to consumer alert as log", zap.Error(err))
return
}
l.obsrecv.EndLogsOp(obsCtx, metadata.Type.String(), pLogs.LogRecordCount(), nil)
rw.WriteHeader(http.StatusOK)
}
func parsePayload(payload []byte) ([]map[string]any, error) {
lines := bytes.Split(payload, []byte("\n"))
logs := make([]map[string]any, 0, len(lines))
for _, line := range lines {
if len(line) == 0 {
continue
}
var log map[string]any
err := json.Unmarshal(line, &log)
if err != nil {
return logs, err
}
logs = append(logs, log)
}
return logs, nil
}
func (l *logsReceiver) processLogs(now pcommon.Timestamp, logs []map[string]any) plog.Logs {
pLogs := plog.NewLogs()
// Group logs by ZoneName field if it was configured so it can be used as a resource attribute
groupedLogs := make(map[string][]map[string]any)
for _, log := range logs {
zone := ""
if v, ok := log["ZoneName"]; ok {
if stringV, ok := v.(string); ok {
zone = stringV
}
}
groupedLogs[zone] = append(groupedLogs[zone], log)
}
for zone, logGroup := range groupedLogs {
resourceLogs := pLogs.ResourceLogs().AppendEmpty()
if zone != "" {
resource := resourceLogs.Resource()
resource.Attributes().PutStr("cloudflare.zone", zone)
}
scopeLogs := resourceLogs.ScopeLogs().AppendEmpty()
scopeLogs.Scope().SetName(metadata.ScopeName)
for _, log := range logGroup {
logRecord := scopeLogs.LogRecords().AppendEmpty()
logRecord.SetObservedTimestamp(now)
if v, ok := log[l.cfg.TimestampField]; ok {
switch l.cfg.TimestampFormat {
case "unix":
var sec int64
switch val := v.(type) {
case int:
sec = int64(val)
case int64:
sec = val
case float64:
sec = int64(val)
case string:
i, err := strconv.ParseInt(val, 10, 64)
if err != nil {
l.logger.Warn("unable to parse "+l.cfg.TimestampField+" as unix seconds", zap.Error(err), zap.String("value", val))
continue
}
sec = i
default:
l.logger.Warn("unable to parse "+l.cfg.TimestampField, zap.String("unsupported type", fmt.Sprintf("%T", v)))
continue
}
logRecord.SetTimestamp(pcommon.NewTimestampFromTime(time.Unix(sec, 0)))
case "unixnano":
var nano int64
switch val := v.(type) {
case int:
nano = int64(val)
case int64:
nano = val
case float64:
nano = int64(val)
case string:
i, err := strconv.ParseInt(val, 10, 64)
if err != nil {
l.logger.Warn("unable to parse "+l.cfg.TimestampField+" as unixnano", zap.Error(err), zap.String("value", val))
continue
}
nano = i
default:
l.logger.Warn("unable to parse "+l.cfg.TimestampField, zap.String("unsupported type", fmt.Sprintf("%T", v)))
continue
}
logRecord.SetTimestamp(pcommon.NewTimestampFromTime(time.Unix(0, nano)))
case "rfc3339":
strVal, ok := v.(string)
if !ok {
l.logger.Warn("unable to parse "+l.cfg.TimestampField+" as rfc3339, not a string", zap.Any("value", v), zap.String("type", fmt.Sprintf("%T", v)))
continue
}
ts, err := time.Parse(time.RFC3339, strVal)
if err != nil {
l.logger.Warn("unable to parse "+l.cfg.TimestampField+" as rfc3339", zap.Error(err), zap.String("value", strVal))
continue
}
logRecord.SetTimestamp(pcommon.NewTimestampFromTime(ts))
default:
l.logger.Warn("unknown timestamp_format configuration", zap.String("timestamp_format", l.cfg.TimestampFormat))
}
} else {
l.logger.Warn("unable to parse "+l.cfg.TimestampField, zap.Any("value", v))
}
if v, ok := log["EdgeResponseStatus"]; ok {
sev := plog.SeverityNumberUnspecified
switch v := v.(type) {
case string:
intV, err := strconv.ParseInt(v, 10, 64)
if err != nil {
l.logger.Warn("unable to parse EdgeResponseStatus", zap.Error(err), zap.String("value", v))
} else {
sev = severityFromStatusCode(intV)
}
case int64:
sev = severityFromStatusCode(v)
case float64:
sev = severityFromStatusCode(int64(v))
}
if sev != plog.SeverityNumberUnspecified {
logRecord.SetSeverityNumber(sev)
logRecord.SetSeverityText(sev.String())
}
}
attrs := logRecord.Attributes()
for field, v := range log {
attrName := field
if len(l.cfg.Attributes) != 0 {
// Only process fields that are in the config mapping
mappedAttr, ok := l.cfg.Attributes[field]
if !ok {
// Skip fields not in mapping when we have a config
continue
}
attrName = mappedAttr
}
// else if l.cfg.Attributes is empty, default to processing all fields with no renaming
switch v := v.(type) {
case string:
attrs.PutStr(attrName, v)
case int:
attrs.PutInt(attrName, int64(v))
case int64:
attrs.PutInt(attrName, v)
case float64:
attrs.PutDouble(attrName, v)
case bool:
attrs.PutBool(attrName, v)
case []any:
if err := attrs.PutEmptySlice(attrName).FromRaw(v); err != nil {
l.logger.Warn("unable to translate field to attribute, unsupported array value",
zap.String("field", field),
zap.Any("value", v),
zap.Error(err))
}
case map[string]any:
// Flatten the map and add each field with a prefixed key
flattened := make(map[string]any)
flattenMap(v, attrName+l.cfg.Separator, l.cfg.Separator, flattened)
for k, val := range flattened {
switch v := val.(type) {
case string:
attrs.PutStr(k, v)
case int:
attrs.PutInt(k, int64(v))
case int64:
attrs.PutInt(k, v)
case float64:
attrs.PutDouble(k, v)
case bool:
attrs.PutBool(k, v)
case []any:
if err := attrs.PutEmptySlice(k).FromRaw(v); err != nil {
l.logger.Warn("unable to translate flattened field to attribute, unsupported array value",
zap.String("field", k),
zap.Any("value", v),
zap.Error(err))
}
default:
l.logger.Warn("unable to translate flattened field to attribute, unsupported type",
zap.String("field", k),
zap.Any("value", v),
zap.String("type", fmt.Sprintf("%T", v)))
}
}
default:
l.logger.Warn("unable to translate field to attribute, unsupported type",
zap.String("field", field),
zap.Any("value", v),
zap.String("type", fmt.Sprintf("%T", v)))
}
}
err := logRecord.Body().SetEmptyMap().FromRaw(log)
if err != nil {
l.logger.Warn("unable to set body", zap.Error(err))
}
}
}
return pLogs
}
// severityFromStatusCode translates HTTP status code to OpenTelemetry severity number.
func severityFromStatusCode(statusCode int64) plog.SeverityNumber {
switch {
case statusCode < 300:
return plog.SeverityNumberInfo
case statusCode < 400:
return plog.SeverityNumberInfo2
case statusCode < 500:
return plog.SeverityNumberWarn
case statusCode < 600:
return plog.SeverityNumberError
default:
return plog.SeverityNumberUnspecified
}
}
// flattenMap recursively flattens a map[string]any into a single level map
// with keys joined by the specified separator
func flattenMap(input map[string]any, prefix, separator string, result map[string]any) {
for k, v := range input {
// Replace hyphens with underscores in the key. Content-Type becomes Content_Type
k = strings.ReplaceAll(k, "-", "_")
newKey := prefix + k
switch val := v.(type) {
case map[string]any:
// Recursively flatten nested maps
flattenMap(val, newKey+separator, separator, result)
default:
result[newKey] = v
}
}
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/lokireceiver/internal"
import (
"compress/flate"
"compress/gzip"
"fmt"
"io"
"math"
"mime"
"net/http"
"github.com/grafana/loki/pkg/push"
)
var (
contentType = http.CanonicalHeaderKey("Content-Type")
contentEnc = http.CanonicalHeaderKey("Content-Encoding")
)
const applicationJSON = "application/json"
func ParseRequest(req *http.Request) (*push.PushRequest, error) {
var body io.Reader
contentEncoding := req.Header.Get(contentEnc)
switch contentEncoding {
case "", "snappy":
body = req.Body
case "gzip":
gzipReader, err := gzip.NewReader(req.Body)
if err != nil {
return nil, err
}
defer gzipReader.Close()
body = gzipReader
case "deflate":
flateReader := flate.NewReader(req.Body)
defer flateReader.Close()
body = flateReader
default:
return nil, fmt.Errorf("Content-Encoding %q not supported", contentEncoding)
}
var pushRequest push.PushRequest
reqContentType := req.Header.Get(contentType)
reqContentType, _ /* params */, err := mime.ParseMediaType(reqContentType)
if err != nil {
return nil, err
}
switch reqContentType {
case applicationJSON:
err = decodePushRequest(body, &pushRequest)
if err != nil {
return nil, err
}
default:
// When no content-type header is set or when it is set to
// `application/x-protobuf`: expect snappy compression.
if err := parseProtoReader(body, int(req.ContentLength), math.MaxInt32, &pushRequest); err != nil {
return nil, err
}
return &pushRequest, nil
}
return &pushRequest, nil
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/lokireceiver/internal"
import (
"io"
"sort"
"strconv"
"strings"
"time"
"unsafe"
"github.com/buger/jsonparser"
"github.com/grafana/loki/pkg/push"
jsoniter "github.com/json-iterator/go"
)
// PushRequest models a log stream push but is unmarshalled to proto push format.
type PushRequest struct {
Streams []Stream `json:"streams"`
}
// Stream helps with unmarshalling of each log stream for push request.
type Stream push.Stream
func (s *Stream) UnmarshalJSON(data []byte) error {
err := jsonparser.ObjectEach(data, func(key, val []byte, ty jsonparser.ValueType, _ int) error {
switch string(key) {
case "stream":
var labels LabelSet
if err := labels.UnmarshalJSON(val); err != nil {
return err
}
s.Labels = labels.String()
case "values":
if ty == jsonparser.Null {
return nil
}
entries, err := unmarshalHTTPToLogProtoEntries(val)
if err != nil {
return err
}
s.Entries = entries
}
return nil
})
return err
}
func unmarshalHTTPToLogProtoEntries(data []byte) ([]push.Entry, error) {
var (
entries []push.Entry
parseError error
)
if _, err := jsonparser.ArrayEach(data, func(value []byte, ty jsonparser.ValueType, _ int, err error) {
if err != nil || parseError != nil {
return
}
if ty == jsonparser.Null {
return
}
e, err := unmarshalHTTPToLogProtoEntry(value)
if err != nil {
parseError = err
return
}
entries = append(entries, e)
}); err != nil {
parseError = err
}
if parseError != nil {
return nil, parseError
}
return entries, nil
}
func unmarshalHTTPToLogProtoEntry(data []byte) (push.Entry, error) {
var (
i int
parseError error
e push.Entry
)
_, err := jsonparser.ArrayEach(data, func(value []byte, t jsonparser.ValueType, _ int, _ error) {
// assert that both items in array are of type string
if (i == 0 || i == 1) && t != jsonparser.String {
parseError = jsonparser.MalformedStringError
return
} else if i == 2 && t != jsonparser.Object {
parseError = jsonparser.MalformedObjectError
return
}
switch i {
case 0: // timestamp
ts, err := jsonparser.ParseInt(value)
if err != nil {
parseError = err
return
}
e.Timestamp = time.Unix(0, ts)
case 1: // value
v, err := jsonparser.ParseString(value)
if err != nil {
parseError = err
return
}
e.Line = v
case 2: // structuredMetadata
var structuredMetadata []push.LabelAdapter
err := jsonparser.ObjectEach(value, func(key, val []byte, dataType jsonparser.ValueType, _ int) error {
if dataType != jsonparser.String {
return jsonparser.MalformedStringError
}
structuredMetadata = append(structuredMetadata, push.LabelAdapter{
Name: string(key),
Value: string(val),
})
return nil
})
if err != nil {
parseError = err
return
}
e.StructuredMetadata = structuredMetadata
}
i++
})
if parseError != nil {
return e, parseError
}
return e, err
}
// LabelSet is a key/value pair mapping of labels
type LabelSet map[string]string
func (l *LabelSet) UnmarshalJSON(data []byte) error {
if *l == nil {
*l = make(LabelSet)
}
return jsonparser.ObjectEach(data, func(key, val []byte, _ jsonparser.ValueType, _ int) error {
v, err := jsonparser.ParseString(val)
if err != nil {
return err
}
k, err := jsonparser.ParseString(key)
if err != nil {
return err
}
(*l)[k] = v
return nil
})
}
// String implements the Stringer interface. It returns a formatted/sorted set of label key/value pairs.
func (l LabelSet) String() string {
var b strings.Builder
keys := make([]string, 0, len(l))
for k := range l {
keys = append(keys, k)
}
sort.Strings(keys)
b.WriteByte('{')
for i, k := range keys {
if i > 0 {
b.WriteByte(',')
b.WriteByte(' ')
}
b.WriteString(k)
b.WriteByte('=')
b.WriteString(strconv.Quote(l[k]))
}
b.WriteByte('}')
return b.String()
}
// decodePushRequest directly decodes json to a push.PushRequest
func decodePushRequest(b io.Reader, r *push.PushRequest) error {
var request PushRequest
if err := jsoniter.NewDecoder(b).Decode(&request); err != nil {
return err
}
*r = push.PushRequest{
Streams: *(*[]push.Stream)(unsafe.Pointer(&request.Streams)),
}
return nil
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/lokireceiver/internal"
import (
"bytes"
"fmt"
"io"
"github.com/gogo/protobuf/proto"
"github.com/golang/snappy"
)
const messageSizeLargerErrFmt = "received message larger than max (%d vs %d)"
// parseProtoReader parses a compressed proto from an io.Reader.
func parseProtoReader(reader io.Reader, expectedSize, maxSize int, req proto.Message) error {
body, err := decompressRequest(reader, expectedSize, maxSize)
if err != nil {
return err
}
// We re-implement proto.Unmarshal here as it calls XXX_Unmarshal first,
// which we can't override without upsetting golint.
req.Reset()
if u, ok := req.(proto.Unmarshaler); ok {
err = u.Unmarshal(body)
} else {
err = proto.NewBuffer(body).Unmarshal(req)
}
if err != nil {
return err
}
return nil
}
func decompressRequest(reader io.Reader, expectedSize, maxSize int) (body []byte, err error) {
defer func() {
if err != nil && len(body) > maxSize {
err = fmt.Errorf(messageSizeLargerErrFmt, len(body), maxSize)
}
}()
if expectedSize > maxSize {
return nil, fmt.Errorf(messageSizeLargerErrFmt, expectedSize, maxSize)
}
buffer, ok := tryBufferFromReader(reader)
if ok {
body, err = decompressFromBuffer(buffer, maxSize)
return body, err
}
body, err = decompressFromReader(reader, expectedSize, maxSize)
return body, err
}
func decompressFromReader(reader io.Reader, expectedSize, maxSize int) ([]byte, error) {
var (
buf bytes.Buffer
body []byte
err error
)
if expectedSize > 0 {
buf.Grow(expectedSize + bytes.MinRead) // extra space guarantees no reallocation
}
// Read from LimitReader with limit max+1. So if the underlying
// reader is over limit, the result will be bigger than max.
reader = io.LimitReader(reader, int64(maxSize)+1)
_, err = buf.ReadFrom(reader)
if err != nil {
return nil, err
}
body, err = decompressFromBuffer(&buf, maxSize)
return body, err
}
func decompressFromBuffer(buffer *bytes.Buffer, maxSize int) ([]byte, error) {
if len(buffer.Bytes()) > maxSize {
return nil, fmt.Errorf(messageSizeLargerErrFmt, len(buffer.Bytes()), maxSize)
}
size, err := snappy.DecodedLen(buffer.Bytes())
if err != nil {
return nil, err
}
if size > maxSize {
return nil, fmt.Errorf(messageSizeLargerErrFmt, size, maxSize)
}
body, err := snappy.Decode(nil, buffer.Bytes())
if err != nil {
return nil, err
}
return body, nil
}
// tryBufferFromReader attempts to cast the reader to a `*bytes.Buffer` this is possible when using httpgrpc.
// If it fails it will return nil and false.
func tryBufferFromReader(reader io.Reader) (*bytes.Buffer, bool) {
if bufReader, ok := reader.(interface {
BytesBuffer() *bytes.Buffer
}); ok && bufReader != nil {
return bufReader.BytesBuffer(), true
}
return nil, false
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package mongodbatlasreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mongodbatlasreceiver"
import (
"context"
"encoding/json"
"fmt"
"sync"
"time"
"go.mongodb.org/atlas/mongodbatlas"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/extension/xextension/storage"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
rcvr "go.opentelemetry.io/collector/receiver"
"go.uber.org/zap"
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mongodbatlasreceiver/internal"
)
const (
accessLogStorageKey = "last_endtime_access_logs_%s"
defaultAccessLogsPollInterval = 5 * time.Minute
defaultAccessLogsPageSize = 20000
defaultAccessLogsMaxPages = 10
)
type accessLogStorageRecord struct {
ClusterName string `json:"cluster_name"`
NextPollStartTime time.Time `json:"next_poll_start_time"`
}
type accessLogClient interface {
GetProject(ctx context.Context, groupID string) (*mongodbatlas.Project, error)
GetClusters(ctx context.Context, groupID string) ([]mongodbatlas.Cluster, error)
GetAccessLogs(ctx context.Context, groupID, clusterName string, opts *internal.GetAccessLogsOptions) (ret []*mongodbatlas.AccessLogs, err error)
}
type accessLogsReceiver struct {
client accessLogClient
logger *zap.Logger
storageClient storage.Client
cfg *Config
consumer consumer.Logs
record map[string][]*accessLogStorageRecord
authResult *bool
wg *sync.WaitGroup
cancel context.CancelFunc
}
func newAccessLogsReceiver(settings rcvr.Settings, cfg *Config, consumer consumer.Logs) (*accessLogsReceiver, error) {
client, err := internal.NewMongoDBAtlasClient(cfg.BaseURL, cfg.PublicKey, string(cfg.PrivateKey), cfg.BackOffConfig, settings.Logger, settings.BuildInfo)
if err != nil {
return nil, fmt.Errorf("failed to create MongoDB Atlas client for access logs receiver: %w", err)
}
r := &accessLogsReceiver{
cancel: func() {},
client: client,
cfg: cfg,
logger: settings.Logger,
consumer: consumer,
wg: &sync.WaitGroup{},
storageClient: storage.NewNopClient(),
record: make(map[string][]*accessLogStorageRecord),
}
for _, p := range cfg.Logs.Projects {
p.ProjectConfig.populateIncludesAndExcludes()
if p.AccessLogs != nil && p.AccessLogs.IsEnabled() {
if p.AccessLogs.PageSize <= 0 {
p.AccessLogs.PageSize = defaultAccessLogsPageSize
}
if p.AccessLogs.MaxPages <= 0 {
p.AccessLogs.MaxPages = defaultAccessLogsMaxPages
}
if p.AccessLogs.PollInterval == 0 {
p.AccessLogs.PollInterval = defaultAccessLogsPollInterval
}
}
}
return r, nil
}
func (alr *accessLogsReceiver) Start(ctx context.Context, _ component.Host, storageClient storage.Client) error {
alr.logger.Debug("Starting up access log receiver")
cancelCtx, cancel := context.WithCancel(ctx)
alr.cancel = cancel
alr.storageClient = storageClient
return alr.startPolling(cancelCtx)
}
func (alr *accessLogsReceiver) Shutdown(_ context.Context) error {
alr.logger.Debug("Shutting down accessLog receiver")
alr.cancel()
alr.wg.Wait()
return nil
}
func (alr *accessLogsReceiver) startPolling(ctx context.Context) error {
for _, pc := range alr.cfg.Logs.Projects {
if pc.AccessLogs == nil || !pc.AccessLogs.IsEnabled() {
continue
}
t := time.NewTicker(pc.AccessLogs.PollInterval)
alr.wg.Go(func() {
for {
select {
case <-t.C:
if err := alr.pollAccessLogs(ctx, pc); err != nil {
alr.logger.Error("error while polling for accessLog", zap.Error(err))
}
case <-ctx.Done():
return
}
}
})
}
return nil
}
func (alr *accessLogsReceiver) pollAccessLogs(ctx context.Context, pc *LogsProjectConfig) error {
st := pcommon.NewTimestampFromTime(time.Now().Add(-1 * pc.AccessLogs.PollInterval)).AsTime()
et := time.Now()
project, err := alr.client.GetProject(ctx, pc.ProjectConfig.Name)
if err != nil {
alr.logger.Error("error retrieving project information", zap.Error(err), zap.String("project", pc.ProjectConfig.Name))
return err
}
alr.loadCheckpoint(ctx, project.ID)
clusters, err := alr.client.GetClusters(ctx, project.ID)
if err != nil {
alr.logger.Error("error retrieving cluster information", zap.Error(err), zap.String("project", pc.ProjectConfig.Name))
return err
}
filteredClusters, err := filterClusters(clusters, pc.ProjectConfig)
if err != nil {
alr.logger.Error("error filtering clusters", zap.Error(err), zap.String("project", pc.ProjectConfig.Name))
return err
}
for i := range filteredClusters {
cluster := &filteredClusters[i]
clusterCheckpoint := alr.getClusterCheckpoint(project.ID, cluster.Name)
if clusterCheckpoint == nil {
clusterCheckpoint = &accessLogStorageRecord{
ClusterName: cluster.Name,
NextPollStartTime: st,
}
alr.setClusterCheckpoint(project.ID, clusterCheckpoint)
}
clusterCheckpoint.NextPollStartTime = alr.pollCluster(ctx, pc, project, cluster, clusterCheckpoint.NextPollStartTime, et)
if err = alr.checkpoint(ctx, project.ID); err != nil {
alr.logger.Warn("error checkpointing", zap.Error(err), zap.String("project", pc.ProjectConfig.Name))
}
}
return nil
}
func (alr *accessLogsReceiver) pollCluster(ctx context.Context, pc *LogsProjectConfig, project *mongodbatlas.Project, cluster *mongodbatlas.Cluster, startTime, now time.Time) time.Time {
nowTimestamp := pcommon.NewTimestampFromTime(now)
opts := &internal.GetAccessLogsOptions{
MaxDate: now,
MinDate: startTime,
AuthResult: alr.authResult,
NLogs: int(pc.AccessLogs.PageSize),
}
pageCount := 0
// Assume failure, in which case we poll starting with the same startTime
// unless we successfully make request(s) for access logs and they are successfully sent to the consumer
nextPollStartTime := startTime
for {
accessLogs, err := alr.client.GetAccessLogs(ctx, project.ID, cluster.Name, opts)
pageCount++
if err != nil {
alr.logger.Error("unable to get access logs", zap.Error(err), zap.String("project", project.Name),
zap.String("clusterID", cluster.ID), zap.String("clusterName", cluster.Name))
return nextPollStartTime
}
// No logs retrieved, try again on next interval with the same start time as the API may not have
// all logs for the given time available to be queried yet (undocumented behavior)
if len(accessLogs) == 0 {
return nextPollStartTime
}
logs := transformAccessLogs(nowTimestamp, accessLogs, project, cluster, alr.logger)
if err = alr.consumer.ConsumeLogs(ctx, logs); err != nil {
alr.logger.Error("error consuming project cluster log", zap.Error(err), zap.String("project", project.Name),
zap.String("clusterID", cluster.ID), zap.String("clusterName", cluster.Name))
return nextPollStartTime
}
// The first page of results will have the latest data, so we want to update the nextPollStartTime
// There is risk of data loss at this point if we are unable to then process the remaining pages
// of data, but that is a limitation of the API that we can't work around.
if pageCount == 1 {
// This slice access is safe as we have previously confirmed that the slice is not empty
mostRecentLogTimestamp, tsErr := getTimestamp(accessLogs[0])
if tsErr != nil {
alr.logger.Error("error getting latest log timestamp for calculating next poll timestamps", zap.Error(tsErr),
zap.String("project", project.Name), zap.String("clusterName", cluster.Name))
// If we are not able to get the latest log timestamp, we have to assume that we are collecting all
// data and don't want to risk duplicated data by re-polling the same data again.
nextPollStartTime = now
} else {
nextPollStartTime = mostRecentLogTimestamp.Add(100 * time.Millisecond)
}
}
// If we get back less than the maximum number of logs, we can assume that we've retrieved all of the logs
// that are currently available for this time period, though some logs may not be available in the API yet.
if len(accessLogs) < int(pc.AccessLogs.PageSize) {
return nextPollStartTime
}
if pageCount >= int(pc.AccessLogs.MaxPages) {
alr.logger.Warn(`reached maximum number of pages of access logs, increase 'max_pages' or
frequency of 'poll_interval' to ensure all access logs are retrieved`, zap.Int("maxPages", int(pc.AccessLogs.MaxPages)))
return nextPollStartTime
}
// If we get back the maximum number of logs, we need to re-query with a new end time. While undocumented, the API
// returns the most recent logs first. If we get the maximum number of logs back, we can assume that
// there are more logs to be retrieved. We'll re-query with the same start time, but the end
// time set to just before the timestamp of the oldest log entry returned.
oldestLogTimestampFromPage, err := getTimestamp(accessLogs[len(accessLogs)-1])
if err != nil {
alr.logger.Error("error getting oldest log timestamp for calculating next request timestamps", zap.Error(err),
zap.String("project", project.Name), zap.String("clusterName", cluster.Name))
return nextPollStartTime
}
opts.MaxDate = oldestLogTimestampFromPage.Add(-1 * time.Millisecond)
// If the new max date is before the min date, we've retrieved all of the logs for this time period
// and receiving the maximum number of logs back is a coincidence.
if opts.MaxDate.Before(opts.MinDate) {
break
}
}
return now
}
func getTimestamp(log *mongodbatlas.AccessLogs) (time.Time, error) {
body, err := parseLogMessage(log)
if err != nil {
// If body couldn't be parsed, we'll still use the outer Timestamp field to determine the new max date.
body = map[string]any{}
}
return getTimestampPreparsedBody(log, body)
}
func getTimestampPreparsedBody(log *mongodbatlas.AccessLogs, body map[string]any) (time.Time, error) {
// If the log message has a timestamp, use that. When present, it has more precision than the timestamp from the access log entry.
if tMap, ok := body["t"]; ok {
if dateMap, ok := tMap.(map[string]any); ok {
if v, ok := dateMap["$date"]; ok {
if dateStr, ok := v.(string); ok {
return time.Parse(time.RFC3339, dateStr)
}
}
}
}
// If the log message doesn't have a timestamp, use the timestamp from the outer access log entry.
t, err := time.Parse(time.RFC3339, log.Timestamp)
if err != nil {
// The documentation claims ISO8601/RFC3339, but the API has been observed returning timestamps in UnixDate format
// UnixDate looks like Wed Apr 26 02:38:56 GMT 2023
unixDate, err2 := time.Parse(time.UnixDate, log.Timestamp)
if err2 != nil {
// Return the original error as the documentation claims ISO8601
return time.Time{}, err
}
return unixDate, nil
}
return t, nil
}
func parseLogMessage(log *mongodbatlas.AccessLogs) (map[string]any, error) {
var body map[string]any
if err := json.Unmarshal([]byte(log.LogLine), &body); err != nil {
return nil, err
}
return body, nil
}
func transformAccessLogs(now pcommon.Timestamp, accessLogs []*mongodbatlas.AccessLogs, p *mongodbatlas.Project, c *mongodbatlas.Cluster, logger *zap.Logger) plog.Logs {
logs := plog.NewLogs()
resourceLogs := logs.ResourceLogs().AppendEmpty()
ra := resourceLogs.Resource().Attributes()
ra.PutStr("mongodbatlas.project.name", p.Name)
ra.PutStr("mongodbatlas.project.id", p.ID)
ra.PutStr("mongodbatlas.region.name", c.ProviderSettings.RegionName)
ra.PutStr("mongodbatlas.provider.name", c.ProviderSettings.ProviderName)
ra.PutStr("mongodbatlas.org.id", p.OrgID)
ra.PutStr("mongodbatlas.cluster.name", c.Name)
// Expected format documented https://www.mongodb.com/docs/atlas/reference/api-resources-spec/#tag/Access-Tracking/operation/listAccessLogsByClusterName
logRecords := resourceLogs.ScopeLogs().AppendEmpty().LogRecords()
for _, accessLog := range accessLogs {
logRecord := logRecords.AppendEmpty()
logBody, err := parseLogMessage(accessLog)
if err != nil {
logger.Error("unable to unmarshal access log into body string", zap.Error(err))
continue
}
err = logRecord.Body().SetEmptyMap().FromRaw(logBody)
if err != nil {
logger.Error("unable to set log record body as map", zap.Error(err))
logRecord.Body().SetStr(accessLog.LogLine)
}
ts, err := getTimestampPreparsedBody(accessLog, logBody)
if err != nil {
logger.Warn("unable to interpret when an access log event was recorded, timestamp not parsed", zap.Error(err), zap.String("timestamp", accessLog.Timestamp))
logRecord.SetTimestamp(now)
} else {
logRecord.SetTimestamp(pcommon.NewTimestampFromTime(ts))
}
logRecord.SetObservedTimestamp(now)
attrs := logRecord.Attributes()
attrs.PutStr("event.domain", "mongodbatlas")
logRecord.SetSeverityNumber(plog.SeverityNumberInfo)
logRecord.SetSeverityText(plog.SeverityNumberInfo.String())
if accessLog.AuthResult != nil {
status := "success"
if !*accessLog.AuthResult {
logRecord.SetSeverityNumber(plog.SeverityNumberWarn)
logRecord.SetSeverityText(plog.SeverityNumberWarn.String())
status = "failure"
}
attrs.PutStr("auth.result", status)
}
if accessLog.FailureReason != "" {
attrs.PutStr("auth.failure_reason", accessLog.FailureReason)
}
attrs.PutStr("auth.source", accessLog.AuthSource)
attrs.PutStr("username", accessLog.Username)
attrs.PutStr("hostname", accessLog.Hostname)
attrs.PutStr("remote.ip", accessLog.IPAddress)
}
return logs
}
func accessLogsCheckpointKey(groupID string) string {
return fmt.Sprintf(accessLogStorageKey, groupID)
}
func (alr *accessLogsReceiver) checkpoint(ctx context.Context, groupID string) error {
marshalBytes, err := json.Marshal(alr.record)
if err != nil {
return fmt.Errorf("unable to write checkpoint: %w", err)
}
return alr.storageClient.Set(ctx, accessLogsCheckpointKey(groupID), marshalBytes)
}
func (alr *accessLogsReceiver) loadCheckpoint(ctx context.Context, groupID string) {
cBytes, err := alr.storageClient.Get(ctx, accessLogsCheckpointKey(groupID))
if err != nil {
alr.logger.Info("unable to load checkpoint from storage client, continuing without a previous checkpoint", zap.Error(err))
if _, ok := alr.record[groupID]; !ok {
alr.record[groupID] = []*accessLogStorageRecord{}
}
return
}
if cBytes == nil {
if _, ok := alr.record[groupID]; !ok {
alr.record[groupID] = []*accessLogStorageRecord{}
}
return
}
var record []*accessLogStorageRecord
if err = json.Unmarshal(cBytes, &record); err != nil {
alr.logger.Error("unable to decode stored record for access logs, continuing without a checkpoint", zap.Error(err))
if _, ok := alr.record[groupID]; !ok {
alr.record[groupID] = []*accessLogStorageRecord{}
}
}
}
func (alr *accessLogsReceiver) getClusterCheckpoint(groupID, clusterName string) *accessLogStorageRecord {
for key, value := range alr.record {
if key == groupID {
for _, v := range value {
if v.ClusterName == clusterName {
return v
}
}
}
}
return nil
}
func (alr *accessLogsReceiver) setClusterCheckpoint(groupID string, clusterCheckpoint *accessLogStorageRecord) {
groupCheckpoints, ok := alr.record[groupID]
if !ok {
alr.record[groupID] = []*accessLogStorageRecord{clusterCheckpoint}
}
var found bool
for idx, v := range groupCheckpoints {
if v.ClusterName == clusterCheckpoint.ClusterName {
found = true
alr.record[groupID][idx] = clusterCheckpoint
}
}
if !found {
alr.record[groupID] = append(alr.record[groupID], clusterCheckpoint)
}
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package mongodbatlasreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mongodbatlasreceiver"
import (
"bytes"
"context"
"crypto/hmac"
"crypto/sha1" // #nosec G505 -- SHA1 is the algorithm mongodbatlas uses, it must be used to calculate the HMAC signature
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"strconv"
"sync"
"time"
"go.mongodb.org/atlas/mongodbatlas"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componentstatus"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/config/confignet"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/config/configretry"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/extension/xextension/storage"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
rcvr "go.opentelemetry.io/collector/receiver"
"go.uber.org/multierr"
"go.uber.org/zap"
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mongodbatlasreceiver/internal"
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mongodbatlasreceiver/internal/model"
)
// maxContentLength is the maximum payload size we will accept from incoming requests.
// Requests are generally ~1000 bytes, so we overshoot that by an order of magnitude.
// This is to protect from overly large requests.
const (
maxContentLength int64 = 16384
signatureHeaderName string = "X-MMS-Signature"
alertModeListen = "listen"
alertModePoll = "poll"
alertCacheKey = "last_recorded_alert"
defaultAlertsPollInterval = 5 * time.Minute
// defaults were based off API docs https://www.mongodb.com/docs/atlas/reference/api/alerts-get-all-alerts/
defaultAlertsPageSize = 100
defaultAlertsMaxPages = 10
)
type alertsClient interface {
GetProject(ctx context.Context, groupID string) (*mongodbatlas.Project, error)
GetAlerts(ctx context.Context, groupID string, opts *internal.AlertPollOptions) ([]mongodbatlas.Alert, bool, error)
}
type alertsReceiver struct {
secret string
mode string
serverConfig *confighttp.ServerConfig
consumer consumer.Logs
wg *sync.WaitGroup
listenClose func(ctx context.Context) error
// only relevant in `poll` mode
projects []*ProjectConfig
client alertsClient
baseURL string
privateKey string
publicKey string
backoffConfig configretry.BackOffConfig
pollInterval time.Duration
record *alertRecord
pageSize int64
maxPages int64
doneChan chan bool
storageClient storage.Client
telemetrySettings component.TelemetrySettings
}
func newAlertsReceiver(params rcvr.Settings, baseConfig *Config, consumer consumer.Logs) (*alertsReceiver, error) {
cfg := baseConfig.Alerts
// Validate TLS
if cfg.TLS != nil {
var err error
_, err = cfg.TLS.LoadTLSConfig(context.Background())
if err != nil {
return nil, err
}
}
for _, p := range cfg.Projects {
p.populateIncludesAndExcludes()
}
recv := &alertsReceiver{
secret: string(cfg.Secret),
consumer: consumer,
mode: cfg.Mode,
projects: cfg.Projects,
backoffConfig: baseConfig.BackOffConfig,
baseURL: baseConfig.BaseURL,
publicKey: baseConfig.PublicKey,
privateKey: string(baseConfig.PrivateKey),
wg: &sync.WaitGroup{},
pollInterval: baseConfig.Alerts.PollInterval,
maxPages: baseConfig.Alerts.MaxPages,
pageSize: baseConfig.Alerts.PageSize,
doneChan: make(chan bool, 1),
telemetrySettings: params.TelemetrySettings,
}
if recv.mode == alertModePoll {
client, err := internal.NewMongoDBAtlasClient(recv.baseURL, recv.publicKey, recv.privateKey, recv.backoffConfig, recv.telemetrySettings.Logger, params.BuildInfo)
if err != nil {
return nil, fmt.Errorf("failed to create MongoDB Atlas client for alerts receiver: %w", err)
}
recv.client = client
return recv, nil
}
serverConfig := confighttp.NewDefaultServerConfig()
serverConfig.NetAddr = confignet.AddrConfig{
Endpoint: cfg.Endpoint,
Transport: "tcp",
}
serverConfig.ReadHeaderTimeout = 20 * time.Second
if cfg.TLS != nil {
serverConfig.TLS = configoptional.Some(*cfg.TLS)
}
recv.serverConfig = &serverConfig
return recv, nil
}
func (a *alertsReceiver) Start(ctx context.Context, host component.Host, storageClient storage.Client) error {
if a.mode == alertModePoll {
return a.startPolling(ctx, storageClient)
}
return a.startListening(ctx, host)
}
func (a *alertsReceiver) startPolling(ctx context.Context, storageClient storage.Client) error {
a.telemetrySettings.Logger.Debug("starting alerts receiver in retrieval mode")
a.storageClient = storageClient
err := a.syncPersistence(ctx)
if err != nil {
a.telemetrySettings.Logger.Error("there was an error syncing the receiver with checkpoint", zap.Error(err))
}
t := time.NewTicker(a.pollInterval)
a.wg.Go(func() {
for {
select {
case <-t.C:
if err := a.retrieveAndProcessAlerts(ctx); err != nil {
a.telemetrySettings.Logger.Error("unable to retrieve alerts", zap.Error(err))
}
case <-a.doneChan:
return
case <-ctx.Done():
return
}
}
})
return nil
}
func (a *alertsReceiver) retrieveAndProcessAlerts(ctx context.Context) error {
for _, p := range a.projects {
project, err := a.client.GetProject(ctx, p.Name)
if err != nil {
a.telemetrySettings.Logger.Error("error retrieving project "+p.Name+":", zap.Error(err))
continue
}
a.pollAndProcess(ctx, p, project)
}
return a.writeCheckpoint(ctx)
}
func (a *alertsReceiver) pollAndProcess(ctx context.Context, pc *ProjectConfig, project *mongodbatlas.Project) {
for pageNum := 1; pageNum <= int(a.maxPages); pageNum++ {
projectAlerts, hasNext, err := a.client.GetAlerts(ctx, project.ID, &internal.AlertPollOptions{
PageNum: pageNum,
PageSize: int(a.pageSize),
})
if err != nil {
a.telemetrySettings.Logger.Error("unable to get alerts for project", zap.Error(err))
break
}
filteredAlerts := a.applyFilters(pc, projectAlerts)
now := pcommon.NewTimestampFromTime(time.Now())
logs, err := a.convertAlerts(now, filteredAlerts, project)
if err != nil {
a.telemetrySettings.Logger.Error("error processing alerts", zap.Error(err))
break
}
if logs.LogRecordCount() > 0 {
if err = a.consumer.ConsumeLogs(ctx, logs); err != nil {
a.telemetrySettings.Logger.Error("error consuming alerts", zap.Error(err))
break
}
}
if !hasNext {
break
}
}
}
func (a *alertsReceiver) startListening(ctx context.Context, host component.Host) error {
a.telemetrySettings.Logger.Debug("starting alerts receiver in listening mode")
server, err := a.serverConfig.ToServer(
ctx,
host.GetExtensions(),
a.telemetrySettings,
http.HandlerFunc(a.handleRequest),
)
if err != nil {
return err
}
listener, err := a.serverConfig.ToListener(ctx)
if err != nil {
return err
}
a.listenClose = server.Shutdown
a.wg.Go(func() {
a.telemetrySettings.Logger.Debug(
"Starting Serve",
zap.String("address", a.serverConfig.NetAddr.Endpoint),
)
err := server.Serve(listener)
a.telemetrySettings.Logger.Debug("Serve done")
if err != http.ErrServerClosed {
componentstatus.ReportStatus(host, componentstatus.NewFatalErrorEvent(err))
}
})
return nil
}
func (a *alertsReceiver) handleRequest(rw http.ResponseWriter, req *http.Request) {
if req.ContentLength < 0 {
rw.WriteHeader(http.StatusLengthRequired)
a.telemetrySettings.Logger.Debug("Got request with no Content-Length specified", zap.String("remote", req.RemoteAddr))
return
}
if req.ContentLength > maxContentLength {
rw.WriteHeader(http.StatusRequestEntityTooLarge)
a.telemetrySettings.Logger.Debug("Got request with large Content-Length specified",
zap.String("remote", req.RemoteAddr),
zap.Int64("content-length", req.ContentLength),
zap.Int64("max-content-length", maxContentLength))
return
}
payloadSigHeader := req.Header.Get(signatureHeaderName)
if payloadSigHeader == "" {
rw.WriteHeader(http.StatusBadRequest)
a.telemetrySettings.Logger.Debug("Got payload with no HMAC signature, dropping...")
return
}
payload := make([]byte, req.ContentLength)
_, err := io.ReadFull(req.Body, payload)
if err != nil {
rw.WriteHeader(http.StatusBadRequest)
a.telemetrySettings.Logger.Debug("Failed to read alerts payload", zap.Error(err), zap.String("remote", req.RemoteAddr))
return
}
if err = verifyHMACSignature(a.secret, payload, payloadSigHeader); err != nil {
rw.WriteHeader(http.StatusBadRequest)
a.telemetrySettings.Logger.Debug("Got payload with invalid HMAC signature, dropping...", zap.Error(err), zap.String("remote", req.RemoteAddr))
return
}
logs, err := payloadToLogs(time.Now(), payload)
if err != nil {
rw.WriteHeader(http.StatusBadRequest)
a.telemetrySettings.Logger.Error("Failed to convert log payload to log record", zap.Error(err))
return
}
if err := a.consumer.ConsumeLogs(req.Context(), logs); err != nil {
rw.WriteHeader(http.StatusInternalServerError)
a.telemetrySettings.Logger.Error("Failed to consumer alert as log", zap.Error(err))
return
}
rw.WriteHeader(http.StatusOK)
}
func (a *alertsReceiver) Shutdown(ctx context.Context) error {
if a.mode == alertModePoll {
return a.shutdownPoller(ctx)
}
return a.shutdownListener(ctx)
}
func (a *alertsReceiver) shutdownListener(ctx context.Context) error {
a.telemetrySettings.Logger.Debug("Shutting down server")
if a.listenClose != nil {
err := a.listenClose(ctx)
if err != nil {
return err
}
}
a.telemetrySettings.Logger.Debug("Waiting for shutdown to complete.")
a.wg.Wait()
return nil
}
func (a *alertsReceiver) shutdownPoller(ctx context.Context) error {
a.telemetrySettings.Logger.Debug("Shutting down client")
close(a.doneChan)
a.wg.Wait()
return a.writeCheckpoint(ctx)
}
func (a *alertsReceiver) convertAlerts(now pcommon.Timestamp, alerts []*mongodbatlas.Alert, project *mongodbatlas.Project) (plog.Logs, error) {
logs := plog.NewLogs()
var errs error
for i := range alerts {
alert := alerts[i]
resourceLogs := logs.ResourceLogs().AppendEmpty()
resourceAttrs := resourceLogs.Resource().Attributes()
resourceAttrs.PutStr("mongodbatlas.group.id", alert.GroupID)
resourceAttrs.PutStr("mongodbatlas.alert.config.id", alert.AlertConfigID)
resourceAttrs.PutStr("mongodbatlas.org.id", project.OrgID)
resourceAttrs.PutStr("mongodbatlas.project.name", project.Name)
putStringToMapNotNil(resourceAttrs, "mongodbatlas.cluster.name", &alert.ClusterName)
putStringToMapNotNil(resourceAttrs, "mongodbatlas.replica_set.name", &alert.ReplicaSetName)
logRecord := resourceLogs.ScopeLogs().AppendEmpty().LogRecords().AppendEmpty()
logRecord.SetObservedTimestamp(now)
ts, err := time.Parse(time.RFC3339, alert.Updated)
if err != nil {
a.telemetrySettings.Logger.Warn("unable to interpret updated time for alert, expecting a RFC3339 timestamp", zap.String("timestamp", alert.Updated))
continue
}
logRecord.SetTimestamp(pcommon.NewTimestampFromTime(ts))
logRecord.SetSeverityNumber(severityFromAPIAlert(alert.Status))
logRecord.SetSeverityText(alert.Status)
// this could be fairly expensive to do, expecting not too many issues unless there are a ton
// of unrecognized alerts to process.
bodyBytes, err := json.Marshal(alert)
if err != nil {
a.telemetrySettings.Logger.Warn("unable to marshal alert into a body string")
continue
}
logRecord.Body().SetStr(string(bodyBytes))
attrs := logRecord.Attributes()
// These attributes are always present
attrs.PutStr("event.domain", "mongodbatlas")
attrs.PutStr("event.name", alert.EventTypeName)
attrs.PutStr("status", alert.Status)
attrs.PutStr("created", alert.Created)
attrs.PutStr("updated", alert.Updated)
attrs.PutStr("id", alert.ID)
// These attributes are optional and may not be present, depending on the alert type.
putStringToMapNotNil(attrs, "metric.name", &alert.MetricName)
putStringToMapNotNil(attrs, "type_name", &alert.EventTypeName)
putStringToMapNotNil(attrs, "last_notified", &alert.LastNotified)
putStringToMapNotNil(attrs, "resolved", &alert.Resolved)
putStringToMapNotNil(attrs, "acknowledgement.comment", &alert.AcknowledgementComment)
putStringToMapNotNil(attrs, "acknowledgement.username", &alert.AcknowledgingUsername)
putStringToMapNotNil(attrs, "acknowledgement.until", &alert.AcknowledgedUntil)
if alert.CurrentValue != nil {
attrs.PutDouble("metric.value", *alert.CurrentValue.Number)
attrs.PutStr("metric.units", alert.CurrentValue.Units)
}
// Only present for HOST, HOST_METRIC, and REPLICA_SET alerts
if alert.HostnameAndPort == "" {
continue
}
host, portStr, err := net.SplitHostPort(alert.HostnameAndPort)
if err != nil {
errs = multierr.Append(errs, fmt.Errorf("failed to split host:port %s: %w", alert.HostnameAndPort, err))
continue
}
port, err := strconv.ParseInt(portStr, 10, 64)
if err != nil {
errs = multierr.Append(errs, fmt.Errorf("failed to parse port %s: %w", portStr, err))
continue
}
attrs.PutStr("net.peer.name", host)
attrs.PutInt("net.peer.port", port)
}
return logs, errs
}
func verifyHMACSignature(secret string, payload []byte, signatureHeader string) error {
b64Decoder := base64.NewDecoder(base64.StdEncoding, bytes.NewBufferString(signatureHeader))
payloadSig, err := io.ReadAll(b64Decoder)
if err != nil {
return err
}
h := hmac.New(sha1.New, []byte(secret))
h.Write(payload)
calculatedSig := h.Sum(nil)
if !hmac.Equal(calculatedSig, payloadSig) {
return errors.New("calculated signature does not equal header signature")
}
return nil
}
func payloadToLogs(now time.Time, payload []byte) (plog.Logs, error) {
var alert model.Alert
err := json.Unmarshal(payload, &alert)
if err != nil {
return plog.Logs{}, err
}
logs := plog.NewLogs()
resourceLogs := logs.ResourceLogs().AppendEmpty()
logRecord := resourceLogs.ScopeLogs().AppendEmpty().LogRecords().AppendEmpty()
logRecord.SetObservedTimestamp(pcommon.NewTimestampFromTime(now))
logRecord.SetTimestamp(timestampFromAlert(alert))
logRecord.SetSeverityNumber(severityFromAlert(alert))
logRecord.Body().SetStr(string(payload))
resourceAttrs := resourceLogs.Resource().Attributes()
resourceAttrs.PutStr("mongodbatlas.group.id", alert.GroupID)
resourceAttrs.PutStr("mongodbatlas.alert.config.id", alert.AlertConfigID)
putStringToMapNotNil(resourceAttrs, "mongodbatlas.cluster.name", alert.ClusterName)
putStringToMapNotNil(resourceAttrs, "mongodbatlas.replica_set.name", alert.ReplicaSetName)
attrs := logRecord.Attributes()
// These attributes are always present
attrs.PutStr("event.domain", "mongodbatlas")
attrs.PutStr("event.name", alert.EventType)
attrs.PutStr("message", alert.HumanReadable)
attrs.PutStr("status", alert.Status)
attrs.PutStr("created", alert.Created)
attrs.PutStr("updated", alert.Updated)
attrs.PutStr("id", alert.ID)
// These attributes are optional and may not be present, depending on the alert type.
putStringToMapNotNil(attrs, "metric.name", alert.MetricName)
putStringToMapNotNil(attrs, "type_name", alert.TypeName)
putStringToMapNotNil(attrs, "user_alias", alert.UserAlias)
putStringToMapNotNil(attrs, "last_notified", alert.LastNotified)
putStringToMapNotNil(attrs, "resolved", alert.Resolved)
putStringToMapNotNil(attrs, "acknowledgement.comment", alert.AcknowledgementComment)
putStringToMapNotNil(attrs, "acknowledgement.username", alert.AcknowledgementUsername)
putStringToMapNotNil(attrs, "acknowledgement.until", alert.AcknowledgedUntil)
if alert.CurrentValue != nil {
attrs.PutDouble("metric.value", alert.CurrentValue.Number)
attrs.PutStr("metric.units", alert.CurrentValue.Units)
}
if alert.HostNameAndPort != nil {
host, portStr, err := net.SplitHostPort(*alert.HostNameAndPort)
if err != nil {
return plog.Logs{}, fmt.Errorf("failed to split host:port %s: %w", *alert.HostNameAndPort, err)
}
port, err := strconv.ParseInt(portStr, 10, 64)
if err != nil {
return plog.Logs{}, fmt.Errorf("failed to parse port %s: %w", portStr, err)
}
attrs.PutStr("net.peer.name", host)
attrs.PutInt("net.peer.port", port)
}
return logs, nil
}
// alertRecord wraps a sync Map so it is goroutine safe as well as
// can have custom marshaling
type alertRecord struct {
sync.Mutex
LastRecordedTime *time.Time `mapstructure:"last_recorded"`
}
func (a *alertRecord) SetLastRecorded(lastUpdated *time.Time) {
a.Lock()
a.LastRecordedTime = lastUpdated
a.Unlock()
}
func (a *alertsReceiver) syncPersistence(ctx context.Context) error {
if a.storageClient == nil {
return nil
}
cBytes, err := a.storageClient.Get(ctx, alertCacheKey)
if err != nil || cBytes == nil {
a.record = &alertRecord{}
return nil
}
var cache alertRecord
if err = json.Unmarshal(cBytes, &cache); err != nil {
return fmt.Errorf("unable to decode stored cache: %w", err)
}
a.record = &cache
return nil
}
func (a *alertsReceiver) writeCheckpoint(ctx context.Context) error {
if a.storageClient == nil {
a.telemetrySettings.Logger.Error("unable to write checkpoint since no storage client was found")
return errors.New("missing non-nil storage client")
}
marshalBytes, err := json.Marshal(&a.record)
if err != nil {
return fmt.Errorf("unable to write checkpoint: %w", err)
}
return a.storageClient.Set(ctx, alertCacheKey, marshalBytes)
}
func (a *alertsReceiver) applyFilters(pConf *ProjectConfig, alerts []mongodbatlas.Alert) []*mongodbatlas.Alert {
filtered := []*mongodbatlas.Alert{}
lastRecordedTime := pcommon.Timestamp(0).AsTime()
if a.record.LastRecordedTime != nil {
lastRecordedTime = *a.record.LastRecordedTime
}
// we need to maintain two timestamps in order to not conflict while iterating
latestInPayload := pcommon.Timestamp(0).AsTime()
for i := range alerts {
alert := &alerts[i]
updatedTime, err := time.Parse(time.RFC3339, alert.Updated)
if err != nil {
a.telemetrySettings.Logger.Warn("unable to interpret updated time for alert, expecting a RFC3339 timestamp", zap.String("timestamp", alert.Updated))
continue
}
if updatedTime.Before(lastRecordedTime) || updatedTime.Equal(lastRecordedTime) {
// already processed if the updated time was before or equal to the last recorded
continue
}
if len(pConf.excludesByClusterName) > 0 {
if _, ok := pConf.excludesByClusterName[alert.ClusterName]; ok {
continue
}
}
if len(pConf.IncludeClusters) > 0 {
if _, ok := pConf.includesByClusterName[alert.ClusterName]; !ok {
continue
}
}
filtered = append(filtered, alert)
if updatedTime.After(latestInPayload) {
latestInPayload = updatedTime
}
}
if latestInPayload.After(lastRecordedTime) {
a.record.SetLastRecorded(&latestInPayload)
}
return filtered
}
func timestampFromAlert(a model.Alert) pcommon.Timestamp {
if time, err := time.Parse(time.RFC3339, a.Updated); err == nil {
return pcommon.NewTimestampFromTime(time)
}
return pcommon.Timestamp(0)
}
// severityFromAlert maps the alert to a severity number.
// Currently, it just maps "OPEN" alerts to WARN, and everything else to INFO.
func severityFromAlert(a model.Alert) plog.SeverityNumber {
// Status is defined here: https://www.mongodb.com/docs/atlas/reference/api/alerts-get-alert/#response-elements
// It may also be "INFORMATIONAL" for single-fire alerts (events)
switch a.Status {
case "OPEN":
return plog.SeverityNumberWarn
default:
return plog.SeverityNumberInfo
}
}
// severityFromAPIAlert is a workaround for shared types between the API and the model
func severityFromAPIAlert(a string) plog.SeverityNumber {
switch a {
case "OPEN":
return plog.SeverityNumberWarn
default:
return plog.SeverityNumberInfo
}
}
func putStringToMapNotNil(m pcommon.Map, k string, v *string) {
if v != nil {
m.PutStr(k, *v)
}
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package mongodbatlasreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mongodbatlasreceiver"
import (
"context"
"fmt"
"go.opentelemetry.io/collector/component"
"go.uber.org/multierr"
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza/adapter"
)
// combinedLogsReceiver wraps alerts and log receivers in a single log receiver to be consumed by the factory
type combinedLogsReceiver struct {
alerts *alertsReceiver
logs *logsReceiver
events *eventsReceiver
accessLogs *accessLogsReceiver
storageID *component.ID
id component.ID
}
// Starts up the combined MongoDB Atlas Logs and Alert Receiver
func (c *combinedLogsReceiver) Start(ctx context.Context, host component.Host) error {
var errs error
storageClient, err := adapter.GetStorageClient(ctx, host, c.storageID, c.id)
if err != nil {
return fmt.Errorf("failed to get storage client: %w", err)
}
if c.alerts != nil {
if err := c.alerts.Start(ctx, host, storageClient); err != nil {
errs = multierr.Append(errs, err)
}
}
if c.logs != nil {
if err := c.logs.Start(ctx, host); err != nil {
errs = multierr.Append(errs, err)
}
}
if c.events != nil {
if err := c.events.Start(ctx, host, storageClient); err != nil {
errs = multierr.Append(errs, err)
}
}
if c.accessLogs != nil {
if err := c.accessLogs.Start(ctx, host, storageClient); err != nil {
errs = multierr.Append(errs, err)
}
}
return errs
}
// Shuts down the combined MongoDB Atlas Logs and Alert Receiver
func (c *combinedLogsReceiver) Shutdown(ctx context.Context) error {
var errs error
if c.alerts != nil {
if err := c.alerts.Shutdown(ctx); err != nil {
errs = multierr.Append(errs, err)
}
}
if c.logs != nil {
if err := c.logs.Shutdown(ctx); err != nil {
errs = multierr.Append(errs, err)
}
}
if c.events != nil {
if err := c.events.Shutdown(ctx); err != nil {
errs = multierr.Append(errs, err)
}
}
if c.accessLogs != nil {
if err := c.accessLogs.Shutdown(ctx); err != nil {
errs = multierr.Append(errs, err)
}
}
return errs
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package mongodbatlasreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mongodbatlasreceiver"
import (
"errors"
"fmt"
"net"
"net/url"
"time"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configopaque"
"go.opentelemetry.io/collector/config/configoptional"
"go.opentelemetry.io/collector/config/configretry"
"go.opentelemetry.io/collector/config/configtls"
"go.opentelemetry.io/collector/scraper/scraperhelper"
"go.uber.org/multierr"
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mongodbatlasreceiver/internal/metadata"
)
var _ component.Config = (*Config)(nil)
type Config struct {
ControllerConfig scraperhelper.ControllerConfig `mapstructure:",squash"`
BaseURL string `mapstructure:"base_url"`
PublicKey string `mapstructure:"public_key"`
PrivateKey configopaque.String `mapstructure:"private_key"`
Granularity string `mapstructure:"granularity"`
MetricsBuilderConfig metadata.MetricsBuilderConfig `mapstructure:",squash"`
Projects []ProjectConfig `mapstructure:"projects"`
Alerts AlertConfig `mapstructure:"alerts"`
Events configoptional.Optional[EventsConfig] `mapstructure:"events"`
Logs LogConfig `mapstructure:"logs"`
BackOffConfig configretry.BackOffConfig `mapstructure:"retry_on_failure"`
StorageID *component.ID `mapstructure:"storage"`
}
type AlertConfig struct {
Enabled bool `mapstructure:"enabled"`
Endpoint string `mapstructure:"endpoint"`
Secret configopaque.String `mapstructure:"secret"`
TLS *configtls.ServerConfig `mapstructure:"tls"`
Mode string `mapstructure:"mode"`
// these parameters are only relevant in retrieval mode
Projects []*ProjectConfig `mapstructure:"projects"`
PollInterval time.Duration `mapstructure:"poll_interval"`
PageSize int64 `mapstructure:"page_size"`
MaxPages int64 `mapstructure:"max_pages"`
}
type LogConfig struct {
Enabled bool `mapstructure:"enabled"`
Projects []*LogsProjectConfig `mapstructure:"projects"`
// prevent unkeyed literal initialization
_ struct{}
}
// EventsConfig is the configuration options for events collection
type EventsConfig struct {
Projects []*ProjectConfig `mapstructure:"projects"`
Organizations []*OrgConfig `mapstructure:"organizations"`
PollInterval time.Duration `mapstructure:"poll_interval"`
Types []string `mapstructure:"types"`
PageSize int64 `mapstructure:"page_size"`
MaxPages int64 `mapstructure:"max_pages"`
}
type LogsProjectConfig struct {
ProjectConfig ProjectConfig `mapstructure:",squash"`
EnableAuditLogs bool `mapstructure:"collect_audit_logs"`
EnableHostLogs *bool `mapstructure:"collect_host_logs"`
AccessLogs *AccessLogsConfig `mapstructure:"access_logs"`
// prevent unkeyed literal initialization
_ struct{}
}
type AccessLogsConfig struct {
Enabled *bool `mapstructure:"enabled"`
PollInterval time.Duration `mapstructure:"poll_interval"`
PageSize int64 `mapstructure:"page_size"`
MaxPages int64 `mapstructure:"max_pages"`
AuthResult *bool `mapstructure:"auth_result"`
}
func (alc *AccessLogsConfig) IsEnabled() bool {
return alc.Enabled == nil || *alc.Enabled
}
type ProjectConfig struct {
Name string `mapstructure:"name"`
ExcludeClusters []string `mapstructure:"exclude_clusters"`
IncludeClusters []string `mapstructure:"include_clusters"`
includesByClusterName map[string]struct{}
excludesByClusterName map[string]struct{}
}
type OrgConfig struct {
ID string `mapstructure:"id"`
// prevent unkeyed literal initialization
_ struct{}
}
func (pc *ProjectConfig) populateIncludesAndExcludes() {
pc.includesByClusterName = map[string]struct{}{}
for _, inclusion := range pc.IncludeClusters {
pc.includesByClusterName[inclusion] = struct{}{}
}
pc.excludesByClusterName = map[string]struct{}{}
for _, exclusion := range pc.ExcludeClusters {
pc.excludesByClusterName[exclusion] = struct{}{}
}
}
var (
// Alerts Receiver Errors
errNoEndpoint = errors.New("an endpoint must be specified")
errNoSecret = errors.New("a webhook secret must be specified")
errNoCert = errors.New("tls was configured, but no cert file was specified")
errNoKey = errors.New("tls was configured, but no key file was specified")
errNoModeRecognized = fmt.Errorf("alert mode not recognized for mode. Known alert modes are: %s,%s", alertModeListen, alertModePoll)
errPageSizeIncorrect = errors.New("page size must be a value between 1 and 500")
// Logs Receiver Errors
errNoProjects = errors.New("at least one 'project' must be specified")
errNoEvents = errors.New("at least one 'project' or 'organizations' event type must be specified")
errClusterConfig = errors.New("only one of 'include_clusters' or 'exclude_clusters' may be specified")
// Access Logs Errors
errMaxPageSize = errors.New("the maximum value for 'page_size' is 20000")
// Config Errors
errConfigEmptyEndpoint = errors.New("baseurl must not be empty")
)
func (c *Config) Validate() error {
var errs error
if err := validateEndpoint(c.BaseURL); err != nil {
return fmt.Errorf("invalid base_url %q: %w", c.BaseURL, err)
}
for _, project := range c.Projects {
if len(project.ExcludeClusters) != 0 && len(project.IncludeClusters) != 0 {
errs = multierr.Append(errs, errClusterConfig)
}
}
errs = multierr.Append(errs, c.Alerts.validate())
errs = multierr.Append(errs, c.Logs.validate())
if c.Events.HasValue() {
errs = multierr.Append(errs, c.Events.Get().validate())
}
return errs
}
func (l *LogConfig) validate() error {
if !l.Enabled {
return nil
}
var errs error
if len(l.Projects) == 0 {
errs = multierr.Append(errs, errNoProjects)
}
for _, project := range l.Projects {
if len(project.ProjectConfig.ExcludeClusters) != 0 && len(project.ProjectConfig.IncludeClusters) != 0 {
errs = multierr.Append(errs, errClusterConfig)
}
if project.AccessLogs != nil && project.AccessLogs.IsEnabled() {
if project.AccessLogs.PageSize > 20000 {
errs = multierr.Append(errs, errMaxPageSize)
}
}
}
return errs
}
func (a *AlertConfig) validate() error {
if !a.Enabled {
// No need to further validate, receiving alerts is disabled.
return nil
}
switch a.Mode {
case alertModePoll:
return a.validatePollConfig()
case alertModeListen:
return a.validateListenConfig()
default:
return errNoModeRecognized
}
}
func (a AlertConfig) validatePollConfig() error {
if len(a.Projects) == 0 {
return errNoProjects
}
// based off API limits https://www.mongodb.com/docs/atlas/reference/api/alerts-get-all-alerts/
if 0 >= a.PageSize || a.PageSize > 500 {
return errPageSizeIncorrect
}
var errs error
for _, project := range a.Projects {
if len(project.ExcludeClusters) != 0 && len(project.IncludeClusters) != 0 {
errs = multierr.Append(errs, errClusterConfig)
}
}
return errs
}
func (a AlertConfig) validateListenConfig() error {
if a.Endpoint == "" {
return errNoEndpoint
}
var errs error
_, _, err := net.SplitHostPort(a.Endpoint)
if err != nil {
errs = multierr.Append(errs, fmt.Errorf("failed to split endpoint into 'host:port' pair: %w", err))
}
if a.Secret == "" {
errs = multierr.Append(errs, errNoSecret)
}
if a.TLS != nil {
if a.TLS.CertFile == "" {
errs = multierr.Append(errs, errNoCert)
}
if a.TLS.KeyFile == "" {
errs = multierr.Append(errs, errNoKey)
}
}
return errs
}
func (e EventsConfig) validate() error {
if len(e.Projects) == 0 && len(e.Organizations) == 0 {
return errNoEvents
}
return nil
}
func validateEndpoint(endpoint string) error {
if endpoint == "" {
return errConfigEmptyEndpoint
}
u, err := url.Parse(endpoint)
if err != nil {
return err
}
switch u.Scheme {
case "http", "https":
default:
return fmt.Errorf(`invalid scheme %q, expected "http" or "https"`, u.Scheme)
}
if u.Host == "" {
return errors.New("host must not be empty")
}
return nil
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package mongodbatlasreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mongodbatlasreceiver"
import (
"context"
"encoding/json"
"errors"
"fmt"
"sync"
"time"
"go.mongodb.org/atlas/mongodbatlas"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/extension/xextension/storage"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
rcvr "go.opentelemetry.io/collector/receiver"
"go.uber.org/zap"
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mongodbatlasreceiver/internal"
)
const (
eventStorageKey = "last_recorded_event"
defaultEventsMaxPages = 25
defaultEventsPageSize = 100
defaultPollInterval = time.Minute
)
type eventsClient interface {
GetProject(ctx context.Context, groupID string) (*mongodbatlas.Project, error)
GetProjectEvents(ctx context.Context, groupID string, opts *internal.GetEventsOptions) (ret []*mongodbatlas.Event, nextPage bool, err error)
GetOrganization(ctx context.Context, orgID string) (*mongodbatlas.Organization, error)
GetOrganizationEvents(ctx context.Context, orgID string, opts *internal.GetEventsOptions) (ret []*mongodbatlas.Event, nextPage bool, err error)
Shutdown() error
}
type eventsReceiver struct {
client eventsClient
logger *zap.Logger
storageClient storage.Client
cfg *Config
consumer consumer.Logs
maxPages int
pageSize int
pollInterval time.Duration
wg *sync.WaitGroup
record *eventRecord // this record is used for checkpointing last processed events
cancel context.CancelFunc
}
type eventRecord struct {
NextStartTime *time.Time `mapstructure:"next_start_time"`
}
func newEventsReceiver(settings rcvr.Settings, c *Config, consumer consumer.Logs) (*eventsReceiver, error) {
client, err := internal.NewMongoDBAtlasClient(c.BaseURL, c.PublicKey, string(c.PrivateKey), c.BackOffConfig, settings.Logger, settings.BuildInfo)
if err != nil {
return nil, fmt.Errorf("failed to create MongoDB Atlas client for events receiver: %w", err)
}
r := &eventsReceiver{
client: client,
cfg: c,
logger: settings.Logger,
consumer: consumer,
pollInterval: c.Events.Get().PollInterval,
wg: &sync.WaitGroup{},
maxPages: int(c.Events.Get().MaxPages),
pageSize: int(c.Events.Get().PageSize),
storageClient: storage.NewNopClient(),
}
if r.maxPages == 0 {
r.maxPages = defaultEventsMaxPages
}
if r.pageSize == 0 {
r.pageSize = defaultEventsPageSize
}
if r.pollInterval == 0 {
r.pollInterval = time.Minute
}
return r, nil
}
func (er *eventsReceiver) Start(ctx context.Context, _ component.Host, storageClient storage.Client) error {
er.logger.Debug("Starting up events receiver")
cancelCtx, cancel := context.WithCancel(ctx)
er.cancel = cancel
er.storageClient = storageClient
er.loadCheckpoint(cancelCtx)
return er.startPolling(cancelCtx)
}
func (er *eventsReceiver) Shutdown(ctx context.Context) error {
er.logger.Debug("Shutting down events receiver")
er.cancel()
er.wg.Wait()
var err []error
err = append(err, er.client.Shutdown(), er.checkpoint(ctx))
return errors.Join(err...)
}
func (er *eventsReceiver) startPolling(ctx context.Context) error {
t := time.NewTicker(er.pollInterval)
er.wg.Go(func() {
for {
select {
case <-t.C:
if err := er.pollEvents(ctx); err != nil {
er.logger.Error("error while polling for events", zap.Error(err))
}
case <-ctx.Done():
return
}
}
})
return nil
}
func (er *eventsReceiver) pollEvents(ctx context.Context) error {
st := pcommon.NewTimestampFromTime(time.Now().Add(-er.pollInterval)).AsTime()
if er.record.NextStartTime != nil {
st = *er.record.NextStartTime
}
et := time.Now()
for _, pc := range er.cfg.Events.Get().Projects {
project, err := er.client.GetProject(ctx, pc.Name)
if err != nil {
er.logger.Error("error retrieving project information for "+pc.Name+":", zap.Error(err))
return err
}
er.pollProject(ctx, project, pc, st, et)
}
for _, pc := range er.cfg.Events.Get().Organizations {
org, err := er.client.GetOrganization(ctx, pc.ID)
if err != nil {
er.logger.Error("error retrieving org information for "+pc.ID+":", zap.Error(err))
return err
}
er.pollOrg(ctx, org, pc, st, et)
}
er.record.NextStartTime = &et
return er.checkpoint(ctx)
}
func (er *eventsReceiver) pollProject(ctx context.Context, project *mongodbatlas.Project, p *ProjectConfig, startTime, now time.Time) {
for pageN := 1; pageN <= er.maxPages; pageN++ {
opts := &internal.GetEventsOptions{
PageNum: pageN,
EventTypes: er.cfg.Events.Get().Types,
MaxDate: now,
MinDate: startTime,
}
projectEvents, hasNext, err := er.client.GetProjectEvents(ctx, project.ID, opts)
if err != nil {
er.logger.Error("unable to get events for project", zap.Error(err), zap.String("project", p.Name))
break
}
now := pcommon.NewTimestampFromTime(now)
logs := er.transformProjectEvents(now, projectEvents, project)
if logs.LogRecordCount() > 0 {
if err = er.consumer.ConsumeLogs(ctx, logs); err != nil {
er.logger.Error("error consuming project events", zap.Error(err))
break
}
}
if !hasNext {
break
}
}
}
func (er *eventsReceiver) pollOrg(ctx context.Context, org *mongodbatlas.Organization, p *OrgConfig, startTime, now time.Time) {
for pageN := 1; pageN <= er.maxPages; pageN++ {
opts := &internal.GetEventsOptions{
PageNum: pageN,
EventTypes: er.cfg.Events.Get().Types,
MaxDate: now,
MinDate: startTime,
}
organizationEvents, hasNext, err := er.client.GetOrganizationEvents(ctx, org.ID, opts)
if err != nil {
er.logger.Error("unable to get events for organization", zap.Error(err), zap.String("organization", p.ID))
break
}
now := pcommon.NewTimestampFromTime(now)
logs := er.transformOrgEvents(now, organizationEvents, org)
if logs.LogRecordCount() > 0 {
if err = er.consumer.ConsumeLogs(ctx, logs); err != nil {
er.logger.Error("error consuming organization events", zap.Error(err))
break
}
}
if !hasNext {
break
}
}
}
func (er *eventsReceiver) transformProjectEvents(now pcommon.Timestamp, events []*mongodbatlas.Event, p *mongodbatlas.Project) plog.Logs {
logs := plog.NewLogs()
resourceLogs := logs.ResourceLogs().AppendEmpty()
ra := resourceLogs.Resource().Attributes()
ra.PutStr("mongodbatlas.project.name", p.Name)
ra.PutStr("mongodbatlas.org.id", p.OrgID)
er.transformEvents(now, events, &resourceLogs)
return logs
}
func (er *eventsReceiver) transformOrgEvents(now pcommon.Timestamp, events []*mongodbatlas.Event, o *mongodbatlas.Organization) plog.Logs {
logs := plog.NewLogs()
resourceLogs := logs.ResourceLogs().AppendEmpty()
ra := resourceLogs.Resource().Attributes()
ra.PutStr("mongodbatlas.org.id", o.ID)
er.transformEvents(now, events, &resourceLogs)
return logs
}
func (er *eventsReceiver) transformEvents(now pcommon.Timestamp, events []*mongodbatlas.Event, resourceLogs *plog.ResourceLogs) {
for _, event := range events {
logRecord := resourceLogs.ScopeLogs().AppendEmpty().LogRecords().AppendEmpty()
bodyBytes, err := json.Marshal(event)
if err != nil {
er.logger.Error("unable to unmarshal event into body string", zap.Error(err))
continue
}
logRecord.Body().SetStr(string(bodyBytes))
// ISO-8601 formatted
ts, err := time.Parse(time.RFC3339, event.Created)
if err != nil {
er.logger.Warn("unable to interpret when an event was created, expecting a RFC3339 timestamp", zap.String("timestamp", event.Created), zap.String("event", event.ID))
logRecord.SetTimestamp(now)
} else {
logRecord.SetTimestamp(pcommon.NewTimestampFromTime(ts))
}
logRecord.SetObservedTimestamp(now)
attrs := logRecord.Attributes()
// always present attributes
attrs.PutStr("event.domain", "mongodbatlas")
attrs.PutStr("type", event.EventTypeName)
attrs.PutStr("id", event.ID)
attrs.PutStr("group.id", event.GroupID)
parseOptionalAttributes(&attrs, event)
}
}
func (er *eventsReceiver) checkpoint(ctx context.Context) error {
marshalBytes, err := json.Marshal(er.record)
if err != nil {
return fmt.Errorf("unable to write checkpoint: %w", err)
}
return er.storageClient.Set(ctx, eventStorageKey, marshalBytes)
}
func (er *eventsReceiver) loadCheckpoint(ctx context.Context) {
cBytes, err := er.storageClient.Get(ctx, eventStorageKey)
if err != nil {
er.logger.Info("unable to load checkpoint from storage client, continuing without a previous checkpoint", zap.Error(err))
er.record = &eventRecord{}
return
}
if cBytes == nil {
er.record = &eventRecord{}
return
}
var record eventRecord
if err = json.Unmarshal(cBytes, &record); err != nil {
er.logger.Error("unable to decode stored record for events, continuing without a checkpoint", zap.Error(err))
er.record = &eventRecord{}
return
}
er.record = &record
}
func parseOptionalAttributes(m *pcommon.Map, event *mongodbatlas.Event) {
if event.AlertID != "" {
m.PutStr("alert.id", event.AlertID)
}
if event.AlertConfigID != "" {
m.PutStr("alert.config.id", event.AlertConfigID)
}
if event.Collection != "" {
m.PutStr("collection", event.Collection)
}
if event.Database != "" {
m.PutStr("database", event.Database)
}
if event.Hostname != "" {
m.PutStr("net.peer.name", event.Hostname)
}
if event.Port != 0 {
m.PutInt("net.peer.port", int64(event.Port))
}
if event.InvoiceID != "" {
m.PutStr("invoice.id", event.InvoiceID)
}
if event.Username != "" {
m.PutStr("user.name", event.Username)
}
if event.TargetUsername != "" {
m.PutStr("target.user.name", event.TargetUsername)
}
if event.UserID != "" {
m.PutStr("user.id", event.UserID)
}
if event.TeamID != "" {
m.PutStr("team.id", event.TeamID)
}
if event.RemoteAddress != "" {
m.PutStr("remote.ip", event.RemoteAddress)
}
if event.MetricName != "" {
m.PutStr("metric.name", event.MetricName)
}
if event.OpType != "" {
m.PutStr("event.op_type", event.OpType)
}
if event.PaymentID != "" {
m.PutStr("payment.id", event.PaymentID)
}
if event.ReplicaSetName != "" {
m.PutStr("replica_set.name", event.ReplicaSetName)
}
if event.CurrentValue != nil {
m.PutDouble("metric.value", *event.CurrentValue.Number)
m.PutStr("metric.units", event.CurrentValue.Units)
}
if event.ShardName != "" {
m.PutStr("shard.name", event.ShardName)
}
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package mongodbatlasreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mongodbatlasreceiver"
import (
"context"
"errors"
"fmt"
"time"
"go.mongodb.org/atlas/mongodbatlas"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configretry"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/xreceiver"
"go.opentelemetry.io/collector/scraper/scraperhelper"
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mongodbatlasreceiver/internal/metadata"
)
const (
defaultGranularity = "PT1M" // 1-minute, as per https://docs.atlas.mongodb.com/reference/api/process-measurements/
defaultAlertsEnabled = false
defaultLogsEnabled = false
)
// NewFactory creates a factory for MongoDB Atlas receiver
func NewFactory() receiver.Factory {
return xreceiver.NewFactory(
metadata.Type,
createDefaultConfig,
xreceiver.WithMetrics(createMetricsReceiver, metadata.MetricsStability),
xreceiver.WithLogs(createCombinedLogReceiver, metadata.LogsStability),
xreceiver.WithDeprecatedTypeAlias(metadata.DeprecatedType),
)
}
func createMetricsReceiver(
_ context.Context,
params receiver.Settings,
rConf component.Config,
consumer consumer.Metrics,
) (receiver.Metrics, error) {
cfg := rConf.(*Config)
recv, err := newMongoDBAtlasReceiver(params, cfg)
if err != nil {
return nil, fmt.Errorf("unable to create a MongoDB Atlas Receiver instance: %w", err)
}
ms, err := newMongoDBAtlasScraper(recv)
if err != nil {
return nil, fmt.Errorf("unable to create a MongoDB Atlas Scraper instance: %w", err)
}
return scraperhelper.NewMetricsController(&cfg.ControllerConfig, params, consumer, scraperhelper.AddMetricsScraper(metadata.Type, ms))
}
func createCombinedLogReceiver(
_ context.Context,
params receiver.Settings,
rConf component.Config,
consumer consumer.Logs,
) (receiver.Logs, error) {
cfg := rConf.(*Config)
if !cfg.Alerts.Enabled && !cfg.Logs.Enabled && !cfg.Events.HasValue() {
return nil, errors.New("one of 'alerts', 'events', or 'logs' must be enabled")
}
var err error
recv := &combinedLogsReceiver{
id: params.ID,
storageID: cfg.StorageID,
}
if cfg.Alerts.Enabled {
recv.alerts, err = newAlertsReceiver(params, cfg, consumer)
if err != nil {
return nil, fmt.Errorf("unable to create a MongoDB Atlas Alerts Receiver instance: %w", err)
}
}
if cfg.Logs.Enabled {
recv.logs, err = newMongoDBAtlasLogsReceiver(params, cfg, consumer)
if err != nil {
return nil, fmt.Errorf("unable to create a MongoDB Atlas Logs Receiver instance: %w", err)
}
// Confirm at least one project is enabled for access logs before adding
for _, project := range cfg.Logs.Projects {
if project.AccessLogs != nil && project.AccessLogs.IsEnabled() {
recv.accessLogs, err = newAccessLogsReceiver(params, cfg, consumer)
if err != nil {
return nil, fmt.Errorf("unable to create a MongoDB Atlas Access Logs Receiver instance: %w", err)
}
break
}
}
}
if cfg.Events.HasValue() {
recv.events, err = newEventsReceiver(params, cfg, consumer)
if err != nil {
return nil, fmt.Errorf("unable to create a MongoDB Atlas Events Receiver instance: %w", err)
}
}
return recv, nil
}
func createDefaultConfig() component.Config {
c := &Config{
BaseURL: mongodbatlas.CloudURL,
ControllerConfig: scraperhelper.NewDefaultControllerConfig(),
Granularity: defaultGranularity,
BackOffConfig: configretry.NewDefaultBackOffConfig(),
MetricsBuilderConfig: metadata.NewDefaultMetricsBuilderConfig(),
Alerts: AlertConfig{
Enabled: defaultAlertsEnabled,
Mode: alertModeListen,
PollInterval: defaultAlertsPollInterval,
PageSize: defaultAlertsPageSize,
MaxPages: defaultAlertsMaxPages,
},
Logs: LogConfig{
Enabled: defaultLogsEnabled,
Projects: []*LogsProjectConfig{},
},
}
// reset default of 1 minute to be 3 minutes in order to avoid null values for some metrics that do not publish
// more frequently
c.ControllerConfig.CollectionInterval = 3 * time.Minute
return c
}
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"fmt"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/filter"
)
// MongodbatlasDbCountsMetricAttributeKey specifies the key of an attribute for the mongodbatlas.db.counts metric.
type MongodbatlasDbCountsMetricAttributeKey string
const (
MongodbatlasDbCountsMetricAttributeKeyObjectType MongodbatlasDbCountsMetricAttributeKey = "object_type"
)
// MongodbatlasDbCountsMetricConfig provides config for the mongodbatlas.db.counts metric.
type MongodbatlasDbCountsMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasDbCountsMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasDbCountsMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasDbCountsMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasDbCountsMetricAttributeKeyObjectType:
default:
return fmt.Errorf("metric mongodbatlas.db.counts doesn't have an attribute %v, valid attributes: [object_type]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasDbSizeMetricAttributeKey specifies the key of an attribute for the mongodbatlas.db.size metric.
type MongodbatlasDbSizeMetricAttributeKey string
const (
MongodbatlasDbSizeMetricAttributeKeyObjectType MongodbatlasDbSizeMetricAttributeKey = "object_type"
)
// MongodbatlasDbSizeMetricConfig provides config for the mongodbatlas.db.size metric.
type MongodbatlasDbSizeMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasDbSizeMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasDbSizeMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasDbSizeMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasDbSizeMetricAttributeKeyObjectType:
default:
return fmt.Errorf("metric mongodbatlas.db.size doesn't have an attribute %v, valid attributes: [object_type]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasDiskPartitionIopsAverageMetricAttributeKey specifies the key of an attribute for the mongodbatlas.disk.partition.iops.average metric.
type MongodbatlasDiskPartitionIopsAverageMetricAttributeKey string
const (
MongodbatlasDiskPartitionIopsAverageMetricAttributeKeyDiskDirection MongodbatlasDiskPartitionIopsAverageMetricAttributeKey = "disk_direction"
)
// MongodbatlasDiskPartitionIopsAverageMetricConfig provides config for the mongodbatlas.disk.partition.iops.average metric.
type MongodbatlasDiskPartitionIopsAverageMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasDiskPartitionIopsAverageMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasDiskPartitionIopsAverageMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasDiskPartitionIopsAverageMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasDiskPartitionIopsAverageMetricAttributeKeyDiskDirection:
default:
return fmt.Errorf("metric mongodbatlas.disk.partition.iops.average doesn't have an attribute %v, valid attributes: [disk_direction]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasDiskPartitionIopsMaxMetricAttributeKey specifies the key of an attribute for the mongodbatlas.disk.partition.iops.max metric.
type MongodbatlasDiskPartitionIopsMaxMetricAttributeKey string
const (
MongodbatlasDiskPartitionIopsMaxMetricAttributeKeyDiskDirection MongodbatlasDiskPartitionIopsMaxMetricAttributeKey = "disk_direction"
)
// MongodbatlasDiskPartitionIopsMaxMetricConfig provides config for the mongodbatlas.disk.partition.iops.max metric.
type MongodbatlasDiskPartitionIopsMaxMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasDiskPartitionIopsMaxMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasDiskPartitionIopsMaxMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasDiskPartitionIopsMaxMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasDiskPartitionIopsMaxMetricAttributeKeyDiskDirection:
default:
return fmt.Errorf("metric mongodbatlas.disk.partition.iops.max doesn't have an attribute %v, valid attributes: [disk_direction]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasDiskPartitionLatencyAverageMetricAttributeKey specifies the key of an attribute for the mongodbatlas.disk.partition.latency.average metric.
type MongodbatlasDiskPartitionLatencyAverageMetricAttributeKey string
const (
MongodbatlasDiskPartitionLatencyAverageMetricAttributeKeyDiskDirection MongodbatlasDiskPartitionLatencyAverageMetricAttributeKey = "disk_direction"
)
// MongodbatlasDiskPartitionLatencyAverageMetricConfig provides config for the mongodbatlas.disk.partition.latency.average metric.
type MongodbatlasDiskPartitionLatencyAverageMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasDiskPartitionLatencyAverageMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasDiskPartitionLatencyAverageMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasDiskPartitionLatencyAverageMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasDiskPartitionLatencyAverageMetricAttributeKeyDiskDirection:
default:
return fmt.Errorf("metric mongodbatlas.disk.partition.latency.average doesn't have an attribute %v, valid attributes: [disk_direction]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasDiskPartitionLatencyMaxMetricAttributeKey specifies the key of an attribute for the mongodbatlas.disk.partition.latency.max metric.
type MongodbatlasDiskPartitionLatencyMaxMetricAttributeKey string
const (
MongodbatlasDiskPartitionLatencyMaxMetricAttributeKeyDiskDirection MongodbatlasDiskPartitionLatencyMaxMetricAttributeKey = "disk_direction"
)
// MongodbatlasDiskPartitionLatencyMaxMetricConfig provides config for the mongodbatlas.disk.partition.latency.max metric.
type MongodbatlasDiskPartitionLatencyMaxMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasDiskPartitionLatencyMaxMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasDiskPartitionLatencyMaxMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasDiskPartitionLatencyMaxMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasDiskPartitionLatencyMaxMetricAttributeKeyDiskDirection:
default:
return fmt.Errorf("metric mongodbatlas.disk.partition.latency.max doesn't have an attribute %v, valid attributes: [disk_direction]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasDiskPartitionQueueDepthMetricConfig provides config for the mongodbatlas.disk.partition.queue.depth metric.
type MongodbatlasDiskPartitionQueueDepthMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
}
func (ms *MongodbatlasDiskPartitionQueueDepthMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
// MongodbatlasDiskPartitionSpaceAverageMetricAttributeKey specifies the key of an attribute for the mongodbatlas.disk.partition.space.average metric.
type MongodbatlasDiskPartitionSpaceAverageMetricAttributeKey string
const (
MongodbatlasDiskPartitionSpaceAverageMetricAttributeKeyDiskStatus MongodbatlasDiskPartitionSpaceAverageMetricAttributeKey = "disk_status"
)
// MongodbatlasDiskPartitionSpaceAverageMetricConfig provides config for the mongodbatlas.disk.partition.space.average metric.
type MongodbatlasDiskPartitionSpaceAverageMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasDiskPartitionSpaceAverageMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasDiskPartitionSpaceAverageMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasDiskPartitionSpaceAverageMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasDiskPartitionSpaceAverageMetricAttributeKeyDiskStatus:
default:
return fmt.Errorf("metric mongodbatlas.disk.partition.space.average doesn't have an attribute %v, valid attributes: [disk_status]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasDiskPartitionSpaceMaxMetricAttributeKey specifies the key of an attribute for the mongodbatlas.disk.partition.space.max metric.
type MongodbatlasDiskPartitionSpaceMaxMetricAttributeKey string
const (
MongodbatlasDiskPartitionSpaceMaxMetricAttributeKeyDiskStatus MongodbatlasDiskPartitionSpaceMaxMetricAttributeKey = "disk_status"
)
// MongodbatlasDiskPartitionSpaceMaxMetricConfig provides config for the mongodbatlas.disk.partition.space.max metric.
type MongodbatlasDiskPartitionSpaceMaxMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasDiskPartitionSpaceMaxMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasDiskPartitionSpaceMaxMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasDiskPartitionSpaceMaxMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasDiskPartitionSpaceMaxMetricAttributeKeyDiskStatus:
default:
return fmt.Errorf("metric mongodbatlas.disk.partition.space.max doesn't have an attribute %v, valid attributes: [disk_status]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasDiskPartitionThroughputMetricAttributeKey specifies the key of an attribute for the mongodbatlas.disk.partition.throughput metric.
type MongodbatlasDiskPartitionThroughputMetricAttributeKey string
const (
MongodbatlasDiskPartitionThroughputMetricAttributeKeyDiskDirection MongodbatlasDiskPartitionThroughputMetricAttributeKey = "disk_direction"
)
// MongodbatlasDiskPartitionThroughputMetricConfig provides config for the mongodbatlas.disk.partition.throughput metric.
type MongodbatlasDiskPartitionThroughputMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasDiskPartitionThroughputMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasDiskPartitionThroughputMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasDiskPartitionThroughputMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasDiskPartitionThroughputMetricAttributeKeyDiskDirection:
default:
return fmt.Errorf("metric mongodbatlas.disk.partition.throughput doesn't have an attribute %v, valid attributes: [disk_direction]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasDiskPartitionUsageAverageMetricAttributeKey specifies the key of an attribute for the mongodbatlas.disk.partition.usage.average metric.
type MongodbatlasDiskPartitionUsageAverageMetricAttributeKey string
const (
MongodbatlasDiskPartitionUsageAverageMetricAttributeKeyDiskStatus MongodbatlasDiskPartitionUsageAverageMetricAttributeKey = "disk_status"
)
// MongodbatlasDiskPartitionUsageAverageMetricConfig provides config for the mongodbatlas.disk.partition.usage.average metric.
type MongodbatlasDiskPartitionUsageAverageMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasDiskPartitionUsageAverageMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasDiskPartitionUsageAverageMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasDiskPartitionUsageAverageMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasDiskPartitionUsageAverageMetricAttributeKeyDiskStatus:
default:
return fmt.Errorf("metric mongodbatlas.disk.partition.usage.average doesn't have an attribute %v, valid attributes: [disk_status]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasDiskPartitionUsageMaxMetricAttributeKey specifies the key of an attribute for the mongodbatlas.disk.partition.usage.max metric.
type MongodbatlasDiskPartitionUsageMaxMetricAttributeKey string
const (
MongodbatlasDiskPartitionUsageMaxMetricAttributeKeyDiskStatus MongodbatlasDiskPartitionUsageMaxMetricAttributeKey = "disk_status"
)
// MongodbatlasDiskPartitionUsageMaxMetricConfig provides config for the mongodbatlas.disk.partition.usage.max metric.
type MongodbatlasDiskPartitionUsageMaxMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasDiskPartitionUsageMaxMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasDiskPartitionUsageMaxMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasDiskPartitionUsageMaxMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasDiskPartitionUsageMaxMetricAttributeKeyDiskStatus:
default:
return fmt.Errorf("metric mongodbatlas.disk.partition.usage.max doesn't have an attribute %v, valid attributes: [disk_status]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasDiskPartitionUtilizationAverageMetricConfig provides config for the mongodbatlas.disk.partition.utilization.average metric.
type MongodbatlasDiskPartitionUtilizationAverageMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
}
func (ms *MongodbatlasDiskPartitionUtilizationAverageMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
// MongodbatlasDiskPartitionUtilizationMaxMetricConfig provides config for the mongodbatlas.disk.partition.utilization.max metric.
type MongodbatlasDiskPartitionUtilizationMaxMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
}
func (ms *MongodbatlasDiskPartitionUtilizationMaxMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
// MongodbatlasProcessAssertsMetricAttributeKey specifies the key of an attribute for the mongodbatlas.process.asserts metric.
type MongodbatlasProcessAssertsMetricAttributeKey string
const (
MongodbatlasProcessAssertsMetricAttributeKeyAssertType MongodbatlasProcessAssertsMetricAttributeKey = "assert_type"
)
// MongodbatlasProcessAssertsMetricConfig provides config for the mongodbatlas.process.asserts metric.
type MongodbatlasProcessAssertsMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasProcessAssertsMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasProcessAssertsMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasProcessAssertsMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasProcessAssertsMetricAttributeKeyAssertType:
default:
return fmt.Errorf("metric mongodbatlas.process.asserts doesn't have an attribute %v, valid attributes: [assert_type]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasProcessBackgroundFlushMetricConfig provides config for the mongodbatlas.process.background_flush metric.
type MongodbatlasProcessBackgroundFlushMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
}
func (ms *MongodbatlasProcessBackgroundFlushMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
// MongodbatlasProcessCacheIoMetricAttributeKey specifies the key of an attribute for the mongodbatlas.process.cache.io metric.
type MongodbatlasProcessCacheIoMetricAttributeKey string
const (
MongodbatlasProcessCacheIoMetricAttributeKeyCacheDirection MongodbatlasProcessCacheIoMetricAttributeKey = "cache_direction"
)
// MongodbatlasProcessCacheIoMetricConfig provides config for the mongodbatlas.process.cache.io metric.
type MongodbatlasProcessCacheIoMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasProcessCacheIoMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasProcessCacheIoMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasProcessCacheIoMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasProcessCacheIoMetricAttributeKeyCacheDirection:
default:
return fmt.Errorf("metric mongodbatlas.process.cache.io doesn't have an attribute %v, valid attributes: [cache_direction]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasProcessCacheRatioMetricAttributeKey specifies the key of an attribute for the mongodbatlas.process.cache.ratio metric.
type MongodbatlasProcessCacheRatioMetricAttributeKey string
const (
MongodbatlasProcessCacheRatioMetricAttributeKeyCacheRatioType MongodbatlasProcessCacheRatioMetricAttributeKey = "cache_ratio_type"
)
// MongodbatlasProcessCacheRatioMetricConfig provides config for the mongodbatlas.process.cache.ratio metric.
type MongodbatlasProcessCacheRatioMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasProcessCacheRatioMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasProcessCacheRatioMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasProcessCacheRatioMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasProcessCacheRatioMetricAttributeKeyCacheRatioType:
default:
return fmt.Errorf("metric mongodbatlas.process.cache.ratio doesn't have an attribute %v, valid attributes: [cache_ratio_type]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasProcessCacheSizeMetricAttributeKey specifies the key of an attribute for the mongodbatlas.process.cache.size metric.
type MongodbatlasProcessCacheSizeMetricAttributeKey string
const (
MongodbatlasProcessCacheSizeMetricAttributeKeyCacheStatus MongodbatlasProcessCacheSizeMetricAttributeKey = "cache_status"
)
// MongodbatlasProcessCacheSizeMetricConfig provides config for the mongodbatlas.process.cache.size metric.
type MongodbatlasProcessCacheSizeMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasProcessCacheSizeMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasProcessCacheSizeMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasProcessCacheSizeMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasProcessCacheSizeMetricAttributeKeyCacheStatus:
default:
return fmt.Errorf("metric mongodbatlas.process.cache.size doesn't have an attribute %v, valid attributes: [cache_status]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasProcessConnectionsMetricConfig provides config for the mongodbatlas.process.connections metric.
type MongodbatlasProcessConnectionsMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
}
func (ms *MongodbatlasProcessConnectionsMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
// MongodbatlasProcessCPUChildrenNormalizedUsageAverageMetricAttributeKey specifies the key of an attribute for the mongodbatlas.process.cpu.children.normalized.usage.average metric.
type MongodbatlasProcessCPUChildrenNormalizedUsageAverageMetricAttributeKey string
const (
MongodbatlasProcessCPUChildrenNormalizedUsageAverageMetricAttributeKeyCPUState MongodbatlasProcessCPUChildrenNormalizedUsageAverageMetricAttributeKey = "cpu_state"
)
// MongodbatlasProcessCPUChildrenNormalizedUsageAverageMetricConfig provides config for the mongodbatlas.process.cpu.children.normalized.usage.average metric.
type MongodbatlasProcessCPUChildrenNormalizedUsageAverageMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasProcessCPUChildrenNormalizedUsageAverageMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasProcessCPUChildrenNormalizedUsageAverageMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasProcessCPUChildrenNormalizedUsageAverageMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasProcessCPUChildrenNormalizedUsageAverageMetricAttributeKeyCPUState:
default:
return fmt.Errorf("metric mongodbatlas.process.cpu.children.normalized.usage.average doesn't have an attribute %v, valid attributes: [cpu_state]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasProcessCPUChildrenNormalizedUsageMaxMetricAttributeKey specifies the key of an attribute for the mongodbatlas.process.cpu.children.normalized.usage.max metric.
type MongodbatlasProcessCPUChildrenNormalizedUsageMaxMetricAttributeKey string
const (
MongodbatlasProcessCPUChildrenNormalizedUsageMaxMetricAttributeKeyCPUState MongodbatlasProcessCPUChildrenNormalizedUsageMaxMetricAttributeKey = "cpu_state"
)
// MongodbatlasProcessCPUChildrenNormalizedUsageMaxMetricConfig provides config for the mongodbatlas.process.cpu.children.normalized.usage.max metric.
type MongodbatlasProcessCPUChildrenNormalizedUsageMaxMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasProcessCPUChildrenNormalizedUsageMaxMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasProcessCPUChildrenNormalizedUsageMaxMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasProcessCPUChildrenNormalizedUsageMaxMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasProcessCPUChildrenNormalizedUsageMaxMetricAttributeKeyCPUState:
default:
return fmt.Errorf("metric mongodbatlas.process.cpu.children.normalized.usage.max doesn't have an attribute %v, valid attributes: [cpu_state]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasProcessCPUChildrenUsageAverageMetricAttributeKey specifies the key of an attribute for the mongodbatlas.process.cpu.children.usage.average metric.
type MongodbatlasProcessCPUChildrenUsageAverageMetricAttributeKey string
const (
MongodbatlasProcessCPUChildrenUsageAverageMetricAttributeKeyCPUState MongodbatlasProcessCPUChildrenUsageAverageMetricAttributeKey = "cpu_state"
)
// MongodbatlasProcessCPUChildrenUsageAverageMetricConfig provides config for the mongodbatlas.process.cpu.children.usage.average metric.
type MongodbatlasProcessCPUChildrenUsageAverageMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasProcessCPUChildrenUsageAverageMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasProcessCPUChildrenUsageAverageMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasProcessCPUChildrenUsageAverageMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasProcessCPUChildrenUsageAverageMetricAttributeKeyCPUState:
default:
return fmt.Errorf("metric mongodbatlas.process.cpu.children.usage.average doesn't have an attribute %v, valid attributes: [cpu_state]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasProcessCPUChildrenUsageMaxMetricAttributeKey specifies the key of an attribute for the mongodbatlas.process.cpu.children.usage.max metric.
type MongodbatlasProcessCPUChildrenUsageMaxMetricAttributeKey string
const (
MongodbatlasProcessCPUChildrenUsageMaxMetricAttributeKeyCPUState MongodbatlasProcessCPUChildrenUsageMaxMetricAttributeKey = "cpu_state"
)
// MongodbatlasProcessCPUChildrenUsageMaxMetricConfig provides config for the mongodbatlas.process.cpu.children.usage.max metric.
type MongodbatlasProcessCPUChildrenUsageMaxMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasProcessCPUChildrenUsageMaxMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasProcessCPUChildrenUsageMaxMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasProcessCPUChildrenUsageMaxMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasProcessCPUChildrenUsageMaxMetricAttributeKeyCPUState:
default:
return fmt.Errorf("metric mongodbatlas.process.cpu.children.usage.max doesn't have an attribute %v, valid attributes: [cpu_state]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasProcessCPUNormalizedUsageAverageMetricAttributeKey specifies the key of an attribute for the mongodbatlas.process.cpu.normalized.usage.average metric.
type MongodbatlasProcessCPUNormalizedUsageAverageMetricAttributeKey string
const (
MongodbatlasProcessCPUNormalizedUsageAverageMetricAttributeKeyCPUState MongodbatlasProcessCPUNormalizedUsageAverageMetricAttributeKey = "cpu_state"
)
// MongodbatlasProcessCPUNormalizedUsageAverageMetricConfig provides config for the mongodbatlas.process.cpu.normalized.usage.average metric.
type MongodbatlasProcessCPUNormalizedUsageAverageMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasProcessCPUNormalizedUsageAverageMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasProcessCPUNormalizedUsageAverageMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasProcessCPUNormalizedUsageAverageMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasProcessCPUNormalizedUsageAverageMetricAttributeKeyCPUState:
default:
return fmt.Errorf("metric mongodbatlas.process.cpu.normalized.usage.average doesn't have an attribute %v, valid attributes: [cpu_state]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasProcessCPUNormalizedUsageMaxMetricAttributeKey specifies the key of an attribute for the mongodbatlas.process.cpu.normalized.usage.max metric.
type MongodbatlasProcessCPUNormalizedUsageMaxMetricAttributeKey string
const (
MongodbatlasProcessCPUNormalizedUsageMaxMetricAttributeKeyCPUState MongodbatlasProcessCPUNormalizedUsageMaxMetricAttributeKey = "cpu_state"
)
// MongodbatlasProcessCPUNormalizedUsageMaxMetricConfig provides config for the mongodbatlas.process.cpu.normalized.usage.max metric.
type MongodbatlasProcessCPUNormalizedUsageMaxMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasProcessCPUNormalizedUsageMaxMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasProcessCPUNormalizedUsageMaxMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasProcessCPUNormalizedUsageMaxMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasProcessCPUNormalizedUsageMaxMetricAttributeKeyCPUState:
default:
return fmt.Errorf("metric mongodbatlas.process.cpu.normalized.usage.max doesn't have an attribute %v, valid attributes: [cpu_state]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasProcessCPUUsageAverageMetricAttributeKey specifies the key of an attribute for the mongodbatlas.process.cpu.usage.average metric.
type MongodbatlasProcessCPUUsageAverageMetricAttributeKey string
const (
MongodbatlasProcessCPUUsageAverageMetricAttributeKeyCPUState MongodbatlasProcessCPUUsageAverageMetricAttributeKey = "cpu_state"
)
// MongodbatlasProcessCPUUsageAverageMetricConfig provides config for the mongodbatlas.process.cpu.usage.average metric.
type MongodbatlasProcessCPUUsageAverageMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasProcessCPUUsageAverageMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasProcessCPUUsageAverageMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasProcessCPUUsageAverageMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasProcessCPUUsageAverageMetricAttributeKeyCPUState:
default:
return fmt.Errorf("metric mongodbatlas.process.cpu.usage.average doesn't have an attribute %v, valid attributes: [cpu_state]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasProcessCPUUsageMaxMetricAttributeKey specifies the key of an attribute for the mongodbatlas.process.cpu.usage.max metric.
type MongodbatlasProcessCPUUsageMaxMetricAttributeKey string
const (
MongodbatlasProcessCPUUsageMaxMetricAttributeKeyCPUState MongodbatlasProcessCPUUsageMaxMetricAttributeKey = "cpu_state"
)
// MongodbatlasProcessCPUUsageMaxMetricConfig provides config for the mongodbatlas.process.cpu.usage.max metric.
type MongodbatlasProcessCPUUsageMaxMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasProcessCPUUsageMaxMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasProcessCPUUsageMaxMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasProcessCPUUsageMaxMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasProcessCPUUsageMaxMetricAttributeKeyCPUState:
default:
return fmt.Errorf("metric mongodbatlas.process.cpu.usage.max doesn't have an attribute %v, valid attributes: [cpu_state]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasProcessCursorsMetricAttributeKey specifies the key of an attribute for the mongodbatlas.process.cursors metric.
type MongodbatlasProcessCursorsMetricAttributeKey string
const (
MongodbatlasProcessCursorsMetricAttributeKeyCursorState MongodbatlasProcessCursorsMetricAttributeKey = "cursor_state"
)
// MongodbatlasProcessCursorsMetricConfig provides config for the mongodbatlas.process.cursors metric.
type MongodbatlasProcessCursorsMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasProcessCursorsMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasProcessCursorsMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasProcessCursorsMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasProcessCursorsMetricAttributeKeyCursorState:
default:
return fmt.Errorf("metric mongodbatlas.process.cursors doesn't have an attribute %v, valid attributes: [cursor_state]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasProcessDbDocumentRateMetricAttributeKey specifies the key of an attribute for the mongodbatlas.process.db.document.rate metric.
type MongodbatlasProcessDbDocumentRateMetricAttributeKey string
const (
MongodbatlasProcessDbDocumentRateMetricAttributeKeyDocumentStatus MongodbatlasProcessDbDocumentRateMetricAttributeKey = "document_status"
)
// MongodbatlasProcessDbDocumentRateMetricConfig provides config for the mongodbatlas.process.db.document.rate metric.
type MongodbatlasProcessDbDocumentRateMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasProcessDbDocumentRateMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasProcessDbDocumentRateMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasProcessDbDocumentRateMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasProcessDbDocumentRateMetricAttributeKeyDocumentStatus:
default:
return fmt.Errorf("metric mongodbatlas.process.db.document.rate doesn't have an attribute %v, valid attributes: [document_status]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasProcessDbOperationsRateMetricAttributeKey specifies the key of an attribute for the mongodbatlas.process.db.operations.rate metric.
type MongodbatlasProcessDbOperationsRateMetricAttributeKey string
const (
MongodbatlasProcessDbOperationsRateMetricAttributeKeyOperation MongodbatlasProcessDbOperationsRateMetricAttributeKey = "operation"
MongodbatlasProcessDbOperationsRateMetricAttributeKeyClusterRole MongodbatlasProcessDbOperationsRateMetricAttributeKey = "cluster_role"
)
// MongodbatlasProcessDbOperationsRateMetricConfig provides config for the mongodbatlas.process.db.operations.rate metric.
type MongodbatlasProcessDbOperationsRateMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasProcessDbOperationsRateMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasProcessDbOperationsRateMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasProcessDbOperationsRateMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasProcessDbOperationsRateMetricAttributeKeyOperation, MongodbatlasProcessDbOperationsRateMetricAttributeKeyClusterRole:
default:
return fmt.Errorf("metric mongodbatlas.process.db.operations.rate doesn't have an attribute %v, valid attributes: [operation, cluster_role]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasProcessDbOperationsTimeMetricAttributeKey specifies the key of an attribute for the mongodbatlas.process.db.operations.time metric.
type MongodbatlasProcessDbOperationsTimeMetricAttributeKey string
const (
MongodbatlasProcessDbOperationsTimeMetricAttributeKeyExecutionType MongodbatlasProcessDbOperationsTimeMetricAttributeKey = "execution_type"
)
// MongodbatlasProcessDbOperationsTimeMetricConfig provides config for the mongodbatlas.process.db.operations.time metric.
type MongodbatlasProcessDbOperationsTimeMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasProcessDbOperationsTimeMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasProcessDbOperationsTimeMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasProcessDbOperationsTimeMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasProcessDbOperationsTimeMetricAttributeKeyExecutionType:
default:
return fmt.Errorf("metric mongodbatlas.process.db.operations.time doesn't have an attribute %v, valid attributes: [execution_type]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasProcessDbQueryExecutorScannedMetricAttributeKey specifies the key of an attribute for the mongodbatlas.process.db.query_executor.scanned metric.
type MongodbatlasProcessDbQueryExecutorScannedMetricAttributeKey string
const (
MongodbatlasProcessDbQueryExecutorScannedMetricAttributeKeyScannedType MongodbatlasProcessDbQueryExecutorScannedMetricAttributeKey = "scanned_type"
)
// MongodbatlasProcessDbQueryExecutorScannedMetricConfig provides config for the mongodbatlas.process.db.query_executor.scanned metric.
type MongodbatlasProcessDbQueryExecutorScannedMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasProcessDbQueryExecutorScannedMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasProcessDbQueryExecutorScannedMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasProcessDbQueryExecutorScannedMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasProcessDbQueryExecutorScannedMetricAttributeKeyScannedType:
default:
return fmt.Errorf("metric mongodbatlas.process.db.query_executor.scanned doesn't have an attribute %v, valid attributes: [scanned_type]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasProcessDbQueryTargetingScannedPerReturnedMetricAttributeKey specifies the key of an attribute for the mongodbatlas.process.db.query_targeting.scanned_per_returned metric.
type MongodbatlasProcessDbQueryTargetingScannedPerReturnedMetricAttributeKey string
const (
MongodbatlasProcessDbQueryTargetingScannedPerReturnedMetricAttributeKeyScannedType MongodbatlasProcessDbQueryTargetingScannedPerReturnedMetricAttributeKey = "scanned_type"
)
// MongodbatlasProcessDbQueryTargetingScannedPerReturnedMetricConfig provides config for the mongodbatlas.process.db.query_targeting.scanned_per_returned metric.
type MongodbatlasProcessDbQueryTargetingScannedPerReturnedMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasProcessDbQueryTargetingScannedPerReturnedMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasProcessDbQueryTargetingScannedPerReturnedMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasProcessDbQueryTargetingScannedPerReturnedMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasProcessDbQueryTargetingScannedPerReturnedMetricAttributeKeyScannedType:
default:
return fmt.Errorf("metric mongodbatlas.process.db.query_targeting.scanned_per_returned doesn't have an attribute %v, valid attributes: [scanned_type]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasProcessDbStorageMetricAttributeKey specifies the key of an attribute for the mongodbatlas.process.db.storage metric.
type MongodbatlasProcessDbStorageMetricAttributeKey string
const (
MongodbatlasProcessDbStorageMetricAttributeKeyStorageStatus MongodbatlasProcessDbStorageMetricAttributeKey = "storage_status"
)
// MongodbatlasProcessDbStorageMetricConfig provides config for the mongodbatlas.process.db.storage metric.
type MongodbatlasProcessDbStorageMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasProcessDbStorageMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasProcessDbStorageMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasProcessDbStorageMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasProcessDbStorageMetricAttributeKeyStorageStatus:
default:
return fmt.Errorf("metric mongodbatlas.process.db.storage doesn't have an attribute %v, valid attributes: [storage_status]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasProcessGlobalLockMetricAttributeKey specifies the key of an attribute for the mongodbatlas.process.global_lock metric.
type MongodbatlasProcessGlobalLockMetricAttributeKey string
const (
MongodbatlasProcessGlobalLockMetricAttributeKeyGlobalLockState MongodbatlasProcessGlobalLockMetricAttributeKey = "global_lock_state"
)
// MongodbatlasProcessGlobalLockMetricConfig provides config for the mongodbatlas.process.global_lock metric.
type MongodbatlasProcessGlobalLockMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasProcessGlobalLockMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasProcessGlobalLockMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasProcessGlobalLockMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasProcessGlobalLockMetricAttributeKeyGlobalLockState:
default:
return fmt.Errorf("metric mongodbatlas.process.global_lock doesn't have an attribute %v, valid attributes: [global_lock_state]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasProcessIndexBtreeMissRatioMetricConfig provides config for the mongodbatlas.process.index.btree_miss_ratio metric.
type MongodbatlasProcessIndexBtreeMissRatioMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
}
func (ms *MongodbatlasProcessIndexBtreeMissRatioMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
// MongodbatlasProcessIndexCountersMetricAttributeKey specifies the key of an attribute for the mongodbatlas.process.index.counters metric.
type MongodbatlasProcessIndexCountersMetricAttributeKey string
const (
MongodbatlasProcessIndexCountersMetricAttributeKeyBtreeCounterType MongodbatlasProcessIndexCountersMetricAttributeKey = "btree_counter_type"
)
// MongodbatlasProcessIndexCountersMetricConfig provides config for the mongodbatlas.process.index.counters metric.
type MongodbatlasProcessIndexCountersMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasProcessIndexCountersMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasProcessIndexCountersMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasProcessIndexCountersMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasProcessIndexCountersMetricAttributeKeyBtreeCounterType:
default:
return fmt.Errorf("metric mongodbatlas.process.index.counters doesn't have an attribute %v, valid attributes: [btree_counter_type]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasProcessJournalingCommitsMetricConfig provides config for the mongodbatlas.process.journaling.commits metric.
type MongodbatlasProcessJournalingCommitsMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
}
func (ms *MongodbatlasProcessJournalingCommitsMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
// MongodbatlasProcessJournalingDataFilesMetricConfig provides config for the mongodbatlas.process.journaling.data_files metric.
type MongodbatlasProcessJournalingDataFilesMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
}
func (ms *MongodbatlasProcessJournalingDataFilesMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
// MongodbatlasProcessJournalingWrittenMetricConfig provides config for the mongodbatlas.process.journaling.written metric.
type MongodbatlasProcessJournalingWrittenMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
}
func (ms *MongodbatlasProcessJournalingWrittenMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
// MongodbatlasProcessMemoryUsageMetricAttributeKey specifies the key of an attribute for the mongodbatlas.process.memory.usage metric.
type MongodbatlasProcessMemoryUsageMetricAttributeKey string
const (
MongodbatlasProcessMemoryUsageMetricAttributeKeyMemoryState MongodbatlasProcessMemoryUsageMetricAttributeKey = "memory_state"
)
// MongodbatlasProcessMemoryUsageMetricConfig provides config for the mongodbatlas.process.memory.usage metric.
type MongodbatlasProcessMemoryUsageMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasProcessMemoryUsageMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasProcessMemoryUsageMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasProcessMemoryUsageMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasProcessMemoryUsageMetricAttributeKeyMemoryState:
default:
return fmt.Errorf("metric mongodbatlas.process.memory.usage doesn't have an attribute %v, valid attributes: [memory_state]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasProcessNetworkIoMetricAttributeKey specifies the key of an attribute for the mongodbatlas.process.network.io metric.
type MongodbatlasProcessNetworkIoMetricAttributeKey string
const (
MongodbatlasProcessNetworkIoMetricAttributeKeyDirection MongodbatlasProcessNetworkIoMetricAttributeKey = "direction"
)
// MongodbatlasProcessNetworkIoMetricConfig provides config for the mongodbatlas.process.network.io metric.
type MongodbatlasProcessNetworkIoMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasProcessNetworkIoMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasProcessNetworkIoMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasProcessNetworkIoMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasProcessNetworkIoMetricAttributeKeyDirection:
default:
return fmt.Errorf("metric mongodbatlas.process.network.io doesn't have an attribute %v, valid attributes: [direction]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasProcessNetworkRequestsMetricConfig provides config for the mongodbatlas.process.network.requests metric.
type MongodbatlasProcessNetworkRequestsMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
}
func (ms *MongodbatlasProcessNetworkRequestsMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
// MongodbatlasProcessOplogRateMetricConfig provides config for the mongodbatlas.process.oplog.rate metric.
type MongodbatlasProcessOplogRateMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
}
func (ms *MongodbatlasProcessOplogRateMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
// MongodbatlasProcessOplogTimeMetricAttributeKey specifies the key of an attribute for the mongodbatlas.process.oplog.time metric.
type MongodbatlasProcessOplogTimeMetricAttributeKey string
const (
MongodbatlasProcessOplogTimeMetricAttributeKeyOplogType MongodbatlasProcessOplogTimeMetricAttributeKey = "oplog_type"
)
// MongodbatlasProcessOplogTimeMetricConfig provides config for the mongodbatlas.process.oplog.time metric.
type MongodbatlasProcessOplogTimeMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasProcessOplogTimeMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasProcessOplogTimeMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasProcessOplogTimeMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasProcessOplogTimeMetricAttributeKeyOplogType:
default:
return fmt.Errorf("metric mongodbatlas.process.oplog.time doesn't have an attribute %v, valid attributes: [oplog_type]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasProcessPageFaultsMetricAttributeKey specifies the key of an attribute for the mongodbatlas.process.page_faults metric.
type MongodbatlasProcessPageFaultsMetricAttributeKey string
const (
MongodbatlasProcessPageFaultsMetricAttributeKeyMemoryIssueType MongodbatlasProcessPageFaultsMetricAttributeKey = "memory_issue_type"
)
// MongodbatlasProcessPageFaultsMetricConfig provides config for the mongodbatlas.process.page_faults metric.
type MongodbatlasProcessPageFaultsMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasProcessPageFaultsMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasProcessPageFaultsMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasProcessPageFaultsMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasProcessPageFaultsMetricAttributeKeyMemoryIssueType:
default:
return fmt.Errorf("metric mongodbatlas.process.page_faults doesn't have an attribute %v, valid attributes: [memory_issue_type]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasProcessRestartsMetricConfig provides config for the mongodbatlas.process.restarts metric.
type MongodbatlasProcessRestartsMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
}
func (ms *MongodbatlasProcessRestartsMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
// MongodbatlasProcessTicketsMetricAttributeKey specifies the key of an attribute for the mongodbatlas.process.tickets metric.
type MongodbatlasProcessTicketsMetricAttributeKey string
const (
MongodbatlasProcessTicketsMetricAttributeKeyTicketType MongodbatlasProcessTicketsMetricAttributeKey = "ticket_type"
)
// MongodbatlasProcessTicketsMetricConfig provides config for the mongodbatlas.process.tickets metric.
type MongodbatlasProcessTicketsMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasProcessTicketsMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasProcessTicketsMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasProcessTicketsMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasProcessTicketsMetricAttributeKeyTicketType:
default:
return fmt.Errorf("metric mongodbatlas.process.tickets doesn't have an attribute %v, valid attributes: [ticket_type]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasSystemCPUNormalizedUsageAverageMetricAttributeKey specifies the key of an attribute for the mongodbatlas.system.cpu.normalized.usage.average metric.
type MongodbatlasSystemCPUNormalizedUsageAverageMetricAttributeKey string
const (
MongodbatlasSystemCPUNormalizedUsageAverageMetricAttributeKeyCPUState MongodbatlasSystemCPUNormalizedUsageAverageMetricAttributeKey = "cpu_state"
)
// MongodbatlasSystemCPUNormalizedUsageAverageMetricConfig provides config for the mongodbatlas.system.cpu.normalized.usage.average metric.
type MongodbatlasSystemCPUNormalizedUsageAverageMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasSystemCPUNormalizedUsageAverageMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasSystemCPUNormalizedUsageAverageMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasSystemCPUNormalizedUsageAverageMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasSystemCPUNormalizedUsageAverageMetricAttributeKeyCPUState:
default:
return fmt.Errorf("metric mongodbatlas.system.cpu.normalized.usage.average doesn't have an attribute %v, valid attributes: [cpu_state]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasSystemCPUNormalizedUsageMaxMetricAttributeKey specifies the key of an attribute for the mongodbatlas.system.cpu.normalized.usage.max metric.
type MongodbatlasSystemCPUNormalizedUsageMaxMetricAttributeKey string
const (
MongodbatlasSystemCPUNormalizedUsageMaxMetricAttributeKeyCPUState MongodbatlasSystemCPUNormalizedUsageMaxMetricAttributeKey = "cpu_state"
)
// MongodbatlasSystemCPUNormalizedUsageMaxMetricConfig provides config for the mongodbatlas.system.cpu.normalized.usage.max metric.
type MongodbatlasSystemCPUNormalizedUsageMaxMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasSystemCPUNormalizedUsageMaxMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasSystemCPUNormalizedUsageMaxMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasSystemCPUNormalizedUsageMaxMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasSystemCPUNormalizedUsageMaxMetricAttributeKeyCPUState:
default:
return fmt.Errorf("metric mongodbatlas.system.cpu.normalized.usage.max doesn't have an attribute %v, valid attributes: [cpu_state]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasSystemCPUUsageAverageMetricAttributeKey specifies the key of an attribute for the mongodbatlas.system.cpu.usage.average metric.
type MongodbatlasSystemCPUUsageAverageMetricAttributeKey string
const (
MongodbatlasSystemCPUUsageAverageMetricAttributeKeyCPUState MongodbatlasSystemCPUUsageAverageMetricAttributeKey = "cpu_state"
)
// MongodbatlasSystemCPUUsageAverageMetricConfig provides config for the mongodbatlas.system.cpu.usage.average metric.
type MongodbatlasSystemCPUUsageAverageMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasSystemCPUUsageAverageMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasSystemCPUUsageAverageMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasSystemCPUUsageAverageMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasSystemCPUUsageAverageMetricAttributeKeyCPUState:
default:
return fmt.Errorf("metric mongodbatlas.system.cpu.usage.average doesn't have an attribute %v, valid attributes: [cpu_state]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasSystemCPUUsageMaxMetricAttributeKey specifies the key of an attribute for the mongodbatlas.system.cpu.usage.max metric.
type MongodbatlasSystemCPUUsageMaxMetricAttributeKey string
const (
MongodbatlasSystemCPUUsageMaxMetricAttributeKeyCPUState MongodbatlasSystemCPUUsageMaxMetricAttributeKey = "cpu_state"
)
// MongodbatlasSystemCPUUsageMaxMetricConfig provides config for the mongodbatlas.system.cpu.usage.max metric.
type MongodbatlasSystemCPUUsageMaxMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasSystemCPUUsageMaxMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasSystemCPUUsageMaxMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasSystemCPUUsageMaxMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasSystemCPUUsageMaxMetricAttributeKeyCPUState:
default:
return fmt.Errorf("metric mongodbatlas.system.cpu.usage.max doesn't have an attribute %v, valid attributes: [cpu_state]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasSystemFtsCPUNormalizedUsageMetricAttributeKey specifies the key of an attribute for the mongodbatlas.system.fts.cpu.normalized.usage metric.
type MongodbatlasSystemFtsCPUNormalizedUsageMetricAttributeKey string
const (
MongodbatlasSystemFtsCPUNormalizedUsageMetricAttributeKeyCPUState MongodbatlasSystemFtsCPUNormalizedUsageMetricAttributeKey = "cpu_state"
)
// MongodbatlasSystemFtsCPUNormalizedUsageMetricConfig provides config for the mongodbatlas.system.fts.cpu.normalized.usage metric.
type MongodbatlasSystemFtsCPUNormalizedUsageMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasSystemFtsCPUNormalizedUsageMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasSystemFtsCPUNormalizedUsageMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasSystemFtsCPUNormalizedUsageMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasSystemFtsCPUNormalizedUsageMetricAttributeKeyCPUState:
default:
return fmt.Errorf("metric mongodbatlas.system.fts.cpu.normalized.usage doesn't have an attribute %v, valid attributes: [cpu_state]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasSystemFtsCPUUsageMetricAttributeKey specifies the key of an attribute for the mongodbatlas.system.fts.cpu.usage metric.
type MongodbatlasSystemFtsCPUUsageMetricAttributeKey string
const (
MongodbatlasSystemFtsCPUUsageMetricAttributeKeyCPUState MongodbatlasSystemFtsCPUUsageMetricAttributeKey = "cpu_state"
)
// MongodbatlasSystemFtsCPUUsageMetricConfig provides config for the mongodbatlas.system.fts.cpu.usage metric.
type MongodbatlasSystemFtsCPUUsageMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasSystemFtsCPUUsageMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasSystemFtsCPUUsageMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasSystemFtsCPUUsageMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasSystemFtsCPUUsageMetricAttributeKeyCPUState:
default:
return fmt.Errorf("metric mongodbatlas.system.fts.cpu.usage doesn't have an attribute %v, valid attributes: [cpu_state]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasSystemFtsDiskUsedMetricConfig provides config for the mongodbatlas.system.fts.disk.used metric.
type MongodbatlasSystemFtsDiskUsedMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
}
func (ms *MongodbatlasSystemFtsDiskUsedMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
// MongodbatlasSystemFtsMemoryUsageMetricAttributeKey specifies the key of an attribute for the mongodbatlas.system.fts.memory.usage metric.
type MongodbatlasSystemFtsMemoryUsageMetricAttributeKey string
const (
MongodbatlasSystemFtsMemoryUsageMetricAttributeKeyMemoryState MongodbatlasSystemFtsMemoryUsageMetricAttributeKey = "memory_state"
)
// MongodbatlasSystemFtsMemoryUsageMetricConfig provides config for the mongodbatlas.system.fts.memory.usage metric.
type MongodbatlasSystemFtsMemoryUsageMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasSystemFtsMemoryUsageMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasSystemFtsMemoryUsageMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasSystemFtsMemoryUsageMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasSystemFtsMemoryUsageMetricAttributeKeyMemoryState:
default:
return fmt.Errorf("metric mongodbatlas.system.fts.memory.usage doesn't have an attribute %v, valid attributes: [memory_state]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasSystemMemoryUsageAverageMetricAttributeKey specifies the key of an attribute for the mongodbatlas.system.memory.usage.average metric.
type MongodbatlasSystemMemoryUsageAverageMetricAttributeKey string
const (
MongodbatlasSystemMemoryUsageAverageMetricAttributeKeyMemoryStatus MongodbatlasSystemMemoryUsageAverageMetricAttributeKey = "memory_status"
)
// MongodbatlasSystemMemoryUsageAverageMetricConfig provides config for the mongodbatlas.system.memory.usage.average metric.
type MongodbatlasSystemMemoryUsageAverageMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasSystemMemoryUsageAverageMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasSystemMemoryUsageAverageMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasSystemMemoryUsageAverageMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasSystemMemoryUsageAverageMetricAttributeKeyMemoryStatus:
default:
return fmt.Errorf("metric mongodbatlas.system.memory.usage.average doesn't have an attribute %v, valid attributes: [memory_status]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasSystemMemoryUsageMaxMetricAttributeKey specifies the key of an attribute for the mongodbatlas.system.memory.usage.max metric.
type MongodbatlasSystemMemoryUsageMaxMetricAttributeKey string
const (
MongodbatlasSystemMemoryUsageMaxMetricAttributeKeyMemoryStatus MongodbatlasSystemMemoryUsageMaxMetricAttributeKey = "memory_status"
)
// MongodbatlasSystemMemoryUsageMaxMetricConfig provides config for the mongodbatlas.system.memory.usage.max metric.
type MongodbatlasSystemMemoryUsageMaxMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasSystemMemoryUsageMaxMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasSystemMemoryUsageMaxMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasSystemMemoryUsageMaxMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasSystemMemoryUsageMaxMetricAttributeKeyMemoryStatus:
default:
return fmt.Errorf("metric mongodbatlas.system.memory.usage.max doesn't have an attribute %v, valid attributes: [memory_status]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasSystemNetworkIoAverageMetricAttributeKey specifies the key of an attribute for the mongodbatlas.system.network.io.average metric.
type MongodbatlasSystemNetworkIoAverageMetricAttributeKey string
const (
MongodbatlasSystemNetworkIoAverageMetricAttributeKeyDirection MongodbatlasSystemNetworkIoAverageMetricAttributeKey = "direction"
)
// MongodbatlasSystemNetworkIoAverageMetricConfig provides config for the mongodbatlas.system.network.io.average metric.
type MongodbatlasSystemNetworkIoAverageMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasSystemNetworkIoAverageMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasSystemNetworkIoAverageMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasSystemNetworkIoAverageMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasSystemNetworkIoAverageMetricAttributeKeyDirection:
default:
return fmt.Errorf("metric mongodbatlas.system.network.io.average doesn't have an attribute %v, valid attributes: [direction]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasSystemNetworkIoMaxMetricAttributeKey specifies the key of an attribute for the mongodbatlas.system.network.io.max metric.
type MongodbatlasSystemNetworkIoMaxMetricAttributeKey string
const (
MongodbatlasSystemNetworkIoMaxMetricAttributeKeyDirection MongodbatlasSystemNetworkIoMaxMetricAttributeKey = "direction"
)
// MongodbatlasSystemNetworkIoMaxMetricConfig provides config for the mongodbatlas.system.network.io.max metric.
type MongodbatlasSystemNetworkIoMaxMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasSystemNetworkIoMaxMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasSystemNetworkIoMaxMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasSystemNetworkIoMaxMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasSystemNetworkIoMaxMetricAttributeKeyDirection:
default:
return fmt.Errorf("metric mongodbatlas.system.network.io.max doesn't have an attribute %v, valid attributes: [direction]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasSystemPagingIoAverageMetricAttributeKey specifies the key of an attribute for the mongodbatlas.system.paging.io.average metric.
type MongodbatlasSystemPagingIoAverageMetricAttributeKey string
const (
MongodbatlasSystemPagingIoAverageMetricAttributeKeyDirection MongodbatlasSystemPagingIoAverageMetricAttributeKey = "direction"
)
// MongodbatlasSystemPagingIoAverageMetricConfig provides config for the mongodbatlas.system.paging.io.average metric.
type MongodbatlasSystemPagingIoAverageMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasSystemPagingIoAverageMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasSystemPagingIoAverageMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasSystemPagingIoAverageMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasSystemPagingIoAverageMetricAttributeKeyDirection:
default:
return fmt.Errorf("metric mongodbatlas.system.paging.io.average doesn't have an attribute %v, valid attributes: [direction]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasSystemPagingIoMaxMetricAttributeKey specifies the key of an attribute for the mongodbatlas.system.paging.io.max metric.
type MongodbatlasSystemPagingIoMaxMetricAttributeKey string
const (
MongodbatlasSystemPagingIoMaxMetricAttributeKeyDirection MongodbatlasSystemPagingIoMaxMetricAttributeKey = "direction"
)
// MongodbatlasSystemPagingIoMaxMetricConfig provides config for the mongodbatlas.system.paging.io.max metric.
type MongodbatlasSystemPagingIoMaxMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasSystemPagingIoMaxMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasSystemPagingIoMaxMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasSystemPagingIoMaxMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasSystemPagingIoMaxMetricAttributeKeyDirection:
default:
return fmt.Errorf("metric mongodbatlas.system.paging.io.max doesn't have an attribute %v, valid attributes: [direction]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasSystemPagingUsageAverageMetricAttributeKey specifies the key of an attribute for the mongodbatlas.system.paging.usage.average metric.
type MongodbatlasSystemPagingUsageAverageMetricAttributeKey string
const (
MongodbatlasSystemPagingUsageAverageMetricAttributeKeyMemoryState MongodbatlasSystemPagingUsageAverageMetricAttributeKey = "memory_state"
)
// MongodbatlasSystemPagingUsageAverageMetricConfig provides config for the mongodbatlas.system.paging.usage.average metric.
type MongodbatlasSystemPagingUsageAverageMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasSystemPagingUsageAverageMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasSystemPagingUsageAverageMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasSystemPagingUsageAverageMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasSystemPagingUsageAverageMetricAttributeKeyMemoryState:
default:
return fmt.Errorf("metric mongodbatlas.system.paging.usage.average doesn't have an attribute %v, valid attributes: [memory_state]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MongodbatlasSystemPagingUsageMaxMetricAttributeKey specifies the key of an attribute for the mongodbatlas.system.paging.usage.max metric.
type MongodbatlasSystemPagingUsageMaxMetricAttributeKey string
const (
MongodbatlasSystemPagingUsageMaxMetricAttributeKeyMemoryState MongodbatlasSystemPagingUsageMaxMetricAttributeKey = "memory_state"
)
// MongodbatlasSystemPagingUsageMaxMetricConfig provides config for the mongodbatlas.system.paging.usage.max metric.
type MongodbatlasSystemPagingUsageMaxMetricConfig struct {
Enabled bool `mapstructure:"enabled"`
enabledSetByUser bool
AggregationStrategy string `mapstructure:"aggregation_strategy"`
EnabledAttributes []MongodbatlasSystemPagingUsageMaxMetricAttributeKey `mapstructure:"attributes"`
}
func (ms *MongodbatlasSystemPagingUsageMaxMetricConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(ms)
if err != nil {
return err
}
ms.enabledSetByUser = parser.IsSet("enabled")
return nil
}
func (ms *MongodbatlasSystemPagingUsageMaxMetricConfig) Validate() error {
for _, val := range ms.EnabledAttributes {
switch val {
case MongodbatlasSystemPagingUsageMaxMetricAttributeKeyMemoryState:
default:
return fmt.Errorf("metric mongodbatlas.system.paging.usage.max doesn't have an attribute %v, valid attributes: [memory_state]", val)
}
}
switch ms.AggregationStrategy {
case AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax:
default:
return fmt.Errorf("invalid aggregation strategy %q, valid strategies: [%s, %s, %s, %s]", ms.AggregationStrategy, AggregationStrategySum, AggregationStrategyAvg, AggregationStrategyMin, AggregationStrategyMax)
}
return nil
}
// MetricsConfig provides config for mongodb_atlas metrics.
type MetricsConfig struct {
MongodbatlasDbCounts MongodbatlasDbCountsMetricConfig `mapstructure:"mongodbatlas.db.counts"`
MongodbatlasDbSize MongodbatlasDbSizeMetricConfig `mapstructure:"mongodbatlas.db.size"`
MongodbatlasDiskPartitionIopsAverage MongodbatlasDiskPartitionIopsAverageMetricConfig `mapstructure:"mongodbatlas.disk.partition.iops.average"`
MongodbatlasDiskPartitionIopsMax MongodbatlasDiskPartitionIopsMaxMetricConfig `mapstructure:"mongodbatlas.disk.partition.iops.max"`
MongodbatlasDiskPartitionLatencyAverage MongodbatlasDiskPartitionLatencyAverageMetricConfig `mapstructure:"mongodbatlas.disk.partition.latency.average"`
MongodbatlasDiskPartitionLatencyMax MongodbatlasDiskPartitionLatencyMaxMetricConfig `mapstructure:"mongodbatlas.disk.partition.latency.max"`
MongodbatlasDiskPartitionQueueDepth MongodbatlasDiskPartitionQueueDepthMetricConfig `mapstructure:"mongodbatlas.disk.partition.queue.depth"`
MongodbatlasDiskPartitionSpaceAverage MongodbatlasDiskPartitionSpaceAverageMetricConfig `mapstructure:"mongodbatlas.disk.partition.space.average"`
MongodbatlasDiskPartitionSpaceMax MongodbatlasDiskPartitionSpaceMaxMetricConfig `mapstructure:"mongodbatlas.disk.partition.space.max"`
MongodbatlasDiskPartitionThroughput MongodbatlasDiskPartitionThroughputMetricConfig `mapstructure:"mongodbatlas.disk.partition.throughput"`
MongodbatlasDiskPartitionUsageAverage MongodbatlasDiskPartitionUsageAverageMetricConfig `mapstructure:"mongodbatlas.disk.partition.usage.average"`
MongodbatlasDiskPartitionUsageMax MongodbatlasDiskPartitionUsageMaxMetricConfig `mapstructure:"mongodbatlas.disk.partition.usage.max"`
MongodbatlasDiskPartitionUtilizationAverage MongodbatlasDiskPartitionUtilizationAverageMetricConfig `mapstructure:"mongodbatlas.disk.partition.utilization.average"`
MongodbatlasDiskPartitionUtilizationMax MongodbatlasDiskPartitionUtilizationMaxMetricConfig `mapstructure:"mongodbatlas.disk.partition.utilization.max"`
MongodbatlasProcessAsserts MongodbatlasProcessAssertsMetricConfig `mapstructure:"mongodbatlas.process.asserts"`
MongodbatlasProcessBackgroundFlush MongodbatlasProcessBackgroundFlushMetricConfig `mapstructure:"mongodbatlas.process.background_flush"`
MongodbatlasProcessCacheIo MongodbatlasProcessCacheIoMetricConfig `mapstructure:"mongodbatlas.process.cache.io"`
MongodbatlasProcessCacheRatio MongodbatlasProcessCacheRatioMetricConfig `mapstructure:"mongodbatlas.process.cache.ratio"`
MongodbatlasProcessCacheSize MongodbatlasProcessCacheSizeMetricConfig `mapstructure:"mongodbatlas.process.cache.size"`
MongodbatlasProcessConnections MongodbatlasProcessConnectionsMetricConfig `mapstructure:"mongodbatlas.process.connections"`
MongodbatlasProcessCPUChildrenNormalizedUsageAverage MongodbatlasProcessCPUChildrenNormalizedUsageAverageMetricConfig `mapstructure:"mongodbatlas.process.cpu.children.normalized.usage.average"`
MongodbatlasProcessCPUChildrenNormalizedUsageMax MongodbatlasProcessCPUChildrenNormalizedUsageMaxMetricConfig `mapstructure:"mongodbatlas.process.cpu.children.normalized.usage.max"`
MongodbatlasProcessCPUChildrenUsageAverage MongodbatlasProcessCPUChildrenUsageAverageMetricConfig `mapstructure:"mongodbatlas.process.cpu.children.usage.average"`
MongodbatlasProcessCPUChildrenUsageMax MongodbatlasProcessCPUChildrenUsageMaxMetricConfig `mapstructure:"mongodbatlas.process.cpu.children.usage.max"`
MongodbatlasProcessCPUNormalizedUsageAverage MongodbatlasProcessCPUNormalizedUsageAverageMetricConfig `mapstructure:"mongodbatlas.process.cpu.normalized.usage.average"`
MongodbatlasProcessCPUNormalizedUsageMax MongodbatlasProcessCPUNormalizedUsageMaxMetricConfig `mapstructure:"mongodbatlas.process.cpu.normalized.usage.max"`
MongodbatlasProcessCPUUsageAverage MongodbatlasProcessCPUUsageAverageMetricConfig `mapstructure:"mongodbatlas.process.cpu.usage.average"`
MongodbatlasProcessCPUUsageMax MongodbatlasProcessCPUUsageMaxMetricConfig `mapstructure:"mongodbatlas.process.cpu.usage.max"`
MongodbatlasProcessCursors MongodbatlasProcessCursorsMetricConfig `mapstructure:"mongodbatlas.process.cursors"`
MongodbatlasProcessDbDocumentRate MongodbatlasProcessDbDocumentRateMetricConfig `mapstructure:"mongodbatlas.process.db.document.rate"`
MongodbatlasProcessDbOperationsRate MongodbatlasProcessDbOperationsRateMetricConfig `mapstructure:"mongodbatlas.process.db.operations.rate"`
MongodbatlasProcessDbOperationsTime MongodbatlasProcessDbOperationsTimeMetricConfig `mapstructure:"mongodbatlas.process.db.operations.time"`
MongodbatlasProcessDbQueryExecutorScanned MongodbatlasProcessDbQueryExecutorScannedMetricConfig `mapstructure:"mongodbatlas.process.db.query_executor.scanned"`
MongodbatlasProcessDbQueryTargetingScannedPerReturned MongodbatlasProcessDbQueryTargetingScannedPerReturnedMetricConfig `mapstructure:"mongodbatlas.process.db.query_targeting.scanned_per_returned"`
MongodbatlasProcessDbStorage MongodbatlasProcessDbStorageMetricConfig `mapstructure:"mongodbatlas.process.db.storage"`
MongodbatlasProcessGlobalLock MongodbatlasProcessGlobalLockMetricConfig `mapstructure:"mongodbatlas.process.global_lock"`
MongodbatlasProcessIndexBtreeMissRatio MongodbatlasProcessIndexBtreeMissRatioMetricConfig `mapstructure:"mongodbatlas.process.index.btree_miss_ratio"`
MongodbatlasProcessIndexCounters MongodbatlasProcessIndexCountersMetricConfig `mapstructure:"mongodbatlas.process.index.counters"`
MongodbatlasProcessJournalingCommits MongodbatlasProcessJournalingCommitsMetricConfig `mapstructure:"mongodbatlas.process.journaling.commits"`
MongodbatlasProcessJournalingDataFiles MongodbatlasProcessJournalingDataFilesMetricConfig `mapstructure:"mongodbatlas.process.journaling.data_files"`
MongodbatlasProcessJournalingWritten MongodbatlasProcessJournalingWrittenMetricConfig `mapstructure:"mongodbatlas.process.journaling.written"`
MongodbatlasProcessMemoryUsage MongodbatlasProcessMemoryUsageMetricConfig `mapstructure:"mongodbatlas.process.memory.usage"`
MongodbatlasProcessNetworkIo MongodbatlasProcessNetworkIoMetricConfig `mapstructure:"mongodbatlas.process.network.io"`
MongodbatlasProcessNetworkRequests MongodbatlasProcessNetworkRequestsMetricConfig `mapstructure:"mongodbatlas.process.network.requests"`
MongodbatlasProcessOplogRate MongodbatlasProcessOplogRateMetricConfig `mapstructure:"mongodbatlas.process.oplog.rate"`
MongodbatlasProcessOplogTime MongodbatlasProcessOplogTimeMetricConfig `mapstructure:"mongodbatlas.process.oplog.time"`
MongodbatlasProcessPageFaults MongodbatlasProcessPageFaultsMetricConfig `mapstructure:"mongodbatlas.process.page_faults"`
MongodbatlasProcessRestarts MongodbatlasProcessRestartsMetricConfig `mapstructure:"mongodbatlas.process.restarts"`
MongodbatlasProcessTickets MongodbatlasProcessTicketsMetricConfig `mapstructure:"mongodbatlas.process.tickets"`
MongodbatlasSystemCPUNormalizedUsageAverage MongodbatlasSystemCPUNormalizedUsageAverageMetricConfig `mapstructure:"mongodbatlas.system.cpu.normalized.usage.average"`
MongodbatlasSystemCPUNormalizedUsageMax MongodbatlasSystemCPUNormalizedUsageMaxMetricConfig `mapstructure:"mongodbatlas.system.cpu.normalized.usage.max"`
MongodbatlasSystemCPUUsageAverage MongodbatlasSystemCPUUsageAverageMetricConfig `mapstructure:"mongodbatlas.system.cpu.usage.average"`
MongodbatlasSystemCPUUsageMax MongodbatlasSystemCPUUsageMaxMetricConfig `mapstructure:"mongodbatlas.system.cpu.usage.max"`
MongodbatlasSystemFtsCPUNormalizedUsage MongodbatlasSystemFtsCPUNormalizedUsageMetricConfig `mapstructure:"mongodbatlas.system.fts.cpu.normalized.usage"`
MongodbatlasSystemFtsCPUUsage MongodbatlasSystemFtsCPUUsageMetricConfig `mapstructure:"mongodbatlas.system.fts.cpu.usage"`
MongodbatlasSystemFtsDiskUsed MongodbatlasSystemFtsDiskUsedMetricConfig `mapstructure:"mongodbatlas.system.fts.disk.used"`
MongodbatlasSystemFtsMemoryUsage MongodbatlasSystemFtsMemoryUsageMetricConfig `mapstructure:"mongodbatlas.system.fts.memory.usage"`
MongodbatlasSystemMemoryUsageAverage MongodbatlasSystemMemoryUsageAverageMetricConfig `mapstructure:"mongodbatlas.system.memory.usage.average"`
MongodbatlasSystemMemoryUsageMax MongodbatlasSystemMemoryUsageMaxMetricConfig `mapstructure:"mongodbatlas.system.memory.usage.max"`
MongodbatlasSystemNetworkIoAverage MongodbatlasSystemNetworkIoAverageMetricConfig `mapstructure:"mongodbatlas.system.network.io.average"`
MongodbatlasSystemNetworkIoMax MongodbatlasSystemNetworkIoMaxMetricConfig `mapstructure:"mongodbatlas.system.network.io.max"`
MongodbatlasSystemPagingIoAverage MongodbatlasSystemPagingIoAverageMetricConfig `mapstructure:"mongodbatlas.system.paging.io.average"`
MongodbatlasSystemPagingIoMax MongodbatlasSystemPagingIoMaxMetricConfig `mapstructure:"mongodbatlas.system.paging.io.max"`
MongodbatlasSystemPagingUsageAverage MongodbatlasSystemPagingUsageAverageMetricConfig `mapstructure:"mongodbatlas.system.paging.usage.average"`
MongodbatlasSystemPagingUsageMax MongodbatlasSystemPagingUsageMaxMetricConfig `mapstructure:"mongodbatlas.system.paging.usage.max"`
}
func DefaultMetricsConfig() MetricsConfig {
return MetricsConfig{
MongodbatlasDbCounts: MongodbatlasDbCountsMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasDbCountsMetricAttributeKey{MongodbatlasDbCountsMetricAttributeKeyObjectType},
},
MongodbatlasDbSize: MongodbatlasDbSizeMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasDbSizeMetricAttributeKey{MongodbatlasDbSizeMetricAttributeKeyObjectType},
},
MongodbatlasDiskPartitionIopsAverage: MongodbatlasDiskPartitionIopsAverageMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasDiskPartitionIopsAverageMetricAttributeKey{MongodbatlasDiskPartitionIopsAverageMetricAttributeKeyDiskDirection},
},
MongodbatlasDiskPartitionIopsMax: MongodbatlasDiskPartitionIopsMaxMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasDiskPartitionIopsMaxMetricAttributeKey{MongodbatlasDiskPartitionIopsMaxMetricAttributeKeyDiskDirection},
},
MongodbatlasDiskPartitionLatencyAverage: MongodbatlasDiskPartitionLatencyAverageMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasDiskPartitionLatencyAverageMetricAttributeKey{MongodbatlasDiskPartitionLatencyAverageMetricAttributeKeyDiskDirection},
},
MongodbatlasDiskPartitionLatencyMax: MongodbatlasDiskPartitionLatencyMaxMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasDiskPartitionLatencyMaxMetricAttributeKey{MongodbatlasDiskPartitionLatencyMaxMetricAttributeKeyDiskDirection},
},
MongodbatlasDiskPartitionQueueDepth: MongodbatlasDiskPartitionQueueDepthMetricConfig{
Enabled: false,
},
MongodbatlasDiskPartitionSpaceAverage: MongodbatlasDiskPartitionSpaceAverageMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasDiskPartitionSpaceAverageMetricAttributeKey{MongodbatlasDiskPartitionSpaceAverageMetricAttributeKeyDiskStatus},
},
MongodbatlasDiskPartitionSpaceMax: MongodbatlasDiskPartitionSpaceMaxMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasDiskPartitionSpaceMaxMetricAttributeKey{MongodbatlasDiskPartitionSpaceMaxMetricAttributeKeyDiskStatus},
},
MongodbatlasDiskPartitionThroughput: MongodbatlasDiskPartitionThroughputMetricConfig{
Enabled: false,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasDiskPartitionThroughputMetricAttributeKey{MongodbatlasDiskPartitionThroughputMetricAttributeKeyDiskDirection},
},
MongodbatlasDiskPartitionUsageAverage: MongodbatlasDiskPartitionUsageAverageMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasDiskPartitionUsageAverageMetricAttributeKey{MongodbatlasDiskPartitionUsageAverageMetricAttributeKeyDiskStatus},
},
MongodbatlasDiskPartitionUsageMax: MongodbatlasDiskPartitionUsageMaxMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasDiskPartitionUsageMaxMetricAttributeKey{MongodbatlasDiskPartitionUsageMaxMetricAttributeKeyDiskStatus},
},
MongodbatlasDiskPartitionUtilizationAverage: MongodbatlasDiskPartitionUtilizationAverageMetricConfig{
Enabled: true,
},
MongodbatlasDiskPartitionUtilizationMax: MongodbatlasDiskPartitionUtilizationMaxMetricConfig{
Enabled: true,
},
MongodbatlasProcessAsserts: MongodbatlasProcessAssertsMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasProcessAssertsMetricAttributeKey{MongodbatlasProcessAssertsMetricAttributeKeyAssertType},
},
MongodbatlasProcessBackgroundFlush: MongodbatlasProcessBackgroundFlushMetricConfig{
Enabled: true,
},
MongodbatlasProcessCacheIo: MongodbatlasProcessCacheIoMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasProcessCacheIoMetricAttributeKey{MongodbatlasProcessCacheIoMetricAttributeKeyCacheDirection},
},
MongodbatlasProcessCacheRatio: MongodbatlasProcessCacheRatioMetricConfig{
Enabled: false,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasProcessCacheRatioMetricAttributeKey{MongodbatlasProcessCacheRatioMetricAttributeKeyCacheRatioType},
},
MongodbatlasProcessCacheSize: MongodbatlasProcessCacheSizeMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategySum,
EnabledAttributes: []MongodbatlasProcessCacheSizeMetricAttributeKey{MongodbatlasProcessCacheSizeMetricAttributeKeyCacheStatus},
},
MongodbatlasProcessConnections: MongodbatlasProcessConnectionsMetricConfig{
Enabled: true,
},
MongodbatlasProcessCPUChildrenNormalizedUsageAverage: MongodbatlasProcessCPUChildrenNormalizedUsageAverageMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasProcessCPUChildrenNormalizedUsageAverageMetricAttributeKey{MongodbatlasProcessCPUChildrenNormalizedUsageAverageMetricAttributeKeyCPUState},
},
MongodbatlasProcessCPUChildrenNormalizedUsageMax: MongodbatlasProcessCPUChildrenNormalizedUsageMaxMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasProcessCPUChildrenNormalizedUsageMaxMetricAttributeKey{MongodbatlasProcessCPUChildrenNormalizedUsageMaxMetricAttributeKeyCPUState},
},
MongodbatlasProcessCPUChildrenUsageAverage: MongodbatlasProcessCPUChildrenUsageAverageMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasProcessCPUChildrenUsageAverageMetricAttributeKey{MongodbatlasProcessCPUChildrenUsageAverageMetricAttributeKeyCPUState},
},
MongodbatlasProcessCPUChildrenUsageMax: MongodbatlasProcessCPUChildrenUsageMaxMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasProcessCPUChildrenUsageMaxMetricAttributeKey{MongodbatlasProcessCPUChildrenUsageMaxMetricAttributeKeyCPUState},
},
MongodbatlasProcessCPUNormalizedUsageAverage: MongodbatlasProcessCPUNormalizedUsageAverageMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasProcessCPUNormalizedUsageAverageMetricAttributeKey{MongodbatlasProcessCPUNormalizedUsageAverageMetricAttributeKeyCPUState},
},
MongodbatlasProcessCPUNormalizedUsageMax: MongodbatlasProcessCPUNormalizedUsageMaxMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasProcessCPUNormalizedUsageMaxMetricAttributeKey{MongodbatlasProcessCPUNormalizedUsageMaxMetricAttributeKeyCPUState},
},
MongodbatlasProcessCPUUsageAverage: MongodbatlasProcessCPUUsageAverageMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasProcessCPUUsageAverageMetricAttributeKey{MongodbatlasProcessCPUUsageAverageMetricAttributeKeyCPUState},
},
MongodbatlasProcessCPUUsageMax: MongodbatlasProcessCPUUsageMaxMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasProcessCPUUsageMaxMetricAttributeKey{MongodbatlasProcessCPUUsageMaxMetricAttributeKeyCPUState},
},
MongodbatlasProcessCursors: MongodbatlasProcessCursorsMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasProcessCursorsMetricAttributeKey{MongodbatlasProcessCursorsMetricAttributeKeyCursorState},
},
MongodbatlasProcessDbDocumentRate: MongodbatlasProcessDbDocumentRateMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasProcessDbDocumentRateMetricAttributeKey{MongodbatlasProcessDbDocumentRateMetricAttributeKeyDocumentStatus},
},
MongodbatlasProcessDbOperationsRate: MongodbatlasProcessDbOperationsRateMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasProcessDbOperationsRateMetricAttributeKey{MongodbatlasProcessDbOperationsRateMetricAttributeKeyOperation, MongodbatlasProcessDbOperationsRateMetricAttributeKeyClusterRole},
},
MongodbatlasProcessDbOperationsTime: MongodbatlasProcessDbOperationsTimeMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategySum,
EnabledAttributes: []MongodbatlasProcessDbOperationsTimeMetricAttributeKey{MongodbatlasProcessDbOperationsTimeMetricAttributeKeyExecutionType},
},
MongodbatlasProcessDbQueryExecutorScanned: MongodbatlasProcessDbQueryExecutorScannedMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasProcessDbQueryExecutorScannedMetricAttributeKey{MongodbatlasProcessDbQueryExecutorScannedMetricAttributeKeyScannedType},
},
MongodbatlasProcessDbQueryTargetingScannedPerReturned: MongodbatlasProcessDbQueryTargetingScannedPerReturnedMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasProcessDbQueryTargetingScannedPerReturnedMetricAttributeKey{MongodbatlasProcessDbQueryTargetingScannedPerReturnedMetricAttributeKeyScannedType},
},
MongodbatlasProcessDbStorage: MongodbatlasProcessDbStorageMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasProcessDbStorageMetricAttributeKey{MongodbatlasProcessDbStorageMetricAttributeKeyStorageStatus},
},
MongodbatlasProcessGlobalLock: MongodbatlasProcessGlobalLockMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasProcessGlobalLockMetricAttributeKey{MongodbatlasProcessGlobalLockMetricAttributeKeyGlobalLockState},
},
MongodbatlasProcessIndexBtreeMissRatio: MongodbatlasProcessIndexBtreeMissRatioMetricConfig{
Enabled: true,
},
MongodbatlasProcessIndexCounters: MongodbatlasProcessIndexCountersMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasProcessIndexCountersMetricAttributeKey{MongodbatlasProcessIndexCountersMetricAttributeKeyBtreeCounterType},
},
MongodbatlasProcessJournalingCommits: MongodbatlasProcessJournalingCommitsMetricConfig{
Enabled: true,
},
MongodbatlasProcessJournalingDataFiles: MongodbatlasProcessJournalingDataFilesMetricConfig{
Enabled: true,
},
MongodbatlasProcessJournalingWritten: MongodbatlasProcessJournalingWrittenMetricConfig{
Enabled: true,
},
MongodbatlasProcessMemoryUsage: MongodbatlasProcessMemoryUsageMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasProcessMemoryUsageMetricAttributeKey{MongodbatlasProcessMemoryUsageMetricAttributeKeyMemoryState},
},
MongodbatlasProcessNetworkIo: MongodbatlasProcessNetworkIoMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasProcessNetworkIoMetricAttributeKey{MongodbatlasProcessNetworkIoMetricAttributeKeyDirection},
},
MongodbatlasProcessNetworkRequests: MongodbatlasProcessNetworkRequestsMetricConfig{
Enabled: true,
},
MongodbatlasProcessOplogRate: MongodbatlasProcessOplogRateMetricConfig{
Enabled: true,
},
MongodbatlasProcessOplogTime: MongodbatlasProcessOplogTimeMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasProcessOplogTimeMetricAttributeKey{MongodbatlasProcessOplogTimeMetricAttributeKeyOplogType},
},
MongodbatlasProcessPageFaults: MongodbatlasProcessPageFaultsMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasProcessPageFaultsMetricAttributeKey{MongodbatlasProcessPageFaultsMetricAttributeKeyMemoryIssueType},
},
MongodbatlasProcessRestarts: MongodbatlasProcessRestartsMetricConfig{
Enabled: true,
},
MongodbatlasProcessTickets: MongodbatlasProcessTicketsMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasProcessTicketsMetricAttributeKey{MongodbatlasProcessTicketsMetricAttributeKeyTicketType},
},
MongodbatlasSystemCPUNormalizedUsageAverage: MongodbatlasSystemCPUNormalizedUsageAverageMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasSystemCPUNormalizedUsageAverageMetricAttributeKey{MongodbatlasSystemCPUNormalizedUsageAverageMetricAttributeKeyCPUState},
},
MongodbatlasSystemCPUNormalizedUsageMax: MongodbatlasSystemCPUNormalizedUsageMaxMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasSystemCPUNormalizedUsageMaxMetricAttributeKey{MongodbatlasSystemCPUNormalizedUsageMaxMetricAttributeKeyCPUState},
},
MongodbatlasSystemCPUUsageAverage: MongodbatlasSystemCPUUsageAverageMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasSystemCPUUsageAverageMetricAttributeKey{MongodbatlasSystemCPUUsageAverageMetricAttributeKeyCPUState},
},
MongodbatlasSystemCPUUsageMax: MongodbatlasSystemCPUUsageMaxMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasSystemCPUUsageMaxMetricAttributeKey{MongodbatlasSystemCPUUsageMaxMetricAttributeKeyCPUState},
},
MongodbatlasSystemFtsCPUNormalizedUsage: MongodbatlasSystemFtsCPUNormalizedUsageMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasSystemFtsCPUNormalizedUsageMetricAttributeKey{MongodbatlasSystemFtsCPUNormalizedUsageMetricAttributeKeyCPUState},
},
MongodbatlasSystemFtsCPUUsage: MongodbatlasSystemFtsCPUUsageMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasSystemFtsCPUUsageMetricAttributeKey{MongodbatlasSystemFtsCPUUsageMetricAttributeKeyCPUState},
},
MongodbatlasSystemFtsDiskUsed: MongodbatlasSystemFtsDiskUsedMetricConfig{
Enabled: true,
},
MongodbatlasSystemFtsMemoryUsage: MongodbatlasSystemFtsMemoryUsageMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategySum,
EnabledAttributes: []MongodbatlasSystemFtsMemoryUsageMetricAttributeKey{MongodbatlasSystemFtsMemoryUsageMetricAttributeKeyMemoryState},
},
MongodbatlasSystemMemoryUsageAverage: MongodbatlasSystemMemoryUsageAverageMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasSystemMemoryUsageAverageMetricAttributeKey{MongodbatlasSystemMemoryUsageAverageMetricAttributeKeyMemoryStatus},
},
MongodbatlasSystemMemoryUsageMax: MongodbatlasSystemMemoryUsageMaxMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasSystemMemoryUsageMaxMetricAttributeKey{MongodbatlasSystemMemoryUsageMaxMetricAttributeKeyMemoryStatus},
},
MongodbatlasSystemNetworkIoAverage: MongodbatlasSystemNetworkIoAverageMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasSystemNetworkIoAverageMetricAttributeKey{MongodbatlasSystemNetworkIoAverageMetricAttributeKeyDirection},
},
MongodbatlasSystemNetworkIoMax: MongodbatlasSystemNetworkIoMaxMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasSystemNetworkIoMaxMetricAttributeKey{MongodbatlasSystemNetworkIoMaxMetricAttributeKeyDirection},
},
MongodbatlasSystemPagingIoAverage: MongodbatlasSystemPagingIoAverageMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasSystemPagingIoAverageMetricAttributeKey{MongodbatlasSystemPagingIoAverageMetricAttributeKeyDirection},
},
MongodbatlasSystemPagingIoMax: MongodbatlasSystemPagingIoMaxMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasSystemPagingIoMaxMetricAttributeKey{MongodbatlasSystemPagingIoMaxMetricAttributeKeyDirection},
},
MongodbatlasSystemPagingUsageAverage: MongodbatlasSystemPagingUsageAverageMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasSystemPagingUsageAverageMetricAttributeKey{MongodbatlasSystemPagingUsageAverageMetricAttributeKeyMemoryState},
},
MongodbatlasSystemPagingUsageMax: MongodbatlasSystemPagingUsageMaxMetricConfig{
Enabled: true,
AggregationStrategy: AggregationStrategyAvg,
EnabledAttributes: []MongodbatlasSystemPagingUsageMaxMetricAttributeKey{MongodbatlasSystemPagingUsageMaxMetricAttributeKeyMemoryState},
},
}
}
// ResourceAttributeConfig provides common config for a particular resource attribute.
type ResourceAttributeConfig struct {
Enabled bool `mapstructure:"enabled"`
// Experimental: MetricsInclude defines a list of filters for attribute values.
// If the list is not empty, only metrics with matching resource attribute values will be emitted.
MetricsInclude []filter.Config `mapstructure:"metrics_include"`
// Experimental: MetricsExclude defines a list of filters for attribute values.
// If the list is not empty, metrics with matching resource attribute values will not be emitted.
// MetricsInclude has higher priority than MetricsExclude.
MetricsExclude []filter.Config `mapstructure:"metrics_exclude"`
enabledSetByUser bool
}
func (rac *ResourceAttributeConfig) Unmarshal(parser *confmap.Conf) error {
if parser == nil {
return nil
}
err := parser.Unmarshal(rac)
if err != nil {
return err
}
rac.enabledSetByUser = parser.IsSet("enabled")
return nil
}
// ResourceAttributesConfig provides config for mongodb_atlas resource attributes.
type ResourceAttributesConfig struct {
MongodbAtlasClusterName ResourceAttributeConfig `mapstructure:"mongodb_atlas.cluster.name"`
MongodbAtlasDbName ResourceAttributeConfig `mapstructure:"mongodb_atlas.db.name"`
MongodbAtlasDiskPartition ResourceAttributeConfig `mapstructure:"mongodb_atlas.disk.partition"`
MongodbAtlasHostName ResourceAttributeConfig `mapstructure:"mongodb_atlas.host.name"`
MongodbAtlasOrgName ResourceAttributeConfig `mapstructure:"mongodb_atlas.org_name"`
MongodbAtlasProcessID ResourceAttributeConfig `mapstructure:"mongodb_atlas.process.id"`
MongodbAtlasProcessPort ResourceAttributeConfig `mapstructure:"mongodb_atlas.process.port"`
MongodbAtlasProcessTypeName ResourceAttributeConfig `mapstructure:"mongodb_atlas.process.type_name"`
MongodbAtlasProjectID ResourceAttributeConfig `mapstructure:"mongodb_atlas.project.id"`
MongodbAtlasProjectName ResourceAttributeConfig `mapstructure:"mongodb_atlas.project.name"`
MongodbAtlasProviderName ResourceAttributeConfig `mapstructure:"mongodb_atlas.provider.name"`
MongodbAtlasRegionName ResourceAttributeConfig `mapstructure:"mongodb_atlas.region.name"`
MongodbAtlasUserAlias ResourceAttributeConfig `mapstructure:"mongodb_atlas.user.alias"`
}
func DefaultResourceAttributesConfig() ResourceAttributesConfig {
return ResourceAttributesConfig{
MongodbAtlasClusterName: ResourceAttributeConfig{
Enabled: false,
},
MongodbAtlasDbName: ResourceAttributeConfig{
Enabled: true,
},
MongodbAtlasDiskPartition: ResourceAttributeConfig{
Enabled: true,
},
MongodbAtlasHostName: ResourceAttributeConfig{
Enabled: true,
},
MongodbAtlasOrgName: ResourceAttributeConfig{
Enabled: true,
},
MongodbAtlasProcessID: ResourceAttributeConfig{
Enabled: true,
},
MongodbAtlasProcessPort: ResourceAttributeConfig{
Enabled: true,
},
MongodbAtlasProcessTypeName: ResourceAttributeConfig{
Enabled: true,
},
MongodbAtlasProjectID: ResourceAttributeConfig{
Enabled: true,
},
MongodbAtlasProjectName: ResourceAttributeConfig{
Enabled: true,
},
MongodbAtlasProviderName: ResourceAttributeConfig{
Enabled: false,
},
MongodbAtlasRegionName: ResourceAttributeConfig{
Enabled: false,
},
MongodbAtlasUserAlias: ResourceAttributeConfig{
Enabled: false,
},
}
}
// MetricsBuilderConfig is a configuration for mongodb_atlas metrics builder.
type MetricsBuilderConfig struct {
Metrics MetricsConfig `mapstructure:"metrics"`
ResourceAttributes ResourceAttributesConfig `mapstructure:"resource_attributes"`
}
func NewDefaultMetricsBuilderConfig() MetricsBuilderConfig {
return MetricsBuilderConfig{
Metrics: DefaultMetricsConfig(),
ResourceAttributes: DefaultResourceAttributesConfig(),
}
}
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/receiver"
)
// LogsBuilder provides an interface for scrapers to report logs while taking care of all the transformations
// required to produce log representation defined in metadata and user config.
type LogsBuilder struct {
logsBuffer plog.Logs
logRecordsBuffer plog.LogRecordSlice
buildInfo component.BuildInfo // contains version information.
}
// LogBuilderOption applies changes to default logs builder.
type LogBuilderOption interface {
apply(*LogsBuilder)
}
func NewLogsBuilder(settings receiver.Settings) *LogsBuilder {
lb := &LogsBuilder{
logsBuffer: plog.NewLogs(),
logRecordsBuffer: plog.NewLogRecordSlice(),
buildInfo: settings.BuildInfo,
}
return lb
}
// NewResourceBuilder returns a new resource builder that should be used to build a resource associated with for the emitted logs.
func (lb *LogsBuilder) NewResourceBuilder() *ResourceBuilder {
return NewResourceBuilder(ResourceAttributesConfig{})
}
// ResourceLogsOption applies changes to provided resource logs.
type ResourceLogsOption interface {
apply(plog.ResourceLogs)
}
type resourceLogsOptionFunc func(plog.ResourceLogs)
func (rlof resourceLogsOptionFunc) apply(rl plog.ResourceLogs) {
rlof(rl)
}
// WithLogsResource sets the provided resource on the emitted ResourceLogs.
// It's recommended to use ResourceBuilder to create the resource.
func WithLogsResource(res pcommon.Resource) ResourceLogsOption {
return resourceLogsOptionFunc(func(rl plog.ResourceLogs) {
res.CopyTo(rl.Resource())
})
}
// AppendLogRecord adds a log record to the logs builder.
func (lb *LogsBuilder) AppendLogRecord(lr plog.LogRecord) {
lr.MoveTo(lb.logRecordsBuffer.AppendEmpty())
}
// EmitForResource saves all the generated logs under a new resource and updates the internal state to be ready for
// recording another set of log records as part of another resource. This function can be helpful when one scraper
// needs to emit logs from several resources. Otherwise calling this function is not required,
// just `Emit` function can be called instead.
// Resource attributes should be provided as ResourceLogsOption arguments.
func (lb *LogsBuilder) EmitForResource(options ...ResourceLogsOption) {
rl := plog.NewResourceLogs()
ils := rl.ScopeLogs().AppendEmpty()
ils.Scope().SetName(ScopeName)
ils.Scope().SetVersion(lb.buildInfo.Version)
for _, op := range options {
op.apply(rl)
}
if lb.logRecordsBuffer.Len() > 0 {
lb.logRecordsBuffer.MoveAndAppendTo(ils.LogRecords())
lb.logRecordsBuffer = plog.NewLogRecordSlice()
}
if ils.LogRecords().Len() > 0 {
rl.MoveTo(lb.logsBuffer.ResourceLogs().AppendEmpty())
}
}
// Emit returns all the logs accumulated by the logs builder and updates the internal state to be ready for
// recording another set of logs. This function will be responsible for applying all the transformations required to
// produce logs representation defined in metadata and user config.
func (lb *LogsBuilder) Emit(options ...ResourceLogsOption) plog.Logs {
lb.EmitForResource(options...)
logs := lb.logsBuffer
lb.logsBuffer = plog.NewLogs()
return logs
}
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"slices"
"time"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/filter"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/xpdata/xhash"
"go.opentelemetry.io/collector/receiver"
)
const (
AggregationStrategySum = "sum"
AggregationStrategyAvg = "avg"
AggregationStrategyMin = "min"
AggregationStrategyMax = "max"
)
// dataPointKey hashes dp's attributes and timestamps for O(1) dedup lookup.
func dataPointKey(dp pmetric.NumberDataPoint) uint64 {
return xhash.Hash64(
xhash.WithMap(dp.Attributes()),
xhash.WithUint64(uint64(dp.StartTimestamp())),
xhash.WithUint64(uint64(dp.Timestamp())),
)
}
// AttributeAssertType specifies the value assert_type attribute.
type AttributeAssertType int
const (
_ AttributeAssertType = iota
AttributeAssertTypeRegular
AttributeAssertTypeWarning
AttributeAssertTypeMsg
AttributeAssertTypeUser
)
// String returns the string representation of the AttributeAssertType.
func (av AttributeAssertType) String() string {
switch av {
case AttributeAssertTypeRegular:
return "regular"
case AttributeAssertTypeWarning:
return "warning"
case AttributeAssertTypeMsg:
return "msg"
case AttributeAssertTypeUser:
return "user"
}
return ""
}
// MapAttributeAssertType is a helper map of string to AttributeAssertType attribute value.
var MapAttributeAssertType = map[string]AttributeAssertType{
"regular": AttributeAssertTypeRegular,
"warning": AttributeAssertTypeWarning,
"msg": AttributeAssertTypeMsg,
"user": AttributeAssertTypeUser,
}
// AttributeBtreeCounterType specifies the value btree_counter_type attribute.
type AttributeBtreeCounterType int
const (
_ AttributeBtreeCounterType = iota
AttributeBtreeCounterTypeAccesses
AttributeBtreeCounterTypeHits
AttributeBtreeCounterTypeMisses
)
// String returns the string representation of the AttributeBtreeCounterType.
func (av AttributeBtreeCounterType) String() string {
switch av {
case AttributeBtreeCounterTypeAccesses:
return "accesses"
case AttributeBtreeCounterTypeHits:
return "hits"
case AttributeBtreeCounterTypeMisses:
return "misses"
}
return ""
}
// MapAttributeBtreeCounterType is a helper map of string to AttributeBtreeCounterType attribute value.
var MapAttributeBtreeCounterType = map[string]AttributeBtreeCounterType{
"accesses": AttributeBtreeCounterTypeAccesses,
"hits": AttributeBtreeCounterTypeHits,
"misses": AttributeBtreeCounterTypeMisses,
}
// AttributeCacheDirection specifies the value cache_direction attribute.
type AttributeCacheDirection int
const (
_ AttributeCacheDirection = iota
AttributeCacheDirectionReadInto
AttributeCacheDirectionWrittenFrom
)
// String returns the string representation of the AttributeCacheDirection.
func (av AttributeCacheDirection) String() string {
switch av {
case AttributeCacheDirectionReadInto:
return "read_into"
case AttributeCacheDirectionWrittenFrom:
return "written_from"
}
return ""
}
// MapAttributeCacheDirection is a helper map of string to AttributeCacheDirection attribute value.
var MapAttributeCacheDirection = map[string]AttributeCacheDirection{
"read_into": AttributeCacheDirectionReadInto,
"written_from": AttributeCacheDirectionWrittenFrom,
}
// AttributeCacheRatioType specifies the value cache_ratio_type attribute.
type AttributeCacheRatioType int
const (
_ AttributeCacheRatioType = iota
AttributeCacheRatioTypeCacheFill
AttributeCacheRatioTypeDirtyFill
)
// String returns the string representation of the AttributeCacheRatioType.
func (av AttributeCacheRatioType) String() string {
switch av {
case AttributeCacheRatioTypeCacheFill:
return "cache_fill"
case AttributeCacheRatioTypeDirtyFill:
return "dirty_fill"
}
return ""
}
// MapAttributeCacheRatioType is a helper map of string to AttributeCacheRatioType attribute value.
var MapAttributeCacheRatioType = map[string]AttributeCacheRatioType{
"cache_fill": AttributeCacheRatioTypeCacheFill,
"dirty_fill": AttributeCacheRatioTypeDirtyFill,
}
// AttributeCacheStatus specifies the value cache_status attribute.
type AttributeCacheStatus int
const (
_ AttributeCacheStatus = iota
AttributeCacheStatusDirty
AttributeCacheStatusUsed
)
// String returns the string representation of the AttributeCacheStatus.
func (av AttributeCacheStatus) String() string {
switch av {
case AttributeCacheStatusDirty:
return "dirty"
case AttributeCacheStatusUsed:
return "used"
}
return ""
}
// MapAttributeCacheStatus is a helper map of string to AttributeCacheStatus attribute value.
var MapAttributeCacheStatus = map[string]AttributeCacheStatus{
"dirty": AttributeCacheStatusDirty,
"used": AttributeCacheStatusUsed,
}
// AttributeClusterRole specifies the value cluster_role attribute.
type AttributeClusterRole int
const (
_ AttributeClusterRole = iota
AttributeClusterRolePrimary
AttributeClusterRoleReplica
)
// String returns the string representation of the AttributeClusterRole.
func (av AttributeClusterRole) String() string {
switch av {
case AttributeClusterRolePrimary:
return "primary"
case AttributeClusterRoleReplica:
return "replica"
}
return ""
}
// MapAttributeClusterRole is a helper map of string to AttributeClusterRole attribute value.
var MapAttributeClusterRole = map[string]AttributeClusterRole{
"primary": AttributeClusterRolePrimary,
"replica": AttributeClusterRoleReplica,
}
// AttributeCPUState specifies the value cpu_state attribute.
type AttributeCPUState int
const (
_ AttributeCPUState = iota
AttributeCPUStateKernel
AttributeCPUStateUser
AttributeCPUStateNice
AttributeCPUStateIowait
AttributeCPUStateIrq
AttributeCPUStateSoftirq
AttributeCPUStateGuest
AttributeCPUStateSteal
)
// String returns the string representation of the AttributeCPUState.
func (av AttributeCPUState) String() string {
switch av {
case AttributeCPUStateKernel:
return "kernel"
case AttributeCPUStateUser:
return "user"
case AttributeCPUStateNice:
return "nice"
case AttributeCPUStateIowait:
return "iowait"
case AttributeCPUStateIrq:
return "irq"
case AttributeCPUStateSoftirq:
return "softirq"
case AttributeCPUStateGuest:
return "guest"
case AttributeCPUStateSteal:
return "steal"
}
return ""
}
// MapAttributeCPUState is a helper map of string to AttributeCPUState attribute value.
var MapAttributeCPUState = map[string]AttributeCPUState{
"kernel": AttributeCPUStateKernel,
"user": AttributeCPUStateUser,
"nice": AttributeCPUStateNice,
"iowait": AttributeCPUStateIowait,
"irq": AttributeCPUStateIrq,
"softirq": AttributeCPUStateSoftirq,
"guest": AttributeCPUStateGuest,
"steal": AttributeCPUStateSteal,
}
// AttributeCursorState specifies the value cursor_state attribute.
type AttributeCursorState int
const (
_ AttributeCursorState = iota
AttributeCursorStateTimedOut
AttributeCursorStateOpen
)
// String returns the string representation of the AttributeCursorState.
func (av AttributeCursorState) String() string {
switch av {
case AttributeCursorStateTimedOut:
return "timed_out"
case AttributeCursorStateOpen:
return "open"
}
return ""
}
// MapAttributeCursorState is a helper map of string to AttributeCursorState attribute value.
var MapAttributeCursorState = map[string]AttributeCursorState{
"timed_out": AttributeCursorStateTimedOut,
"open": AttributeCursorStateOpen,
}
// AttributeDirection specifies the value direction attribute.
type AttributeDirection int
const (
_ AttributeDirection = iota
AttributeDirectionReceive
AttributeDirectionTransmit
)
// String returns the string representation of the AttributeDirection.
func (av AttributeDirection) String() string {
switch av {
case AttributeDirectionReceive:
return "receive"
case AttributeDirectionTransmit:
return "transmit"
}
return ""
}
// MapAttributeDirection is a helper map of string to AttributeDirection attribute value.
var MapAttributeDirection = map[string]AttributeDirection{
"receive": AttributeDirectionReceive,
"transmit": AttributeDirectionTransmit,
}
// AttributeDiskDirection specifies the value disk_direction attribute.
type AttributeDiskDirection int
const (
_ AttributeDiskDirection = iota
AttributeDiskDirectionRead
AttributeDiskDirectionWrite
AttributeDiskDirectionTotal
)
// String returns the string representation of the AttributeDiskDirection.
func (av AttributeDiskDirection) String() string {
switch av {
case AttributeDiskDirectionRead:
return "read"
case AttributeDiskDirectionWrite:
return "write"
case AttributeDiskDirectionTotal:
return "total"
}
return ""
}
// MapAttributeDiskDirection is a helper map of string to AttributeDiskDirection attribute value.
var MapAttributeDiskDirection = map[string]AttributeDiskDirection{
"read": AttributeDiskDirectionRead,
"write": AttributeDiskDirectionWrite,
"total": AttributeDiskDirectionTotal,
}
// AttributeDiskStatus specifies the value disk_status attribute.
type AttributeDiskStatus int
const (
_ AttributeDiskStatus = iota
AttributeDiskStatusFree
AttributeDiskStatusUsed
)
// String returns the string representation of the AttributeDiskStatus.
func (av AttributeDiskStatus) String() string {
switch av {
case AttributeDiskStatusFree:
return "free"
case AttributeDiskStatusUsed:
return "used"
}
return ""
}
// MapAttributeDiskStatus is a helper map of string to AttributeDiskStatus attribute value.
var MapAttributeDiskStatus = map[string]AttributeDiskStatus{
"free": AttributeDiskStatusFree,
"used": AttributeDiskStatusUsed,
}
// AttributeDocumentStatus specifies the value document_status attribute.
type AttributeDocumentStatus int
const (
_ AttributeDocumentStatus = iota
AttributeDocumentStatusReturned
AttributeDocumentStatusInserted
AttributeDocumentStatusUpdated
AttributeDocumentStatusDeleted
)
// String returns the string representation of the AttributeDocumentStatus.
func (av AttributeDocumentStatus) String() string {
switch av {
case AttributeDocumentStatusReturned:
return "returned"
case AttributeDocumentStatusInserted:
return "inserted"
case AttributeDocumentStatusUpdated:
return "updated"
case AttributeDocumentStatusDeleted:
return "deleted"
}
return ""
}
// MapAttributeDocumentStatus is a helper map of string to AttributeDocumentStatus attribute value.
var MapAttributeDocumentStatus = map[string]AttributeDocumentStatus{
"returned": AttributeDocumentStatusReturned,
"inserted": AttributeDocumentStatusInserted,
"updated": AttributeDocumentStatusUpdated,
"deleted": AttributeDocumentStatusDeleted,
}
// AttributeExecutionType specifies the value execution_type attribute.
type AttributeExecutionType int
const (
_ AttributeExecutionType = iota
AttributeExecutionTypeReads
AttributeExecutionTypeWrites
AttributeExecutionTypeCommands
)
// String returns the string representation of the AttributeExecutionType.
func (av AttributeExecutionType) String() string {
switch av {
case AttributeExecutionTypeReads:
return "reads"
case AttributeExecutionTypeWrites:
return "writes"
case AttributeExecutionTypeCommands:
return "commands"
}
return ""
}
// MapAttributeExecutionType is a helper map of string to AttributeExecutionType attribute value.
var MapAttributeExecutionType = map[string]AttributeExecutionType{
"reads": AttributeExecutionTypeReads,
"writes": AttributeExecutionTypeWrites,
"commands": AttributeExecutionTypeCommands,
}
// AttributeGlobalLockState specifies the value global_lock_state attribute.
type AttributeGlobalLockState int
const (
_ AttributeGlobalLockState = iota
AttributeGlobalLockStateCurrentQueueTotal
AttributeGlobalLockStateCurrentQueueReaders
AttributeGlobalLockStateCurrentQueueWriters
)
// String returns the string representation of the AttributeGlobalLockState.
func (av AttributeGlobalLockState) String() string {
switch av {
case AttributeGlobalLockStateCurrentQueueTotal:
return "current_queue_total"
case AttributeGlobalLockStateCurrentQueueReaders:
return "current_queue_readers"
case AttributeGlobalLockStateCurrentQueueWriters:
return "current_queue_writers"
}
return ""
}
// MapAttributeGlobalLockState is a helper map of string to AttributeGlobalLockState attribute value.
var MapAttributeGlobalLockState = map[string]AttributeGlobalLockState{
"current_queue_total": AttributeGlobalLockStateCurrentQueueTotal,
"current_queue_readers": AttributeGlobalLockStateCurrentQueueReaders,
"current_queue_writers": AttributeGlobalLockStateCurrentQueueWriters,
}
// AttributeMemoryIssueType specifies the value memory_issue_type attribute.
type AttributeMemoryIssueType int
const (
_ AttributeMemoryIssueType = iota
AttributeMemoryIssueTypeExtraInfo
AttributeMemoryIssueTypeGlobalAccessesNotInMemory
AttributeMemoryIssueTypeExceptionsThrown
)
// String returns the string representation of the AttributeMemoryIssueType.
func (av AttributeMemoryIssueType) String() string {
switch av {
case AttributeMemoryIssueTypeExtraInfo:
return "extra_info"
case AttributeMemoryIssueTypeGlobalAccessesNotInMemory:
return "global_accesses_not_in_memory"
case AttributeMemoryIssueTypeExceptionsThrown:
return "exceptions_thrown"
}
return ""
}
// MapAttributeMemoryIssueType is a helper map of string to AttributeMemoryIssueType attribute value.
var MapAttributeMemoryIssueType = map[string]AttributeMemoryIssueType{
"extra_info": AttributeMemoryIssueTypeExtraInfo,
"global_accesses_not_in_memory": AttributeMemoryIssueTypeGlobalAccessesNotInMemory,
"exceptions_thrown": AttributeMemoryIssueTypeExceptionsThrown,
}
// AttributeMemoryState specifies the value memory_state attribute.
type AttributeMemoryState int
const (
_ AttributeMemoryState = iota
AttributeMemoryStateResident
AttributeMemoryStateVirtual
AttributeMemoryStateMapped
AttributeMemoryStateComputed
AttributeMemoryStateShared
AttributeMemoryStateFree
AttributeMemoryStateUsed
)
// String returns the string representation of the AttributeMemoryState.
func (av AttributeMemoryState) String() string {
switch av {
case AttributeMemoryStateResident:
return "resident"
case AttributeMemoryStateVirtual:
return "virtual"
case AttributeMemoryStateMapped:
return "mapped"
case AttributeMemoryStateComputed:
return "computed"
case AttributeMemoryStateShared:
return "shared"
case AttributeMemoryStateFree:
return "free"
case AttributeMemoryStateUsed:
return "used"
}
return ""
}
// MapAttributeMemoryState is a helper map of string to AttributeMemoryState attribute value.
var MapAttributeMemoryState = map[string]AttributeMemoryState{
"resident": AttributeMemoryStateResident,
"virtual": AttributeMemoryStateVirtual,
"mapped": AttributeMemoryStateMapped,
"computed": AttributeMemoryStateComputed,
"shared": AttributeMemoryStateShared,
"free": AttributeMemoryStateFree,
"used": AttributeMemoryStateUsed,
}
// AttributeMemoryStatus specifies the value memory_status attribute.
type AttributeMemoryStatus int
const (
_ AttributeMemoryStatus = iota
AttributeMemoryStatusAvailable
AttributeMemoryStatusBuffers
AttributeMemoryStatusCached
AttributeMemoryStatusFree
AttributeMemoryStatusShared
AttributeMemoryStatusUsed
)
// String returns the string representation of the AttributeMemoryStatus.
func (av AttributeMemoryStatus) String() string {
switch av {
case AttributeMemoryStatusAvailable:
return "available"
case AttributeMemoryStatusBuffers:
return "buffers"
case AttributeMemoryStatusCached:
return "cached"
case AttributeMemoryStatusFree:
return "free"
case AttributeMemoryStatusShared:
return "shared"
case AttributeMemoryStatusUsed:
return "used"
}
return ""
}
// MapAttributeMemoryStatus is a helper map of string to AttributeMemoryStatus attribute value.
var MapAttributeMemoryStatus = map[string]AttributeMemoryStatus{
"available": AttributeMemoryStatusAvailable,
"buffers": AttributeMemoryStatusBuffers,
"cached": AttributeMemoryStatusCached,
"free": AttributeMemoryStatusFree,
"shared": AttributeMemoryStatusShared,
"used": AttributeMemoryStatusUsed,
}
// AttributeObjectType specifies the value object_type attribute.
type AttributeObjectType int
const (
_ AttributeObjectType = iota
AttributeObjectTypeCollection
AttributeObjectTypeIndex
AttributeObjectTypeExtent
AttributeObjectTypeObject
AttributeObjectTypeView
AttributeObjectTypeStorage
AttributeObjectTypeData
)
// String returns the string representation of the AttributeObjectType.
func (av AttributeObjectType) String() string {
switch av {
case AttributeObjectTypeCollection:
return "collection"
case AttributeObjectTypeIndex:
return "index"
case AttributeObjectTypeExtent:
return "extent"
case AttributeObjectTypeObject:
return "object"
case AttributeObjectTypeView:
return "view"
case AttributeObjectTypeStorage:
return "storage"
case AttributeObjectTypeData:
return "data"
}
return ""
}
// MapAttributeObjectType is a helper map of string to AttributeObjectType attribute value.
var MapAttributeObjectType = map[string]AttributeObjectType{
"collection": AttributeObjectTypeCollection,
"index": AttributeObjectTypeIndex,
"extent": AttributeObjectTypeExtent,
"object": AttributeObjectTypeObject,
"view": AttributeObjectTypeView,
"storage": AttributeObjectTypeStorage,
"data": AttributeObjectTypeData,
}
// AttributeOperation specifies the value operation attribute.
type AttributeOperation int
const (
_ AttributeOperation = iota
AttributeOperationCmd
AttributeOperationQuery
AttributeOperationUpdate
AttributeOperationDelete
AttributeOperationGetmore
AttributeOperationInsert
AttributeOperationScanAndOrder
AttributeOperationTTLDeleted
)
// String returns the string representation of the AttributeOperation.
func (av AttributeOperation) String() string {
switch av {
case AttributeOperationCmd:
return "cmd"
case AttributeOperationQuery:
return "query"
case AttributeOperationUpdate:
return "update"
case AttributeOperationDelete:
return "delete"
case AttributeOperationGetmore:
return "getmore"
case AttributeOperationInsert:
return "insert"
case AttributeOperationScanAndOrder:
return "scan_and_order"
case AttributeOperationTTLDeleted:
return "ttl_deleted"
}
return ""
}
// MapAttributeOperation is a helper map of string to AttributeOperation attribute value.
var MapAttributeOperation = map[string]AttributeOperation{
"cmd": AttributeOperationCmd,
"query": AttributeOperationQuery,
"update": AttributeOperationUpdate,
"delete": AttributeOperationDelete,
"getmore": AttributeOperationGetmore,
"insert": AttributeOperationInsert,
"scan_and_order": AttributeOperationScanAndOrder,
"ttl_deleted": AttributeOperationTTLDeleted,
}
// AttributeOplogType specifies the value oplog_type attribute.
type AttributeOplogType int
const (
_ AttributeOplogType = iota
AttributeOplogTypeSlaveLagMasterTime
AttributeOplogTypeMasterTime
AttributeOplogTypeMasterLagTimeDiff
)
// String returns the string representation of the AttributeOplogType.
func (av AttributeOplogType) String() string {
switch av {
case AttributeOplogTypeSlaveLagMasterTime:
return "slave_lag_master_time"
case AttributeOplogTypeMasterTime:
return "master_time"
case AttributeOplogTypeMasterLagTimeDiff:
return "master_lag_time_diff"
}
return ""
}
// MapAttributeOplogType is a helper map of string to AttributeOplogType attribute value.
var MapAttributeOplogType = map[string]AttributeOplogType{
"slave_lag_master_time": AttributeOplogTypeSlaveLagMasterTime,
"master_time": AttributeOplogTypeMasterTime,
"master_lag_time_diff": AttributeOplogTypeMasterLagTimeDiff,
}
// AttributeScannedType specifies the value scanned_type attribute.
type AttributeScannedType int
const (
_ AttributeScannedType = iota
AttributeScannedTypeIndexItems
AttributeScannedTypeObjects
)
// String returns the string representation of the AttributeScannedType.
func (av AttributeScannedType) String() string {
switch av {
case AttributeScannedTypeIndexItems:
return "index_items"
case AttributeScannedTypeObjects:
return "objects"
}
return ""
}
// MapAttributeScannedType is a helper map of string to AttributeScannedType attribute value.
var MapAttributeScannedType = map[string]AttributeScannedType{
"index_items": AttributeScannedTypeIndexItems,
"objects": AttributeScannedTypeObjects,
}
// AttributeStorageStatus specifies the value storage_status attribute.
type AttributeStorageStatus int
const (
_ AttributeStorageStatus = iota
AttributeStorageStatusTotal
AttributeStorageStatusDataSize
AttributeStorageStatusIndexSize
AttributeStorageStatusDataSizeWoSystem
)
// String returns the string representation of the AttributeStorageStatus.
func (av AttributeStorageStatus) String() string {
switch av {
case AttributeStorageStatusTotal:
return "total"
case AttributeStorageStatusDataSize:
return "data_size"
case AttributeStorageStatusIndexSize:
return "index_size"
case AttributeStorageStatusDataSizeWoSystem:
return "data_size_wo_system"
}
return ""
}
// MapAttributeStorageStatus is a helper map of string to AttributeStorageStatus attribute value.
var MapAttributeStorageStatus = map[string]AttributeStorageStatus{
"total": AttributeStorageStatusTotal,
"data_size": AttributeStorageStatusDataSize,
"index_size": AttributeStorageStatusIndexSize,
"data_size_wo_system": AttributeStorageStatusDataSizeWoSystem,
}
// AttributeTicketType specifies the value ticket_type attribute.
type AttributeTicketType int
const (
_ AttributeTicketType = iota
AttributeTicketTypeAvailableReads
AttributeTicketTypeAvailableWrites
)
// String returns the string representation of the AttributeTicketType.
func (av AttributeTicketType) String() string {
switch av {
case AttributeTicketTypeAvailableReads:
return "available_reads"
case AttributeTicketTypeAvailableWrites:
return "available_writes"
}
return ""
}
// MapAttributeTicketType is a helper map of string to AttributeTicketType attribute value.
var MapAttributeTicketType = map[string]AttributeTicketType{
"available_reads": AttributeTicketTypeAvailableReads,
"available_writes": AttributeTicketTypeAvailableWrites,
}
var MetricsInfo = metricsInfo{
MongodbatlasDbCounts: metricInfo{
Name: "mongodbatlas.db.counts",
Attributes: []string{"object_type"},
},
MongodbatlasDbSize: metricInfo{
Name: "mongodbatlas.db.size",
Attributes: []string{"object_type"},
},
MongodbatlasDiskPartitionIopsAverage: metricInfo{
Name: "mongodbatlas.disk.partition.iops.average",
Attributes: []string{"disk_direction"},
},
MongodbatlasDiskPartitionIopsMax: metricInfo{
Name: "mongodbatlas.disk.partition.iops.max",
Attributes: []string{"disk_direction"},
},
MongodbatlasDiskPartitionLatencyAverage: metricInfo{
Name: "mongodbatlas.disk.partition.latency.average",
Attributes: []string{"disk_direction"},
},
MongodbatlasDiskPartitionLatencyMax: metricInfo{
Name: "mongodbatlas.disk.partition.latency.max",
Attributes: []string{"disk_direction"},
},
MongodbatlasDiskPartitionQueueDepth: metricInfo{
Name: "mongodbatlas.disk.partition.queue.depth",
},
MongodbatlasDiskPartitionSpaceAverage: metricInfo{
Name: "mongodbatlas.disk.partition.space.average",
Attributes: []string{"disk_status"},
},
MongodbatlasDiskPartitionSpaceMax: metricInfo{
Name: "mongodbatlas.disk.partition.space.max",
Attributes: []string{"disk_status"},
},
MongodbatlasDiskPartitionThroughput: metricInfo{
Name: "mongodbatlas.disk.partition.throughput",
Attributes: []string{"disk_direction"},
},
MongodbatlasDiskPartitionUsageAverage: metricInfo{
Name: "mongodbatlas.disk.partition.usage.average",
Attributes: []string{"disk_status"},
},
MongodbatlasDiskPartitionUsageMax: metricInfo{
Name: "mongodbatlas.disk.partition.usage.max",
Attributes: []string{"disk_status"},
},
MongodbatlasDiskPartitionUtilizationAverage: metricInfo{
Name: "mongodbatlas.disk.partition.utilization.average",
},
MongodbatlasDiskPartitionUtilizationMax: metricInfo{
Name: "mongodbatlas.disk.partition.utilization.max",
},
MongodbatlasProcessAsserts: metricInfo{
Name: "mongodbatlas.process.asserts",
Attributes: []string{"assert_type"},
},
MongodbatlasProcessBackgroundFlush: metricInfo{
Name: "mongodbatlas.process.background_flush",
},
MongodbatlasProcessCacheIo: metricInfo{
Name: "mongodbatlas.process.cache.io",
Attributes: []string{"cache_direction"},
},
MongodbatlasProcessCacheRatio: metricInfo{
Name: "mongodbatlas.process.cache.ratio",
Attributes: []string{"cache_ratio_type"},
},
MongodbatlasProcessCacheSize: metricInfo{
Name: "mongodbatlas.process.cache.size",
Attributes: []string{"cache_status"},
},
MongodbatlasProcessConnections: metricInfo{
Name: "mongodbatlas.process.connections",
},
MongodbatlasProcessCPUChildrenNormalizedUsageAverage: metricInfo{
Name: "mongodbatlas.process.cpu.children.normalized.usage.average",
Attributes: []string{"cpu_state"},
},
MongodbatlasProcessCPUChildrenNormalizedUsageMax: metricInfo{
Name: "mongodbatlas.process.cpu.children.normalized.usage.max",
Attributes: []string{"cpu_state"},
},
MongodbatlasProcessCPUChildrenUsageAverage: metricInfo{
Name: "mongodbatlas.process.cpu.children.usage.average",
Attributes: []string{"cpu_state"},
},
MongodbatlasProcessCPUChildrenUsageMax: metricInfo{
Name: "mongodbatlas.process.cpu.children.usage.max",
Attributes: []string{"cpu_state"},
},
MongodbatlasProcessCPUNormalizedUsageAverage: metricInfo{
Name: "mongodbatlas.process.cpu.normalized.usage.average",
Attributes: []string{"cpu_state"},
},
MongodbatlasProcessCPUNormalizedUsageMax: metricInfo{
Name: "mongodbatlas.process.cpu.normalized.usage.max",
Attributes: []string{"cpu_state"},
},
MongodbatlasProcessCPUUsageAverage: metricInfo{
Name: "mongodbatlas.process.cpu.usage.average",
Attributes: []string{"cpu_state"},
},
MongodbatlasProcessCPUUsageMax: metricInfo{
Name: "mongodbatlas.process.cpu.usage.max",
Attributes: []string{"cpu_state"},
},
MongodbatlasProcessCursors: metricInfo{
Name: "mongodbatlas.process.cursors",
Attributes: []string{"cursor_state"},
},
MongodbatlasProcessDbDocumentRate: metricInfo{
Name: "mongodbatlas.process.db.document.rate",
Attributes: []string{"document_status"},
},
MongodbatlasProcessDbOperationsRate: metricInfo{
Name: "mongodbatlas.process.db.operations.rate",
Attributes: []string{"operation", "cluster_role"},
},
MongodbatlasProcessDbOperationsTime: metricInfo{
Name: "mongodbatlas.process.db.operations.time",
Attributes: []string{"execution_type"},
},
MongodbatlasProcessDbQueryExecutorScanned: metricInfo{
Name: "mongodbatlas.process.db.query_executor.scanned",
Attributes: []string{"scanned_type"},
},
MongodbatlasProcessDbQueryTargetingScannedPerReturned: metricInfo{
Name: "mongodbatlas.process.db.query_targeting.scanned_per_returned",
Attributes: []string{"scanned_type"},
},
MongodbatlasProcessDbStorage: metricInfo{
Name: "mongodbatlas.process.db.storage",
Attributes: []string{"storage_status"},
},
MongodbatlasProcessGlobalLock: metricInfo{
Name: "mongodbatlas.process.global_lock",
Attributes: []string{"global_lock_state"},
},
MongodbatlasProcessIndexBtreeMissRatio: metricInfo{
Name: "mongodbatlas.process.index.btree_miss_ratio",
},
MongodbatlasProcessIndexCounters: metricInfo{
Name: "mongodbatlas.process.index.counters",
Attributes: []string{"btree_counter_type"},
},
MongodbatlasProcessJournalingCommits: metricInfo{
Name: "mongodbatlas.process.journaling.commits",
},
MongodbatlasProcessJournalingDataFiles: metricInfo{
Name: "mongodbatlas.process.journaling.data_files",
},
MongodbatlasProcessJournalingWritten: metricInfo{
Name: "mongodbatlas.process.journaling.written",
},
MongodbatlasProcessMemoryUsage: metricInfo{
Name: "mongodbatlas.process.memory.usage",
Attributes: []string{"memory_state"},
},
MongodbatlasProcessNetworkIo: metricInfo{
Name: "mongodbatlas.process.network.io",
Attributes: []string{"direction"},
},
MongodbatlasProcessNetworkRequests: metricInfo{
Name: "mongodbatlas.process.network.requests",
},
MongodbatlasProcessOplogRate: metricInfo{
Name: "mongodbatlas.process.oplog.rate",
},
MongodbatlasProcessOplogTime: metricInfo{
Name: "mongodbatlas.process.oplog.time",
Attributes: []string{"oplog_type"},
},
MongodbatlasProcessPageFaults: metricInfo{
Name: "mongodbatlas.process.page_faults",
Attributes: []string{"memory_issue_type"},
},
MongodbatlasProcessRestarts: metricInfo{
Name: "mongodbatlas.process.restarts",
},
MongodbatlasProcessTickets: metricInfo{
Name: "mongodbatlas.process.tickets",
Attributes: []string{"ticket_type"},
},
MongodbatlasSystemCPUNormalizedUsageAverage: metricInfo{
Name: "mongodbatlas.system.cpu.normalized.usage.average",
Attributes: []string{"cpu_state"},
},
MongodbatlasSystemCPUNormalizedUsageMax: metricInfo{
Name: "mongodbatlas.system.cpu.normalized.usage.max",
Attributes: []string{"cpu_state"},
},
MongodbatlasSystemCPUUsageAverage: metricInfo{
Name: "mongodbatlas.system.cpu.usage.average",
Attributes: []string{"cpu_state"},
},
MongodbatlasSystemCPUUsageMax: metricInfo{
Name: "mongodbatlas.system.cpu.usage.max",
Attributes: []string{"cpu_state"},
},
MongodbatlasSystemFtsCPUNormalizedUsage: metricInfo{
Name: "mongodbatlas.system.fts.cpu.normalized.usage",
Attributes: []string{"cpu_state"},
},
MongodbatlasSystemFtsCPUUsage: metricInfo{
Name: "mongodbatlas.system.fts.cpu.usage",
Attributes: []string{"cpu_state"},
},
MongodbatlasSystemFtsDiskUsed: metricInfo{
Name: "mongodbatlas.system.fts.disk.used",
},
MongodbatlasSystemFtsMemoryUsage: metricInfo{
Name: "mongodbatlas.system.fts.memory.usage",
Attributes: []string{"memory_state"},
},
MongodbatlasSystemMemoryUsageAverage: metricInfo{
Name: "mongodbatlas.system.memory.usage.average",
Attributes: []string{"memory_status"},
},
MongodbatlasSystemMemoryUsageMax: metricInfo{
Name: "mongodbatlas.system.memory.usage.max",
Attributes: []string{"memory_status"},
},
MongodbatlasSystemNetworkIoAverage: metricInfo{
Name: "mongodbatlas.system.network.io.average",
Attributes: []string{"direction"},
},
MongodbatlasSystemNetworkIoMax: metricInfo{
Name: "mongodbatlas.system.network.io.max",
Attributes: []string{"direction"},
},
MongodbatlasSystemPagingIoAverage: metricInfo{
Name: "mongodbatlas.system.paging.io.average",
Attributes: []string{"direction"},
},
MongodbatlasSystemPagingIoMax: metricInfo{
Name: "mongodbatlas.system.paging.io.max",
Attributes: []string{"direction"},
},
MongodbatlasSystemPagingUsageAverage: metricInfo{
Name: "mongodbatlas.system.paging.usage.average",
Attributes: []string{"memory_state"},
},
MongodbatlasSystemPagingUsageMax: metricInfo{
Name: "mongodbatlas.system.paging.usage.max",
Attributes: []string{"memory_state"},
},
}
type metricsInfo struct {
MongodbatlasDbCounts metricInfo
MongodbatlasDbSize metricInfo
MongodbatlasDiskPartitionIopsAverage metricInfo
MongodbatlasDiskPartitionIopsMax metricInfo
MongodbatlasDiskPartitionLatencyAverage metricInfo
MongodbatlasDiskPartitionLatencyMax metricInfo
MongodbatlasDiskPartitionQueueDepth metricInfo
MongodbatlasDiskPartitionSpaceAverage metricInfo
MongodbatlasDiskPartitionSpaceMax metricInfo
MongodbatlasDiskPartitionThroughput metricInfo
MongodbatlasDiskPartitionUsageAverage metricInfo
MongodbatlasDiskPartitionUsageMax metricInfo
MongodbatlasDiskPartitionUtilizationAverage metricInfo
MongodbatlasDiskPartitionUtilizationMax metricInfo
MongodbatlasProcessAsserts metricInfo
MongodbatlasProcessBackgroundFlush metricInfo
MongodbatlasProcessCacheIo metricInfo
MongodbatlasProcessCacheRatio metricInfo
MongodbatlasProcessCacheSize metricInfo
MongodbatlasProcessConnections metricInfo
MongodbatlasProcessCPUChildrenNormalizedUsageAverage metricInfo
MongodbatlasProcessCPUChildrenNormalizedUsageMax metricInfo
MongodbatlasProcessCPUChildrenUsageAverage metricInfo
MongodbatlasProcessCPUChildrenUsageMax metricInfo
MongodbatlasProcessCPUNormalizedUsageAverage metricInfo
MongodbatlasProcessCPUNormalizedUsageMax metricInfo
MongodbatlasProcessCPUUsageAverage metricInfo
MongodbatlasProcessCPUUsageMax metricInfo
MongodbatlasProcessCursors metricInfo
MongodbatlasProcessDbDocumentRate metricInfo
MongodbatlasProcessDbOperationsRate metricInfo
MongodbatlasProcessDbOperationsTime metricInfo
MongodbatlasProcessDbQueryExecutorScanned metricInfo
MongodbatlasProcessDbQueryTargetingScannedPerReturned metricInfo
MongodbatlasProcessDbStorage metricInfo
MongodbatlasProcessGlobalLock metricInfo
MongodbatlasProcessIndexBtreeMissRatio metricInfo
MongodbatlasProcessIndexCounters metricInfo
MongodbatlasProcessJournalingCommits metricInfo
MongodbatlasProcessJournalingDataFiles metricInfo
MongodbatlasProcessJournalingWritten metricInfo
MongodbatlasProcessMemoryUsage metricInfo
MongodbatlasProcessNetworkIo metricInfo
MongodbatlasProcessNetworkRequests metricInfo
MongodbatlasProcessOplogRate metricInfo
MongodbatlasProcessOplogTime metricInfo
MongodbatlasProcessPageFaults metricInfo
MongodbatlasProcessRestarts metricInfo
MongodbatlasProcessTickets metricInfo
MongodbatlasSystemCPUNormalizedUsageAverage metricInfo
MongodbatlasSystemCPUNormalizedUsageMax metricInfo
MongodbatlasSystemCPUUsageAverage metricInfo
MongodbatlasSystemCPUUsageMax metricInfo
MongodbatlasSystemFtsCPUNormalizedUsage metricInfo
MongodbatlasSystemFtsCPUUsage metricInfo
MongodbatlasSystemFtsDiskUsed metricInfo
MongodbatlasSystemFtsMemoryUsage metricInfo
MongodbatlasSystemMemoryUsageAverage metricInfo
MongodbatlasSystemMemoryUsageMax metricInfo
MongodbatlasSystemNetworkIoAverage metricInfo
MongodbatlasSystemNetworkIoMax metricInfo
MongodbatlasSystemPagingIoAverage metricInfo
MongodbatlasSystemPagingIoMax metricInfo
MongodbatlasSystemPagingUsageAverage metricInfo
MongodbatlasSystemPagingUsageMax metricInfo
}
type metricInfo struct {
Name string
Attributes []string
}
type metricMongodbatlasDbCounts struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasDbCountsMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.db.counts metric with initial data.
func (m *metricMongodbatlasDbCounts) init() {
m.data.SetName("mongodbatlas.db.counts")
m.data.SetDescription("Database feature size")
m.data.SetUnit("{objects}")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasDbCounts) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, objectTypeAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasDbCountsMetricAttributeKeyObjectType) {
dp.Attributes().PutStr("object_type", objectTypeAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasDbCounts) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasDbCounts) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasDbCounts(cfg MongodbatlasDbCountsMetricConfig) metricMongodbatlasDbCounts {
m := metricMongodbatlasDbCounts{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasDbSize struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasDbSizeMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.db.size metric with initial data.
func (m *metricMongodbatlasDbSize) init() {
m.data.SetName("mongodbatlas.db.size")
m.data.SetDescription("Database feature size")
m.data.SetUnit("By")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasDbSize) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, objectTypeAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasDbSizeMetricAttributeKeyObjectType) {
dp.Attributes().PutStr("object_type", objectTypeAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasDbSize) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasDbSize) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasDbSize(cfg MongodbatlasDbSizeMetricConfig) metricMongodbatlasDbSize {
m := metricMongodbatlasDbSize{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasDiskPartitionIopsAverage struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasDiskPartitionIopsAverageMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.disk.partition.iops.average metric with initial data.
func (m *metricMongodbatlasDiskPartitionIopsAverage) init() {
m.data.SetName("mongodbatlas.disk.partition.iops.average")
m.data.SetDescription("Disk partition iops")
m.data.SetUnit("{ops}/s")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasDiskPartitionIopsAverage) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, diskDirectionAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasDiskPartitionIopsAverageMetricAttributeKeyDiskDirection) {
dp.Attributes().PutStr("disk_direction", diskDirectionAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasDiskPartitionIopsAverage) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasDiskPartitionIopsAverage) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasDiskPartitionIopsAverage(cfg MongodbatlasDiskPartitionIopsAverageMetricConfig) metricMongodbatlasDiskPartitionIopsAverage {
m := metricMongodbatlasDiskPartitionIopsAverage{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasDiskPartitionIopsMax struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasDiskPartitionIopsMaxMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.disk.partition.iops.max metric with initial data.
func (m *metricMongodbatlasDiskPartitionIopsMax) init() {
m.data.SetName("mongodbatlas.disk.partition.iops.max")
m.data.SetDescription("Disk partition iops")
m.data.SetUnit("{ops}/s")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasDiskPartitionIopsMax) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, diskDirectionAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasDiskPartitionIopsMaxMetricAttributeKeyDiskDirection) {
dp.Attributes().PutStr("disk_direction", diskDirectionAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasDiskPartitionIopsMax) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasDiskPartitionIopsMax) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasDiskPartitionIopsMax(cfg MongodbatlasDiskPartitionIopsMaxMetricConfig) metricMongodbatlasDiskPartitionIopsMax {
m := metricMongodbatlasDiskPartitionIopsMax{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasDiskPartitionLatencyAverage struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasDiskPartitionLatencyAverageMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.disk.partition.latency.average metric with initial data.
func (m *metricMongodbatlasDiskPartitionLatencyAverage) init() {
m.data.SetName("mongodbatlas.disk.partition.latency.average")
m.data.SetDescription("Disk partition latency")
m.data.SetUnit("ms")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasDiskPartitionLatencyAverage) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, diskDirectionAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasDiskPartitionLatencyAverageMetricAttributeKeyDiskDirection) {
dp.Attributes().PutStr("disk_direction", diskDirectionAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasDiskPartitionLatencyAverage) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasDiskPartitionLatencyAverage) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasDiskPartitionLatencyAverage(cfg MongodbatlasDiskPartitionLatencyAverageMetricConfig) metricMongodbatlasDiskPartitionLatencyAverage {
m := metricMongodbatlasDiskPartitionLatencyAverage{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasDiskPartitionLatencyMax struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasDiskPartitionLatencyMaxMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.disk.partition.latency.max metric with initial data.
func (m *metricMongodbatlasDiskPartitionLatencyMax) init() {
m.data.SetName("mongodbatlas.disk.partition.latency.max")
m.data.SetDescription("Disk partition latency")
m.data.SetUnit("ms")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasDiskPartitionLatencyMax) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, diskDirectionAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasDiskPartitionLatencyMaxMetricAttributeKeyDiskDirection) {
dp.Attributes().PutStr("disk_direction", diskDirectionAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasDiskPartitionLatencyMax) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasDiskPartitionLatencyMax) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasDiskPartitionLatencyMax(cfg MongodbatlasDiskPartitionLatencyMaxMetricConfig) metricMongodbatlasDiskPartitionLatencyMax {
m := metricMongodbatlasDiskPartitionLatencyMax{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasDiskPartitionQueueDepth struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasDiskPartitionQueueDepthMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
}
// init fills mongodbatlas.disk.partition.queue.depth metric with initial data.
func (m *metricMongodbatlasDiskPartitionQueueDepth) init() {
m.data.SetName("mongodbatlas.disk.partition.queue.depth")
m.data.SetDescription("Disk queue depth")
m.data.SetUnit("1")
m.data.SetEmptyGauge()
}
func (m *metricMongodbatlasDiskPartitionQueueDepth) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64) {
if !m.config.Enabled {
return
}
dp := m.data.Gauge().DataPoints().AppendEmpty()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
dp.SetDoubleValue(val)
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasDiskPartitionQueueDepth) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasDiskPartitionQueueDepth) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasDiskPartitionQueueDepth(cfg MongodbatlasDiskPartitionQueueDepthMetricConfig) metricMongodbatlasDiskPartitionQueueDepth {
m := metricMongodbatlasDiskPartitionQueueDepth{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasDiskPartitionSpaceAverage struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasDiskPartitionSpaceAverageMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.disk.partition.space.average metric with initial data.
func (m *metricMongodbatlasDiskPartitionSpaceAverage) init() {
m.data.SetName("mongodbatlas.disk.partition.space.average")
m.data.SetDescription("Disk partition space")
m.data.SetUnit("By")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasDiskPartitionSpaceAverage) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, diskStatusAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasDiskPartitionSpaceAverageMetricAttributeKeyDiskStatus) {
dp.Attributes().PutStr("disk_status", diskStatusAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasDiskPartitionSpaceAverage) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasDiskPartitionSpaceAverage) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasDiskPartitionSpaceAverage(cfg MongodbatlasDiskPartitionSpaceAverageMetricConfig) metricMongodbatlasDiskPartitionSpaceAverage {
m := metricMongodbatlasDiskPartitionSpaceAverage{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasDiskPartitionSpaceMax struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasDiskPartitionSpaceMaxMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.disk.partition.space.max metric with initial data.
func (m *metricMongodbatlasDiskPartitionSpaceMax) init() {
m.data.SetName("mongodbatlas.disk.partition.space.max")
m.data.SetDescription("Disk partition space")
m.data.SetUnit("By")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasDiskPartitionSpaceMax) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, diskStatusAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasDiskPartitionSpaceMaxMetricAttributeKeyDiskStatus) {
dp.Attributes().PutStr("disk_status", diskStatusAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasDiskPartitionSpaceMax) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasDiskPartitionSpaceMax) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasDiskPartitionSpaceMax(cfg MongodbatlasDiskPartitionSpaceMaxMetricConfig) metricMongodbatlasDiskPartitionSpaceMax {
m := metricMongodbatlasDiskPartitionSpaceMax{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasDiskPartitionThroughput struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasDiskPartitionThroughputMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.disk.partition.throughput metric with initial data.
func (m *metricMongodbatlasDiskPartitionThroughput) init() {
m.data.SetName("mongodbatlas.disk.partition.throughput")
m.data.SetDescription("Disk throughput")
m.data.SetUnit("By/s")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasDiskPartitionThroughput) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, diskDirectionAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasDiskPartitionThroughputMetricAttributeKeyDiskDirection) {
dp.Attributes().PutStr("disk_direction", diskDirectionAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasDiskPartitionThroughput) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasDiskPartitionThroughput) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasDiskPartitionThroughput(cfg MongodbatlasDiskPartitionThroughputMetricConfig) metricMongodbatlasDiskPartitionThroughput {
m := metricMongodbatlasDiskPartitionThroughput{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasDiskPartitionUsageAverage struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasDiskPartitionUsageAverageMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.disk.partition.usage.average metric with initial data.
func (m *metricMongodbatlasDiskPartitionUsageAverage) init() {
m.data.SetName("mongodbatlas.disk.partition.usage.average")
m.data.SetDescription("Disk partition usage (%)")
m.data.SetUnit("1")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasDiskPartitionUsageAverage) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, diskStatusAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasDiskPartitionUsageAverageMetricAttributeKeyDiskStatus) {
dp.Attributes().PutStr("disk_status", diskStatusAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasDiskPartitionUsageAverage) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasDiskPartitionUsageAverage) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasDiskPartitionUsageAverage(cfg MongodbatlasDiskPartitionUsageAverageMetricConfig) metricMongodbatlasDiskPartitionUsageAverage {
m := metricMongodbatlasDiskPartitionUsageAverage{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasDiskPartitionUsageMax struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasDiskPartitionUsageMaxMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.disk.partition.usage.max metric with initial data.
func (m *metricMongodbatlasDiskPartitionUsageMax) init() {
m.data.SetName("mongodbatlas.disk.partition.usage.max")
m.data.SetDescription("Disk partition usage (%)")
m.data.SetUnit("1")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasDiskPartitionUsageMax) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, diskStatusAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasDiskPartitionUsageMaxMetricAttributeKeyDiskStatus) {
dp.Attributes().PutStr("disk_status", diskStatusAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasDiskPartitionUsageMax) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasDiskPartitionUsageMax) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasDiskPartitionUsageMax(cfg MongodbatlasDiskPartitionUsageMaxMetricConfig) metricMongodbatlasDiskPartitionUsageMax {
m := metricMongodbatlasDiskPartitionUsageMax{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasDiskPartitionUtilizationAverage struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasDiskPartitionUtilizationAverageMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
}
// init fills mongodbatlas.disk.partition.utilization.average metric with initial data.
func (m *metricMongodbatlasDiskPartitionUtilizationAverage) init() {
m.data.SetName("mongodbatlas.disk.partition.utilization.average")
m.data.SetDescription("The percentage of time during which requests are being issued to and serviced by the partition.")
m.data.SetUnit("1")
m.data.SetEmptyGauge()
}
func (m *metricMongodbatlasDiskPartitionUtilizationAverage) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64) {
if !m.config.Enabled {
return
}
dp := m.data.Gauge().DataPoints().AppendEmpty()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
dp.SetDoubleValue(val)
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasDiskPartitionUtilizationAverage) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasDiskPartitionUtilizationAverage) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasDiskPartitionUtilizationAverage(cfg MongodbatlasDiskPartitionUtilizationAverageMetricConfig) metricMongodbatlasDiskPartitionUtilizationAverage {
m := metricMongodbatlasDiskPartitionUtilizationAverage{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasDiskPartitionUtilizationMax struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasDiskPartitionUtilizationMaxMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
}
// init fills mongodbatlas.disk.partition.utilization.max metric with initial data.
func (m *metricMongodbatlasDiskPartitionUtilizationMax) init() {
m.data.SetName("mongodbatlas.disk.partition.utilization.max")
m.data.SetDescription("The maximum percentage of time during which requests are being issued to and serviced by the partition.")
m.data.SetUnit("1")
m.data.SetEmptyGauge()
}
func (m *metricMongodbatlasDiskPartitionUtilizationMax) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64) {
if !m.config.Enabled {
return
}
dp := m.data.Gauge().DataPoints().AppendEmpty()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
dp.SetDoubleValue(val)
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasDiskPartitionUtilizationMax) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasDiskPartitionUtilizationMax) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasDiskPartitionUtilizationMax(cfg MongodbatlasDiskPartitionUtilizationMaxMetricConfig) metricMongodbatlasDiskPartitionUtilizationMax {
m := metricMongodbatlasDiskPartitionUtilizationMax{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessAsserts struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessAssertsMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.process.asserts metric with initial data.
func (m *metricMongodbatlasProcessAsserts) init() {
m.data.SetName("mongodbatlas.process.asserts")
m.data.SetDescription("Number of assertions per second")
m.data.SetUnit("{assertions}/s")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasProcessAsserts) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, assertTypeAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasProcessAssertsMetricAttributeKeyAssertType) {
dp.Attributes().PutStr("assert_type", assertTypeAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessAsserts) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessAsserts) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessAsserts(cfg MongodbatlasProcessAssertsMetricConfig) metricMongodbatlasProcessAsserts {
m := metricMongodbatlasProcessAsserts{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessBackgroundFlush struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessBackgroundFlushMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
}
// init fills mongodbatlas.process.background_flush metric with initial data.
func (m *metricMongodbatlasProcessBackgroundFlush) init() {
m.data.SetName("mongodbatlas.process.background_flush")
m.data.SetDescription("Amount of data flushed in the background")
m.data.SetUnit("1")
m.data.SetEmptyGauge()
}
func (m *metricMongodbatlasProcessBackgroundFlush) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64) {
if !m.config.Enabled {
return
}
dp := m.data.Gauge().DataPoints().AppendEmpty()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
dp.SetDoubleValue(val)
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessBackgroundFlush) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessBackgroundFlush) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessBackgroundFlush(cfg MongodbatlasProcessBackgroundFlushMetricConfig) metricMongodbatlasProcessBackgroundFlush {
m := metricMongodbatlasProcessBackgroundFlush{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessCacheIo struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessCacheIoMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.process.cache.io metric with initial data.
func (m *metricMongodbatlasProcessCacheIo) init() {
m.data.SetName("mongodbatlas.process.cache.io")
m.data.SetDescription("Cache throughput (per second)")
m.data.SetUnit("By")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasProcessCacheIo) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, cacheDirectionAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasProcessCacheIoMetricAttributeKeyCacheDirection) {
dp.Attributes().PutStr("cache_direction", cacheDirectionAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessCacheIo) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessCacheIo) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessCacheIo(cfg MongodbatlasProcessCacheIoMetricConfig) metricMongodbatlasProcessCacheIo {
m := metricMongodbatlasProcessCacheIo{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessCacheRatio struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessCacheRatioMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.process.cache.ratio metric with initial data.
func (m *metricMongodbatlasProcessCacheRatio) init() {
m.data.SetName("mongodbatlas.process.cache.ratio")
m.data.SetDescription("Cache ratios represented as (%)")
m.data.SetUnit("%")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasProcessCacheRatio) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, cacheRatioTypeAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasProcessCacheRatioMetricAttributeKeyCacheRatioType) {
dp.Attributes().PutStr("cache_ratio_type", cacheRatioTypeAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessCacheRatio) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessCacheRatio) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessCacheRatio(cfg MongodbatlasProcessCacheRatioMetricConfig) metricMongodbatlasProcessCacheRatio {
m := metricMongodbatlasProcessCacheRatio{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessCacheSize struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessCacheSizeMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.process.cache.size metric with initial data.
func (m *metricMongodbatlasProcessCacheSize) init() {
m.data.SetName("mongodbatlas.process.cache.size")
m.data.SetDescription("Cache sizes")
m.data.SetUnit("By")
m.data.SetEmptySum()
m.data.Sum().SetIsMonotonic(false)
m.data.Sum().SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
m.data.Sum().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasProcessCacheSize) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, cacheStatusAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasProcessCacheSizeMetricAttributeKeyCacheStatus) {
dp.Attributes().PutStr("cache_status", cacheStatusAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Sum().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessCacheSize) updateCapacity() {
if m.data.Sum().DataPoints().Len() > m.capacity {
m.capacity = m.data.Sum().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessCacheSize) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Sum().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Sum().DataPoints().At(i).SetDoubleValue(m.data.Sum().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessCacheSize(cfg MongodbatlasProcessCacheSizeMetricConfig) metricMongodbatlasProcessCacheSize {
m := metricMongodbatlasProcessCacheSize{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessConnections struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessConnectionsMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
}
// init fills mongodbatlas.process.connections metric with initial data.
func (m *metricMongodbatlasProcessConnections) init() {
m.data.SetName("mongodbatlas.process.connections")
m.data.SetDescription("Number of current connections")
m.data.SetUnit("{connections}")
m.data.SetEmptySum()
m.data.Sum().SetIsMonotonic(false)
m.data.Sum().SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
}
func (m *metricMongodbatlasProcessConnections) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64) {
if !m.config.Enabled {
return
}
dp := m.data.Sum().DataPoints().AppendEmpty()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
dp.SetDoubleValue(val)
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessConnections) updateCapacity() {
if m.data.Sum().DataPoints().Len() > m.capacity {
m.capacity = m.data.Sum().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessConnections) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Sum().DataPoints().Len() > 0 {
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessConnections(cfg MongodbatlasProcessConnectionsMetricConfig) metricMongodbatlasProcessConnections {
m := metricMongodbatlasProcessConnections{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessCPUChildrenNormalizedUsageAverage struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessCPUChildrenNormalizedUsageAverageMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.process.cpu.children.normalized.usage.average metric with initial data.
func (m *metricMongodbatlasProcessCPUChildrenNormalizedUsageAverage) init() {
m.data.SetName("mongodbatlas.process.cpu.children.normalized.usage.average")
m.data.SetDescription("CPU Usage for child processes, normalized to pct")
m.data.SetUnit("1")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasProcessCPUChildrenNormalizedUsageAverage) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, cpuStateAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasProcessCPUChildrenNormalizedUsageAverageMetricAttributeKeyCPUState) {
dp.Attributes().PutStr("cpu_state", cpuStateAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessCPUChildrenNormalizedUsageAverage) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessCPUChildrenNormalizedUsageAverage) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessCPUChildrenNormalizedUsageAverage(cfg MongodbatlasProcessCPUChildrenNormalizedUsageAverageMetricConfig) metricMongodbatlasProcessCPUChildrenNormalizedUsageAverage {
m := metricMongodbatlasProcessCPUChildrenNormalizedUsageAverage{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessCPUChildrenNormalizedUsageMax struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessCPUChildrenNormalizedUsageMaxMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.process.cpu.children.normalized.usage.max metric with initial data.
func (m *metricMongodbatlasProcessCPUChildrenNormalizedUsageMax) init() {
m.data.SetName("mongodbatlas.process.cpu.children.normalized.usage.max")
m.data.SetDescription("CPU Usage for child processes, normalized to pct")
m.data.SetUnit("1")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasProcessCPUChildrenNormalizedUsageMax) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, cpuStateAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasProcessCPUChildrenNormalizedUsageMaxMetricAttributeKeyCPUState) {
dp.Attributes().PutStr("cpu_state", cpuStateAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessCPUChildrenNormalizedUsageMax) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessCPUChildrenNormalizedUsageMax) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessCPUChildrenNormalizedUsageMax(cfg MongodbatlasProcessCPUChildrenNormalizedUsageMaxMetricConfig) metricMongodbatlasProcessCPUChildrenNormalizedUsageMax {
m := metricMongodbatlasProcessCPUChildrenNormalizedUsageMax{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessCPUChildrenUsageAverage struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessCPUChildrenUsageAverageMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.process.cpu.children.usage.average metric with initial data.
func (m *metricMongodbatlasProcessCPUChildrenUsageAverage) init() {
m.data.SetName("mongodbatlas.process.cpu.children.usage.average")
m.data.SetDescription("CPU Usage for child processes (%)")
m.data.SetUnit("1")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasProcessCPUChildrenUsageAverage) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, cpuStateAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasProcessCPUChildrenUsageAverageMetricAttributeKeyCPUState) {
dp.Attributes().PutStr("cpu_state", cpuStateAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessCPUChildrenUsageAverage) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessCPUChildrenUsageAverage) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessCPUChildrenUsageAverage(cfg MongodbatlasProcessCPUChildrenUsageAverageMetricConfig) metricMongodbatlasProcessCPUChildrenUsageAverage {
m := metricMongodbatlasProcessCPUChildrenUsageAverage{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessCPUChildrenUsageMax struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessCPUChildrenUsageMaxMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.process.cpu.children.usage.max metric with initial data.
func (m *metricMongodbatlasProcessCPUChildrenUsageMax) init() {
m.data.SetName("mongodbatlas.process.cpu.children.usage.max")
m.data.SetDescription("CPU Usage for child processes (%)")
m.data.SetUnit("1")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasProcessCPUChildrenUsageMax) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, cpuStateAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasProcessCPUChildrenUsageMaxMetricAttributeKeyCPUState) {
dp.Attributes().PutStr("cpu_state", cpuStateAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessCPUChildrenUsageMax) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessCPUChildrenUsageMax) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessCPUChildrenUsageMax(cfg MongodbatlasProcessCPUChildrenUsageMaxMetricConfig) metricMongodbatlasProcessCPUChildrenUsageMax {
m := metricMongodbatlasProcessCPUChildrenUsageMax{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessCPUNormalizedUsageAverage struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessCPUNormalizedUsageAverageMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.process.cpu.normalized.usage.average metric with initial data.
func (m *metricMongodbatlasProcessCPUNormalizedUsageAverage) init() {
m.data.SetName("mongodbatlas.process.cpu.normalized.usage.average")
m.data.SetDescription("CPU Usage, normalized to pct")
m.data.SetUnit("1")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasProcessCPUNormalizedUsageAverage) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, cpuStateAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasProcessCPUNormalizedUsageAverageMetricAttributeKeyCPUState) {
dp.Attributes().PutStr("cpu_state", cpuStateAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessCPUNormalizedUsageAverage) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessCPUNormalizedUsageAverage) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessCPUNormalizedUsageAverage(cfg MongodbatlasProcessCPUNormalizedUsageAverageMetricConfig) metricMongodbatlasProcessCPUNormalizedUsageAverage {
m := metricMongodbatlasProcessCPUNormalizedUsageAverage{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessCPUNormalizedUsageMax struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessCPUNormalizedUsageMaxMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.process.cpu.normalized.usage.max metric with initial data.
func (m *metricMongodbatlasProcessCPUNormalizedUsageMax) init() {
m.data.SetName("mongodbatlas.process.cpu.normalized.usage.max")
m.data.SetDescription("CPU Usage, normalized to pct")
m.data.SetUnit("1")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasProcessCPUNormalizedUsageMax) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, cpuStateAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasProcessCPUNormalizedUsageMaxMetricAttributeKeyCPUState) {
dp.Attributes().PutStr("cpu_state", cpuStateAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessCPUNormalizedUsageMax) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessCPUNormalizedUsageMax) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessCPUNormalizedUsageMax(cfg MongodbatlasProcessCPUNormalizedUsageMaxMetricConfig) metricMongodbatlasProcessCPUNormalizedUsageMax {
m := metricMongodbatlasProcessCPUNormalizedUsageMax{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessCPUUsageAverage struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessCPUUsageAverageMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.process.cpu.usage.average metric with initial data.
func (m *metricMongodbatlasProcessCPUUsageAverage) init() {
m.data.SetName("mongodbatlas.process.cpu.usage.average")
m.data.SetDescription("CPU Usage (%)")
m.data.SetUnit("1")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasProcessCPUUsageAverage) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, cpuStateAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasProcessCPUUsageAverageMetricAttributeKeyCPUState) {
dp.Attributes().PutStr("cpu_state", cpuStateAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessCPUUsageAverage) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessCPUUsageAverage) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessCPUUsageAverage(cfg MongodbatlasProcessCPUUsageAverageMetricConfig) metricMongodbatlasProcessCPUUsageAverage {
m := metricMongodbatlasProcessCPUUsageAverage{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessCPUUsageMax struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessCPUUsageMaxMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.process.cpu.usage.max metric with initial data.
func (m *metricMongodbatlasProcessCPUUsageMax) init() {
m.data.SetName("mongodbatlas.process.cpu.usage.max")
m.data.SetDescription("CPU Usage (%)")
m.data.SetUnit("1")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasProcessCPUUsageMax) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, cpuStateAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasProcessCPUUsageMaxMetricAttributeKeyCPUState) {
dp.Attributes().PutStr("cpu_state", cpuStateAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessCPUUsageMax) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessCPUUsageMax) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessCPUUsageMax(cfg MongodbatlasProcessCPUUsageMaxMetricConfig) metricMongodbatlasProcessCPUUsageMax {
m := metricMongodbatlasProcessCPUUsageMax{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessCursors struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessCursorsMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.process.cursors metric with initial data.
func (m *metricMongodbatlasProcessCursors) init() {
m.data.SetName("mongodbatlas.process.cursors")
m.data.SetDescription("Number of cursors")
m.data.SetUnit("{cursors}")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasProcessCursors) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, cursorStateAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasProcessCursorsMetricAttributeKeyCursorState) {
dp.Attributes().PutStr("cursor_state", cursorStateAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessCursors) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessCursors) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessCursors(cfg MongodbatlasProcessCursorsMetricConfig) metricMongodbatlasProcessCursors {
m := metricMongodbatlasProcessCursors{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessDbDocumentRate struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessDbDocumentRateMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.process.db.document.rate metric with initial data.
func (m *metricMongodbatlasProcessDbDocumentRate) init() {
m.data.SetName("mongodbatlas.process.db.document.rate")
m.data.SetDescription("Document access rates")
m.data.SetUnit("{documents}/s")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasProcessDbDocumentRate) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, documentStatusAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasProcessDbDocumentRateMetricAttributeKeyDocumentStatus) {
dp.Attributes().PutStr("document_status", documentStatusAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessDbDocumentRate) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessDbDocumentRate) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessDbDocumentRate(cfg MongodbatlasProcessDbDocumentRateMetricConfig) metricMongodbatlasProcessDbDocumentRate {
m := metricMongodbatlasProcessDbDocumentRate{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessDbOperationsRate struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessDbOperationsRateMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.process.db.operations.rate metric with initial data.
func (m *metricMongodbatlasProcessDbOperationsRate) init() {
m.data.SetName("mongodbatlas.process.db.operations.rate")
m.data.SetDescription("DB Operation Rates")
m.data.SetUnit("{operations}/s")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasProcessDbOperationsRate) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, operationAttributeValue string, clusterRoleAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasProcessDbOperationsRateMetricAttributeKeyOperation) {
dp.Attributes().PutStr("operation", operationAttributeValue)
}
if slices.Contains(m.config.EnabledAttributes, MongodbatlasProcessDbOperationsRateMetricAttributeKeyClusterRole) {
dp.Attributes().PutStr("cluster_role", clusterRoleAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessDbOperationsRate) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessDbOperationsRate) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessDbOperationsRate(cfg MongodbatlasProcessDbOperationsRateMetricConfig) metricMongodbatlasProcessDbOperationsRate {
m := metricMongodbatlasProcessDbOperationsRate{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessDbOperationsTime struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessDbOperationsTimeMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.process.db.operations.time metric with initial data.
func (m *metricMongodbatlasProcessDbOperationsTime) init() {
m.data.SetName("mongodbatlas.process.db.operations.time")
m.data.SetDescription("DB Operation Times")
m.data.SetUnit("ms")
m.data.SetEmptySum()
m.data.Sum().SetIsMonotonic(true)
m.data.Sum().SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
m.data.Sum().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasProcessDbOperationsTime) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, executionTypeAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasProcessDbOperationsTimeMetricAttributeKeyExecutionType) {
dp.Attributes().PutStr("execution_type", executionTypeAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Sum().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessDbOperationsTime) updateCapacity() {
if m.data.Sum().DataPoints().Len() > m.capacity {
m.capacity = m.data.Sum().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessDbOperationsTime) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Sum().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Sum().DataPoints().At(i).SetDoubleValue(m.data.Sum().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessDbOperationsTime(cfg MongodbatlasProcessDbOperationsTimeMetricConfig) metricMongodbatlasProcessDbOperationsTime {
m := metricMongodbatlasProcessDbOperationsTime{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessDbQueryExecutorScanned struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessDbQueryExecutorScannedMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.process.db.query_executor.scanned metric with initial data.
func (m *metricMongodbatlasProcessDbQueryExecutorScanned) init() {
m.data.SetName("mongodbatlas.process.db.query_executor.scanned")
m.data.SetDescription("Scanned objects")
m.data.SetUnit("{objects}/s")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasProcessDbQueryExecutorScanned) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, scannedTypeAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasProcessDbQueryExecutorScannedMetricAttributeKeyScannedType) {
dp.Attributes().PutStr("scanned_type", scannedTypeAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessDbQueryExecutorScanned) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessDbQueryExecutorScanned) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessDbQueryExecutorScanned(cfg MongodbatlasProcessDbQueryExecutorScannedMetricConfig) metricMongodbatlasProcessDbQueryExecutorScanned {
m := metricMongodbatlasProcessDbQueryExecutorScanned{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessDbQueryTargetingScannedPerReturned struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessDbQueryTargetingScannedPerReturnedMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.process.db.query_targeting.scanned_per_returned metric with initial data.
func (m *metricMongodbatlasProcessDbQueryTargetingScannedPerReturned) init() {
m.data.SetName("mongodbatlas.process.db.query_targeting.scanned_per_returned")
m.data.SetDescription("Scanned objects per returned")
m.data.SetUnit("{scanned}/{returned}")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasProcessDbQueryTargetingScannedPerReturned) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, scannedTypeAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasProcessDbQueryTargetingScannedPerReturnedMetricAttributeKeyScannedType) {
dp.Attributes().PutStr("scanned_type", scannedTypeAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessDbQueryTargetingScannedPerReturned) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessDbQueryTargetingScannedPerReturned) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessDbQueryTargetingScannedPerReturned(cfg MongodbatlasProcessDbQueryTargetingScannedPerReturnedMetricConfig) metricMongodbatlasProcessDbQueryTargetingScannedPerReturned {
m := metricMongodbatlasProcessDbQueryTargetingScannedPerReturned{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessDbStorage struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessDbStorageMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.process.db.storage metric with initial data.
func (m *metricMongodbatlasProcessDbStorage) init() {
m.data.SetName("mongodbatlas.process.db.storage")
m.data.SetDescription("Storage used by the database")
m.data.SetUnit("By")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasProcessDbStorage) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, storageStatusAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasProcessDbStorageMetricAttributeKeyStorageStatus) {
dp.Attributes().PutStr("storage_status", storageStatusAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessDbStorage) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessDbStorage) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessDbStorage(cfg MongodbatlasProcessDbStorageMetricConfig) metricMongodbatlasProcessDbStorage {
m := metricMongodbatlasProcessDbStorage{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessGlobalLock struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessGlobalLockMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.process.global_lock metric with initial data.
func (m *metricMongodbatlasProcessGlobalLock) init() {
m.data.SetName("mongodbatlas.process.global_lock")
m.data.SetDescription("Number and status of locks")
m.data.SetUnit("{locks}")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasProcessGlobalLock) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, globalLockStateAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasProcessGlobalLockMetricAttributeKeyGlobalLockState) {
dp.Attributes().PutStr("global_lock_state", globalLockStateAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessGlobalLock) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessGlobalLock) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessGlobalLock(cfg MongodbatlasProcessGlobalLockMetricConfig) metricMongodbatlasProcessGlobalLock {
m := metricMongodbatlasProcessGlobalLock{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessIndexBtreeMissRatio struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessIndexBtreeMissRatioMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
}
// init fills mongodbatlas.process.index.btree_miss_ratio metric with initial data.
func (m *metricMongodbatlasProcessIndexBtreeMissRatio) init() {
m.data.SetName("mongodbatlas.process.index.btree_miss_ratio")
m.data.SetDescription("Index miss ratio (%)")
m.data.SetUnit("1")
m.data.SetEmptyGauge()
}
func (m *metricMongodbatlasProcessIndexBtreeMissRatio) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64) {
if !m.config.Enabled {
return
}
dp := m.data.Gauge().DataPoints().AppendEmpty()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
dp.SetDoubleValue(val)
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessIndexBtreeMissRatio) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessIndexBtreeMissRatio) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessIndexBtreeMissRatio(cfg MongodbatlasProcessIndexBtreeMissRatioMetricConfig) metricMongodbatlasProcessIndexBtreeMissRatio {
m := metricMongodbatlasProcessIndexBtreeMissRatio{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessIndexCounters struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessIndexCountersMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.process.index.counters metric with initial data.
func (m *metricMongodbatlasProcessIndexCounters) init() {
m.data.SetName("mongodbatlas.process.index.counters")
m.data.SetDescription("Indexes")
m.data.SetUnit("{indexes}")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasProcessIndexCounters) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, btreeCounterTypeAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasProcessIndexCountersMetricAttributeKeyBtreeCounterType) {
dp.Attributes().PutStr("btree_counter_type", btreeCounterTypeAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessIndexCounters) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessIndexCounters) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessIndexCounters(cfg MongodbatlasProcessIndexCountersMetricConfig) metricMongodbatlasProcessIndexCounters {
m := metricMongodbatlasProcessIndexCounters{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessJournalingCommits struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessJournalingCommitsMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
}
// init fills mongodbatlas.process.journaling.commits metric with initial data.
func (m *metricMongodbatlasProcessJournalingCommits) init() {
m.data.SetName("mongodbatlas.process.journaling.commits")
m.data.SetDescription("Journaling commits")
m.data.SetUnit("{commits}")
m.data.SetEmptyGauge()
}
func (m *metricMongodbatlasProcessJournalingCommits) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64) {
if !m.config.Enabled {
return
}
dp := m.data.Gauge().DataPoints().AppendEmpty()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
dp.SetDoubleValue(val)
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessJournalingCommits) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessJournalingCommits) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessJournalingCommits(cfg MongodbatlasProcessJournalingCommitsMetricConfig) metricMongodbatlasProcessJournalingCommits {
m := metricMongodbatlasProcessJournalingCommits{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessJournalingDataFiles struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessJournalingDataFilesMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
}
// init fills mongodbatlas.process.journaling.data_files metric with initial data.
func (m *metricMongodbatlasProcessJournalingDataFiles) init() {
m.data.SetName("mongodbatlas.process.journaling.data_files")
m.data.SetDescription("Data file sizes")
m.data.SetUnit("MiBy")
m.data.SetEmptyGauge()
}
func (m *metricMongodbatlasProcessJournalingDataFiles) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64) {
if !m.config.Enabled {
return
}
dp := m.data.Gauge().DataPoints().AppendEmpty()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
dp.SetDoubleValue(val)
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessJournalingDataFiles) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessJournalingDataFiles) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessJournalingDataFiles(cfg MongodbatlasProcessJournalingDataFilesMetricConfig) metricMongodbatlasProcessJournalingDataFiles {
m := metricMongodbatlasProcessJournalingDataFiles{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessJournalingWritten struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessJournalingWrittenMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
}
// init fills mongodbatlas.process.journaling.written metric with initial data.
func (m *metricMongodbatlasProcessJournalingWritten) init() {
m.data.SetName("mongodbatlas.process.journaling.written")
m.data.SetDescription("Journals written")
m.data.SetUnit("MiBy")
m.data.SetEmptyGauge()
}
func (m *metricMongodbatlasProcessJournalingWritten) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64) {
if !m.config.Enabled {
return
}
dp := m.data.Gauge().DataPoints().AppendEmpty()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
dp.SetDoubleValue(val)
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessJournalingWritten) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessJournalingWritten) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessJournalingWritten(cfg MongodbatlasProcessJournalingWrittenMetricConfig) metricMongodbatlasProcessJournalingWritten {
m := metricMongodbatlasProcessJournalingWritten{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessMemoryUsage struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessMemoryUsageMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.process.memory.usage metric with initial data.
func (m *metricMongodbatlasProcessMemoryUsage) init() {
m.data.SetName("mongodbatlas.process.memory.usage")
m.data.SetDescription("Memory Usage")
m.data.SetUnit("By")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasProcessMemoryUsage) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, memoryStateAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasProcessMemoryUsageMetricAttributeKeyMemoryState) {
dp.Attributes().PutStr("memory_state", memoryStateAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessMemoryUsage) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessMemoryUsage) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessMemoryUsage(cfg MongodbatlasProcessMemoryUsageMetricConfig) metricMongodbatlasProcessMemoryUsage {
m := metricMongodbatlasProcessMemoryUsage{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessNetworkIo struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessNetworkIoMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.process.network.io metric with initial data.
func (m *metricMongodbatlasProcessNetworkIo) init() {
m.data.SetName("mongodbatlas.process.network.io")
m.data.SetDescription("Network IO")
m.data.SetUnit("By/s")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasProcessNetworkIo) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, directionAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasProcessNetworkIoMetricAttributeKeyDirection) {
dp.Attributes().PutStr("direction", directionAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessNetworkIo) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessNetworkIo) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessNetworkIo(cfg MongodbatlasProcessNetworkIoMetricConfig) metricMongodbatlasProcessNetworkIo {
m := metricMongodbatlasProcessNetworkIo{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessNetworkRequests struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessNetworkRequestsMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
}
// init fills mongodbatlas.process.network.requests metric with initial data.
func (m *metricMongodbatlasProcessNetworkRequests) init() {
m.data.SetName("mongodbatlas.process.network.requests")
m.data.SetDescription("Network requests")
m.data.SetUnit("{requests}")
m.data.SetEmptySum()
m.data.Sum().SetIsMonotonic(true)
m.data.Sum().SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
}
func (m *metricMongodbatlasProcessNetworkRequests) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64) {
if !m.config.Enabled {
return
}
dp := m.data.Sum().DataPoints().AppendEmpty()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
dp.SetDoubleValue(val)
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessNetworkRequests) updateCapacity() {
if m.data.Sum().DataPoints().Len() > m.capacity {
m.capacity = m.data.Sum().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessNetworkRequests) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Sum().DataPoints().Len() > 0 {
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessNetworkRequests(cfg MongodbatlasProcessNetworkRequestsMetricConfig) metricMongodbatlasProcessNetworkRequests {
m := metricMongodbatlasProcessNetworkRequests{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessOplogRate struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessOplogRateMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
}
// init fills mongodbatlas.process.oplog.rate metric with initial data.
func (m *metricMongodbatlasProcessOplogRate) init() {
m.data.SetName("mongodbatlas.process.oplog.rate")
m.data.SetDescription("Execution rate by operation")
m.data.SetUnit("GiBy/h")
m.data.SetEmptyGauge()
}
func (m *metricMongodbatlasProcessOplogRate) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64) {
if !m.config.Enabled {
return
}
dp := m.data.Gauge().DataPoints().AppendEmpty()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
dp.SetDoubleValue(val)
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessOplogRate) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessOplogRate) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessOplogRate(cfg MongodbatlasProcessOplogRateMetricConfig) metricMongodbatlasProcessOplogRate {
m := metricMongodbatlasProcessOplogRate{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessOplogTime struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessOplogTimeMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.process.oplog.time metric with initial data.
func (m *metricMongodbatlasProcessOplogTime) init() {
m.data.SetName("mongodbatlas.process.oplog.time")
m.data.SetDescription("Execution time by operation")
m.data.SetUnit("s")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasProcessOplogTime) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, oplogTypeAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasProcessOplogTimeMetricAttributeKeyOplogType) {
dp.Attributes().PutStr("oplog_type", oplogTypeAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessOplogTime) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessOplogTime) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessOplogTime(cfg MongodbatlasProcessOplogTimeMetricConfig) metricMongodbatlasProcessOplogTime {
m := metricMongodbatlasProcessOplogTime{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessPageFaults struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessPageFaultsMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.process.page_faults metric with initial data.
func (m *metricMongodbatlasProcessPageFaults) init() {
m.data.SetName("mongodbatlas.process.page_faults")
m.data.SetDescription("Page faults")
m.data.SetUnit("{faults}/s")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasProcessPageFaults) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, memoryIssueTypeAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasProcessPageFaultsMetricAttributeKeyMemoryIssueType) {
dp.Attributes().PutStr("memory_issue_type", memoryIssueTypeAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessPageFaults) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessPageFaults) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessPageFaults(cfg MongodbatlasProcessPageFaultsMetricConfig) metricMongodbatlasProcessPageFaults {
m := metricMongodbatlasProcessPageFaults{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessRestarts struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessRestartsMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
}
// init fills mongodbatlas.process.restarts metric with initial data.
func (m *metricMongodbatlasProcessRestarts) init() {
m.data.SetName("mongodbatlas.process.restarts")
m.data.SetDescription("Restarts in last hour")
m.data.SetUnit("{restarts}/h")
m.data.SetEmptyGauge()
}
func (m *metricMongodbatlasProcessRestarts) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64) {
if !m.config.Enabled {
return
}
dp := m.data.Gauge().DataPoints().AppendEmpty()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
dp.SetDoubleValue(val)
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessRestarts) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessRestarts) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessRestarts(cfg MongodbatlasProcessRestartsMetricConfig) metricMongodbatlasProcessRestarts {
m := metricMongodbatlasProcessRestarts{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasProcessTickets struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasProcessTicketsMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.process.tickets metric with initial data.
func (m *metricMongodbatlasProcessTickets) init() {
m.data.SetName("mongodbatlas.process.tickets")
m.data.SetDescription("Tickets")
m.data.SetUnit("{tickets}")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasProcessTickets) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, ticketTypeAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasProcessTicketsMetricAttributeKeyTicketType) {
dp.Attributes().PutStr("ticket_type", ticketTypeAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasProcessTickets) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasProcessTickets) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasProcessTickets(cfg MongodbatlasProcessTicketsMetricConfig) metricMongodbatlasProcessTickets {
m := metricMongodbatlasProcessTickets{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasSystemCPUNormalizedUsageAverage struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasSystemCPUNormalizedUsageAverageMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.system.cpu.normalized.usage.average metric with initial data.
func (m *metricMongodbatlasSystemCPUNormalizedUsageAverage) init() {
m.data.SetName("mongodbatlas.system.cpu.normalized.usage.average")
m.data.SetDescription("System CPU Normalized to pct")
m.data.SetUnit("1")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasSystemCPUNormalizedUsageAverage) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, cpuStateAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasSystemCPUNormalizedUsageAverageMetricAttributeKeyCPUState) {
dp.Attributes().PutStr("cpu_state", cpuStateAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasSystemCPUNormalizedUsageAverage) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasSystemCPUNormalizedUsageAverage) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasSystemCPUNormalizedUsageAverage(cfg MongodbatlasSystemCPUNormalizedUsageAverageMetricConfig) metricMongodbatlasSystemCPUNormalizedUsageAverage {
m := metricMongodbatlasSystemCPUNormalizedUsageAverage{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasSystemCPUNormalizedUsageMax struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasSystemCPUNormalizedUsageMaxMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.system.cpu.normalized.usage.max metric with initial data.
func (m *metricMongodbatlasSystemCPUNormalizedUsageMax) init() {
m.data.SetName("mongodbatlas.system.cpu.normalized.usage.max")
m.data.SetDescription("System CPU Normalized to pct")
m.data.SetUnit("1")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasSystemCPUNormalizedUsageMax) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, cpuStateAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasSystemCPUNormalizedUsageMaxMetricAttributeKeyCPUState) {
dp.Attributes().PutStr("cpu_state", cpuStateAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasSystemCPUNormalizedUsageMax) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasSystemCPUNormalizedUsageMax) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasSystemCPUNormalizedUsageMax(cfg MongodbatlasSystemCPUNormalizedUsageMaxMetricConfig) metricMongodbatlasSystemCPUNormalizedUsageMax {
m := metricMongodbatlasSystemCPUNormalizedUsageMax{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasSystemCPUUsageAverage struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasSystemCPUUsageAverageMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.system.cpu.usage.average metric with initial data.
func (m *metricMongodbatlasSystemCPUUsageAverage) init() {
m.data.SetName("mongodbatlas.system.cpu.usage.average")
m.data.SetDescription("System CPU Usage (%)")
m.data.SetUnit("1")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasSystemCPUUsageAverage) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, cpuStateAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasSystemCPUUsageAverageMetricAttributeKeyCPUState) {
dp.Attributes().PutStr("cpu_state", cpuStateAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasSystemCPUUsageAverage) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasSystemCPUUsageAverage) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasSystemCPUUsageAverage(cfg MongodbatlasSystemCPUUsageAverageMetricConfig) metricMongodbatlasSystemCPUUsageAverage {
m := metricMongodbatlasSystemCPUUsageAverage{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasSystemCPUUsageMax struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasSystemCPUUsageMaxMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.system.cpu.usage.max metric with initial data.
func (m *metricMongodbatlasSystemCPUUsageMax) init() {
m.data.SetName("mongodbatlas.system.cpu.usage.max")
m.data.SetDescription("System CPU Usage (%)")
m.data.SetUnit("1")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasSystemCPUUsageMax) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, cpuStateAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasSystemCPUUsageMaxMetricAttributeKeyCPUState) {
dp.Attributes().PutStr("cpu_state", cpuStateAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasSystemCPUUsageMax) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasSystemCPUUsageMax) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasSystemCPUUsageMax(cfg MongodbatlasSystemCPUUsageMaxMetricConfig) metricMongodbatlasSystemCPUUsageMax {
m := metricMongodbatlasSystemCPUUsageMax{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasSystemFtsCPUNormalizedUsage struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasSystemFtsCPUNormalizedUsageMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.system.fts.cpu.normalized.usage metric with initial data.
func (m *metricMongodbatlasSystemFtsCPUNormalizedUsage) init() {
m.data.SetName("mongodbatlas.system.fts.cpu.normalized.usage")
m.data.SetDescription("Full text search disk usage (%)")
m.data.SetUnit("1")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasSystemFtsCPUNormalizedUsage) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, cpuStateAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasSystemFtsCPUNormalizedUsageMetricAttributeKeyCPUState) {
dp.Attributes().PutStr("cpu_state", cpuStateAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasSystemFtsCPUNormalizedUsage) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasSystemFtsCPUNormalizedUsage) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasSystemFtsCPUNormalizedUsage(cfg MongodbatlasSystemFtsCPUNormalizedUsageMetricConfig) metricMongodbatlasSystemFtsCPUNormalizedUsage {
m := metricMongodbatlasSystemFtsCPUNormalizedUsage{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasSystemFtsCPUUsage struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasSystemFtsCPUUsageMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.system.fts.cpu.usage metric with initial data.
func (m *metricMongodbatlasSystemFtsCPUUsage) init() {
m.data.SetName("mongodbatlas.system.fts.cpu.usage")
m.data.SetDescription("Full-text search (%)")
m.data.SetUnit("1")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasSystemFtsCPUUsage) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, cpuStateAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasSystemFtsCPUUsageMetricAttributeKeyCPUState) {
dp.Attributes().PutStr("cpu_state", cpuStateAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasSystemFtsCPUUsage) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasSystemFtsCPUUsage) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasSystemFtsCPUUsage(cfg MongodbatlasSystemFtsCPUUsageMetricConfig) metricMongodbatlasSystemFtsCPUUsage {
m := metricMongodbatlasSystemFtsCPUUsage{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasSystemFtsDiskUsed struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasSystemFtsDiskUsedMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
}
// init fills mongodbatlas.system.fts.disk.used metric with initial data.
func (m *metricMongodbatlasSystemFtsDiskUsed) init() {
m.data.SetName("mongodbatlas.system.fts.disk.used")
m.data.SetDescription("Full text search disk usage")
m.data.SetUnit("By")
m.data.SetEmptyGauge()
}
func (m *metricMongodbatlasSystemFtsDiskUsed) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64) {
if !m.config.Enabled {
return
}
dp := m.data.Gauge().DataPoints().AppendEmpty()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
dp.SetDoubleValue(val)
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasSystemFtsDiskUsed) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasSystemFtsDiskUsed) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasSystemFtsDiskUsed(cfg MongodbatlasSystemFtsDiskUsedMetricConfig) metricMongodbatlasSystemFtsDiskUsed {
m := metricMongodbatlasSystemFtsDiskUsed{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasSystemFtsMemoryUsage struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasSystemFtsMemoryUsageMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.system.fts.memory.usage metric with initial data.
func (m *metricMongodbatlasSystemFtsMemoryUsage) init() {
m.data.SetName("mongodbatlas.system.fts.memory.usage")
m.data.SetDescription("Full-text search")
m.data.SetUnit("MiBy")
m.data.SetEmptySum()
m.data.Sum().SetIsMonotonic(true)
m.data.Sum().SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
m.data.Sum().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasSystemFtsMemoryUsage) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, memoryStateAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasSystemFtsMemoryUsageMetricAttributeKeyMemoryState) {
dp.Attributes().PutStr("memory_state", memoryStateAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Sum().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasSystemFtsMemoryUsage) updateCapacity() {
if m.data.Sum().DataPoints().Len() > m.capacity {
m.capacity = m.data.Sum().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasSystemFtsMemoryUsage) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Sum().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Sum().DataPoints().At(i).SetDoubleValue(m.data.Sum().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasSystemFtsMemoryUsage(cfg MongodbatlasSystemFtsMemoryUsageMetricConfig) metricMongodbatlasSystemFtsMemoryUsage {
m := metricMongodbatlasSystemFtsMemoryUsage{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasSystemMemoryUsageAverage struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasSystemMemoryUsageAverageMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.system.memory.usage.average metric with initial data.
func (m *metricMongodbatlasSystemMemoryUsageAverage) init() {
m.data.SetName("mongodbatlas.system.memory.usage.average")
m.data.SetDescription("System Memory Usage")
m.data.SetUnit("KiBy")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasSystemMemoryUsageAverage) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, memoryStatusAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasSystemMemoryUsageAverageMetricAttributeKeyMemoryStatus) {
dp.Attributes().PutStr("memory_status", memoryStatusAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasSystemMemoryUsageAverage) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasSystemMemoryUsageAverage) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasSystemMemoryUsageAverage(cfg MongodbatlasSystemMemoryUsageAverageMetricConfig) metricMongodbatlasSystemMemoryUsageAverage {
m := metricMongodbatlasSystemMemoryUsageAverage{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasSystemMemoryUsageMax struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasSystemMemoryUsageMaxMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.system.memory.usage.max metric with initial data.
func (m *metricMongodbatlasSystemMemoryUsageMax) init() {
m.data.SetName("mongodbatlas.system.memory.usage.max")
m.data.SetDescription("System Memory Usage")
m.data.SetUnit("KiBy")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasSystemMemoryUsageMax) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, memoryStatusAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasSystemMemoryUsageMaxMetricAttributeKeyMemoryStatus) {
dp.Attributes().PutStr("memory_status", memoryStatusAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasSystemMemoryUsageMax) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasSystemMemoryUsageMax) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasSystemMemoryUsageMax(cfg MongodbatlasSystemMemoryUsageMaxMetricConfig) metricMongodbatlasSystemMemoryUsageMax {
m := metricMongodbatlasSystemMemoryUsageMax{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasSystemNetworkIoAverage struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasSystemNetworkIoAverageMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.system.network.io.average metric with initial data.
func (m *metricMongodbatlasSystemNetworkIoAverage) init() {
m.data.SetName("mongodbatlas.system.network.io.average")
m.data.SetDescription("System Network IO")
m.data.SetUnit("By/s")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasSystemNetworkIoAverage) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, directionAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasSystemNetworkIoAverageMetricAttributeKeyDirection) {
dp.Attributes().PutStr("direction", directionAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasSystemNetworkIoAverage) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasSystemNetworkIoAverage) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasSystemNetworkIoAverage(cfg MongodbatlasSystemNetworkIoAverageMetricConfig) metricMongodbatlasSystemNetworkIoAverage {
m := metricMongodbatlasSystemNetworkIoAverage{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasSystemNetworkIoMax struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasSystemNetworkIoMaxMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.system.network.io.max metric with initial data.
func (m *metricMongodbatlasSystemNetworkIoMax) init() {
m.data.SetName("mongodbatlas.system.network.io.max")
m.data.SetDescription("System Network IO")
m.data.SetUnit("By/s")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasSystemNetworkIoMax) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, directionAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasSystemNetworkIoMaxMetricAttributeKeyDirection) {
dp.Attributes().PutStr("direction", directionAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasSystemNetworkIoMax) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasSystemNetworkIoMax) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasSystemNetworkIoMax(cfg MongodbatlasSystemNetworkIoMaxMetricConfig) metricMongodbatlasSystemNetworkIoMax {
m := metricMongodbatlasSystemNetworkIoMax{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasSystemPagingIoAverage struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasSystemPagingIoAverageMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.system.paging.io.average metric with initial data.
func (m *metricMongodbatlasSystemPagingIoAverage) init() {
m.data.SetName("mongodbatlas.system.paging.io.average")
m.data.SetDescription("Swap IO")
m.data.SetUnit("{pages}/s")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasSystemPagingIoAverage) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, directionAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasSystemPagingIoAverageMetricAttributeKeyDirection) {
dp.Attributes().PutStr("direction", directionAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasSystemPagingIoAverage) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasSystemPagingIoAverage) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasSystemPagingIoAverage(cfg MongodbatlasSystemPagingIoAverageMetricConfig) metricMongodbatlasSystemPagingIoAverage {
m := metricMongodbatlasSystemPagingIoAverage{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasSystemPagingIoMax struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasSystemPagingIoMaxMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.system.paging.io.max metric with initial data.
func (m *metricMongodbatlasSystemPagingIoMax) init() {
m.data.SetName("mongodbatlas.system.paging.io.max")
m.data.SetDescription("Swap IO")
m.data.SetUnit("{pages}/s")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasSystemPagingIoMax) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, directionAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasSystemPagingIoMaxMetricAttributeKeyDirection) {
dp.Attributes().PutStr("direction", directionAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasSystemPagingIoMax) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasSystemPagingIoMax) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasSystemPagingIoMax(cfg MongodbatlasSystemPagingIoMaxMetricConfig) metricMongodbatlasSystemPagingIoMax {
m := metricMongodbatlasSystemPagingIoMax{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasSystemPagingUsageAverage struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasSystemPagingUsageAverageMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.system.paging.usage.average metric with initial data.
func (m *metricMongodbatlasSystemPagingUsageAverage) init() {
m.data.SetName("mongodbatlas.system.paging.usage.average")
m.data.SetDescription("Swap usage")
m.data.SetUnit("KiBy")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasSystemPagingUsageAverage) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, memoryStateAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasSystemPagingUsageAverageMetricAttributeKeyMemoryState) {
dp.Attributes().PutStr("memory_state", memoryStateAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasSystemPagingUsageAverage) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasSystemPagingUsageAverage) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasSystemPagingUsageAverage(cfg MongodbatlasSystemPagingUsageAverageMetricConfig) metricMongodbatlasSystemPagingUsageAverage {
m := metricMongodbatlasSystemPagingUsageAverage{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
type metricMongodbatlasSystemPagingUsageMax struct {
data pmetric.Metric // data buffer for generated metric.
config MongodbatlasSystemPagingUsageMaxMetricConfig // metric config provided by user.
capacity int // max observed number of data points added to the metric.
aggDataPoints []float64 // slice containing number of aggregated datapoints at each index
dpIndex map[uint64]int // maps a data point's hash to its index, for O(1) dedup lookup.
}
// init fills mongodbatlas.system.paging.usage.max metric with initial data.
func (m *metricMongodbatlasSystemPagingUsageMax) init() {
m.data.SetName("mongodbatlas.system.paging.usage.max")
m.data.SetDescription("Swap usage")
m.data.SetUnit("KiBy")
m.data.SetEmptyGauge()
m.data.Gauge().DataPoints().EnsureCapacity(m.capacity)
m.aggDataPoints = m.aggDataPoints[:0]
m.dpIndex = make(map[uint64]int, m.capacity)
}
func (m *metricMongodbatlasSystemPagingUsageMax) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val float64, memoryStateAttributeValue string) {
if !m.config.Enabled {
return
}
dp := pmetric.NewNumberDataPoint()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
if slices.Contains(m.config.EnabledAttributes, MongodbatlasSystemPagingUsageMaxMetricAttributeKeyMemoryState) {
dp.Attributes().PutStr("memory_state", memoryStateAttributeValue)
}
var s string
key := dataPointKey(dp)
dps := m.data.Gauge().DataPoints()
if i, ok := m.dpIndex[key]; ok {
dpi := dps.At(i)
switch s = m.config.AggregationStrategy; s {
case AggregationStrategySum, AggregationStrategyAvg:
dpi.SetDoubleValue(dpi.DoubleValue() + val)
m.aggDataPoints[i] += 1
return
case AggregationStrategyMin:
if dpi.DoubleValue() > val {
dpi.SetDoubleValue(val)
}
return
case AggregationStrategyMax:
if dpi.DoubleValue() < val {
dpi.SetDoubleValue(val)
}
return
}
}
dp.SetDoubleValue(val)
m.aggDataPoints = append(m.aggDataPoints, 1)
m.dpIndex[key] = dps.Len()
dp.MoveTo(dps.AppendEmpty())
}
// updateCapacity saves max length of data point slices that will be used for the slice capacity.
func (m *metricMongodbatlasSystemPagingUsageMax) updateCapacity() {
if m.data.Gauge().DataPoints().Len() > m.capacity {
m.capacity = m.data.Gauge().DataPoints().Len()
}
}
// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points.
func (m *metricMongodbatlasSystemPagingUsageMax) emit(metrics pmetric.MetricSlice) {
if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 {
if m.config.AggregationStrategy == AggregationStrategyAvg {
for i, aggCount := range m.aggDataPoints {
m.data.Gauge().DataPoints().At(i).SetDoubleValue(m.data.Gauge().DataPoints().At(i).DoubleValue() / aggCount)
}
}
m.updateCapacity()
m.data.MoveTo(metrics.AppendEmpty())
m.init()
}
}
func newMetricMongodbatlasSystemPagingUsageMax(cfg MongodbatlasSystemPagingUsageMaxMetricConfig) metricMongodbatlasSystemPagingUsageMax {
m := metricMongodbatlasSystemPagingUsageMax{config: cfg}
if cfg.Enabled {
m.data = pmetric.NewMetric()
m.init()
}
return m
}
// MetricsBuilder provides an interface for scrapers to report metrics while taking care of all the transformations
// required to produce metric representation defined in metadata and user config.
type MetricsBuilder struct {
config MetricsBuilderConfig // config of the metrics builder.
startTime pcommon.Timestamp // start time that will be applied to all recorded data points.
metricsCapacity int // maximum observed number of metrics per resource.
metricsBuffer pmetric.Metrics // accumulates metrics data before emitting.
buildInfo component.BuildInfo // contains version information.
resourceAttributeIncludeFilter map[string]filter.Filter
resourceAttributeExcludeFilter map[string]filter.Filter
metricMongodbatlasDbCounts metricMongodbatlasDbCounts
metricMongodbatlasDbSize metricMongodbatlasDbSize
metricMongodbatlasDiskPartitionIopsAverage metricMongodbatlasDiskPartitionIopsAverage
metricMongodbatlasDiskPartitionIopsMax metricMongodbatlasDiskPartitionIopsMax
metricMongodbatlasDiskPartitionLatencyAverage metricMongodbatlasDiskPartitionLatencyAverage
metricMongodbatlasDiskPartitionLatencyMax metricMongodbatlasDiskPartitionLatencyMax
metricMongodbatlasDiskPartitionQueueDepth metricMongodbatlasDiskPartitionQueueDepth
metricMongodbatlasDiskPartitionSpaceAverage metricMongodbatlasDiskPartitionSpaceAverage
metricMongodbatlasDiskPartitionSpaceMax metricMongodbatlasDiskPartitionSpaceMax
metricMongodbatlasDiskPartitionThroughput metricMongodbatlasDiskPartitionThroughput
metricMongodbatlasDiskPartitionUsageAverage metricMongodbatlasDiskPartitionUsageAverage
metricMongodbatlasDiskPartitionUsageMax metricMongodbatlasDiskPartitionUsageMax
metricMongodbatlasDiskPartitionUtilizationAverage metricMongodbatlasDiskPartitionUtilizationAverage
metricMongodbatlasDiskPartitionUtilizationMax metricMongodbatlasDiskPartitionUtilizationMax
metricMongodbatlasProcessAsserts metricMongodbatlasProcessAsserts
metricMongodbatlasProcessBackgroundFlush metricMongodbatlasProcessBackgroundFlush
metricMongodbatlasProcessCacheIo metricMongodbatlasProcessCacheIo
metricMongodbatlasProcessCacheRatio metricMongodbatlasProcessCacheRatio
metricMongodbatlasProcessCacheSize metricMongodbatlasProcessCacheSize
metricMongodbatlasProcessConnections metricMongodbatlasProcessConnections
metricMongodbatlasProcessCPUChildrenNormalizedUsageAverage metricMongodbatlasProcessCPUChildrenNormalizedUsageAverage
metricMongodbatlasProcessCPUChildrenNormalizedUsageMax metricMongodbatlasProcessCPUChildrenNormalizedUsageMax
metricMongodbatlasProcessCPUChildrenUsageAverage metricMongodbatlasProcessCPUChildrenUsageAverage
metricMongodbatlasProcessCPUChildrenUsageMax metricMongodbatlasProcessCPUChildrenUsageMax
metricMongodbatlasProcessCPUNormalizedUsageAverage metricMongodbatlasProcessCPUNormalizedUsageAverage
metricMongodbatlasProcessCPUNormalizedUsageMax metricMongodbatlasProcessCPUNormalizedUsageMax
metricMongodbatlasProcessCPUUsageAverage metricMongodbatlasProcessCPUUsageAverage
metricMongodbatlasProcessCPUUsageMax metricMongodbatlasProcessCPUUsageMax
metricMongodbatlasProcessCursors metricMongodbatlasProcessCursors
metricMongodbatlasProcessDbDocumentRate metricMongodbatlasProcessDbDocumentRate
metricMongodbatlasProcessDbOperationsRate metricMongodbatlasProcessDbOperationsRate
metricMongodbatlasProcessDbOperationsTime metricMongodbatlasProcessDbOperationsTime
metricMongodbatlasProcessDbQueryExecutorScanned metricMongodbatlasProcessDbQueryExecutorScanned
metricMongodbatlasProcessDbQueryTargetingScannedPerReturned metricMongodbatlasProcessDbQueryTargetingScannedPerReturned
metricMongodbatlasProcessDbStorage metricMongodbatlasProcessDbStorage
metricMongodbatlasProcessGlobalLock metricMongodbatlasProcessGlobalLock
metricMongodbatlasProcessIndexBtreeMissRatio metricMongodbatlasProcessIndexBtreeMissRatio
metricMongodbatlasProcessIndexCounters metricMongodbatlasProcessIndexCounters
metricMongodbatlasProcessJournalingCommits metricMongodbatlasProcessJournalingCommits
metricMongodbatlasProcessJournalingDataFiles metricMongodbatlasProcessJournalingDataFiles
metricMongodbatlasProcessJournalingWritten metricMongodbatlasProcessJournalingWritten
metricMongodbatlasProcessMemoryUsage metricMongodbatlasProcessMemoryUsage
metricMongodbatlasProcessNetworkIo metricMongodbatlasProcessNetworkIo
metricMongodbatlasProcessNetworkRequests metricMongodbatlasProcessNetworkRequests
metricMongodbatlasProcessOplogRate metricMongodbatlasProcessOplogRate
metricMongodbatlasProcessOplogTime metricMongodbatlasProcessOplogTime
metricMongodbatlasProcessPageFaults metricMongodbatlasProcessPageFaults
metricMongodbatlasProcessRestarts metricMongodbatlasProcessRestarts
metricMongodbatlasProcessTickets metricMongodbatlasProcessTickets
metricMongodbatlasSystemCPUNormalizedUsageAverage metricMongodbatlasSystemCPUNormalizedUsageAverage
metricMongodbatlasSystemCPUNormalizedUsageMax metricMongodbatlasSystemCPUNormalizedUsageMax
metricMongodbatlasSystemCPUUsageAverage metricMongodbatlasSystemCPUUsageAverage
metricMongodbatlasSystemCPUUsageMax metricMongodbatlasSystemCPUUsageMax
metricMongodbatlasSystemFtsCPUNormalizedUsage metricMongodbatlasSystemFtsCPUNormalizedUsage
metricMongodbatlasSystemFtsCPUUsage metricMongodbatlasSystemFtsCPUUsage
metricMongodbatlasSystemFtsDiskUsed metricMongodbatlasSystemFtsDiskUsed
metricMongodbatlasSystemFtsMemoryUsage metricMongodbatlasSystemFtsMemoryUsage
metricMongodbatlasSystemMemoryUsageAverage metricMongodbatlasSystemMemoryUsageAverage
metricMongodbatlasSystemMemoryUsageMax metricMongodbatlasSystemMemoryUsageMax
metricMongodbatlasSystemNetworkIoAverage metricMongodbatlasSystemNetworkIoAverage
metricMongodbatlasSystemNetworkIoMax metricMongodbatlasSystemNetworkIoMax
metricMongodbatlasSystemPagingIoAverage metricMongodbatlasSystemPagingIoAverage
metricMongodbatlasSystemPagingIoMax metricMongodbatlasSystemPagingIoMax
metricMongodbatlasSystemPagingUsageAverage metricMongodbatlasSystemPagingUsageAverage
metricMongodbatlasSystemPagingUsageMax metricMongodbatlasSystemPagingUsageMax
}
// MetricBuilderOption applies changes to default metrics builder.
type MetricBuilderOption interface {
apply(*MetricsBuilder)
}
type metricBuilderOptionFunc func(mb *MetricsBuilder)
func (mbof metricBuilderOptionFunc) apply(mb *MetricsBuilder) {
mbof(mb)
}
// WithStartTime sets startTime on the metrics builder.
func WithStartTime(startTime pcommon.Timestamp) MetricBuilderOption {
return metricBuilderOptionFunc(func(mb *MetricsBuilder) {
mb.startTime = startTime
})
}
func NewMetricsBuilder(mbc MetricsBuilderConfig, settings receiver.Settings, options ...MetricBuilderOption) *MetricsBuilder {
mb := &MetricsBuilder{
config: mbc,
startTime: pcommon.NewTimestampFromTime(time.Now()),
metricsBuffer: pmetric.NewMetrics(),
buildInfo: settings.BuildInfo,
metricMongodbatlasDbCounts: newMetricMongodbatlasDbCounts(mbc.Metrics.MongodbatlasDbCounts),
metricMongodbatlasDbSize: newMetricMongodbatlasDbSize(mbc.Metrics.MongodbatlasDbSize),
metricMongodbatlasDiskPartitionIopsAverage: newMetricMongodbatlasDiskPartitionIopsAverage(mbc.Metrics.MongodbatlasDiskPartitionIopsAverage),
metricMongodbatlasDiskPartitionIopsMax: newMetricMongodbatlasDiskPartitionIopsMax(mbc.Metrics.MongodbatlasDiskPartitionIopsMax),
metricMongodbatlasDiskPartitionLatencyAverage: newMetricMongodbatlasDiskPartitionLatencyAverage(mbc.Metrics.MongodbatlasDiskPartitionLatencyAverage),
metricMongodbatlasDiskPartitionLatencyMax: newMetricMongodbatlasDiskPartitionLatencyMax(mbc.Metrics.MongodbatlasDiskPartitionLatencyMax),
metricMongodbatlasDiskPartitionQueueDepth: newMetricMongodbatlasDiskPartitionQueueDepth(mbc.Metrics.MongodbatlasDiskPartitionQueueDepth),
metricMongodbatlasDiskPartitionSpaceAverage: newMetricMongodbatlasDiskPartitionSpaceAverage(mbc.Metrics.MongodbatlasDiskPartitionSpaceAverage),
metricMongodbatlasDiskPartitionSpaceMax: newMetricMongodbatlasDiskPartitionSpaceMax(mbc.Metrics.MongodbatlasDiskPartitionSpaceMax),
metricMongodbatlasDiskPartitionThroughput: newMetricMongodbatlasDiskPartitionThroughput(mbc.Metrics.MongodbatlasDiskPartitionThroughput),
metricMongodbatlasDiskPartitionUsageAverage: newMetricMongodbatlasDiskPartitionUsageAverage(mbc.Metrics.MongodbatlasDiskPartitionUsageAverage),
metricMongodbatlasDiskPartitionUsageMax: newMetricMongodbatlasDiskPartitionUsageMax(mbc.Metrics.MongodbatlasDiskPartitionUsageMax),
metricMongodbatlasDiskPartitionUtilizationAverage: newMetricMongodbatlasDiskPartitionUtilizationAverage(mbc.Metrics.MongodbatlasDiskPartitionUtilizationAverage),
metricMongodbatlasDiskPartitionUtilizationMax: newMetricMongodbatlasDiskPartitionUtilizationMax(mbc.Metrics.MongodbatlasDiskPartitionUtilizationMax),
metricMongodbatlasProcessAsserts: newMetricMongodbatlasProcessAsserts(mbc.Metrics.MongodbatlasProcessAsserts),
metricMongodbatlasProcessBackgroundFlush: newMetricMongodbatlasProcessBackgroundFlush(mbc.Metrics.MongodbatlasProcessBackgroundFlush),
metricMongodbatlasProcessCacheIo: newMetricMongodbatlasProcessCacheIo(mbc.Metrics.MongodbatlasProcessCacheIo),
metricMongodbatlasProcessCacheRatio: newMetricMongodbatlasProcessCacheRatio(mbc.Metrics.MongodbatlasProcessCacheRatio),
metricMongodbatlasProcessCacheSize: newMetricMongodbatlasProcessCacheSize(mbc.Metrics.MongodbatlasProcessCacheSize),
metricMongodbatlasProcessConnections: newMetricMongodbatlasProcessConnections(mbc.Metrics.MongodbatlasProcessConnections),
metricMongodbatlasProcessCPUChildrenNormalizedUsageAverage: newMetricMongodbatlasProcessCPUChildrenNormalizedUsageAverage(mbc.Metrics.MongodbatlasProcessCPUChildrenNormalizedUsageAverage),
metricMongodbatlasProcessCPUChildrenNormalizedUsageMax: newMetricMongodbatlasProcessCPUChildrenNormalizedUsageMax(mbc.Metrics.MongodbatlasProcessCPUChildrenNormalizedUsageMax),
metricMongodbatlasProcessCPUChildrenUsageAverage: newMetricMongodbatlasProcessCPUChildrenUsageAverage(mbc.Metrics.MongodbatlasProcessCPUChildrenUsageAverage),
metricMongodbatlasProcessCPUChildrenUsageMax: newMetricMongodbatlasProcessCPUChildrenUsageMax(mbc.Metrics.MongodbatlasProcessCPUChildrenUsageMax),
metricMongodbatlasProcessCPUNormalizedUsageAverage: newMetricMongodbatlasProcessCPUNormalizedUsageAverage(mbc.Metrics.MongodbatlasProcessCPUNormalizedUsageAverage),
metricMongodbatlasProcessCPUNormalizedUsageMax: newMetricMongodbatlasProcessCPUNormalizedUsageMax(mbc.Metrics.MongodbatlasProcessCPUNormalizedUsageMax),
metricMongodbatlasProcessCPUUsageAverage: newMetricMongodbatlasProcessCPUUsageAverage(mbc.Metrics.MongodbatlasProcessCPUUsageAverage),
metricMongodbatlasProcessCPUUsageMax: newMetricMongodbatlasProcessCPUUsageMax(mbc.Metrics.MongodbatlasProcessCPUUsageMax),
metricMongodbatlasProcessCursors: newMetricMongodbatlasProcessCursors(mbc.Metrics.MongodbatlasProcessCursors),
metricMongodbatlasProcessDbDocumentRate: newMetricMongodbatlasProcessDbDocumentRate(mbc.Metrics.MongodbatlasProcessDbDocumentRate),
metricMongodbatlasProcessDbOperationsRate: newMetricMongodbatlasProcessDbOperationsRate(mbc.Metrics.MongodbatlasProcessDbOperationsRate),
metricMongodbatlasProcessDbOperationsTime: newMetricMongodbatlasProcessDbOperationsTime(mbc.Metrics.MongodbatlasProcessDbOperationsTime),
metricMongodbatlasProcessDbQueryExecutorScanned: newMetricMongodbatlasProcessDbQueryExecutorScanned(mbc.Metrics.MongodbatlasProcessDbQueryExecutorScanned),
metricMongodbatlasProcessDbQueryTargetingScannedPerReturned: newMetricMongodbatlasProcessDbQueryTargetingScannedPerReturned(mbc.Metrics.MongodbatlasProcessDbQueryTargetingScannedPerReturned),
metricMongodbatlasProcessDbStorage: newMetricMongodbatlasProcessDbStorage(mbc.Metrics.MongodbatlasProcessDbStorage),
metricMongodbatlasProcessGlobalLock: newMetricMongodbatlasProcessGlobalLock(mbc.Metrics.MongodbatlasProcessGlobalLock),
metricMongodbatlasProcessIndexBtreeMissRatio: newMetricMongodbatlasProcessIndexBtreeMissRatio(mbc.Metrics.MongodbatlasProcessIndexBtreeMissRatio),
metricMongodbatlasProcessIndexCounters: newMetricMongodbatlasProcessIndexCounters(mbc.Metrics.MongodbatlasProcessIndexCounters),
metricMongodbatlasProcessJournalingCommits: newMetricMongodbatlasProcessJournalingCommits(mbc.Metrics.MongodbatlasProcessJournalingCommits),
metricMongodbatlasProcessJournalingDataFiles: newMetricMongodbatlasProcessJournalingDataFiles(mbc.Metrics.MongodbatlasProcessJournalingDataFiles),
metricMongodbatlasProcessJournalingWritten: newMetricMongodbatlasProcessJournalingWritten(mbc.Metrics.MongodbatlasProcessJournalingWritten),
metricMongodbatlasProcessMemoryUsage: newMetricMongodbatlasProcessMemoryUsage(mbc.Metrics.MongodbatlasProcessMemoryUsage),
metricMongodbatlasProcessNetworkIo: newMetricMongodbatlasProcessNetworkIo(mbc.Metrics.MongodbatlasProcessNetworkIo),
metricMongodbatlasProcessNetworkRequests: newMetricMongodbatlasProcessNetworkRequests(mbc.Metrics.MongodbatlasProcessNetworkRequests),
metricMongodbatlasProcessOplogRate: newMetricMongodbatlasProcessOplogRate(mbc.Metrics.MongodbatlasProcessOplogRate),
metricMongodbatlasProcessOplogTime: newMetricMongodbatlasProcessOplogTime(mbc.Metrics.MongodbatlasProcessOplogTime),
metricMongodbatlasProcessPageFaults: newMetricMongodbatlasProcessPageFaults(mbc.Metrics.MongodbatlasProcessPageFaults),
metricMongodbatlasProcessRestarts: newMetricMongodbatlasProcessRestarts(mbc.Metrics.MongodbatlasProcessRestarts),
metricMongodbatlasProcessTickets: newMetricMongodbatlasProcessTickets(mbc.Metrics.MongodbatlasProcessTickets),
metricMongodbatlasSystemCPUNormalizedUsageAverage: newMetricMongodbatlasSystemCPUNormalizedUsageAverage(mbc.Metrics.MongodbatlasSystemCPUNormalizedUsageAverage),
metricMongodbatlasSystemCPUNormalizedUsageMax: newMetricMongodbatlasSystemCPUNormalizedUsageMax(mbc.Metrics.MongodbatlasSystemCPUNormalizedUsageMax),
metricMongodbatlasSystemCPUUsageAverage: newMetricMongodbatlasSystemCPUUsageAverage(mbc.Metrics.MongodbatlasSystemCPUUsageAverage),
metricMongodbatlasSystemCPUUsageMax: newMetricMongodbatlasSystemCPUUsageMax(mbc.Metrics.MongodbatlasSystemCPUUsageMax),
metricMongodbatlasSystemFtsCPUNormalizedUsage: newMetricMongodbatlasSystemFtsCPUNormalizedUsage(mbc.Metrics.MongodbatlasSystemFtsCPUNormalizedUsage),
metricMongodbatlasSystemFtsCPUUsage: newMetricMongodbatlasSystemFtsCPUUsage(mbc.Metrics.MongodbatlasSystemFtsCPUUsage),
metricMongodbatlasSystemFtsDiskUsed: newMetricMongodbatlasSystemFtsDiskUsed(mbc.Metrics.MongodbatlasSystemFtsDiskUsed),
metricMongodbatlasSystemFtsMemoryUsage: newMetricMongodbatlasSystemFtsMemoryUsage(mbc.Metrics.MongodbatlasSystemFtsMemoryUsage),
metricMongodbatlasSystemMemoryUsageAverage: newMetricMongodbatlasSystemMemoryUsageAverage(mbc.Metrics.MongodbatlasSystemMemoryUsageAverage),
metricMongodbatlasSystemMemoryUsageMax: newMetricMongodbatlasSystemMemoryUsageMax(mbc.Metrics.MongodbatlasSystemMemoryUsageMax),
metricMongodbatlasSystemNetworkIoAverage: newMetricMongodbatlasSystemNetworkIoAverage(mbc.Metrics.MongodbatlasSystemNetworkIoAverage),
metricMongodbatlasSystemNetworkIoMax: newMetricMongodbatlasSystemNetworkIoMax(mbc.Metrics.MongodbatlasSystemNetworkIoMax),
metricMongodbatlasSystemPagingIoAverage: newMetricMongodbatlasSystemPagingIoAverage(mbc.Metrics.MongodbatlasSystemPagingIoAverage),
metricMongodbatlasSystemPagingIoMax: newMetricMongodbatlasSystemPagingIoMax(mbc.Metrics.MongodbatlasSystemPagingIoMax),
metricMongodbatlasSystemPagingUsageAverage: newMetricMongodbatlasSystemPagingUsageAverage(mbc.Metrics.MongodbatlasSystemPagingUsageAverage),
metricMongodbatlasSystemPagingUsageMax: newMetricMongodbatlasSystemPagingUsageMax(mbc.Metrics.MongodbatlasSystemPagingUsageMax),
resourceAttributeIncludeFilter: make(map[string]filter.Filter),
resourceAttributeExcludeFilter: make(map[string]filter.Filter),
}
if mbc.ResourceAttributes.MongodbAtlasClusterName.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["mongodb_atlas.cluster.name"] = filter.CreateFilter(mbc.ResourceAttributes.MongodbAtlasClusterName.MetricsInclude)
}
if mbc.ResourceAttributes.MongodbAtlasClusterName.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["mongodb_atlas.cluster.name"] = filter.CreateFilter(mbc.ResourceAttributes.MongodbAtlasClusterName.MetricsExclude)
}
if mbc.ResourceAttributes.MongodbAtlasDbName.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["mongodb_atlas.db.name"] = filter.CreateFilter(mbc.ResourceAttributes.MongodbAtlasDbName.MetricsInclude)
}
if mbc.ResourceAttributes.MongodbAtlasDbName.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["mongodb_atlas.db.name"] = filter.CreateFilter(mbc.ResourceAttributes.MongodbAtlasDbName.MetricsExclude)
}
if mbc.ResourceAttributes.MongodbAtlasDiskPartition.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["mongodb_atlas.disk.partition"] = filter.CreateFilter(mbc.ResourceAttributes.MongodbAtlasDiskPartition.MetricsInclude)
}
if mbc.ResourceAttributes.MongodbAtlasDiskPartition.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["mongodb_atlas.disk.partition"] = filter.CreateFilter(mbc.ResourceAttributes.MongodbAtlasDiskPartition.MetricsExclude)
}
if mbc.ResourceAttributes.MongodbAtlasHostName.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["mongodb_atlas.host.name"] = filter.CreateFilter(mbc.ResourceAttributes.MongodbAtlasHostName.MetricsInclude)
}
if mbc.ResourceAttributes.MongodbAtlasHostName.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["mongodb_atlas.host.name"] = filter.CreateFilter(mbc.ResourceAttributes.MongodbAtlasHostName.MetricsExclude)
}
if mbc.ResourceAttributes.MongodbAtlasOrgName.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["mongodb_atlas.org_name"] = filter.CreateFilter(mbc.ResourceAttributes.MongodbAtlasOrgName.MetricsInclude)
}
if mbc.ResourceAttributes.MongodbAtlasOrgName.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["mongodb_atlas.org_name"] = filter.CreateFilter(mbc.ResourceAttributes.MongodbAtlasOrgName.MetricsExclude)
}
if mbc.ResourceAttributes.MongodbAtlasProcessID.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["mongodb_atlas.process.id"] = filter.CreateFilter(mbc.ResourceAttributes.MongodbAtlasProcessID.MetricsInclude)
}
if mbc.ResourceAttributes.MongodbAtlasProcessID.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["mongodb_atlas.process.id"] = filter.CreateFilter(mbc.ResourceAttributes.MongodbAtlasProcessID.MetricsExclude)
}
if mbc.ResourceAttributes.MongodbAtlasProcessPort.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["mongodb_atlas.process.port"] = filter.CreateFilter(mbc.ResourceAttributes.MongodbAtlasProcessPort.MetricsInclude)
}
if mbc.ResourceAttributes.MongodbAtlasProcessPort.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["mongodb_atlas.process.port"] = filter.CreateFilter(mbc.ResourceAttributes.MongodbAtlasProcessPort.MetricsExclude)
}
if mbc.ResourceAttributes.MongodbAtlasProcessTypeName.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["mongodb_atlas.process.type_name"] = filter.CreateFilter(mbc.ResourceAttributes.MongodbAtlasProcessTypeName.MetricsInclude)
}
if mbc.ResourceAttributes.MongodbAtlasProcessTypeName.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["mongodb_atlas.process.type_name"] = filter.CreateFilter(mbc.ResourceAttributes.MongodbAtlasProcessTypeName.MetricsExclude)
}
if mbc.ResourceAttributes.MongodbAtlasProjectID.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["mongodb_atlas.project.id"] = filter.CreateFilter(mbc.ResourceAttributes.MongodbAtlasProjectID.MetricsInclude)
}
if mbc.ResourceAttributes.MongodbAtlasProjectID.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["mongodb_atlas.project.id"] = filter.CreateFilter(mbc.ResourceAttributes.MongodbAtlasProjectID.MetricsExclude)
}
if mbc.ResourceAttributes.MongodbAtlasProjectName.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["mongodb_atlas.project.name"] = filter.CreateFilter(mbc.ResourceAttributes.MongodbAtlasProjectName.MetricsInclude)
}
if mbc.ResourceAttributes.MongodbAtlasProjectName.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["mongodb_atlas.project.name"] = filter.CreateFilter(mbc.ResourceAttributes.MongodbAtlasProjectName.MetricsExclude)
}
if mbc.ResourceAttributes.MongodbAtlasProviderName.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["mongodb_atlas.provider.name"] = filter.CreateFilter(mbc.ResourceAttributes.MongodbAtlasProviderName.MetricsInclude)
}
if mbc.ResourceAttributes.MongodbAtlasProviderName.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["mongodb_atlas.provider.name"] = filter.CreateFilter(mbc.ResourceAttributes.MongodbAtlasProviderName.MetricsExclude)
}
if mbc.ResourceAttributes.MongodbAtlasRegionName.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["mongodb_atlas.region.name"] = filter.CreateFilter(mbc.ResourceAttributes.MongodbAtlasRegionName.MetricsInclude)
}
if mbc.ResourceAttributes.MongodbAtlasRegionName.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["mongodb_atlas.region.name"] = filter.CreateFilter(mbc.ResourceAttributes.MongodbAtlasRegionName.MetricsExclude)
}
if mbc.ResourceAttributes.MongodbAtlasUserAlias.MetricsInclude != nil {
mb.resourceAttributeIncludeFilter["mongodb_atlas.user.alias"] = filter.CreateFilter(mbc.ResourceAttributes.MongodbAtlasUserAlias.MetricsInclude)
}
if mbc.ResourceAttributes.MongodbAtlasUserAlias.MetricsExclude != nil {
mb.resourceAttributeExcludeFilter["mongodb_atlas.user.alias"] = filter.CreateFilter(mbc.ResourceAttributes.MongodbAtlasUserAlias.MetricsExclude)
}
for _, op := range options {
op.apply(mb)
}
return mb
}
// NewResourceBuilder returns a new resource builder that should be used to build a resource associated with for the emitted metrics.
func (mb *MetricsBuilder) NewResourceBuilder() *ResourceBuilder {
return NewResourceBuilder(mb.config.ResourceAttributes)
}
// updateCapacity updates max length of metrics and resource attributes that will be used for the slice capacity.
func (mb *MetricsBuilder) updateCapacity(rm pmetric.ResourceMetrics) {
if mb.metricsCapacity < rm.ScopeMetrics().At(0).Metrics().Len() {
mb.metricsCapacity = rm.ScopeMetrics().At(0).Metrics().Len()
}
}
// ResourceMetricsOption applies changes to provided resource metrics.
type ResourceMetricsOption interface {
apply(pmetric.ResourceMetrics)
}
type resourceMetricsOptionFunc func(pmetric.ResourceMetrics)
func (rmof resourceMetricsOptionFunc) apply(rm pmetric.ResourceMetrics) {
rmof(rm)
}
// WithResource sets the provided resource on the emitted ResourceMetrics.
// It's recommended to use ResourceBuilder to create the resource.
func WithResource(res pcommon.Resource) ResourceMetricsOption {
return resourceMetricsOptionFunc(func(rm pmetric.ResourceMetrics) {
res.CopyTo(rm.Resource())
})
}
// WithStartTimeOverride overrides start time for all the resource metrics data points.
// This option should be only used if different start time has to be set on metrics coming from different resources.
func WithStartTimeOverride(start pcommon.Timestamp) ResourceMetricsOption {
return resourceMetricsOptionFunc(func(rm pmetric.ResourceMetrics) {
var dps pmetric.NumberDataPointSlice
metrics := rm.ScopeMetrics().At(0).Metrics()
for i := 0; i < metrics.Len(); i++ {
switch metrics.At(i).Type() {
case pmetric.MetricTypeGauge:
dps = metrics.At(i).Gauge().DataPoints()
case pmetric.MetricTypeSum:
dps = metrics.At(i).Sum().DataPoints()
}
for j := 0; j < dps.Len(); j++ {
dps.At(j).SetStartTimestamp(start)
}
}
})
}
// EmitForResource saves all the generated metrics under a new resource and updates the internal state to be ready for
// recording another set of data points as part of another resource. This function can be helpful when one scraper
// needs to emit metrics from several resources. Otherwise calling this function is not required,
// just `Emit` function can be called instead.
// Resource attributes should be provided as ResourceMetricsOption arguments.
func (mb *MetricsBuilder) EmitForResource(options ...ResourceMetricsOption) {
rm := pmetric.NewResourceMetrics()
ils := rm.ScopeMetrics().AppendEmpty()
ils.Scope().SetName(ScopeName)
ils.Scope().SetVersion(mb.buildInfo.Version)
ils.Metrics().EnsureCapacity(mb.metricsCapacity)
mb.metricMongodbatlasDbCounts.emit(ils.Metrics())
mb.metricMongodbatlasDbSize.emit(ils.Metrics())
mb.metricMongodbatlasDiskPartitionIopsAverage.emit(ils.Metrics())
mb.metricMongodbatlasDiskPartitionIopsMax.emit(ils.Metrics())
mb.metricMongodbatlasDiskPartitionLatencyAverage.emit(ils.Metrics())
mb.metricMongodbatlasDiskPartitionLatencyMax.emit(ils.Metrics())
mb.metricMongodbatlasDiskPartitionQueueDepth.emit(ils.Metrics())
mb.metricMongodbatlasDiskPartitionSpaceAverage.emit(ils.Metrics())
mb.metricMongodbatlasDiskPartitionSpaceMax.emit(ils.Metrics())
mb.metricMongodbatlasDiskPartitionThroughput.emit(ils.Metrics())
mb.metricMongodbatlasDiskPartitionUsageAverage.emit(ils.Metrics())
mb.metricMongodbatlasDiskPartitionUsageMax.emit(ils.Metrics())
mb.metricMongodbatlasDiskPartitionUtilizationAverage.emit(ils.Metrics())
mb.metricMongodbatlasDiskPartitionUtilizationMax.emit(ils.Metrics())
mb.metricMongodbatlasProcessAsserts.emit(ils.Metrics())
mb.metricMongodbatlasProcessBackgroundFlush.emit(ils.Metrics())
mb.metricMongodbatlasProcessCacheIo.emit(ils.Metrics())
mb.metricMongodbatlasProcessCacheRatio.emit(ils.Metrics())
mb.metricMongodbatlasProcessCacheSize.emit(ils.Metrics())
mb.metricMongodbatlasProcessConnections.emit(ils.Metrics())
mb.metricMongodbatlasProcessCPUChildrenNormalizedUsageAverage.emit(ils.Metrics())
mb.metricMongodbatlasProcessCPUChildrenNormalizedUsageMax.emit(ils.Metrics())
mb.metricMongodbatlasProcessCPUChildrenUsageAverage.emit(ils.Metrics())
mb.metricMongodbatlasProcessCPUChildrenUsageMax.emit(ils.Metrics())
mb.metricMongodbatlasProcessCPUNormalizedUsageAverage.emit(ils.Metrics())
mb.metricMongodbatlasProcessCPUNormalizedUsageMax.emit(ils.Metrics())
mb.metricMongodbatlasProcessCPUUsageAverage.emit(ils.Metrics())
mb.metricMongodbatlasProcessCPUUsageMax.emit(ils.Metrics())
mb.metricMongodbatlasProcessCursors.emit(ils.Metrics())
mb.metricMongodbatlasProcessDbDocumentRate.emit(ils.Metrics())
mb.metricMongodbatlasProcessDbOperationsRate.emit(ils.Metrics())
mb.metricMongodbatlasProcessDbOperationsTime.emit(ils.Metrics())
mb.metricMongodbatlasProcessDbQueryExecutorScanned.emit(ils.Metrics())
mb.metricMongodbatlasProcessDbQueryTargetingScannedPerReturned.emit(ils.Metrics())
mb.metricMongodbatlasProcessDbStorage.emit(ils.Metrics())
mb.metricMongodbatlasProcessGlobalLock.emit(ils.Metrics())
mb.metricMongodbatlasProcessIndexBtreeMissRatio.emit(ils.Metrics())
mb.metricMongodbatlasProcessIndexCounters.emit(ils.Metrics())
mb.metricMongodbatlasProcessJournalingCommits.emit(ils.Metrics())
mb.metricMongodbatlasProcessJournalingDataFiles.emit(ils.Metrics())
mb.metricMongodbatlasProcessJournalingWritten.emit(ils.Metrics())
mb.metricMongodbatlasProcessMemoryUsage.emit(ils.Metrics())
mb.metricMongodbatlasProcessNetworkIo.emit(ils.Metrics())
mb.metricMongodbatlasProcessNetworkRequests.emit(ils.Metrics())
mb.metricMongodbatlasProcessOplogRate.emit(ils.Metrics())
mb.metricMongodbatlasProcessOplogTime.emit(ils.Metrics())
mb.metricMongodbatlasProcessPageFaults.emit(ils.Metrics())
mb.metricMongodbatlasProcessRestarts.emit(ils.Metrics())
mb.metricMongodbatlasProcessTickets.emit(ils.Metrics())
mb.metricMongodbatlasSystemCPUNormalizedUsageAverage.emit(ils.Metrics())
mb.metricMongodbatlasSystemCPUNormalizedUsageMax.emit(ils.Metrics())
mb.metricMongodbatlasSystemCPUUsageAverage.emit(ils.Metrics())
mb.metricMongodbatlasSystemCPUUsageMax.emit(ils.Metrics())
mb.metricMongodbatlasSystemFtsCPUNormalizedUsage.emit(ils.Metrics())
mb.metricMongodbatlasSystemFtsCPUUsage.emit(ils.Metrics())
mb.metricMongodbatlasSystemFtsDiskUsed.emit(ils.Metrics())
mb.metricMongodbatlasSystemFtsMemoryUsage.emit(ils.Metrics())
mb.metricMongodbatlasSystemMemoryUsageAverage.emit(ils.Metrics())
mb.metricMongodbatlasSystemMemoryUsageMax.emit(ils.Metrics())
mb.metricMongodbatlasSystemNetworkIoAverage.emit(ils.Metrics())
mb.metricMongodbatlasSystemNetworkIoMax.emit(ils.Metrics())
mb.metricMongodbatlasSystemPagingIoAverage.emit(ils.Metrics())
mb.metricMongodbatlasSystemPagingIoMax.emit(ils.Metrics())
mb.metricMongodbatlasSystemPagingUsageAverage.emit(ils.Metrics())
mb.metricMongodbatlasSystemPagingUsageMax.emit(ils.Metrics())
for _, op := range options {
op.apply(rm)
}
for attr, filter := range mb.resourceAttributeIncludeFilter {
if val, ok := rm.Resource().Attributes().Get(attr); ok && !filter.Matches(val.AsString()) {
return
}
}
for attr, filter := range mb.resourceAttributeExcludeFilter {
if val, ok := rm.Resource().Attributes().Get(attr); ok && filter.Matches(val.AsString()) {
return
}
}
if ils.Metrics().Len() > 0 {
mb.updateCapacity(rm)
rm.MoveTo(mb.metricsBuffer.ResourceMetrics().AppendEmpty())
}
}
// Emit returns all the metrics accumulated by the metrics builder and updates the internal state to be ready for
// recording another set of metrics. This function will be responsible for applying all the transformations required to
// produce metric representation defined in metadata and user config, e.g. delta or cumulative.
func (mb *MetricsBuilder) Emit(options ...ResourceMetricsOption) pmetric.Metrics {
mb.EmitForResource(options...)
metrics := mb.metricsBuffer
mb.metricsBuffer = pmetric.NewMetrics()
return metrics
}
// RecordMongodbatlasDbCountsDataPoint adds a data point to mongodbatlas.db.counts metric.
func (mb *MetricsBuilder) RecordMongodbatlasDbCountsDataPoint(ts pcommon.Timestamp, val float64, objectTypeAttributeValue AttributeObjectType) {
mb.metricMongodbatlasDbCounts.recordDataPoint(mb.startTime, ts, val, objectTypeAttributeValue.String())
}
// RecordMongodbatlasDbSizeDataPoint adds a data point to mongodbatlas.db.size metric.
func (mb *MetricsBuilder) RecordMongodbatlasDbSizeDataPoint(ts pcommon.Timestamp, val float64, objectTypeAttributeValue AttributeObjectType) {
mb.metricMongodbatlasDbSize.recordDataPoint(mb.startTime, ts, val, objectTypeAttributeValue.String())
}
// RecordMongodbatlasDiskPartitionIopsAverageDataPoint adds a data point to mongodbatlas.disk.partition.iops.average metric.
func (mb *MetricsBuilder) RecordMongodbatlasDiskPartitionIopsAverageDataPoint(ts pcommon.Timestamp, val float64, diskDirectionAttributeValue AttributeDiskDirection) {
mb.metricMongodbatlasDiskPartitionIopsAverage.recordDataPoint(mb.startTime, ts, val, diskDirectionAttributeValue.String())
}
// RecordMongodbatlasDiskPartitionIopsMaxDataPoint adds a data point to mongodbatlas.disk.partition.iops.max metric.
func (mb *MetricsBuilder) RecordMongodbatlasDiskPartitionIopsMaxDataPoint(ts pcommon.Timestamp, val float64, diskDirectionAttributeValue AttributeDiskDirection) {
mb.metricMongodbatlasDiskPartitionIopsMax.recordDataPoint(mb.startTime, ts, val, diskDirectionAttributeValue.String())
}
// RecordMongodbatlasDiskPartitionLatencyAverageDataPoint adds a data point to mongodbatlas.disk.partition.latency.average metric.
func (mb *MetricsBuilder) RecordMongodbatlasDiskPartitionLatencyAverageDataPoint(ts pcommon.Timestamp, val float64, diskDirectionAttributeValue AttributeDiskDirection) {
mb.metricMongodbatlasDiskPartitionLatencyAverage.recordDataPoint(mb.startTime, ts, val, diskDirectionAttributeValue.String())
}
// RecordMongodbatlasDiskPartitionLatencyMaxDataPoint adds a data point to mongodbatlas.disk.partition.latency.max metric.
func (mb *MetricsBuilder) RecordMongodbatlasDiskPartitionLatencyMaxDataPoint(ts pcommon.Timestamp, val float64, diskDirectionAttributeValue AttributeDiskDirection) {
mb.metricMongodbatlasDiskPartitionLatencyMax.recordDataPoint(mb.startTime, ts, val, diskDirectionAttributeValue.String())
}
// RecordMongodbatlasDiskPartitionQueueDepthDataPoint adds a data point to mongodbatlas.disk.partition.queue.depth metric.
func (mb *MetricsBuilder) RecordMongodbatlasDiskPartitionQueueDepthDataPoint(ts pcommon.Timestamp, val float64) {
mb.metricMongodbatlasDiskPartitionQueueDepth.recordDataPoint(mb.startTime, ts, val)
}
// RecordMongodbatlasDiskPartitionSpaceAverageDataPoint adds a data point to mongodbatlas.disk.partition.space.average metric.
func (mb *MetricsBuilder) RecordMongodbatlasDiskPartitionSpaceAverageDataPoint(ts pcommon.Timestamp, val float64, diskStatusAttributeValue AttributeDiskStatus) {
mb.metricMongodbatlasDiskPartitionSpaceAverage.recordDataPoint(mb.startTime, ts, val, diskStatusAttributeValue.String())
}
// RecordMongodbatlasDiskPartitionSpaceMaxDataPoint adds a data point to mongodbatlas.disk.partition.space.max metric.
func (mb *MetricsBuilder) RecordMongodbatlasDiskPartitionSpaceMaxDataPoint(ts pcommon.Timestamp, val float64, diskStatusAttributeValue AttributeDiskStatus) {
mb.metricMongodbatlasDiskPartitionSpaceMax.recordDataPoint(mb.startTime, ts, val, diskStatusAttributeValue.String())
}
// RecordMongodbatlasDiskPartitionThroughputDataPoint adds a data point to mongodbatlas.disk.partition.throughput metric.
func (mb *MetricsBuilder) RecordMongodbatlasDiskPartitionThroughputDataPoint(ts pcommon.Timestamp, val float64, diskDirectionAttributeValue AttributeDiskDirection) {
mb.metricMongodbatlasDiskPartitionThroughput.recordDataPoint(mb.startTime, ts, val, diskDirectionAttributeValue.String())
}
// RecordMongodbatlasDiskPartitionUsageAverageDataPoint adds a data point to mongodbatlas.disk.partition.usage.average metric.
func (mb *MetricsBuilder) RecordMongodbatlasDiskPartitionUsageAverageDataPoint(ts pcommon.Timestamp, val float64, diskStatusAttributeValue AttributeDiskStatus) {
mb.metricMongodbatlasDiskPartitionUsageAverage.recordDataPoint(mb.startTime, ts, val, diskStatusAttributeValue.String())
}
// RecordMongodbatlasDiskPartitionUsageMaxDataPoint adds a data point to mongodbatlas.disk.partition.usage.max metric.
func (mb *MetricsBuilder) RecordMongodbatlasDiskPartitionUsageMaxDataPoint(ts pcommon.Timestamp, val float64, diskStatusAttributeValue AttributeDiskStatus) {
mb.metricMongodbatlasDiskPartitionUsageMax.recordDataPoint(mb.startTime, ts, val, diskStatusAttributeValue.String())
}
// RecordMongodbatlasDiskPartitionUtilizationAverageDataPoint adds a data point to mongodbatlas.disk.partition.utilization.average metric.
func (mb *MetricsBuilder) RecordMongodbatlasDiskPartitionUtilizationAverageDataPoint(ts pcommon.Timestamp, val float64) {
mb.metricMongodbatlasDiskPartitionUtilizationAverage.recordDataPoint(mb.startTime, ts, val)
}
// RecordMongodbatlasDiskPartitionUtilizationMaxDataPoint adds a data point to mongodbatlas.disk.partition.utilization.max metric.
func (mb *MetricsBuilder) RecordMongodbatlasDiskPartitionUtilizationMaxDataPoint(ts pcommon.Timestamp, val float64) {
mb.metricMongodbatlasDiskPartitionUtilizationMax.recordDataPoint(mb.startTime, ts, val)
}
// RecordMongodbatlasProcessAssertsDataPoint adds a data point to mongodbatlas.process.asserts metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessAssertsDataPoint(ts pcommon.Timestamp, val float64, assertTypeAttributeValue AttributeAssertType) {
mb.metricMongodbatlasProcessAsserts.recordDataPoint(mb.startTime, ts, val, assertTypeAttributeValue.String())
}
// RecordMongodbatlasProcessBackgroundFlushDataPoint adds a data point to mongodbatlas.process.background_flush metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessBackgroundFlushDataPoint(ts pcommon.Timestamp, val float64) {
mb.metricMongodbatlasProcessBackgroundFlush.recordDataPoint(mb.startTime, ts, val)
}
// RecordMongodbatlasProcessCacheIoDataPoint adds a data point to mongodbatlas.process.cache.io metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessCacheIoDataPoint(ts pcommon.Timestamp, val float64, cacheDirectionAttributeValue AttributeCacheDirection) {
mb.metricMongodbatlasProcessCacheIo.recordDataPoint(mb.startTime, ts, val, cacheDirectionAttributeValue.String())
}
// RecordMongodbatlasProcessCacheRatioDataPoint adds a data point to mongodbatlas.process.cache.ratio metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessCacheRatioDataPoint(ts pcommon.Timestamp, val float64, cacheRatioTypeAttributeValue AttributeCacheRatioType) {
mb.metricMongodbatlasProcessCacheRatio.recordDataPoint(mb.startTime, ts, val, cacheRatioTypeAttributeValue.String())
}
// RecordMongodbatlasProcessCacheSizeDataPoint adds a data point to mongodbatlas.process.cache.size metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessCacheSizeDataPoint(ts pcommon.Timestamp, val float64, cacheStatusAttributeValue AttributeCacheStatus) {
mb.metricMongodbatlasProcessCacheSize.recordDataPoint(mb.startTime, ts, val, cacheStatusAttributeValue.String())
}
// RecordMongodbatlasProcessConnectionsDataPoint adds a data point to mongodbatlas.process.connections metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessConnectionsDataPoint(ts pcommon.Timestamp, val float64) {
mb.metricMongodbatlasProcessConnections.recordDataPoint(mb.startTime, ts, val)
}
// RecordMongodbatlasProcessCPUChildrenNormalizedUsageAverageDataPoint adds a data point to mongodbatlas.process.cpu.children.normalized.usage.average metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessCPUChildrenNormalizedUsageAverageDataPoint(ts pcommon.Timestamp, val float64, cpuStateAttributeValue AttributeCPUState) {
mb.metricMongodbatlasProcessCPUChildrenNormalizedUsageAverage.recordDataPoint(mb.startTime, ts, val, cpuStateAttributeValue.String())
}
// RecordMongodbatlasProcessCPUChildrenNormalizedUsageMaxDataPoint adds a data point to mongodbatlas.process.cpu.children.normalized.usage.max metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessCPUChildrenNormalizedUsageMaxDataPoint(ts pcommon.Timestamp, val float64, cpuStateAttributeValue AttributeCPUState) {
mb.metricMongodbatlasProcessCPUChildrenNormalizedUsageMax.recordDataPoint(mb.startTime, ts, val, cpuStateAttributeValue.String())
}
// RecordMongodbatlasProcessCPUChildrenUsageAverageDataPoint adds a data point to mongodbatlas.process.cpu.children.usage.average metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessCPUChildrenUsageAverageDataPoint(ts pcommon.Timestamp, val float64, cpuStateAttributeValue AttributeCPUState) {
mb.metricMongodbatlasProcessCPUChildrenUsageAverage.recordDataPoint(mb.startTime, ts, val, cpuStateAttributeValue.String())
}
// RecordMongodbatlasProcessCPUChildrenUsageMaxDataPoint adds a data point to mongodbatlas.process.cpu.children.usage.max metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessCPUChildrenUsageMaxDataPoint(ts pcommon.Timestamp, val float64, cpuStateAttributeValue AttributeCPUState) {
mb.metricMongodbatlasProcessCPUChildrenUsageMax.recordDataPoint(mb.startTime, ts, val, cpuStateAttributeValue.String())
}
// RecordMongodbatlasProcessCPUNormalizedUsageAverageDataPoint adds a data point to mongodbatlas.process.cpu.normalized.usage.average metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessCPUNormalizedUsageAverageDataPoint(ts pcommon.Timestamp, val float64, cpuStateAttributeValue AttributeCPUState) {
mb.metricMongodbatlasProcessCPUNormalizedUsageAverage.recordDataPoint(mb.startTime, ts, val, cpuStateAttributeValue.String())
}
// RecordMongodbatlasProcessCPUNormalizedUsageMaxDataPoint adds a data point to mongodbatlas.process.cpu.normalized.usage.max metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessCPUNormalizedUsageMaxDataPoint(ts pcommon.Timestamp, val float64, cpuStateAttributeValue AttributeCPUState) {
mb.metricMongodbatlasProcessCPUNormalizedUsageMax.recordDataPoint(mb.startTime, ts, val, cpuStateAttributeValue.String())
}
// RecordMongodbatlasProcessCPUUsageAverageDataPoint adds a data point to mongodbatlas.process.cpu.usage.average metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessCPUUsageAverageDataPoint(ts pcommon.Timestamp, val float64, cpuStateAttributeValue AttributeCPUState) {
mb.metricMongodbatlasProcessCPUUsageAverage.recordDataPoint(mb.startTime, ts, val, cpuStateAttributeValue.String())
}
// RecordMongodbatlasProcessCPUUsageMaxDataPoint adds a data point to mongodbatlas.process.cpu.usage.max metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessCPUUsageMaxDataPoint(ts pcommon.Timestamp, val float64, cpuStateAttributeValue AttributeCPUState) {
mb.metricMongodbatlasProcessCPUUsageMax.recordDataPoint(mb.startTime, ts, val, cpuStateAttributeValue.String())
}
// RecordMongodbatlasProcessCursorsDataPoint adds a data point to mongodbatlas.process.cursors metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessCursorsDataPoint(ts pcommon.Timestamp, val float64, cursorStateAttributeValue AttributeCursorState) {
mb.metricMongodbatlasProcessCursors.recordDataPoint(mb.startTime, ts, val, cursorStateAttributeValue.String())
}
// RecordMongodbatlasProcessDbDocumentRateDataPoint adds a data point to mongodbatlas.process.db.document.rate metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessDbDocumentRateDataPoint(ts pcommon.Timestamp, val float64, documentStatusAttributeValue AttributeDocumentStatus) {
mb.metricMongodbatlasProcessDbDocumentRate.recordDataPoint(mb.startTime, ts, val, documentStatusAttributeValue.String())
}
// RecordMongodbatlasProcessDbOperationsRateDataPoint adds a data point to mongodbatlas.process.db.operations.rate metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessDbOperationsRateDataPoint(ts pcommon.Timestamp, val float64, operationAttributeValue AttributeOperation, clusterRoleAttributeValue AttributeClusterRole) {
mb.metricMongodbatlasProcessDbOperationsRate.recordDataPoint(mb.startTime, ts, val, operationAttributeValue.String(), clusterRoleAttributeValue.String())
}
// RecordMongodbatlasProcessDbOperationsTimeDataPoint adds a data point to mongodbatlas.process.db.operations.time metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessDbOperationsTimeDataPoint(ts pcommon.Timestamp, val float64, executionTypeAttributeValue AttributeExecutionType) {
mb.metricMongodbatlasProcessDbOperationsTime.recordDataPoint(mb.startTime, ts, val, executionTypeAttributeValue.String())
}
// RecordMongodbatlasProcessDbQueryExecutorScannedDataPoint adds a data point to mongodbatlas.process.db.query_executor.scanned metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessDbQueryExecutorScannedDataPoint(ts pcommon.Timestamp, val float64, scannedTypeAttributeValue AttributeScannedType) {
mb.metricMongodbatlasProcessDbQueryExecutorScanned.recordDataPoint(mb.startTime, ts, val, scannedTypeAttributeValue.String())
}
// RecordMongodbatlasProcessDbQueryTargetingScannedPerReturnedDataPoint adds a data point to mongodbatlas.process.db.query_targeting.scanned_per_returned metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessDbQueryTargetingScannedPerReturnedDataPoint(ts pcommon.Timestamp, val float64, scannedTypeAttributeValue AttributeScannedType) {
mb.metricMongodbatlasProcessDbQueryTargetingScannedPerReturned.recordDataPoint(mb.startTime, ts, val, scannedTypeAttributeValue.String())
}
// RecordMongodbatlasProcessDbStorageDataPoint adds a data point to mongodbatlas.process.db.storage metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessDbStorageDataPoint(ts pcommon.Timestamp, val float64, storageStatusAttributeValue AttributeStorageStatus) {
mb.metricMongodbatlasProcessDbStorage.recordDataPoint(mb.startTime, ts, val, storageStatusAttributeValue.String())
}
// RecordMongodbatlasProcessGlobalLockDataPoint adds a data point to mongodbatlas.process.global_lock metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessGlobalLockDataPoint(ts pcommon.Timestamp, val float64, globalLockStateAttributeValue AttributeGlobalLockState) {
mb.metricMongodbatlasProcessGlobalLock.recordDataPoint(mb.startTime, ts, val, globalLockStateAttributeValue.String())
}
// RecordMongodbatlasProcessIndexBtreeMissRatioDataPoint adds a data point to mongodbatlas.process.index.btree_miss_ratio metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessIndexBtreeMissRatioDataPoint(ts pcommon.Timestamp, val float64) {
mb.metricMongodbatlasProcessIndexBtreeMissRatio.recordDataPoint(mb.startTime, ts, val)
}
// RecordMongodbatlasProcessIndexCountersDataPoint adds a data point to mongodbatlas.process.index.counters metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessIndexCountersDataPoint(ts pcommon.Timestamp, val float64, btreeCounterTypeAttributeValue AttributeBtreeCounterType) {
mb.metricMongodbatlasProcessIndexCounters.recordDataPoint(mb.startTime, ts, val, btreeCounterTypeAttributeValue.String())
}
// RecordMongodbatlasProcessJournalingCommitsDataPoint adds a data point to mongodbatlas.process.journaling.commits metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessJournalingCommitsDataPoint(ts pcommon.Timestamp, val float64) {
mb.metricMongodbatlasProcessJournalingCommits.recordDataPoint(mb.startTime, ts, val)
}
// RecordMongodbatlasProcessJournalingDataFilesDataPoint adds a data point to mongodbatlas.process.journaling.data_files metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessJournalingDataFilesDataPoint(ts pcommon.Timestamp, val float64) {
mb.metricMongodbatlasProcessJournalingDataFiles.recordDataPoint(mb.startTime, ts, val)
}
// RecordMongodbatlasProcessJournalingWrittenDataPoint adds a data point to mongodbatlas.process.journaling.written metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessJournalingWrittenDataPoint(ts pcommon.Timestamp, val float64) {
mb.metricMongodbatlasProcessJournalingWritten.recordDataPoint(mb.startTime, ts, val)
}
// RecordMongodbatlasProcessMemoryUsageDataPoint adds a data point to mongodbatlas.process.memory.usage metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessMemoryUsageDataPoint(ts pcommon.Timestamp, val float64, memoryStateAttributeValue AttributeMemoryState) {
mb.metricMongodbatlasProcessMemoryUsage.recordDataPoint(mb.startTime, ts, val, memoryStateAttributeValue.String())
}
// RecordMongodbatlasProcessNetworkIoDataPoint adds a data point to mongodbatlas.process.network.io metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessNetworkIoDataPoint(ts pcommon.Timestamp, val float64, directionAttributeValue AttributeDirection) {
mb.metricMongodbatlasProcessNetworkIo.recordDataPoint(mb.startTime, ts, val, directionAttributeValue.String())
}
// RecordMongodbatlasProcessNetworkRequestsDataPoint adds a data point to mongodbatlas.process.network.requests metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessNetworkRequestsDataPoint(ts pcommon.Timestamp, val float64) {
mb.metricMongodbatlasProcessNetworkRequests.recordDataPoint(mb.startTime, ts, val)
}
// RecordMongodbatlasProcessOplogRateDataPoint adds a data point to mongodbatlas.process.oplog.rate metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessOplogRateDataPoint(ts pcommon.Timestamp, val float64) {
mb.metricMongodbatlasProcessOplogRate.recordDataPoint(mb.startTime, ts, val)
}
// RecordMongodbatlasProcessOplogTimeDataPoint adds a data point to mongodbatlas.process.oplog.time metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessOplogTimeDataPoint(ts pcommon.Timestamp, val float64, oplogTypeAttributeValue AttributeOplogType) {
mb.metricMongodbatlasProcessOplogTime.recordDataPoint(mb.startTime, ts, val, oplogTypeAttributeValue.String())
}
// RecordMongodbatlasProcessPageFaultsDataPoint adds a data point to mongodbatlas.process.page_faults metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessPageFaultsDataPoint(ts pcommon.Timestamp, val float64, memoryIssueTypeAttributeValue AttributeMemoryIssueType) {
mb.metricMongodbatlasProcessPageFaults.recordDataPoint(mb.startTime, ts, val, memoryIssueTypeAttributeValue.String())
}
// RecordMongodbatlasProcessRestartsDataPoint adds a data point to mongodbatlas.process.restarts metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessRestartsDataPoint(ts pcommon.Timestamp, val float64) {
mb.metricMongodbatlasProcessRestarts.recordDataPoint(mb.startTime, ts, val)
}
// RecordMongodbatlasProcessTicketsDataPoint adds a data point to mongodbatlas.process.tickets metric.
func (mb *MetricsBuilder) RecordMongodbatlasProcessTicketsDataPoint(ts pcommon.Timestamp, val float64, ticketTypeAttributeValue AttributeTicketType) {
mb.metricMongodbatlasProcessTickets.recordDataPoint(mb.startTime, ts, val, ticketTypeAttributeValue.String())
}
// RecordMongodbatlasSystemCPUNormalizedUsageAverageDataPoint adds a data point to mongodbatlas.system.cpu.normalized.usage.average metric.
func (mb *MetricsBuilder) RecordMongodbatlasSystemCPUNormalizedUsageAverageDataPoint(ts pcommon.Timestamp, val float64, cpuStateAttributeValue AttributeCPUState) {
mb.metricMongodbatlasSystemCPUNormalizedUsageAverage.recordDataPoint(mb.startTime, ts, val, cpuStateAttributeValue.String())
}
// RecordMongodbatlasSystemCPUNormalizedUsageMaxDataPoint adds a data point to mongodbatlas.system.cpu.normalized.usage.max metric.
func (mb *MetricsBuilder) RecordMongodbatlasSystemCPUNormalizedUsageMaxDataPoint(ts pcommon.Timestamp, val float64, cpuStateAttributeValue AttributeCPUState) {
mb.metricMongodbatlasSystemCPUNormalizedUsageMax.recordDataPoint(mb.startTime, ts, val, cpuStateAttributeValue.String())
}
// RecordMongodbatlasSystemCPUUsageAverageDataPoint adds a data point to mongodbatlas.system.cpu.usage.average metric.
func (mb *MetricsBuilder) RecordMongodbatlasSystemCPUUsageAverageDataPoint(ts pcommon.Timestamp, val float64, cpuStateAttributeValue AttributeCPUState) {
mb.metricMongodbatlasSystemCPUUsageAverage.recordDataPoint(mb.startTime, ts, val, cpuStateAttributeValue.String())
}
// RecordMongodbatlasSystemCPUUsageMaxDataPoint adds a data point to mongodbatlas.system.cpu.usage.max metric.
func (mb *MetricsBuilder) RecordMongodbatlasSystemCPUUsageMaxDataPoint(ts pcommon.Timestamp, val float64, cpuStateAttributeValue AttributeCPUState) {
mb.metricMongodbatlasSystemCPUUsageMax.recordDataPoint(mb.startTime, ts, val, cpuStateAttributeValue.String())
}
// RecordMongodbatlasSystemFtsCPUNormalizedUsageDataPoint adds a data point to mongodbatlas.system.fts.cpu.normalized.usage metric.
func (mb *MetricsBuilder) RecordMongodbatlasSystemFtsCPUNormalizedUsageDataPoint(ts pcommon.Timestamp, val float64, cpuStateAttributeValue AttributeCPUState) {
mb.metricMongodbatlasSystemFtsCPUNormalizedUsage.recordDataPoint(mb.startTime, ts, val, cpuStateAttributeValue.String())
}
// RecordMongodbatlasSystemFtsCPUUsageDataPoint adds a data point to mongodbatlas.system.fts.cpu.usage metric.
func (mb *MetricsBuilder) RecordMongodbatlasSystemFtsCPUUsageDataPoint(ts pcommon.Timestamp, val float64, cpuStateAttributeValue AttributeCPUState) {
mb.metricMongodbatlasSystemFtsCPUUsage.recordDataPoint(mb.startTime, ts, val, cpuStateAttributeValue.String())
}
// RecordMongodbatlasSystemFtsDiskUsedDataPoint adds a data point to mongodbatlas.system.fts.disk.used metric.
func (mb *MetricsBuilder) RecordMongodbatlasSystemFtsDiskUsedDataPoint(ts pcommon.Timestamp, val float64) {
mb.metricMongodbatlasSystemFtsDiskUsed.recordDataPoint(mb.startTime, ts, val)
}
// RecordMongodbatlasSystemFtsMemoryUsageDataPoint adds a data point to mongodbatlas.system.fts.memory.usage metric.
func (mb *MetricsBuilder) RecordMongodbatlasSystemFtsMemoryUsageDataPoint(ts pcommon.Timestamp, val float64, memoryStateAttributeValue AttributeMemoryState) {
mb.metricMongodbatlasSystemFtsMemoryUsage.recordDataPoint(mb.startTime, ts, val, memoryStateAttributeValue.String())
}
// RecordMongodbatlasSystemMemoryUsageAverageDataPoint adds a data point to mongodbatlas.system.memory.usage.average metric.
func (mb *MetricsBuilder) RecordMongodbatlasSystemMemoryUsageAverageDataPoint(ts pcommon.Timestamp, val float64, memoryStatusAttributeValue AttributeMemoryStatus) {
mb.metricMongodbatlasSystemMemoryUsageAverage.recordDataPoint(mb.startTime, ts, val, memoryStatusAttributeValue.String())
}
// RecordMongodbatlasSystemMemoryUsageMaxDataPoint adds a data point to mongodbatlas.system.memory.usage.max metric.
func (mb *MetricsBuilder) RecordMongodbatlasSystemMemoryUsageMaxDataPoint(ts pcommon.Timestamp, val float64, memoryStatusAttributeValue AttributeMemoryStatus) {
mb.metricMongodbatlasSystemMemoryUsageMax.recordDataPoint(mb.startTime, ts, val, memoryStatusAttributeValue.String())
}
// RecordMongodbatlasSystemNetworkIoAverageDataPoint adds a data point to mongodbatlas.system.network.io.average metric.
func (mb *MetricsBuilder) RecordMongodbatlasSystemNetworkIoAverageDataPoint(ts pcommon.Timestamp, val float64, directionAttributeValue AttributeDirection) {
mb.metricMongodbatlasSystemNetworkIoAverage.recordDataPoint(mb.startTime, ts, val, directionAttributeValue.String())
}
// RecordMongodbatlasSystemNetworkIoMaxDataPoint adds a data point to mongodbatlas.system.network.io.max metric.
func (mb *MetricsBuilder) RecordMongodbatlasSystemNetworkIoMaxDataPoint(ts pcommon.Timestamp, val float64, directionAttributeValue AttributeDirection) {
mb.metricMongodbatlasSystemNetworkIoMax.recordDataPoint(mb.startTime, ts, val, directionAttributeValue.String())
}
// RecordMongodbatlasSystemPagingIoAverageDataPoint adds a data point to mongodbatlas.system.paging.io.average metric.
func (mb *MetricsBuilder) RecordMongodbatlasSystemPagingIoAverageDataPoint(ts pcommon.Timestamp, val float64, directionAttributeValue AttributeDirection) {
mb.metricMongodbatlasSystemPagingIoAverage.recordDataPoint(mb.startTime, ts, val, directionAttributeValue.String())
}
// RecordMongodbatlasSystemPagingIoMaxDataPoint adds a data point to mongodbatlas.system.paging.io.max metric.
func (mb *MetricsBuilder) RecordMongodbatlasSystemPagingIoMaxDataPoint(ts pcommon.Timestamp, val float64, directionAttributeValue AttributeDirection) {
mb.metricMongodbatlasSystemPagingIoMax.recordDataPoint(mb.startTime, ts, val, directionAttributeValue.String())
}
// RecordMongodbatlasSystemPagingUsageAverageDataPoint adds a data point to mongodbatlas.system.paging.usage.average metric.
func (mb *MetricsBuilder) RecordMongodbatlasSystemPagingUsageAverageDataPoint(ts pcommon.Timestamp, val float64, memoryStateAttributeValue AttributeMemoryState) {
mb.metricMongodbatlasSystemPagingUsageAverage.recordDataPoint(mb.startTime, ts, val, memoryStateAttributeValue.String())
}
// RecordMongodbatlasSystemPagingUsageMaxDataPoint adds a data point to mongodbatlas.system.paging.usage.max metric.
func (mb *MetricsBuilder) RecordMongodbatlasSystemPagingUsageMaxDataPoint(ts pcommon.Timestamp, val float64, memoryStateAttributeValue AttributeMemoryState) {
mb.metricMongodbatlasSystemPagingUsageMax.recordDataPoint(mb.startTime, ts, val, memoryStateAttributeValue.String())
}
// Reset resets metrics builder to its initial state. It should be used when external metrics source is restarted,
// and metrics builder should update its startTime and reset it's internal state accordingly.
func (mb *MetricsBuilder) Reset(options ...MetricBuilderOption) {
mb.startTime = pcommon.NewTimestampFromTime(time.Now())
for _, op := range options {
op.apply(mb)
}
}
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/pdata/pcommon"
)
// ResourceBuilder is a helper struct to build resources predefined in metadata.yaml.
// The ResourceBuilder is not thread-safe and must not to be used in multiple goroutines.
type ResourceBuilder struct {
config ResourceAttributesConfig
res pcommon.Resource
}
// NewResourceBuilder creates a new ResourceBuilder. This method should be called on the start of the application.
func NewResourceBuilder(rac ResourceAttributesConfig) *ResourceBuilder {
return &ResourceBuilder{
config: rac,
res: pcommon.NewResource(),
}
}
// SetMongodbAtlasClusterName sets provided value as "mongodb_atlas.cluster.name" attribute.
func (rb *ResourceBuilder) SetMongodbAtlasClusterName(val string) {
if rb.config.MongodbAtlasClusterName.Enabled {
rb.res.Attributes().PutStr("mongodb_atlas.cluster.name", val)
}
}
// SetMongodbAtlasDbName sets provided value as "mongodb_atlas.db.name" attribute.
func (rb *ResourceBuilder) SetMongodbAtlasDbName(val string) {
if rb.config.MongodbAtlasDbName.Enabled {
rb.res.Attributes().PutStr("mongodb_atlas.db.name", val)
}
}
// SetMongodbAtlasDiskPartition sets provided value as "mongodb_atlas.disk.partition" attribute.
func (rb *ResourceBuilder) SetMongodbAtlasDiskPartition(val string) {
if rb.config.MongodbAtlasDiskPartition.Enabled {
rb.res.Attributes().PutStr("mongodb_atlas.disk.partition", val)
}
}
// SetMongodbAtlasHostName sets provided value as "mongodb_atlas.host.name" attribute.
func (rb *ResourceBuilder) SetMongodbAtlasHostName(val string) {
if rb.config.MongodbAtlasHostName.Enabled {
rb.res.Attributes().PutStr("mongodb_atlas.host.name", val)
}
}
// SetMongodbAtlasOrgName sets provided value as "mongodb_atlas.org_name" attribute.
func (rb *ResourceBuilder) SetMongodbAtlasOrgName(val string) {
if rb.config.MongodbAtlasOrgName.Enabled {
rb.res.Attributes().PutStr("mongodb_atlas.org_name", val)
}
}
// SetMongodbAtlasProcessID sets provided value as "mongodb_atlas.process.id" attribute.
func (rb *ResourceBuilder) SetMongodbAtlasProcessID(val string) {
if rb.config.MongodbAtlasProcessID.Enabled {
rb.res.Attributes().PutStr("mongodb_atlas.process.id", val)
}
}
// SetMongodbAtlasProcessPort sets provided value as "mongodb_atlas.process.port" attribute.
func (rb *ResourceBuilder) SetMongodbAtlasProcessPort(val string) {
if rb.config.MongodbAtlasProcessPort.Enabled {
rb.res.Attributes().PutStr("mongodb_atlas.process.port", val)
}
}
// SetMongodbAtlasProcessTypeName sets provided value as "mongodb_atlas.process.type_name" attribute.
func (rb *ResourceBuilder) SetMongodbAtlasProcessTypeName(val string) {
if rb.config.MongodbAtlasProcessTypeName.Enabled {
rb.res.Attributes().PutStr("mongodb_atlas.process.type_name", val)
}
}
// SetMongodbAtlasProjectID sets provided value as "mongodb_atlas.project.id" attribute.
func (rb *ResourceBuilder) SetMongodbAtlasProjectID(val string) {
if rb.config.MongodbAtlasProjectID.Enabled {
rb.res.Attributes().PutStr("mongodb_atlas.project.id", val)
}
}
// SetMongodbAtlasProjectName sets provided value as "mongodb_atlas.project.name" attribute.
func (rb *ResourceBuilder) SetMongodbAtlasProjectName(val string) {
if rb.config.MongodbAtlasProjectName.Enabled {
rb.res.Attributes().PutStr("mongodb_atlas.project.name", val)
}
}
// SetMongodbAtlasProviderName sets provided value as "mongodb_atlas.provider.name" attribute.
func (rb *ResourceBuilder) SetMongodbAtlasProviderName(val string) {
if rb.config.MongodbAtlasProviderName.Enabled {
rb.res.Attributes().PutStr("mongodb_atlas.provider.name", val)
}
}
// SetMongodbAtlasRegionName sets provided value as "mongodb_atlas.region.name" attribute.
func (rb *ResourceBuilder) SetMongodbAtlasRegionName(val string) {
if rb.config.MongodbAtlasRegionName.Enabled {
rb.res.Attributes().PutStr("mongodb_atlas.region.name", val)
}
}
// SetMongodbAtlasUserAlias sets provided value as "mongodb_atlas.user.alias" attribute.
func (rb *ResourceBuilder) SetMongodbAtlasUserAlias(val string) {
if rb.config.MongodbAtlasUserAlias.Enabled {
rb.res.Attributes().PutStr("mongodb_atlas.user.alias", val)
}
}
// Emit returns the built resource and resets the internal builder state.
func (rb *ResourceBuilder) Emit() pcommon.Resource {
r := rb.res
rb.res = pcommon.NewResource()
return r
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package metadata // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mongodbatlasreceiver/internal/metadata"
import (
"time"
"go.mongodb.org/atlas/mongodbatlas"
"go.opentelemetry.io/collector/pdata/pcommon"
)
// metricRecordFunc records the data point to the metric builder at the supplied timestamp
type metricRecordFunc func(*MetricsBuilder, *mongodbatlas.DataPoints, pcommon.Timestamp)
// getRecordFunc returns the metricRecordFunc that matches the metric name. Nil if none is found.
func getRecordFunc(metricName string) metricRecordFunc {
switch metricName {
// MongoDB CPU usage. For hosts with more than one CPU core, these values can exceed 100%.
case "PROCESS_CPU_USER":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessCPUUsageAverageDataPoint(ts, float64(*dp.Value), AttributeCPUStateUser)
}
case "MAX_PROCESS_CPU_USER":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessCPUUsageMaxDataPoint(ts, float64(*dp.Value), AttributeCPUStateUser)
}
case "PROCESS_CPU_KERNEL":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessCPUUsageAverageDataPoint(ts, float64(*dp.Value), AttributeCPUStateKernel)
}
case "MAX_PROCESS_CPU_KERNEL":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessCPUUsageMaxDataPoint(ts, float64(*dp.Value), AttributeCPUStateKernel)
}
case "PROCESS_CPU_CHILDREN_USER":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessCPUChildrenUsageAverageDataPoint(ts, float64(*dp.Value), AttributeCPUStateUser)
}
case "MAX_PROCESS_CPU_CHILDREN_USER":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessCPUChildrenUsageMaxDataPoint(ts, float64(*dp.Value), AttributeCPUStateUser)
}
case "PROCESS_CPU_CHILDREN_KERNEL":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessCPUChildrenUsageAverageDataPoint(ts, float64(*dp.Value), AttributeCPUStateKernel)
}
case "MAX_PROCESS_CPU_CHILDREN_KERNEL":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessCPUChildrenUsageMaxDataPoint(ts, float64(*dp.Value), AttributeCPUStateKernel)
}
// MongoDB CPU usage scaled to a range of 0% to 100%. Atlas computes this value by dividing by the number of CPU cores.
case "PROCESS_NORMALIZED_CPU_USER":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessCPUNormalizedUsageAverageDataPoint(ts, float64(*dp.Value), AttributeCPUStateUser)
}
case "MAX_PROCESS_NORMALIZED_CPU_USER":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessCPUNormalizedUsageMaxDataPoint(ts, float64(*dp.Value), AttributeCPUStateUser)
}
case "PROCESS_NORMALIZED_CPU_KERNEL":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessCPUNormalizedUsageAverageDataPoint(ts, float64(*dp.Value), AttributeCPUStateKernel)
}
case "MAX_PROCESS_NORMALIZED_CPU_KERNEL":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessCPUNormalizedUsageMaxDataPoint(ts, float64(*dp.Value), AttributeCPUStateKernel)
}
case "PROCESS_NORMALIZED_CPU_CHILDREN_USER":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessCPUChildrenNormalizedUsageAverageDataPoint(ts, float64(*dp.Value), AttributeCPUStateUser)
}
// Context: Process
case "MAX_PROCESS_NORMALIZED_CPU_CHILDREN_USER":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessCPUChildrenNormalizedUsageMaxDataPoint(ts, float64(*dp.Value), AttributeCPUStateUser)
}
case "PROCESS_NORMALIZED_CPU_CHILDREN_KERNEL":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessCPUChildrenNormalizedUsageAverageDataPoint(ts, float64(*dp.Value), AttributeCPUStateKernel)
}
case "MAX_PROCESS_NORMALIZED_CPU_CHILDREN_KERNEL":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessCPUChildrenNormalizedUsageMaxDataPoint(ts, float64(*dp.Value), AttributeCPUStateKernel)
}
// Rate of asserts for a MongoDB process found in the asserts document that the serverStatus command generates.
case "ASSERT_REGULAR":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessAssertsDataPoint(ts, float64(*dp.Value), AttributeAssertTypeRegular)
}
case "ASSERT_WARNING":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessAssertsDataPoint(ts, float64(*dp.Value), AttributeAssertTypeWarning)
}
case "ASSERT_MSG":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessAssertsDataPoint(ts, float64(*dp.Value), AttributeAssertTypeMsg)
}
case "ASSERT_USER":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessAssertsDataPoint(ts, float64(*dp.Value), AttributeAssertTypeUser)
}
// Amount of data flushed in the background.
case "BACKGROUND_FLUSH_AVG":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessBackgroundFlushDataPoint(ts, float64(*dp.Value))
}
// Amount of bytes in the WiredTiger storage engine cache and tickets found in the wiredTiger.cache and wiredTiger.concurrentTransactions documents that the serverStatus command generates.
case "CACHE_BYTES_READ_INTO":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessCacheIoDataPoint(ts, float64(*dp.Value), AttributeCacheDirectionReadInto)
}
case "CACHE_BYTES_WRITTEN_FROM":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessCacheIoDataPoint(ts, float64(*dp.Value), AttributeCacheDirectionWrittenFrom)
}
case "CACHE_DIRTY_BYTES":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessCacheSizeDataPoint(ts, float64(*dp.Value), AttributeCacheStatusDirty)
}
case "CACHE_USED_BYTES":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessCacheSizeDataPoint(ts, float64(*dp.Value), AttributeCacheStatusUsed)
}
case "CACHE_FILL_RATIO":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessCacheRatioDataPoint(ts, float64(*dp.Value), AttributeCacheRatioTypeCacheFill)
}
case "DIRTY_FILL_RATIO":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessCacheRatioDataPoint(ts, float64(*dp.Value), AttributeCacheRatioTypeDirtyFill)
}
case "TICKETS_AVAILABLE_READS":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessTicketsDataPoint(ts, float64(*dp.Value), AttributeTicketTypeAvailableReads)
}
case "TICKETS_AVAILABLE_WRITE":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessTicketsDataPoint(ts, float64(*dp.Value), AttributeTicketTypeAvailableWrites)
}
// Number of connections to a MongoDB process found in the connections document that the serverStatus command generates.
case "CONNECTIONS":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessConnectionsDataPoint(ts, float64(*dp.Value))
}
// Number of cursors for a MongoDB process found in the metrics.cursor document that the serverStatus command generates.
case "CURSORS_TOTAL_OPEN":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessCursorsDataPoint(ts, float64(*dp.Value), AttributeCursorStateOpen)
}
case "CURSORS_TOTAL_TIMED_OUT":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessCursorsDataPoint(ts, float64(*dp.Value), AttributeCursorStateTimedOut)
}
// Numbers of Memory Issues and Page Faults for a MongoDB process.
case "EXTRA_INFO_PAGE_FAULTS":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessPageFaultsDataPoint(ts, float64(*dp.Value), AttributeMemoryIssueTypeExtraInfo)
}
case "GLOBAL_ACCESSES_NOT_IN_MEMORY":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessPageFaultsDataPoint(ts, float64(*dp.Value), AttributeMemoryIssueTypeGlobalAccessesNotInMemory)
}
case "GLOBAL_PAGE_FAULT_EXCEPTIONS_THROWN":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessPageFaultsDataPoint(ts, float64(*dp.Value), AttributeMemoryIssueTypeExceptionsThrown)
}
// Number of operations waiting on locks for the MongoDB process that the serverStatus command generates. Cloud Manager computes these values based on the type of storage engine.
case "GLOBAL_LOCK_CURRENT_QUEUE_TOTAL":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessGlobalLockDataPoint(ts, float64(*dp.Value), AttributeGlobalLockStateCurrentQueueTotal)
}
case "GLOBAL_LOCK_CURRENT_QUEUE_READERS":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessGlobalLockDataPoint(ts, float64(*dp.Value), AttributeGlobalLockStateCurrentQueueReaders)
}
case "GLOBAL_LOCK_CURRENT_QUEUE_WRITERS":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessGlobalLockDataPoint(ts, float64(*dp.Value), AttributeGlobalLockStateCurrentQueueWriters)
}
// Number of index btree operations.
case "INDEX_COUNTERS_BTREE_ACCESSES":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessIndexCountersDataPoint(ts, float64(*dp.Value), AttributeBtreeCounterTypeAccesses)
}
case "INDEX_COUNTERS_BTREE_HITS":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessIndexCountersDataPoint(ts, float64(*dp.Value), AttributeBtreeCounterTypeHits)
}
case "INDEX_COUNTERS_BTREE_MISSES":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessIndexCountersDataPoint(ts, float64(*dp.Value), AttributeBtreeCounterTypeMisses)
}
case "INDEX_COUNTERS_BTREE_MISS_RATIO":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessIndexBtreeMissRatioDataPoint(ts, float64(*dp.Value))
}
// Number of journaling operations.
case "JOURNALING_COMMITS_IN_WRITE_LOCK":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessJournalingCommitsDataPoint(ts, float64(*dp.Value))
}
case "JOURNALING_MB":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessJournalingWrittenDataPoint(ts, float64(*dp.Value))
}
case "JOURNALING_WRITE_DATA_FILES_MB":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessJournalingDataFilesDataPoint(ts, float64(*dp.Value))
}
// Amount of memory for a MongoDB process found in the mem document that the serverStatus command collects.
case "MEMORY_RESIDENT":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessMemoryUsageDataPoint(ts, float64(*dp.Value), AttributeMemoryStateResident)
}
case "MEMORY_VIRTUAL":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessMemoryUsageDataPoint(ts, float64(*dp.Value), AttributeMemoryStateVirtual)
}
case "MEMORY_MAPPED":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessMemoryUsageDataPoint(ts, float64(*dp.Value), AttributeMemoryStateMapped)
}
case "COMPUTED_MEMORY":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessMemoryUsageDataPoint(ts, float64(*dp.Value), AttributeMemoryStateComputed)
}
// Amount of throughput for MongoDB process found in the network document that the serverStatus command collects.
case "NETWORK_BYTES_IN":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessNetworkIoDataPoint(ts, float64(*dp.Value), AttributeDirectionReceive)
}
case "NETWORK_BYTES_OUT":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessNetworkIoDataPoint(ts, float64(*dp.Value), AttributeDirectionTransmit)
}
case "NETWORK_NUM_REQUESTS":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessNetworkRequestsDataPoint(ts, float64(*dp.Value))
}
// Durations and throughput of the MongoDB process' oplog.
case "OPLOG_SLAVE_LAG_MASTER_TIME":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessOplogTimeDataPoint(ts, float64(*dp.Value), AttributeOplogTypeSlaveLagMasterTime)
}
case "OPLOG_MASTER_TIME":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessOplogTimeDataPoint(ts, float64(*dp.Value), AttributeOplogTypeMasterTime)
}
case "OPLOG_MASTER_LAG_TIME_DIFF":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessOplogTimeDataPoint(ts, float64(*dp.Value), AttributeOplogTypeMasterLagTimeDiff)
}
case "OPLOG_RATE_GB_PER_HOUR":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessOplogRateDataPoint(ts, float64(*dp.Value))
}
// Number of database operations on a MongoDB process since the process last started.
case "DB_STORAGE_TOTAL":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessDbStorageDataPoint(ts, float64(*dp.Value), AttributeStorageStatusTotal)
}
case "DB_DATA_SIZE_TOTAL":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessDbStorageDataPoint(ts, float64(*dp.Value), AttributeStorageStatusDataSize)
}
case "DB_INDEX_SIZE_TOTAL":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessDbStorageDataPoint(ts, float64(*dp.Value), AttributeStorageStatusIndexSize)
}
case "DB_DATA_SIZE_TOTAL_WO_SYSTEM":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessDbStorageDataPoint(ts, float64(*dp.Value), AttributeStorageStatusDataSizeWoSystem)
}
// Rate of database operations on a MongoDB process since the process last started found in the opcounters document that the serverStatus command collects.
case "OPCOUNTER_CMD":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessDbOperationsRateDataPoint(ts, float64(*dp.Value), AttributeOperationCmd, AttributeClusterRolePrimary)
}
case "OPCOUNTER_QUERY":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessDbOperationsRateDataPoint(ts, float64(*dp.Value), AttributeOperationQuery, AttributeClusterRolePrimary)
}
case "OPCOUNTER_UPDATE":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessDbOperationsRateDataPoint(ts, float64(*dp.Value), AttributeOperationUpdate, AttributeClusterRolePrimary)
}
case "OPCOUNTER_DELETE":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessDbOperationsRateDataPoint(ts, float64(*dp.Value), AttributeOperationDelete, AttributeClusterRolePrimary)
}
case "OPCOUNTER_GETMORE":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessDbOperationsRateDataPoint(ts, float64(*dp.Value), AttributeOperationGetmore, AttributeClusterRolePrimary)
}
case "OPCOUNTER_INSERT":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessDbOperationsRateDataPoint(ts, float64(*dp.Value), AttributeOperationInsert, AttributeClusterRolePrimary)
}
case "OPCOUNTER_TTL_DELETED":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessDbOperationsRateDataPoint(ts, float64(*dp.Value), AttributeOperationTTLDeleted, AttributeClusterRolePrimary)
}
// Rate of database operations on MongoDB secondaries found in the opcountersRepl document that the serverStatus command collects.
case "OPCOUNTER_REPL_CMD":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessDbOperationsRateDataPoint(ts, float64(*dp.Value), AttributeOperationCmd, AttributeClusterRoleReplica)
}
case "OPCOUNTER_REPL_UPDATE":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessDbOperationsRateDataPoint(ts, float64(*dp.Value), AttributeOperationUpdate, AttributeClusterRoleReplica)
}
case "OPCOUNTER_REPL_DELETE":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessDbOperationsRateDataPoint(ts, float64(*dp.Value), AttributeOperationDelete, AttributeClusterRoleReplica)
}
case "OPCOUNTER_REPL_INSERT":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessDbOperationsRateDataPoint(ts, float64(*dp.Value), AttributeOperationInsert, AttributeClusterRoleReplica)
}
// Average rate of documents returned, inserted, updated, or deleted per second during a selected time period.
case "DOCUMENT_METRICS_RETURNED":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessDbDocumentRateDataPoint(ts, float64(*dp.Value), AttributeDocumentStatusReturned)
}
case "DOCUMENT_METRICS_INSERTED":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessDbDocumentRateDataPoint(ts, float64(*dp.Value), AttributeDocumentStatusInserted)
}
case "DOCUMENT_METRICS_UPDATED":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessDbDocumentRateDataPoint(ts, float64(*dp.Value), AttributeDocumentStatusUpdated)
}
case "DOCUMENT_METRICS_DELETED":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessDbDocumentRateDataPoint(ts, float64(*dp.Value), AttributeDocumentStatusDeleted)
}
// Average rate for operations per second during a selected time period that perform a sort but cannot perform the sort using an index.
case "OPERATIONS_SCAN_AND_ORDER":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessDbOperationsRateDataPoint(ts, float64(*dp.Value), AttributeOperationScanAndOrder, AttributeClusterRolePrimary)
}
// Average execution time in milliseconds per read, write, or command operation during a selected time period.
case "OP_EXECUTION_TIME_READS":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessDbOperationsTimeDataPoint(ts, float64(*dp.Value), AttributeExecutionTypeReads)
}
case "OP_EXECUTION_TIME_WRITES":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessDbOperationsTimeDataPoint(ts, float64(*dp.Value), AttributeExecutionTypeWrites)
}
case "OP_EXECUTION_TIME_COMMANDS":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessDbOperationsTimeDataPoint(ts, float64(*dp.Value), AttributeExecutionTypeCommands)
}
// Number of times the host restarted within the previous hour.
case "RESTARTS_IN_LAST_HOUR":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessRestartsDataPoint(ts, float64(*dp.Value))
}
// Average rate per second to scan index items during queries and query-plan evaluations found in the value of totalKeysExamined from the explain command.
case "QUERY_EXECUTOR_SCANNED":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessDbQueryExecutorScannedDataPoint(ts, float64(*dp.Value), AttributeScannedTypeIndexItems)
}
// Average rate of documents scanned per second during queries and query-plan evaluations found in the value of totalDocsExamined from the explain command.
case "QUERY_EXECUTOR_SCANNED_OBJECTS":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessDbQueryExecutorScannedDataPoint(ts, float64(*dp.Value), AttributeScannedTypeObjects)
}
// Ratio of the number of index items scanned to the number of documents returned.
case "QUERY_TARGETING_SCANNED_PER_RETURNED":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessDbQueryTargetingScannedPerReturnedDataPoint(ts, float64(*dp.Value), AttributeScannedTypeIndexItems)
}
// Ratio of the number of documents scanned to the number of documents returned.
case "QUERY_TARGETING_SCANNED_OBJECTS_PER_RETURNED":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasProcessDbQueryTargetingScannedPerReturnedDataPoint(ts, float64(*dp.Value), AttributeScannedTypeObjects)
}
// CPU usage of processes on the host. For hosts with more than one CPU core, this value can exceed 100%.
case "SYSTEM_CPU_USER":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemCPUUsageAverageDataPoint(ts, float64(*dp.Value), AttributeCPUStateUser)
}
case "MAX_SYSTEM_CPU_USER":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemCPUUsageMaxDataPoint(ts, float64(*dp.Value), AttributeCPUStateUser)
}
case "SYSTEM_CPU_KERNEL":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemCPUUsageAverageDataPoint(ts, float64(*dp.Value), AttributeCPUStateKernel)
}
case "MAX_SYSTEM_CPU_KERNEL":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemCPUUsageMaxDataPoint(ts, float64(*dp.Value), AttributeCPUStateKernel)
}
case "SYSTEM_CPU_NICE":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemCPUUsageAverageDataPoint(ts, float64(*dp.Value), AttributeCPUStateNice)
}
case "MAX_SYSTEM_CPU_NICE":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemCPUUsageMaxDataPoint(ts, float64(*dp.Value), AttributeCPUStateNice)
}
case "SYSTEM_CPU_IOWAIT":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemCPUUsageAverageDataPoint(ts, float64(*dp.Value), AttributeCPUStateIowait)
}
case "MAX_SYSTEM_CPU_IOWAIT":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemCPUUsageMaxDataPoint(ts, float64(*dp.Value), AttributeCPUStateIowait)
}
case "SYSTEM_CPU_IRQ":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemCPUUsageAverageDataPoint(ts, float64(*dp.Value), AttributeCPUStateIrq)
}
case "MAX_SYSTEM_CPU_IRQ":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemCPUUsageMaxDataPoint(ts, float64(*dp.Value), AttributeCPUStateIrq)
}
case "SYSTEM_CPU_SOFTIRQ":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemCPUUsageAverageDataPoint(ts, float64(*dp.Value), AttributeCPUStateSoftirq)
}
case "MAX_SYSTEM_CPU_SOFTIRQ":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemCPUUsageMaxDataPoint(ts, float64(*dp.Value), AttributeCPUStateSoftirq)
}
case "SYSTEM_CPU_GUEST":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemCPUUsageAverageDataPoint(ts, float64(*dp.Value), AttributeCPUStateGuest)
}
case "MAX_SYSTEM_CPU_GUEST":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemCPUUsageMaxDataPoint(ts, float64(*dp.Value), AttributeCPUStateGuest)
}
case "SYSTEM_CPU_STEAL":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemCPUUsageAverageDataPoint(ts, float64(*dp.Value), AttributeCPUStateSteal)
}
case "MAX_SYSTEM_CPU_STEAL":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemCPUUsageMaxDataPoint(ts, float64(*dp.Value), AttributeCPUStateSteal)
}
// CPU usage of processes on the host scaled to a range of 0 to 100% by dividing by the number of CPU cores.
case "SYSTEM_NORMALIZED_CPU_USER":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemCPUNormalizedUsageAverageDataPoint(ts, float64(*dp.Value), AttributeCPUStateUser)
}
case "MAX_SYSTEM_NORMALIZED_CPU_USER":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemCPUNormalizedUsageMaxDataPoint(ts, float64(*dp.Value), AttributeCPUStateUser)
}
case "MAX_SYSTEM_NORMALIZED_CPU_NICE":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemCPUNormalizedUsageMaxDataPoint(ts, float64(*dp.Value), AttributeCPUStateNice)
}
case "SYSTEM_NORMALIZED_CPU_KERNEL":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemCPUNormalizedUsageAverageDataPoint(ts, float64(*dp.Value), AttributeCPUStateKernel)
}
case "MAX_SYSTEM_NORMALIZED_CPU_KERNEL":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemCPUNormalizedUsageMaxDataPoint(ts, float64(*dp.Value), AttributeCPUStateKernel)
}
case "SYSTEM_NORMALIZED_CPU_NICE":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemCPUNormalizedUsageAverageDataPoint(ts, float64(*dp.Value), AttributeCPUStateNice)
}
case "SYSTEM_NORMALIZED_CPU_IOWAIT":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemCPUNormalizedUsageAverageDataPoint(ts, float64(*dp.Value), AttributeCPUStateIowait)
}
case "MAX_SYSTEM_NORMALIZED_CPU_IOWAIT":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemCPUNormalizedUsageMaxDataPoint(ts, float64(*dp.Value), AttributeCPUStateIowait)
}
case "SYSTEM_NORMALIZED_CPU_IRQ":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemCPUNormalizedUsageAverageDataPoint(ts, float64(*dp.Value), AttributeCPUStateIrq)
}
case "MAX_SYSTEM_NORMALIZED_CPU_IRQ":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemCPUNormalizedUsageMaxDataPoint(ts, float64(*dp.Value), AttributeCPUStateIrq)
}
case "SYSTEM_NORMALIZED_CPU_SOFTIRQ":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemCPUNormalizedUsageAverageDataPoint(ts, float64(*dp.Value), AttributeCPUStateSoftirq)
}
case "MAX_SYSTEM_NORMALIZED_CPU_SOFTIRQ":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemCPUNormalizedUsageMaxDataPoint(ts, float64(*dp.Value), AttributeCPUStateSoftirq)
}
case "SYSTEM_NORMALIZED_CPU_GUEST":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemCPUNormalizedUsageAverageDataPoint(ts, float64(*dp.Value), AttributeCPUStateGuest)
}
case "MAX_SYSTEM_NORMALIZED_CPU_GUEST":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemCPUNormalizedUsageMaxDataPoint(ts, float64(*dp.Value), AttributeCPUStateGuest)
}
case "SYSTEM_NORMALIZED_CPU_STEAL":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemCPUNormalizedUsageAverageDataPoint(ts, float64(*dp.Value), AttributeCPUStateSteal)
}
case "MAX_SYSTEM_NORMALIZED_CPU_STEAL":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemCPUNormalizedUsageMaxDataPoint(ts, float64(*dp.Value), AttributeCPUStateSteal)
}
// Physical memory usage, in bytes, that the host uses.
case "SYSTEM_MEMORY_AVAILABLE":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemMemoryUsageAverageDataPoint(ts, float64(*dp.Value), AttributeMemoryStatusAvailable)
}
case "MAX_SYSTEM_MEMORY_AVAILABLE":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemMemoryUsageMaxDataPoint(ts, float64(*dp.Value), AttributeMemoryStatusAvailable)
}
case "SYSTEM_MEMORY_BUFFERS":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemMemoryUsageAverageDataPoint(ts, float64(*dp.Value), AttributeMemoryStatusBuffers)
}
case "MAX_SYSTEM_MEMORY_BUFFERS":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemMemoryUsageMaxDataPoint(ts, float64(*dp.Value), AttributeMemoryStatusBuffers)
}
case "SYSTEM_MEMORY_CACHED":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemMemoryUsageAverageDataPoint(ts, float64(*dp.Value), AttributeMemoryStatusCached)
}
case "MAX_SYSTEM_MEMORY_CACHED":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemMemoryUsageMaxDataPoint(ts, float64(*dp.Value), AttributeMemoryStatusCached)
}
case "SYSTEM_MEMORY_FREE":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemMemoryUsageAverageDataPoint(ts, float64(*dp.Value), AttributeMemoryStatusFree)
}
case "MAX_SYSTEM_MEMORY_FREE":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemMemoryUsageMaxDataPoint(ts, float64(*dp.Value), AttributeMemoryStatusFree)
}
case "SYSTEM_MEMORY_SHARED":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemMemoryUsageAverageDataPoint(ts, float64(*dp.Value), AttributeMemoryStatusShared)
}
case "MAX_SYSTEM_MEMORY_SHARED":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemMemoryUsageMaxDataPoint(ts, float64(*dp.Value), AttributeMemoryStatusShared)
}
case "SYSTEM_MEMORY_USED":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemMemoryUsageAverageDataPoint(ts, float64(*dp.Value), AttributeMemoryStatusUsed)
}
case "MAX_SYSTEM_MEMORY_USED":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemMemoryUsageMaxDataPoint(ts, float64(*dp.Value), AttributeMemoryStatusUsed)
}
// Average rate of physical bytes per second that the eth0 network interface received and transmitted.
case "SYSTEM_NETWORK_IN":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemNetworkIoAverageDataPoint(ts, float64(*dp.Value), AttributeDirectionReceive)
}
case "MAX_SYSTEM_NETWORK_IN":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemNetworkIoMaxDataPoint(ts, float64(*dp.Value), AttributeDirectionReceive)
}
case "SYSTEM_NETWORK_OUT":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemNetworkIoAverageDataPoint(ts, float64(*dp.Value), AttributeDirectionTransmit)
}
case "MAX_SYSTEM_NETWORK_OUT":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemNetworkIoMaxDataPoint(ts, float64(*dp.Value), AttributeDirectionTransmit)
}
// Total amount of memory that swap uses.
case "SWAP_USAGE_USED":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemPagingUsageAverageDataPoint(ts, float64(*dp.Value), AttributeMemoryStateUsed)
}
case "MAX_SWAP_USAGE_USED":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemPagingUsageMaxDataPoint(ts, float64(*dp.Value), AttributeMemoryStateUsed)
}
case "SWAP_USAGE_FREE":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemPagingUsageAverageDataPoint(ts, float64(*dp.Value), AttributeMemoryStateFree)
}
case "MAX_SWAP_USAGE_FREE":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemPagingUsageMaxDataPoint(ts, float64(*dp.Value), AttributeMemoryStateFree)
}
// Total amount of memory written and read from swap.
case "SWAP_IO_IN":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemPagingIoAverageDataPoint(ts, float64(*dp.Value), AttributeDirectionReceive)
}
case "MAX_SWAP_IO_IN":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemPagingIoMaxDataPoint(ts, float64(*dp.Value), AttributeDirectionReceive)
}
case "SWAP_IO_OUT":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemPagingIoAverageDataPoint(ts, float64(*dp.Value), AttributeDirectionTransmit)
}
case "MAX_SWAP_IO_OUT":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemPagingIoMaxDataPoint(ts, float64(*dp.Value), AttributeDirectionTransmit)
}
// Memory usage, in bytes, that Atlas Search processes use.
case "FTS_PROCESS_RESIDENT_MEMORY":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemFtsMemoryUsageDataPoint(ts, float64(*dp.Value), AttributeMemoryStateResident)
}
case "FTS_PROCESS_VIRTUAL_MEMORY":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemFtsMemoryUsageDataPoint(ts, float64(*dp.Value), AttributeMemoryStateVirtual)
}
case "FTS_PROCESS_SHARED_MEMORY":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemFtsMemoryUsageDataPoint(ts, float64(*dp.Value), AttributeMemoryStateShared)
}
case "FTS_MEMORY_MAPPED":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemFtsMemoryUsageDataPoint(ts, float64(*dp.Value), AttributeMemoryStateMapped)
}
// Disk space, in bytes, that Atlas Search indexes use.
// FTS_DISK_UTILIZATION is the documented field name, but FTS_DISK_USAGE is what is returned from the API.
// Including both so if the API changes to match the documentation this metric is still collected.
case "FTS_DISK_USAGE", "FTS_DISK_UTILIZATION":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemFtsDiskUsedDataPoint(ts, float64(*dp.Value))
}
// Percentage of CPU that Atlas Search processes use.
case "FTS_PROCESS_CPU_USER":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemFtsCPUUsageDataPoint(ts, float64(*dp.Value), AttributeCPUStateUser)
}
case "FTS_PROCESS_CPU_KERNEL":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemFtsCPUUsageDataPoint(ts, float64(*dp.Value), AttributeCPUStateKernel)
}
case "FTS_PROCESS_NORMALIZED_CPU_USER":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemFtsCPUNormalizedUsageDataPoint(ts, float64(*dp.Value), AttributeCPUStateUser)
}
case "FTS_PROCESS_NORMALIZED_CPU_KERNEL":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasSystemFtsCPUNormalizedUsageDataPoint(ts, float64(*dp.Value), AttributeCPUStateKernel)
}
// Process Disk Measurements (https://docs.atlas.mongodb.com/reference/api/process-disks-measurements/)
// Measures throughput of I/O operations for the disk partition used for MongoDB.
case "DISK_PARTITION_IOPS_READ":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasDiskPartitionIopsAverageDataPoint(ts, float64(*dp.Value), AttributeDiskDirectionRead)
}
case "MAX_DISK_PARTITION_IOPS_READ":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasDiskPartitionIopsAverageDataPoint(ts, float64(*dp.Value), AttributeDiskDirectionRead)
}
case "DISK_PARTITION_IOPS_WRITE":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasDiskPartitionIopsAverageDataPoint(ts, float64(*dp.Value), AttributeDiskDirectionWrite)
}
case "MAX_DISK_PARTITION_IOPS_WRITE":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasDiskPartitionIopsMaxDataPoint(ts, float64(*dp.Value), AttributeDiskDirectionWrite)
}
case "DISK_PARTITION_IOPS_TOTAL":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasDiskPartitionIopsAverageDataPoint(ts, float64(*dp.Value), AttributeDiskDirectionTotal)
}
case "MAX_DISK_PARTITION_IOPS_TOTAL":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasDiskPartitionIopsMaxDataPoint(ts, float64(*dp.Value), AttributeDiskDirectionTotal)
}
// Measures throughput of data read and written to the disk partition (not cache) used by MongoDB.
case "DISK_PARTITION_THROUGHPUT_READ":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasDiskPartitionThroughputDataPoint(ts, float64(*dp.Value), AttributeDiskDirectionRead)
}
case "DISK_PARTITION_THROUGHPUT_WRITE":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasDiskPartitionThroughputDataPoint(ts, float64(*dp.Value), AttributeDiskDirectionWrite)
}
// This is a calculated metric that is the sum of the read and write throughput.
case "DISK_PARTITION_THROUGHPUT_TOTAL":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasDiskPartitionThroughputDataPoint(ts, float64(*dp.Value), AttributeDiskDirectionTotal)
}
// Measures the queue depth of the disk partition used by MongoDB.
case "DISK_QUEUE_DEPTH":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasDiskPartitionQueueDepthDataPoint(ts, float64(*dp.Value))
}
// Measures latency per operation type of the disk partition used by MongoDB.
case "DISK_PARTITION_LATENCY_READ":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasDiskPartitionLatencyAverageDataPoint(ts, float64(*dp.Value), AttributeDiskDirectionRead)
}
case "MAX_DISK_PARTITION_LATENCY_READ":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasDiskPartitionLatencyMaxDataPoint(ts, float64(*dp.Value), AttributeDiskDirectionRead)
}
case "DISK_PARTITION_LATENCY_WRITE":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasDiskPartitionLatencyAverageDataPoint(ts, float64(*dp.Value), AttributeDiskDirectionWrite)
}
case "MAX_DISK_PARTITION_LATENCY_WRITE":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasDiskPartitionLatencyMaxDataPoint(ts, float64(*dp.Value), AttributeDiskDirectionWrite)
}
// The percentage of time during which requests are being issued to and serviced by the partition.
// This includes requests from any process, not just MongoDB processes.
case "DISK_PARTITION_UTILIZATION":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasDiskPartitionUtilizationAverageDataPoint(ts, float64(*dp.Value))
}
case "MAX_DISK_PARTITION_UTILIZATION":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasDiskPartitionUtilizationMaxDataPoint(ts, float64(*dp.Value))
}
// Measures the free disk space and used disk space on the disk partition used by MongoDB.
case "DISK_PARTITION_SPACE_FREE":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasDiskPartitionSpaceAverageDataPoint(ts, float64(*dp.Value), AttributeDiskStatusFree)
}
case "MAX_DISK_PARTITION_SPACE_FREE":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasDiskPartitionSpaceMaxDataPoint(ts, float64(*dp.Value), AttributeDiskStatusFree)
}
case "DISK_PARTITION_SPACE_USED":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasDiskPartitionSpaceAverageDataPoint(ts, float64(*dp.Value), AttributeDiskStatusUsed)
}
case "MAX_DISK_PARTITION_SPACE_USED":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasDiskPartitionSpaceMaxDataPoint(ts, float64(*dp.Value), AttributeDiskStatusUsed)
}
case "DISK_PARTITION_SPACE_PERCENT_FREE":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasDiskPartitionUsageAverageDataPoint(ts, float64(*dp.Value), AttributeDiskStatusFree)
}
case "MAX_DISK_PARTITION_SPACE_PERCENT_FREE":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasDiskPartitionUsageMaxDataPoint(ts, float64(*dp.Value), AttributeDiskStatusFree)
}
case "DISK_PARTITION_SPACE_PERCENT_USED":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasDiskPartitionUsageAverageDataPoint(ts, float64(*dp.Value), AttributeDiskStatusUsed)
}
case "MAX_DISK_PARTITION_SPACE_PERCENT_USED":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasDiskPartitionUsageMaxDataPoint(ts, float64(*dp.Value), AttributeDiskStatusUsed)
}
// Process Database Measurements (https://docs.atlas.mongodb.com/reference/api/process-disks-measurements/)
case "DATABASE_COLLECTION_COUNT":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasDbCountsDataPoint(ts, float64(*dp.Value), AttributeObjectTypeCollection)
}
case "DATABASE_INDEX_COUNT":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasDbCountsDataPoint(ts, float64(*dp.Value), AttributeObjectTypeIndex)
}
case "DATABASE_EXTENT_COUNT":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasDbCountsDataPoint(ts, float64(*dp.Value), AttributeObjectTypeExtent)
}
case "DATABASE_OBJECT_COUNT":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasDbCountsDataPoint(ts, float64(*dp.Value), AttributeObjectTypeObject)
}
case "DATABASE_VIEW_COUNT":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasDbCountsDataPoint(ts, float64(*dp.Value), AttributeObjectTypeView)
}
case "DATABASE_AVERAGE_OBJECT_SIZE":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasDbSizeDataPoint(ts, float64(*dp.Value), AttributeObjectTypeObject)
}
case "DATABASE_STORAGE_SIZE":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasDbSizeDataPoint(ts, float64(*dp.Value), AttributeObjectTypeStorage)
}
case "DATABASE_INDEX_SIZE":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasDbSizeDataPoint(ts, float64(*dp.Value), AttributeObjectTypeIndex)
}
case "DATABASE_DATA_SIZE":
return func(mb *MetricsBuilder, dp *mongodbatlas.DataPoints, ts pcommon.Timestamp) {
mb.RecordMongodbatlasDbSizeDataPoint(ts, float64(*dp.Value), AttributeObjectTypeData)
}
default:
return nil
}
}
func MeasurementsToMetric(mb *MetricsBuilder, meas *mongodbatlas.Measurements) error {
recordFunc := getRecordFunc(meas.Name)
if recordFunc == nil {
return nil
}
return addDataPoint(mb, meas, recordFunc)
}
func addDataPoint(mb *MetricsBuilder, meas *mongodbatlas.Measurements, recordFunc metricRecordFunc) error {
for _, point := range meas.DataPoints {
if point.Value != nil {
curTime, err := time.Parse(time.RFC3339, point.Timestamp)
if err != nil {
return err
}
recordFunc(mb, point, pcommon.NewTimestampFromTime(curTime))
}
}
return nil
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mongodbatlasreceiver/internal"
import (
"fmt"
"go.mongodb.org/atlas/mongodbatlas"
"go.uber.org/multierr"
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mongodbatlasreceiver/internal/metadata"
)
func processMeasurements(
mb *metadata.MetricsBuilder,
measurements []*mongodbatlas.Measurements,
) error {
var errs error
for _, meas := range measurements {
err := metadata.MeasurementsToMetric(mb, meas)
if err != nil {
errs = multierr.Append(errs, err)
}
}
err := calculateTotalMetrics(mb, measurements)
if err != nil {
errs = multierr.Append(errs, err)
}
if errs != nil {
return fmt.Errorf("errors occurred while processing measurements: %w", errs)
}
return nil
}
func calculateTotalMetrics(
mb *metadata.MetricsBuilder,
measurements []*mongodbatlas.Measurements,
) error {
var err error
dptTotalMeasCombined := false
var dptTotalMeas *mongodbatlas.Measurements
for _, meas := range measurements {
switch meas.Name {
case "DISK_PARTITION_THROUGHPUT_READ", "DISK_PARTITION_THROUGHPUT_WRITE":
if dptTotalMeas == nil {
dptTotalMeas = cloneMeasurement(meas)
dptTotalMeas.Name = "DISK_PARTITION_THROUGHPUT_TOTAL"
continue
}
// Combine data point values with matching timestamps
for j, totalMeas := range dptTotalMeas.DataPoints {
if totalMeas.Timestamp != meas.DataPoints[j].Timestamp ||
(totalMeas.Value == nil && meas.DataPoints[j].Value == nil) {
continue
}
if totalMeas.Value == nil {
totalMeas.Value = new(float32)
}
addValue := *meas.DataPoints[j].Value
if meas.DataPoints[j].Value == nil {
addValue = 0
}
*totalMeas.Value += addValue
dptTotalMeasCombined = true
}
default:
}
}
if dptTotalMeasCombined {
err = metadata.MeasurementsToMetric(mb, dptTotalMeas)
}
return err
}
func cloneMeasurement(meas *mongodbatlas.Measurements) *mongodbatlas.Measurements {
clone := &mongodbatlas.Measurements{
Name: meas.Name,
Units: meas.Units,
DataPoints: make([]*mongodbatlas.DataPoints, len(meas.DataPoints)),
}
for i, dp := range meas.DataPoints {
if dp != nil {
newDP := *dp
clone.DataPoints[i] = &newDP
}
}
return clone
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package model // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mongodbatlasreceiver/internal/model"
import (
"go.opentelemetry.io/collector/pdata/pcommon"
)
// LogEntry represents a MongoDB Atlas JSON log entry
type LogEntry struct {
Timestamp LogTimestamp `json:"t"`
Severity string `json:"s"`
Component string `json:"c"`
ID int64 `json:"id"`
Context string `json:"ctx"`
Message string `json:"msg"`
Attributes map[string]any `json:"attr"`
// Raw is the original log line. It is not a part of the payload, but transient data added during decoding.
Raw string `json:"-"`
}
// AuditLog represents a MongoDB Atlas JSON audit log entry
type AuditLog struct {
Type string `json:"atype"`
Timestamp LogTimestamp `json:"ts"`
ID *ID `json:"uuid,omitempty"`
Local Address `json:"local"`
Remote Address `json:"remote"`
Users []AuditUser `json:"users"`
Roles []AuditRole `json:"roles"`
Result int `json:"result"`
Param map[string]any `json:"param"`
// Raw is the original log line. It is not a part of the payload, but transient data added during decoding.
Raw string `json:"-"`
}
// logTimestamp is the structure that represents a Log Timestamp
type LogTimestamp struct {
Date string `json:"$date"`
}
type ID struct {
Binary string `json:"$binary"`
Type string `json:"$type"`
}
type Address struct {
IP *string `json:"ip,omitempty"`
Port *int `json:"port,omitempty"`
SystemUser *bool `json:"isSystemUser,omitempty"`
UnixSocket *string `json:"unix,omitempty"`
}
type AuditRole struct {
Role string `json:"role"`
Database string `json:"db"`
}
func (ar AuditRole) Pdata() pcommon.Map {
m := pcommon.NewMap()
m.EnsureCapacity(2)
m.PutStr("role", ar.Role)
m.PutStr("db", ar.Database)
return m
}
type AuditUser struct {
User string `json:"user"`
Database string `json:"db"`
}
func (ar AuditUser) Pdata() pcommon.Map {
m := pcommon.NewMap()
m.EnsureCapacity(2)
m.PutStr("user", ar.User)
m.PutStr("db", ar.Database)
return m
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mongodbatlasreceiver/internal"
import (
"bytes"
"context"
"errors"
"fmt"
"net/http"
"strconv"
"sync"
"time"
"github.com/cenkalti/backoff/v4"
"github.com/mongodb-forks/digest"
"go.mongodb.org/atlas/mongodbatlas"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configretry"
"go.uber.org/zap"
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mongodbatlasreceiver/internal/metadata"
)
type clientRoundTripper struct {
originalTransport http.RoundTripper
log *zap.Logger
backoffConfig configretry.BackOffConfig
stopped bool
mutex sync.Mutex
shutdownChan chan struct{}
}
func newClientRoundTripper(
originalTransport http.RoundTripper,
log *zap.Logger,
backoffConfig configretry.BackOffConfig,
) *clientRoundTripper {
return &clientRoundTripper{
originalTransport: originalTransport,
log: log,
backoffConfig: backoffConfig,
shutdownChan: make(chan struct{}, 1),
}
}
func (rt *clientRoundTripper) isStopped() bool {
rt.mutex.Lock()
defer rt.mutex.Unlock()
return rt.stopped
}
func (rt *clientRoundTripper) stop() {
rt.mutex.Lock()
defer rt.mutex.Unlock()
rt.stopped = true
}
func (rt *clientRoundTripper) Shutdown() error {
if rt.isStopped() {
return nil
}
rt.stop()
rt.shutdownChan <- struct{}{}
close(rt.shutdownChan)
return nil
}
func (rt *clientRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
if rt.isStopped() {
return nil, errors.New("request cancelled due to shutdown")
}
resp, err := rt.originalTransport.RoundTrip(r)
if err != nil {
return nil, err // Can't do anything
}
if resp.StatusCode == http.StatusTooManyRequests {
expBackoff := &backoff.ExponentialBackOff{
InitialInterval: rt.backoffConfig.InitialInterval,
RandomizationFactor: backoff.DefaultRandomizationFactor,
Multiplier: backoff.DefaultMultiplier,
MaxInterval: rt.backoffConfig.MaxInterval,
MaxElapsedTime: rt.backoffConfig.MaxElapsedTime,
Stop: backoff.Stop,
Clock: backoff.SystemClock,
}
expBackoff.Reset()
attempts := 0
for {
attempts++
delay := expBackoff.NextBackOff()
if delay == backoff.Stop {
return resp, err
}
rt.log.Warn("server busy, retrying request",
zap.Int("attempts", attempts),
zap.Duration("delay", delay))
select {
case <-r.Context().Done():
return resp, errors.New("request was cancelled or timed out")
case <-rt.shutdownChan:
return resp, errors.New("request is cancelled due to server shutdown")
case <-time.After(delay):
}
resp, err = rt.originalTransport.RoundTrip(r)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusTooManyRequests {
break
}
}
}
return resp, err
}
// MongoDBAtlasClient wraps the official MongoDB Atlas client to manage pagination
// and mapping to OpenTelemetry metric and log structures.
type MongoDBAtlasClient struct {
log *zap.Logger
client *mongodbatlas.Client
transport *http.Transport
roundTripper *clientRoundTripper
}
// NewMongoDBAtlasClient creates a new MongoDB Atlas client wrapper
func NewMongoDBAtlasClient(
baseURL string,
publicKey string,
privateKey string,
backoffConfig configretry.BackOffConfig,
log *zap.Logger,
buildInfo component.BuildInfo,
) (*MongoDBAtlasClient, error) {
defaultTransporter := http.DefaultTransport.(*http.Transport)
t := digest.NewTransportWithHTTPTransport(publicKey, privateKey, defaultTransporter)
roundTripper := newClientRoundTripper(t, log, backoffConfig)
tc := &http.Client{Transport: roundTripper}
if baseURL == "" {
baseURL = mongodbatlas.CloudURL
}
client, err := mongodbatlas.New(tc,
mongodbatlas.SetBaseURL(baseURL),
mongodbatlas.SetUserAgent(fmt.Sprintf("%s/%s", buildInfo.Command, buildInfo.Version)),
)
if err != nil {
return nil, fmt.Errorf("failed to create MongoDB Atlas client: %w", err)
}
return &MongoDBAtlasClient{
log,
client,
defaultTransporter,
roundTripper,
}, nil
}
func (s *MongoDBAtlasClient) Shutdown() error {
s.transport.CloseIdleConnections()
return s.roundTripper.Shutdown()
}
// Check both the returned error and the status of the HTTP response
func checkMongoDBClientErr(err error, response *mongodbatlas.Response) error {
if err != nil {
return err
}
if response != nil {
return response.CheckResponse(response.Body)
}
return nil
}
func hasNext(links []*mongodbatlas.Link) bool {
for _, link := range links {
if link.Rel == "next" {
return true
}
}
return false
}
// Organizations returns a list of all organizations available with the supplied credentials
func (s *MongoDBAtlasClient) Organizations(ctx context.Context) ([]*mongodbatlas.Organization, error) {
var allOrgs []*mongodbatlas.Organization
page := 1
for {
orgs, hasNext, err := s.getOrganizationsPage(ctx, page)
page++
if err != nil {
// TODO: Add error to a metric
// Stop, returning what we have (probably empty slice)
return allOrgs, fmt.Errorf("error retrieving organizations from MongoDB Atlas API: %w", err)
}
allOrgs = append(allOrgs, orgs...)
if !hasNext {
break
}
}
return allOrgs, nil
}
func (s *MongoDBAtlasClient) getOrganizationsPage(
ctx context.Context,
pageNum int,
) ([]*mongodbatlas.Organization, bool, error) {
orgs, response, err := s.client.Organizations.List(ctx, &mongodbatlas.OrganizationsListOptions{
ListOptions: mongodbatlas.ListOptions{
PageNum: pageNum,
},
})
err = checkMongoDBClientErr(err, response)
if err != nil {
return nil, false, fmt.Errorf("error in retrieving organizations: %w", err)
}
return orgs.Results, hasNext(orgs.Links), nil
}
// GetOrganization retrieves a single organization specified by orgID
func (s *MongoDBAtlasClient) GetOrganization(ctx context.Context, orgID string) (*mongodbatlas.Organization, error) {
org, response, err := s.client.Organizations.Get(ctx, orgID)
err = checkMongoDBClientErr(err, response)
if err != nil {
return nil, fmt.Errorf("error retrieving project page: %w", err)
}
return org, nil
}
// Projects returns a list of projects accessible within the provided organization
func (s *MongoDBAtlasClient) Projects(
ctx context.Context,
orgID string,
) ([]*mongodbatlas.Project, error) {
var allProjects []*mongodbatlas.Project
page := 1
for {
projects, hasNext, err := s.getProjectsPage(ctx, orgID, page)
page++
if err != nil {
return allProjects, fmt.Errorf("error retrieving list of projects from MongoDB Atlas API: %w", err)
}
allProjects = append(allProjects, projects...)
if !hasNext {
break
}
}
return allProjects, nil
}
// GetProject returns a single project specified by projectName
func (s *MongoDBAtlasClient) GetProject(ctx context.Context, projectName string) (*mongodbatlas.Project, error) {
project, response, err := s.client.Projects.GetOneProjectByName(ctx, projectName)
err = checkMongoDBClientErr(err, response)
if err != nil {
return nil, fmt.Errorf("error retrieving project page: %w", err)
}
return project, nil
}
func (s *MongoDBAtlasClient) getProjectsPage(
ctx context.Context,
orgID string,
pageNum int,
) ([]*mongodbatlas.Project, bool, error) {
projects, response, err := s.client.Organizations.Projects(
ctx,
orgID,
&mongodbatlas.ProjectsListOptions{
ListOptions: mongodbatlas.ListOptions{PageNum: pageNum},
},
)
err = checkMongoDBClientErr(err, response)
if err != nil {
return nil, false, fmt.Errorf("error retrieving project page: %w", err)
}
return projects.Results, hasNext(projects.Links), nil
}
// Processes returns the list of processes running for a given project.
func (s *MongoDBAtlasClient) Processes(
ctx context.Context,
projectID string,
) ([]*mongodbatlas.Process, error) {
// A paginated API, but the MongoDB client just returns the values from the first page
// Note: MongoDB Atlas also has the idea of a Cluster- we can retrieve a list of clusters from
// the Project, but a Cluster does not have a link to its Process list and a Process does not
// have a link to its Cluster (save through the hostname, which is not a documented relationship).
processes, response, err := s.client.Processes.List(
ctx,
projectID,
&mongodbatlas.ProcessesListOptions{
ListOptions: mongodbatlas.ListOptions{
PageNum: 0,
ItemsPerPage: 0,
IncludeCount: true,
},
},
)
err = checkMongoDBClientErr(err, response)
if err != nil {
return nil, fmt.Errorf("error retrieving processes from MongoDB Atlas API: %w", err)
}
return processes, nil
}
func (s *MongoDBAtlasClient) getProcessDatabasesPage(
ctx context.Context,
projectID string,
host string,
port int,
pageNum int,
) ([]*mongodbatlas.ProcessDatabase, bool, error) {
databases, response, err := s.client.ProcessDatabases.List(
ctx,
projectID,
host,
port,
&mongodbatlas.ListOptions{PageNum: pageNum},
)
err = checkMongoDBClientErr(err, response)
if err != nil {
return nil, false, err
}
return databases.Results, hasNext(databases.Links), nil
}
// ProcessDatabases lists databases that are running in a given MongoDB Atlas process
func (s *MongoDBAtlasClient) ProcessDatabases(
ctx context.Context,
projectID string,
host string,
port int,
) ([]*mongodbatlas.ProcessDatabase, error) {
var allProcessDatabases []*mongodbatlas.ProcessDatabase
pageNum := 1
for {
processes, hasMore, err := s.getProcessDatabasesPage(ctx, projectID, host, port, pageNum)
pageNum++
if err != nil {
return allProcessDatabases, err
}
allProcessDatabases = append(allProcessDatabases, processes...)
if !hasMore {
break
}
}
return allProcessDatabases, nil
}
// ProcessMetrics returns a set of metrics associated with the specified running process.
func (s *MongoDBAtlasClient) ProcessMetrics(
ctx context.Context,
mb *metadata.MetricsBuilder,
projectID string,
host string,
port int,
start string,
end string,
resolution string,
) error {
var allMeasurements []*mongodbatlas.Measurements
pageNum := 1
for {
measurements, hasMore, err := s.getProcessMeasurementsPage(
ctx,
projectID,
host,
port,
pageNum,
start,
end,
resolution,
)
if err != nil {
s.log.Debug("Error retrieving process metrics from MongoDB Atlas API", zap.Error(err))
break // Return partial results
}
pageNum++
allMeasurements = append(allMeasurements, measurements...)
if !hasMore {
break
}
}
return processMeasurements(mb, allMeasurements)
}
func (s *MongoDBAtlasClient) getProcessMeasurementsPage(
ctx context.Context,
projectID string,
host string,
port int,
pageNum int,
start string,
end string,
resolution string,
) ([]*mongodbatlas.Measurements, bool, error) {
measurements, result, err := s.client.ProcessMeasurements.List(
ctx,
projectID,
host,
port,
&mongodbatlas.ProcessMeasurementListOptions{
ListOptions: &mongodbatlas.ListOptions{PageNum: pageNum},
Granularity: resolution,
Start: start,
End: end,
},
)
err = checkMongoDBClientErr(err, result)
if err != nil {
return nil, false, err
}
return measurements.Measurements, hasNext(measurements.Links), nil
}
// ProcessDatabaseMetrics returns metrics about a particular database running within a MongoDB Atlas process
func (s *MongoDBAtlasClient) ProcessDatabaseMetrics(
ctx context.Context,
mb *metadata.MetricsBuilder,
projectID string,
host string,
port int,
dbname string,
start string,
end string,
resolution string,
) error {
var allMeasurements []*mongodbatlas.Measurements
pageNum := 1
for {
measurements, hasMore, err := s.getProcessDatabaseMeasurementsPage(
ctx,
projectID,
host,
port,
dbname,
pageNum,
start,
end,
resolution,
)
if err != nil {
return err
}
pageNum++
allMeasurements = append(allMeasurements, measurements...)
if !hasMore {
break
}
}
return processMeasurements(mb, allMeasurements)
}
func (s *MongoDBAtlasClient) getProcessDatabaseMeasurementsPage(
ctx context.Context,
projectID string,
host string,
port int,
dbname string,
pageNum int,
start string,
end string,
resolution string,
) ([]*mongodbatlas.Measurements, bool, error) {
measurements, result, err := s.client.ProcessDatabaseMeasurements.List(
ctx,
projectID,
host,
port,
dbname,
&mongodbatlas.ProcessMeasurementListOptions{
ListOptions: &mongodbatlas.ListOptions{PageNum: pageNum},
Granularity: resolution,
Start: start,
End: end,
},
)
err = checkMongoDBClientErr(err, result)
if err != nil {
return nil, false, err
}
return measurements.Measurements, hasNext(measurements.Links), nil
}
// ProcessDisks enumerates the disks accessible to a specified MongoDB Atlas process
func (s *MongoDBAtlasClient) ProcessDisks(
ctx context.Context,
projectID string,
host string,
port int,
) []*mongodbatlas.ProcessDisk {
var allDisks []*mongodbatlas.ProcessDisk
pageNum := 1
for {
disks, hasMore, err := s.getProcessDisksPage(ctx, projectID, host, port, pageNum)
if err != nil {
s.log.Debug("Error retrieving disk metrics from MongoDB Atlas API", zap.Error(err))
break // Return partial results
}
pageNum++
allDisks = append(allDisks, disks...)
if !hasMore {
break
}
}
return allDisks
}
func (s *MongoDBAtlasClient) getProcessDisksPage(
ctx context.Context,
projectID string,
host string,
port int,
pageNum int,
) ([]*mongodbatlas.ProcessDisk, bool, error) {
disks, result, err := s.client.ProcessDisks.List(
ctx,
projectID,
host,
port,
&mongodbatlas.ListOptions{PageNum: pageNum},
)
err = checkMongoDBClientErr(err, result)
if err != nil {
return nil, false, err
}
return disks.Results, hasNext(disks.Links), nil
}
// ProcessDiskMetrics returns metrics supplied for a particular disk partition used by a MongoDB Atlas process
func (s *MongoDBAtlasClient) ProcessDiskMetrics(
ctx context.Context,
mb *metadata.MetricsBuilder,
projectID string,
host string,
port int,
partitionName string,
start string,
end string,
resolution string,
) error {
var allMeasurements []*mongodbatlas.Measurements
pageNum := 1
for {
measurements, hasMore, err := s.processDiskMeasurementsPage(
ctx,
projectID,
host,
port,
partitionName,
pageNum,
start,
end,
resolution,
)
if err != nil {
return err
}
pageNum++
allMeasurements = append(allMeasurements, measurements...)
if !hasMore {
break
}
}
return processMeasurements(mb, allMeasurements)
}
func (s *MongoDBAtlasClient) processDiskMeasurementsPage(
ctx context.Context,
projectID string,
host string,
port int,
partitionName string,
pageNum int,
start string,
end string,
resolution string,
) ([]*mongodbatlas.Measurements, bool, error) {
measurements, result, err := s.client.ProcessDiskMeasurements.List(
ctx,
projectID,
host,
port,
partitionName,
&mongodbatlas.ProcessMeasurementListOptions{
ListOptions: &mongodbatlas.ListOptions{PageNum: pageNum},
Granularity: resolution,
Start: start,
End: end,
},
)
err = checkMongoDBClientErr(err, result)
if err != nil {
return nil, false, err
}
return measurements.Measurements, hasNext(measurements.Links), nil
}
// GetLogs retrieves the logs from the mongo API using API call: https://www.mongodb.com/docs/atlas/reference/api/logs/#syntax
func (s *MongoDBAtlasClient) GetLogs(ctx context.Context, groupID, hostname, logName string, start, end time.Time) (*bytes.Buffer, error) {
buf := bytes.NewBuffer([]byte{})
dateRange := &mongodbatlas.DateRangetOptions{StartDate: toUnixString(start), EndDate: toUnixString(end)}
resp, err := s.client.Logs.Get(ctx, groupID, hostname, logName, buf, dateRange)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("received status code: %d", resp.StatusCode)
}
return buf, nil
}
// GetClusters retrieves the clusters from the mongo API using API call: https://www.mongodb.com/docs/atlas/reference/api/clusters-get-all/#request
func (s *MongoDBAtlasClient) GetClusters(ctx context.Context, groupID string) ([]mongodbatlas.Cluster, error) {
options := mongodbatlas.ListOptions{}
clusters, _, err := s.client.Clusters.List(ctx, groupID, &options)
if err != nil {
return nil, err
}
return clusters, nil
}
type AlertPollOptions struct {
PageNum int
PageSize int
}
// GetAlerts returns the alerts specified for the set projects
func (s *MongoDBAtlasClient) GetAlerts(ctx context.Context, groupID string, opts *AlertPollOptions) (ret []mongodbatlas.Alert, nextPage bool, err error) {
lo := mongodbatlas.ListOptions{
PageNum: opts.PageNum,
ItemsPerPage: opts.PageSize,
}
options := mongodbatlas.AlertsListOptions{ListOptions: lo}
alerts, response, err := s.client.Alerts.List(ctx, groupID, &options)
err = checkMongoDBClientErr(err, response)
if err != nil {
return nil, false, err
}
return alerts.Results, hasNext(response.Links), nil
}
// GetEventsOptions are the options to use for making a request to get Project Events
type GetEventsOptions struct {
// Which page of the paginated events
PageNum int
// How large the Pages will be
PageSize int
// The list of Event Types https://www.mongodb.com/docs/atlas/reference/api/events-projects-get-all/#event-type-values
// to grab from the API
EventTypes []string
// The oldest date to look back for the events
MinDate time.Time
// the newest time to accept events
MaxDate time.Time
}
// GetProjectEvents returns the events specified for the set projects
func (s *MongoDBAtlasClient) GetProjectEvents(ctx context.Context, groupID string, opts *GetEventsOptions) (ret []*mongodbatlas.Event, nextPage bool, err error) {
lo := mongodbatlas.ListOptions{
PageNum: opts.PageNum,
ItemsPerPage: opts.PageSize,
}
options := mongodbatlas.EventListOptions{
ListOptions: lo,
// Earliest Timestamp in ISO 8601 date and time format in UTC from when Atlas should return events.
MinDate: opts.MinDate.Format(time.RFC3339),
}
if len(opts.EventTypes) > 0 {
options.EventType = opts.EventTypes
}
events, response, err := s.client.Events.ListProjectEvents(ctx, groupID, &options)
err = checkMongoDBClientErr(err, response)
if err != nil {
return nil, false, err
}
return events.Results, hasNext(response.Links), nil
}
// GetOrgEvents returns the events specified for the set organizations
func (s *MongoDBAtlasClient) GetOrganizationEvents(ctx context.Context, orgID string, opts *GetEventsOptions) (ret []*mongodbatlas.Event, nextPage bool, err error) {
lo := mongodbatlas.ListOptions{
PageNum: opts.PageNum,
ItemsPerPage: opts.PageSize,
}
options := mongodbatlas.EventListOptions{
ListOptions: lo,
// Earliest Timestamp in ISO 8601 date and time format in UTC from when Atlas should return events.
MinDate: opts.MinDate.Format(time.RFC3339),
}
if len(opts.EventTypes) > 0 {
options.EventType = opts.EventTypes
}
events, response, err := s.client.Events.ListOrganizationEvents(ctx, orgID, &options)
err = checkMongoDBClientErr(err, response)
if err != nil {
return nil, false, err
}
return events.Results, hasNext(response.Links), nil
}
// GetAccessLogsOptions are the options to use for making a request to get Access Logs
type GetAccessLogsOptions struct {
// The oldest date to look back for the events
MinDate time.Time
// the newest time to accept events
MaxDate time.Time
// If true, only return successful access attempts; if false, only return failed access attempts
// If nil, return both successful and failed access attempts
AuthResult *bool
// Maximum number of entries to return
NLogs int
}
// GetAccessLogs returns the access logs specified for the cluster requested
func (s *MongoDBAtlasClient) GetAccessLogs(ctx context.Context, groupID, clusterName string, opts *GetAccessLogsOptions) (ret []*mongodbatlas.AccessLogs, err error) {
options := mongodbatlas.AccessLogOptions{
// Earliest Timestamp in epoch milliseconds from when Atlas should access log results
Start: strconv.FormatInt(opts.MinDate.UTC().UnixMilli(), 10),
// Latest Timestamp in epoch milliseconds from when Atlas should access log results
End: strconv.FormatInt(opts.MaxDate.UTC().UnixMilli(), 10),
// If true, only return successful access attempts; if false, only return failed access attempts
// If nil, return both successful and failed access attempts
AuthResult: opts.AuthResult,
// Maximum number of entries to return (0-20000)
NLogs: opts.NLogs,
}
accessLogs, response, err := s.client.AccessTracking.ListByCluster(ctx, groupID, clusterName, &options)
err = checkMongoDBClientErr(err, response)
if err != nil {
return nil, err
}
return accessLogs.AccessLogs, nil
}
func toUnixString(t time.Time) string {
return strconv.Itoa(int(t.Unix()))
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package mongodbatlasreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mongodbatlasreceiver"
import (
"bufio"
"compress/gzip"
"encoding/json"
"io"
"regexp"
"go.uber.org/zap"
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mongodbatlasreceiver/internal/model"
)
func decodeLogs(logger *zap.Logger, clusterMajorVersion string, r io.Reader) ([]model.LogEntry, error) {
switch clusterMajorVersion {
case mongoDBMajorVersion4_2:
// 4.2 clusters use a console log format
return decode4_2(logger.Named("console_decoder"), r)
default:
// All other versions use JSON logging
return decodeJSON(logger.Named("json_decoder"), r)
}
}
func decodeJSON(logger *zap.Logger, r io.Reader) ([]model.LogEntry, error) {
// Pass this into a gzip reader for decoding
gzipReader, err := gzip.NewReader(r)
if err != nil {
return nil, err
}
scanner := bufio.NewScanner(gzipReader)
var entries []model.LogEntry
for {
if !scanner.Scan() {
// Scan failed; This might just be EOF, in which case Err will be nil, or it could be some other IO error.
return entries, scanner.Err()
}
var entry model.LogEntry
if err := json.Unmarshal(scanner.Bytes(), &entry); err != nil {
logger.Error("Failed to parse log entry as JSON", zap.String("entry", scanner.Text()))
continue
}
entry.Raw = scanner.Text()
entries = append(entries, entry)
}
}
var mongo4_2LogRegex = regexp.MustCompile(`^(?P<timestamp>\S+)\s+(?P<severity>\w+)\s+(?P<component>[\w-]+)\s+\[(?P<context>\S+)\]\s+(?P<message>.*)$`)
func decode4_2(logger *zap.Logger, r io.Reader) ([]model.LogEntry, error) {
// Pass this into a gzip reader for decoding
gzipReader, err := gzip.NewReader(r)
if err != nil {
return nil, err
}
scanner := bufio.NewScanner(gzipReader)
var entries []model.LogEntry
for {
if !scanner.Scan() {
// Scan failed; This might just be EOF, in which case Err will be nil, or it could be some other IO error.
return entries, scanner.Err()
}
submatches := mongo4_2LogRegex.FindStringSubmatch(scanner.Text())
if len(submatches) != 6 {
// Match failed for line; We will skip this line and continue processing others.
logger.Error("Entry did not match regex", zap.String("entry", scanner.Text()))
continue
}
entry := model.LogEntry{
Timestamp: model.LogTimestamp{
Date: submatches[1],
},
Severity: submatches[2],
Component: submatches[3],
Context: submatches[4],
Message: submatches[5],
Raw: submatches[0],
}
entries = append(entries, entry)
}
}
func decodeAuditJSON(logger *zap.Logger, r io.Reader) ([]model.AuditLog, error) {
// Pass this into a gzip reader for decoding
gzipReader, err := gzip.NewReader(r)
if err != nil {
return nil, err
}
scanner := bufio.NewScanner(gzipReader)
var entries []model.AuditLog
for {
if !scanner.Scan() {
// Scan failed; This might just be EOF, in which case Err will be nil, or it could be some other IO error.
return entries, scanner.Err()
}
var entry model.AuditLog
if err := json.Unmarshal(scanner.Bytes(), &entry); err != nil {
logger.Error("Failed to parse audit log entry as JSON", zap.String("entry", scanner.Text()))
continue
}
entry.Raw = scanner.Text()
entries = append(entries, entry)
}
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package mongodbatlasreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mongodbatlasreceiver"
import (
"context"
"errors"
"fmt"
"io"
"net"
"strings"
"sync"
"time"
"go.mongodb.org/atlas/mongodbatlas"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
rcvr "go.opentelemetry.io/collector/receiver"
"go.uber.org/zap"
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mongodbatlasreceiver/internal"
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mongodbatlasreceiver/internal/model"
)
const mongoDBMajorVersion4_2 = "4.2"
type logsReceiver struct {
log *zap.Logger
cfg *Config
client *internal.MongoDBAtlasClient
consumer consumer.Logs
stopperChan chan struct{}
wg sync.WaitGroup
start time.Time
end time.Time
}
type projectContext struct {
Project mongodbatlas.Project
orgName string
}
// MongoDB Atlas Documentation recommends a polling interval of 5 minutes: https://www.mongodb.com/docs/atlas/reference/api/logs/#logs
const collectionInterval = time.Minute * 5
func newMongoDBAtlasLogsReceiver(settings rcvr.Settings, cfg *Config, consumer consumer.Logs) (*logsReceiver, error) {
client, err := internal.NewMongoDBAtlasClient(cfg.BaseURL, cfg.PublicKey, string(cfg.PrivateKey), cfg.BackOffConfig, settings.Logger, settings.BuildInfo)
if err != nil {
return nil, fmt.Errorf("failed to create MongoDB Atlas client for logs receiver: %w", err)
}
for _, p := range cfg.Logs.Projects {
p.ProjectConfig.populateIncludesAndExcludes()
}
return &logsReceiver{
log: settings.Logger,
cfg: cfg,
client: client,
stopperChan: make(chan struct{}),
consumer: consumer,
}, nil
}
// Log receiver logic
func (s *logsReceiver) Start(ctx context.Context, _ component.Host) error {
s.wg.Go(func() {
s.start = time.Now().Add(-collectionInterval)
s.end = time.Now()
for {
s.collect(ctx)
// collection interval loop,
select {
case <-ctx.Done():
return
case <-s.stopperChan:
return
case <-time.After(collectionInterval):
s.start = s.end
s.end = time.Now()
}
}
})
return nil
}
func (s *logsReceiver) Shutdown(_ context.Context) error {
close(s.stopperChan)
s.wg.Wait()
return s.client.Shutdown()
}
// parseHostNames parses out the hostname from the specified cluster host
func parseHostNames(s string, logger *zap.Logger) []string {
var hostnames []string
if s == "" {
return []string{}
}
for t := range strings.SplitSeq(s, ",") {
// separate hostname from scheme and port
host, _, err := net.SplitHostPort(strings.TrimPrefix(t, "mongodb://"))
if err != nil {
logger.Error("Could not parse out hostname: " + host)
continue
}
hostnames = append(hostnames, host)
}
return hostnames
}
// collect spins off functionality of the receiver from the Start function
func (s *logsReceiver) collect(ctx context.Context) {
for _, projectCfg := range s.cfg.Logs.Projects {
project, err := s.client.GetProject(ctx, projectCfg.ProjectConfig.Name)
if err != nil {
s.log.Error("Error retrieving project "+projectCfg.ProjectConfig.Name+":", zap.Error(err))
continue
}
pc := projectContext{Project: *project}
org, err := s.client.GetOrganization(ctx, project.OrgID)
if err != nil {
s.log.Error("Error retrieving organization", zap.Error(err))
pc.orgName = "unknown"
} else {
pc.orgName = org.Name
}
// get clusters for each of the projects
clusters, err := s.processClusters(ctx, *projectCfg, project.ID)
if err != nil {
s.log.Error("Failure to process Clusters", zap.Error(err))
}
s.collectClusterLogs(clusters, *projectCfg, pc)
}
}
func (s *logsReceiver) processClusters(ctx context.Context, projectCfg LogsProjectConfig, projectID string) ([]mongodbatlas.Cluster, error) {
clusters, err := s.client.GetClusters(ctx, projectID)
if err != nil {
s.log.Error("Failure to collect clusters from project: %w", zap.Error(err))
return nil, err
}
return filterClusters(clusters, projectCfg.ProjectConfig)
}
type clusterInfo struct {
ClusterName string
RegionName string
ProviderName string
MongoDBMajorVersion string
}
func (s *logsReceiver) collectClusterLogs(clusters []mongodbatlas.Cluster, projectCfg LogsProjectConfig, pc projectContext) {
for i := range clusters {
cluster := &clusters[i]
c := clusterInfo{
ClusterName: cluster.Name,
RegionName: cluster.ProviderSettings.RegionName,
ProviderName: cluster.ProviderSettings.ProviderName,
MongoDBMajorVersion: cluster.MongoDBMajorVersion,
}
hostnames := parseHostNames(cluster.ConnectionStrings.Standard, s.log)
for _, hostname := range hostnames {
// Defaults to true if not specified
if projectCfg.EnableHostLogs == nil || *projectCfg.EnableHostLogs {
s.log.Debug("Collecting logs for host", zap.String("hostname", hostname), zap.String("cluster", cluster.Name))
s.collectLogs(pc, hostname, "mongodb.gz", c)
s.collectLogs(pc, hostname, "mongos.gz", c)
}
// Defaults to false if not specified
if projectCfg.EnableAuditLogs {
s.log.Debug("Collecting audit logs for host", zap.String("hostname", hostname), zap.String("cluster", cluster.Name))
s.collectAuditLogs(pc, hostname, "mongodb-audit-log.gz", c)
s.collectAuditLogs(pc, hostname, "mongos-audit-log.gz", c)
}
}
}
}
func filterClusters(clusters []mongodbatlas.Cluster, projectCfg ProjectConfig) ([]mongodbatlas.Cluster, error) {
include, exclude := projectCfg.IncludeClusters, projectCfg.ExcludeClusters
var allowed bool
var clusterNameSet map[string]struct{}
// check to include or exclude clusters
switch {
// keep all clusters if include and exclude are not specified
case len(include) == 0 && len(exclude) == 0:
return clusters, nil
// include is initialized
case len(include) > 0 && len(exclude) == 0:
allowed = true
clusterNameSet = projectCfg.includesByClusterName
// exclude is initialized
case len(exclude) > 0 && len(include) == 0:
allowed = false
clusterNameSet = projectCfg.excludesByClusterName
// both are initialized
default:
return nil, errors.New("both Include and Exclude clusters configured")
}
var filtered []mongodbatlas.Cluster
for i := range clusters {
cluster := clusters[i]
if _, ok := clusterNameSet[cluster.Name]; (!ok && !allowed) || (ok && allowed) {
filtered = append(filtered, cluster)
}
}
return filtered, nil
}
func (s *logsReceiver) getHostLogs(groupID, hostname, logName, clusterMajorVersion string) ([]model.LogEntry, error) {
// Get gzip bytes buffer from API
buf, err := s.client.GetLogs(context.Background(), groupID, hostname, logName, s.start, s.end)
if err != nil {
return nil, err
}
return decodeLogs(s.log, clusterMajorVersion, buf)
}
func (s *logsReceiver) getHostAuditLogs(groupID, hostname, logName string) ([]model.AuditLog, error) {
// Get gzip bytes buffer from API
buf, err := s.client.GetLogs(context.Background(), groupID, hostname, logName, s.start, s.end)
if err != nil {
return nil, err
}
return decodeAuditJSON(s.log, buf)
}
func (s *logsReceiver) collectLogs(pc projectContext, hostname, logName string, clusterInfo clusterInfo) {
logs, err := s.getHostLogs(pc.Project.ID, hostname, logName, clusterInfo.MongoDBMajorVersion)
if err != nil && !errors.Is(err, io.EOF) {
s.log.Warn("Failed to retrieve host logs", zap.Error(err), zap.String("hostname", hostname), zap.String("log", logName), zap.Time("startTime", s.start), zap.Time("endTime", s.end))
return
}
if len(logs) == 0 {
s.log.Warn("Attempted to retrieve host logs but received 0 logs", zap.Error(err), zap.String("log", logName), zap.String("hostname", hostname), zap.Time("startTime", s.start), zap.Time("endTime", s.end))
return
}
plog := mongodbEventToLogData(s.log,
logs,
pc,
hostname,
logName,
clusterInfo)
err = s.consumer.ConsumeLogs(context.Background(), plog)
if err != nil {
s.log.Error("Failed to consume logs", zap.Error(err))
}
}
func (s *logsReceiver) collectAuditLogs(pc projectContext, hostname, logName string, clusterInfo clusterInfo) {
logs, err := s.getHostAuditLogs(
pc.Project.ID,
hostname,
logName,
)
if err != nil && !errors.Is(err, io.EOF) {
s.log.Warn("Failed to retrieve audit logs", zap.Error(err), zap.String("hostname", hostname), zap.String("log", logName), zap.Time("startTime", s.start), zap.Time("endTime", s.end))
return
}
if len(logs) == 0 {
s.log.Warn("Attempted to retrieve audit logs but received 0 logs", zap.Error(err), zap.String("hostname", hostname), zap.String("log", logName), zap.Time("startTime", s.start), zap.Time("endTime", s.end))
return
}
plog, err := mongodbAuditEventToLogData(s.log,
logs,
pc,
hostname,
logName,
clusterInfo)
if err != nil {
s.log.Warn("Failed to translate audit logs: "+logName, zap.Error(err))
return
}
err = s.consumer.ConsumeLogs(context.Background(), plog)
if err != nil {
s.log.Error("Failed to consume logs", zap.Error(err))
}
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package mongodbatlasreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mongodbatlasreceiver"
import (
"time"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.uber.org/multierr"
"go.uber.org/zap"
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mongodbatlasreceiver/internal/model"
)
const (
// Number of log attributes to add to the plog.LogRecordSlice for host logs.
totalLogAttributes = 10
// Number of log attributes to add to the plog.LogRecordSlice for audit logs.
totalAuditLogAttributes = 16
// Number of resource attributes to add to the plog.ResourceLogs.
totalResourceAttributes = 4
)
// jsonTimestampLayout for the timestamp format in the plog.Logs structure
const (
jsonTimestampLayout = "2006-01-02T15:04:05.000-07:00"
consoleTimestampLayout = "2006-01-02T15:04:05.000-0700"
)
// Severity mapping of the mongodb atlas logs
var severityMap = map[string]plog.SeverityNumber{
"F": plog.SeverityNumberFatal,
"E": plog.SeverityNumberError,
"W": plog.SeverityNumberWarn,
"I": plog.SeverityNumberInfo,
"D": plog.SeverityNumberDebug,
"D1": plog.SeverityNumberDebug,
"D2": plog.SeverityNumberDebug2,
"D3": plog.SeverityNumberDebug3,
"D4": plog.SeverityNumberDebug4,
"D5": plog.SeverityNumberDebug4,
}
// mongoAuditEventToLogRecord converts model.AuditLog event to plog.LogRecordSlice and adds the resource attributes.
func mongodbAuditEventToLogData(logger *zap.Logger, logs []model.AuditLog, pc projectContext, hostname, logName string, clusterInfo clusterInfo) (plog.Logs, error) {
ld := plog.NewLogs()
rl := ld.ResourceLogs().AppendEmpty()
sl := rl.ScopeLogs().AppendEmpty()
resourceAttrs := rl.Resource().Attributes()
resourceAttrs.EnsureCapacity(totalResourceAttributes)
// Attributes related to the object causing the event.
resourceAttrs.PutStr("mongodb_atlas.org", pc.orgName)
resourceAttrs.PutStr("mongodb_atlas.project", pc.Project.Name)
resourceAttrs.PutStr("mongodb_atlas.cluster", clusterInfo.ClusterName)
resourceAttrs.PutStr("mongodb_atlas.region.name", clusterInfo.RegionName)
resourceAttrs.PutStr("mongodb_atlas.provider.name", clusterInfo.ProviderName)
resourceAttrs.PutStr("mongodb_atlas.host.name", hostname)
var errs []error
for i := range logs {
log := &logs[i]
lr := sl.LogRecords().AppendEmpty()
logTsFormat := tsLayout(clusterInfo.MongoDBMajorVersion)
t, err := time.Parse(logTsFormat, log.Timestamp.Date)
if err != nil {
logger.Warn("Time failed to parse correctly", zap.Error(err))
}
lr.SetTimestamp(pcommon.NewTimestampFromTime(t))
lr.SetObservedTimestamp(pcommon.NewTimestampFromTime(time.Now()))
// Insert Raw Log message into Body of LogRecord
lr.Body().SetStr(log.Raw)
// Since Audit Logs don't have a severity/level
// Set the "SeverityNumber" and "SeverityText" to INFO
lr.SetSeverityNumber(plog.SeverityNumberInfo)
lr.SetSeverityText("INFO")
attrs := lr.Attributes()
attrs.EnsureCapacity(totalAuditLogAttributes)
attrs.PutStr("atype", log.Type)
if log.Local.IP != nil {
attrs.PutStr("local.ip", *log.Local.IP)
}
if log.Local.Port != nil {
attrs.PutInt("local.port", int64(*log.Local.Port))
}
if log.Local.SystemUser != nil {
attrs.PutBool("local.isSystemUser", *log.Local.SystemUser)
}
if log.Local.UnixSocket != nil {
attrs.PutStr("local.unix", *log.Local.UnixSocket)
}
if log.Remote.IP != nil {
attrs.PutStr("remote.ip", *log.Remote.IP)
}
if log.Remote.Port != nil {
attrs.PutInt("remote.port", int64(*log.Remote.Port))
}
if log.Remote.SystemUser != nil {
attrs.PutBool("remote.isSystemUser", *log.Remote.SystemUser)
}
if log.Remote.UnixSocket != nil {
attrs.PutStr("remote.unix", *log.Remote.UnixSocket)
}
if log.ID != nil {
attrs.PutStr("uuid.binary", log.ID.Binary)
attrs.PutStr("uuid.type", log.ID.Type)
}
attrs.PutInt("result", int64(log.Result))
if err = attrs.PutEmptyMap("param").FromRaw(log.Param); err != nil {
errs = append(errs, err)
}
usersSlice := attrs.PutEmptySlice("users")
usersSlice.EnsureCapacity(len(log.Users))
for _, user := range log.Users {
user.Pdata().CopyTo(usersSlice.AppendEmpty().SetEmptyMap())
}
rolesSlice := attrs.PutEmptySlice("roles")
rolesSlice.EnsureCapacity(len(log.Roles))
for _, roles := range log.Roles {
roles.Pdata().CopyTo(rolesSlice.AppendEmpty().SetEmptyMap())
}
attrs.PutStr("log_name", logName)
}
return ld, multierr.Combine(errs...)
}
// mongoEventToLogRecord converts model.LogEntry event to plog.LogRecordSlice and adds the resource attributes.
func mongodbEventToLogData(logger *zap.Logger, logs []model.LogEntry, pc projectContext, hostname, logName string, clusterInfo clusterInfo) plog.Logs {
ld := plog.NewLogs()
rl := ld.ResourceLogs().AppendEmpty()
sl := rl.ScopeLogs().AppendEmpty()
resourceAttrs := rl.Resource().Attributes()
resourceAttrs.EnsureCapacity(totalResourceAttributes)
// Attributes related to the object causing the event.
resourceAttrs.PutStr("mongodb_atlas.org", pc.orgName)
resourceAttrs.PutStr("mongodb_atlas.project", pc.Project.Name)
resourceAttrs.PutStr("mongodb_atlas.cluster", clusterInfo.ClusterName)
resourceAttrs.PutStr("mongodb_atlas.region.name", clusterInfo.RegionName)
resourceAttrs.PutStr("mongodb_atlas.provider.name", clusterInfo.ProviderName)
resourceAttrs.PutStr("mongodb_atlas.host.name", hostname)
logTsFormat := tsLayout(clusterInfo.MongoDBMajorVersion)
for _, log := range logs {
lr := sl.LogRecords().AppendEmpty()
t, err := time.Parse(logTsFormat, log.Timestamp.Date)
if err != nil {
logger.Warn("Time failed to parse correctly", zap.Error(err))
}
lr.SetTimestamp(pcommon.NewTimestampFromTime(t))
lr.SetObservedTimestamp(pcommon.NewTimestampFromTime(time.Now()))
// Insert Raw Log message into Body of LogRecord
lr.Body().SetStr(log.Raw)
// Set the "SeverityNumber" and "SeverityText" if a known type of
// severity is found.
if severityNumber, ok := severityMap[log.Severity]; ok {
lr.SetSeverityNumber(severityNumber)
lr.SetSeverityText(log.Severity)
} else {
logger.Debug("unknown severity type", zap.String("type", log.Severity))
}
attrs := lr.Attributes()
attrs.EnsureCapacity(totalLogAttributes)
//nolint:errcheck
attrs.FromRaw(log.Attributes)
attrs.PutStr("message", log.Message)
attrs.PutStr("component", log.Component)
attrs.PutStr("context", log.Context)
// log ID is not present on MongoDB 4.2 systems
if clusterInfo.MongoDBMajorVersion != mongoDBMajorVersion4_2 {
attrs.PutInt("id", log.ID)
}
attrs.PutStr("log_name", logName)
}
return ld
}
func tsLayout(clusterVersion string) string {
switch clusterVersion {
case mongoDBMajorVersion4_2:
return consoleTimestampLayout
default:
return jsonTimestampLayout
}
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package mongodbatlasreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mongodbatlasreceiver"
import (
"context"
"fmt"
"strconv"
"strings"
"time"
"go.mongodb.org/atlas/mongodbatlas"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/scraper"
"go.uber.org/zap"
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mongodbatlasreceiver/internal"
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mongodbatlasreceiver/internal/metadata"
)
type mongodbatlasreceiver struct {
log *zap.Logger
cfg *Config
client *internal.MongoDBAtlasClient
lastRun time.Time
mb *metadata.MetricsBuilder
stopperChan chan struct{}
}
type timeconstraints struct {
start string
end string
resolution string
}
func newMongoDBAtlasReceiver(settings receiver.Settings, cfg *Config) (*mongodbatlasreceiver, error) {
client, err := internal.NewMongoDBAtlasClient(cfg.BaseURL, cfg.PublicKey, string(cfg.PrivateKey), cfg.BackOffConfig, settings.Logger, settings.BuildInfo)
if err != nil {
return nil, fmt.Errorf("failed to create MongoDB Atlas client receiver: %w", err)
}
// Use index-based iteration: cfg.Projects is []ProjectConfig (value slice),
// so a range-copy would make populateIncludesAndExcludes a no-op on the
// original elements.
for i := range cfg.Projects {
cfg.Projects[i].populateIncludesAndExcludes()
}
return &mongodbatlasreceiver{
log: settings.Logger,
cfg: cfg,
client: client,
mb: metadata.NewMetricsBuilder(cfg.MetricsBuilderConfig, settings),
stopperChan: make(chan struct{}),
}, nil
}
func newMongoDBAtlasScraper(recv *mongodbatlasreceiver) (scraper.Metrics, error) {
return scraper.NewMetrics(recv.scrape, scraper.WithShutdown(recv.shutdown))
}
func (s *mongodbatlasreceiver) scrape(ctx context.Context) (pmetric.Metrics, error) {
now := time.Now()
if err := s.poll(ctx, s.timeConstraints(now)); err != nil {
return pmetric.Metrics{}, err
}
s.lastRun = now
return s.mb.Emit(), nil
}
func (s *mongodbatlasreceiver) timeConstraints(now time.Time) timeconstraints {
var start time.Time
if s.lastRun.IsZero() {
start = now.Add(s.cfg.ControllerConfig.CollectionInterval * -1)
} else {
start = s.lastRun
}
return timeconstraints{
start.UTC().Format(time.RFC3339),
now.UTC().Format(time.RFC3339),
s.cfg.Granularity,
}
}
func (s *mongodbatlasreceiver) shutdown(context.Context) error {
return s.client.Shutdown()
}
// poll decides whether to poll all projects or a specific project based on the configuration.
func (s *mongodbatlasreceiver) poll(ctx context.Context, time timeconstraints) error {
if len(s.cfg.Projects) == 0 {
return s.pollAllProjects(ctx, time)
}
return s.pollProjects(ctx, time)
}
// pollAllProjects handles polling across all projects within the organizations.
func (s *mongodbatlasreceiver) pollAllProjects(ctx context.Context, time timeconstraints) error {
orgs, err := s.client.Organizations(ctx)
if err != nil {
return fmt.Errorf("error retrieving organizations: %w", err)
}
for _, org := range orgs {
proj, err := s.client.Projects(ctx, org.ID)
if err != nil {
s.log.Error("error retrieving projects", zap.String("orgID", org.ID), zap.Error(err))
continue
}
for _, project := range proj {
// Since there is no specific ProjectConfig for these projects, pass nil.
if err := s.processProject(ctx, time, org.Name, project, nil); err != nil {
s.log.Error("error processing project", zap.String("projectID", project.ID), zap.Error(err))
}
}
}
return nil
}
// pollProject handles polling for specific projects as configured.
func (s *mongodbatlasreceiver) pollProjects(ctx context.Context, time timeconstraints) error {
for _, projectCfg := range s.cfg.Projects {
project, err := s.client.GetProject(ctx, projectCfg.Name)
if err != nil {
s.log.Error("error retrieving project", zap.String("projectName", projectCfg.Name), zap.Error(err))
continue
}
org, err := s.client.GetOrganization(ctx, project.OrgID)
if err != nil {
s.log.Error("error retrieving organization from project", zap.String("projectName", projectCfg.Name), zap.Error(err))
continue
}
if err := s.processProject(ctx, time, org.Name, project, &projectCfg); err != nil {
s.log.Error("error processing project", zap.String("projectID", project.ID), zap.Error(err))
}
}
return nil
}
func (s *mongodbatlasreceiver) processProject(ctx context.Context, time timeconstraints, orgName string, project *mongodbatlas.Project, projectCfg *ProjectConfig) error {
nodeClusterMap, providerMap, err := s.getNodeClusterNameMap(ctx, project.ID)
if err != nil {
return fmt.Errorf("error collecting clusters from project %s: %w", project.ID, err)
}
processes, err := s.client.Processes(ctx, project.ID)
if err != nil {
return fmt.Errorf("error retrieving MongoDB Atlas processes for project %s: %w", project.ID, err)
}
for _, process := range processes {
clusterName := nodeClusterMap[process.UserAlias]
providerValues := providerMap[clusterName]
if !shouldProcessCluster(projectCfg, clusterName) {
// Skip processing for this cluster
continue
}
if err := s.extractProcessMetrics(ctx, time, orgName, project, process, clusterName, providerValues); err != nil {
return fmt.Errorf("error when polling process metrics from MongoDB Atlas for process %s: %w", process.ID, err)
}
if err := s.extractProcessDatabaseMetrics(ctx, time, orgName, project, process, clusterName, providerValues); err != nil {
return fmt.Errorf("error when polling process database metrics from MongoDB Atlas for process %s: %w", process.ID, err)
}
if err := s.extractProcessDiskMetrics(ctx, time, orgName, project, process, clusterName, providerValues); err != nil {
return fmt.Errorf("error when polling process disk metrics from MongoDB Atlas for process %s: %w", process.ID, err)
}
}
return nil
}
// shouldProcessCluster checks whether a given cluster should be processed based on the project configuration.
func shouldProcessCluster(projectCfg *ProjectConfig, clusterName string) bool {
if projectCfg == nil {
// If there is no project config, process all clusters.
return true
}
_, isIncluded := projectCfg.includesByClusterName[clusterName]
_, isExcluded := projectCfg.excludesByClusterName[clusterName]
// Return false immediately if the cluster is excluded.
if isExcluded {
return false
}
// If IncludeClusters is empty, or the cluster is explicitly included, return true.
return len(projectCfg.IncludeClusters) == 0 || isIncluded
}
type providerValues struct {
RegionName string
ProviderName string
}
func (s *mongodbatlasreceiver) getNodeClusterNameMap(
ctx context.Context,
projectID string,
) (map[string]string, map[string]providerValues, error) {
providerMap := make(map[string]providerValues)
clusterMap := make(map[string]string)
clusters, err := s.client.GetClusters(ctx, projectID)
if err != nil {
return nil, nil, err
}
for i := range clusters {
cluster := &clusters[i]
// URI in the form mongodb://host1.mongodb.net:27017,host2.mongodb.net:27017,host3.mongodb.net:27017
for node := range strings.SplitSeq(strings.TrimPrefix(cluster.MongoURI, "mongodb://"), ",") {
// Remove the port from the node
n, _, _ := strings.Cut(node, ":")
clusterMap[n] = cluster.Name
}
providerMap[cluster.Name] = providerValues{
RegionName: cluster.ProviderSettings.RegionName,
ProviderName: cluster.ProviderSettings.ProviderName,
}
}
return clusterMap, providerMap, nil
}
func (s *mongodbatlasreceiver) extractProcessMetrics(
ctx context.Context,
time timeconstraints,
orgName string,
project *mongodbatlas.Project,
process *mongodbatlas.Process,
clusterName string,
providerValues providerValues,
) error {
if err := s.client.ProcessMetrics(
ctx,
s.mb,
project.ID,
process.Hostname,
process.Port,
time.start,
time.end,
time.resolution,
); err != nil {
return fmt.Errorf("error when polling process metrics from MongoDB Atlas: %w", err)
}
rb := s.mb.NewResourceBuilder()
rb.SetMongodbAtlasOrgName(orgName)
rb.SetMongodbAtlasProjectName(project.Name)
rb.SetMongodbAtlasProjectID(project.ID)
rb.SetMongodbAtlasHostName(process.Hostname)
rb.SetMongodbAtlasUserAlias(process.UserAlias)
rb.SetMongodbAtlasClusterName(clusterName)
rb.SetMongodbAtlasProcessPort(strconv.Itoa(process.Port))
rb.SetMongodbAtlasProcessTypeName(process.TypeName)
rb.SetMongodbAtlasProcessID(process.ID)
rb.SetMongodbAtlasRegionName(providerValues.RegionName)
rb.SetMongodbAtlasProviderName(providerValues.ProviderName)
s.mb.EmitForResource(metadata.WithResource(rb.Emit()))
return nil
}
func (s *mongodbatlasreceiver) extractProcessDatabaseMetrics(
ctx context.Context,
time timeconstraints,
orgName string,
project *mongodbatlas.Project,
process *mongodbatlas.Process,
clusterName string,
providerValues providerValues,
) error {
processDatabases, err := s.client.ProcessDatabases(
ctx,
project.ID,
process.Hostname,
process.Port,
)
if err != nil {
return fmt.Errorf("error retrieving process databases: %w", err)
}
for _, db := range processDatabases {
if err := s.client.ProcessDatabaseMetrics(
ctx,
s.mb,
project.ID,
process.Hostname,
process.Port,
db.DatabaseName,
time.start,
time.end,
time.resolution,
); err != nil {
return fmt.Errorf("error when polling database metrics from MongoDB Atlas: %w", err)
}
rb := s.mb.NewResourceBuilder()
rb.SetMongodbAtlasOrgName(orgName)
rb.SetMongodbAtlasProjectName(project.Name)
rb.SetMongodbAtlasProjectID(project.ID)
rb.SetMongodbAtlasHostName(process.Hostname)
rb.SetMongodbAtlasUserAlias(process.UserAlias)
rb.SetMongodbAtlasClusterName(clusterName)
rb.SetMongodbAtlasProcessPort(strconv.Itoa(process.Port))
rb.SetMongodbAtlasProcessTypeName(process.TypeName)
rb.SetMongodbAtlasProcessID(process.ID)
rb.SetMongodbAtlasDbName(db.DatabaseName)
rb.SetMongodbAtlasRegionName(providerValues.RegionName)
rb.SetMongodbAtlasProviderName(providerValues.ProviderName)
s.mb.EmitForResource(metadata.WithResource(rb.Emit()))
}
return nil
}
func (s *mongodbatlasreceiver) extractProcessDiskMetrics(
ctx context.Context,
time timeconstraints,
orgName string,
project *mongodbatlas.Project,
process *mongodbatlas.Process,
clusterName string,
providerValues providerValues,
) error {
for _, disk := range s.client.ProcessDisks(ctx, project.ID, process.Hostname, process.Port) {
if err := s.client.ProcessDiskMetrics(
ctx,
s.mb,
project.ID,
process.Hostname,
process.Port,
disk.PartitionName,
time.start,
time.end,
time.resolution,
); err != nil {
return fmt.Errorf("error when polling disk metrics from MongoDB Atlas: %w", err)
}
rb := s.mb.NewResourceBuilder()
rb.SetMongodbAtlasOrgName(orgName)
rb.SetMongodbAtlasProjectName(project.Name)
rb.SetMongodbAtlasProjectID(project.ID)
rb.SetMongodbAtlasHostName(process.Hostname)
rb.SetMongodbAtlasUserAlias(process.UserAlias)
rb.SetMongodbAtlasClusterName(clusterName)
rb.SetMongodbAtlasProcessPort(strconv.Itoa(process.Port))
rb.SetMongodbAtlasProcessTypeName(process.TypeName)
rb.SetMongodbAtlasProcessID(process.ID)
rb.SetMongodbAtlasDiskPartition(disk.PartitionName)
rb.SetMongodbAtlasRegionName(providerValues.RegionName)
rb.SetMongodbAtlasProviderName(providerValues.ProviderName)
s.mb.EmitForResource(metadata.WithResource(rb.Emit()))
}
return nil
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package splunkhecreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/splunkhecreceiver"
import (
"context"
"time"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/config/confignet"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/receiver"
conventions "go.opentelemetry.io/otel/semconv/v1.43.0"
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/sharedcomponent"
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/splunk"
translator "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/splunk"
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/splunkhecreceiver/internal/metadata"
)
// This file implements factory for Splunk HEC receiver.
const (
// Default endpoint to bind to.
defaultEndpoint = "localhost:8088"
defaultServerTimeout = 20 * time.Second
)
// NewFactory creates a factory for Splunk HEC receiver.
func NewFactory() receiver.Factory {
return receiver.NewFactory(
metadata.Type,
createDefaultConfig,
receiver.WithMetrics(createMetricsReceiver, metadata.MetricsStability),
receiver.WithLogs(createLogsReceiver, metadata.LogsStability),
)
}
// CreateDefaultConfig creates the default configuration for Splunk HEC receiver.
func createDefaultConfig() component.Config {
netAddr := confignet.NewDefaultAddrConfig()
netAddr.Transport = confignet.TransportTypeTCP
netAddr.Endpoint = defaultEndpoint
serverConfig := confighttp.NewDefaultServerConfig()
serverConfig.WriteTimeout = defaultServerTimeout
serverConfig.ReadHeaderTimeout = defaultServerTimeout
serverConfig.NetAddr = netAddr
return &Config{
ServerConfig: serverConfig,
AccessTokenPassthroughConfig: splunk.AccessTokenPassthroughConfig{},
HecToOtelAttrs: translator.HecToOtelAttrs{
Source: splunk.DefaultSourceLabel,
SourceType: splunk.DefaultSourceTypeLabel,
Index: splunk.DefaultIndexLabel,
Host: string(conventions.HostNameKey),
},
RawPath: splunk.DefaultRawPath,
HealthPath: splunk.DefaultHealthPath,
Ack: Ack{
Extension: nil,
Path: splunk.DefaultAckPath,
},
Splitting: SplittingStrategyLine,
}
}
// CreateMetrics creates a metrics receiver based on provided config.
func createMetricsReceiver(
_ context.Context,
params receiver.Settings,
cfg component.Config,
consumer consumer.Metrics,
) (receiver.Metrics, error) {
var err error
var recv receiver.Metrics
rCfg := cfg.(*Config)
r := receivers.GetOrAdd(cfg, func() component.Component {
recv, err = newReceiver(params, *rCfg)
return recv
})
if err != nil {
return nil, err
}
r.Unwrap().(*splunkReceiver).metricsConsumer = consumer
return r, nil
}
// createLogsReceiver creates a logs receiver based on provided config.
func createLogsReceiver(
_ context.Context,
params receiver.Settings,
cfg component.Config,
consumer consumer.Logs,
) (receiver.Logs, error) {
var err error
var recv receiver.Logs
rCfg := cfg.(*Config)
r := receivers.GetOrAdd(cfg, func() component.Component {
recv, err = newReceiver(params, *rCfg)
return recv
})
if err != nil {
return nil, err
}
r.Unwrap().(*splunkReceiver).logsConsumer = consumer
return r, nil
}
var receivers = sharedcomponent.NewSharedComponents()
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/receiver"
)
// LogsBuilder provides an interface for scrapers to report logs while taking care of all the transformations
// required to produce log representation defined in metadata and user config.
type LogsBuilder struct {
logsBuffer plog.Logs
logRecordsBuffer plog.LogRecordSlice
buildInfo component.BuildInfo // contains version information.
}
// LogBuilderOption applies changes to default logs builder.
type LogBuilderOption interface {
apply(*LogsBuilder)
}
func NewLogsBuilder(settings receiver.Settings) *LogsBuilder {
lb := &LogsBuilder{
logsBuffer: plog.NewLogs(),
logRecordsBuffer: plog.NewLogRecordSlice(),
buildInfo: settings.BuildInfo,
}
return lb
}
// ResourceLogsOption applies changes to provided resource logs.
type ResourceLogsOption interface {
apply(plog.ResourceLogs)
}
type resourceLogsOptionFunc func(plog.ResourceLogs)
func (rlof resourceLogsOptionFunc) apply(rl plog.ResourceLogs) {
rlof(rl)
}
// WithLogsResource sets the provided resource on the emitted ResourceLogs.
// It's recommended to use ResourceBuilder to create the resource.
func WithLogsResource(res pcommon.Resource) ResourceLogsOption {
return resourceLogsOptionFunc(func(rl plog.ResourceLogs) {
res.CopyTo(rl.Resource())
})
}
// AppendLogRecord adds a log record to the logs builder.
func (lb *LogsBuilder) AppendLogRecord(lr plog.LogRecord) {
lr.MoveTo(lb.logRecordsBuffer.AppendEmpty())
}
// EmitForResource saves all the generated logs under a new resource and updates the internal state to be ready for
// recording another set of log records as part of another resource. This function can be helpful when one scraper
// needs to emit logs from several resources. Otherwise calling this function is not required,
// just `Emit` function can be called instead.
// Resource attributes should be provided as ResourceLogsOption arguments.
func (lb *LogsBuilder) EmitForResource(options ...ResourceLogsOption) {
rl := plog.NewResourceLogs()
ils := rl.ScopeLogs().AppendEmpty()
ils.Scope().SetName(ScopeName)
ils.Scope().SetVersion(lb.buildInfo.Version)
for _, op := range options {
op.apply(rl)
}
if lb.logRecordsBuffer.Len() > 0 {
lb.logRecordsBuffer.MoveAndAppendTo(ils.LogRecords())
lb.logRecordsBuffer = plog.NewLogRecordSlice()
}
if ils.LogRecords().Len() > 0 {
rl.MoveTo(lb.logsBuffer.ResourceLogs().AppendEmpty())
}
}
// Emit returns all the logs accumulated by the logs builder and updates the internal state to be ready for
// recording another set of logs. This function will be responsible for applying all the transformations required to
// produce logs representation defined in metadata and user config.
func (lb *LogsBuilder) Emit(options ...ResourceLogsOption) plog.Logs {
lb.EmitForResource(options...)
logs := lb.logsBuffer
lb.logsBuffer = plog.NewLogs()
return logs
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package splunkhecreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/splunkhecreceiver"
import (
"compress/gzip"
"context"
"errors"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"sync"
"time"
"github.com/goccy/go-json"
"github.com/google/uuid"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componentstatus"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/receiverhelper"
"go.uber.org/zap"
"github.com/open-telemetry/opentelemetry-collector-contrib/extension/ackextension"
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/splunk"
translator "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/splunk"
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/splunkhecreceiver/internal/metadata"
)
const (
ackResponse = `{"acks": %s}`
responseOK = `{"text": "Success", "code": 0}`
responseOKWithAckID = `{"text": "Success", "code": 0, "ackId": %d}`
responseHecHealthy = `{"text": "HEC is healthy", "code": 17}`
responseInvalidMethodPostOnly = `"Only \"POST\" method is supported"`
responseInvalidEncoding = `"\"Content-Encoding\" must be \"gzip\" or empty"`
responseInvalidDataFormat = `{"text":"Invalid data format","code":6}`
responseErrEventRequired = `{"text":"Event field is required","code":12}`
responseErrEventBlank = `{"text":"Event field cannot be blank","code":13}`
responseErrGzipReader = `"Error on gzip body"`
responseErrUnmarshalBody = `"Failed to unmarshal message body"`
responseErrInternalServerError = `"Internal Server Error"`
responseErrUnsupportedMetricEvent = `"Unsupported metric event"`
responseErrUnsupportedLogEvent = `"Unsupported log event"`
responseErrHandlingIndexedFields = `{"text":"Error in handling indexed fields","code":15,"invalid-event-number":%d}`
responseErrDataChannelMissing = `{"text": "Data channel is missing","code":10}`
responseErrInvalidDataChannel = `{"text": "Invalid data channel", "code": 11}`
responseNoData = `{"text":"No data","code":5}`
// Centralizing some HTTP and related string constants.
gzipEncoding = "gzip"
httpContentEncodingHeader = "Content-Encoding"
httpContentTypeHeader = "Content-Type"
httpJSONTypeHeader = "application/json"
)
var (
errEmptyEndpoint = errors.New("empty endpoint")
errInvalidMethod = errors.New("invalid http method")
errInvalidEncoding = errors.New("invalid encoding")
errExtensionMissing = errors.New("ack extension not found")
okRespBody = []byte(responseOK)
eventRequiredRespBody = []byte(responseErrEventRequired)
eventBlankRespBody = []byte(responseErrEventBlank)
requiredDataChannelHeader = []byte(responseErrDataChannelMissing)
invalidEncodingRespBody = []byte(responseInvalidEncoding)
invalidFormatRespBody = []byte(responseInvalidDataFormat)
invalidMethodRespBodyPostOnly = []byte(responseInvalidMethodPostOnly)
errGzipReaderRespBody = []byte(responseErrGzipReader)
errUnmarshalBodyRespBody = []byte(responseErrUnmarshalBody)
errInternalServerError = []byte(responseErrInternalServerError)
errUnsupportedMetricEvent = []byte(responseErrUnsupportedMetricEvent)
errUnsupportedLogEvent = []byte(responseErrUnsupportedLogEvent)
noDataRespBody = []byte(responseNoData)
)
// splunkReceiver implements the receiver.Metrics for Splunk HEC metric protocol.
type splunkReceiver struct {
settings receiver.Settings
config *Config
logsConsumer consumer.Logs
metricsConsumer consumer.Metrics
server *http.Server
shutdownWG sync.WaitGroup
obsrecv *receiverhelper.ObsReport
gzipReaderPool *sync.Pool
ackExt ackextension.AckExtension
}
var (
_ receiver.Metrics = (*splunkReceiver)(nil)
_ receiver.Logs = (*splunkReceiver)(nil)
)
// newReceiver creates the Splunk HEC receiver with the given configuration.
func newReceiver(settings receiver.Settings, config Config) (*splunkReceiver, error) {
if config.ServerConfig.NetAddr.Endpoint == "" {
return nil, errEmptyEndpoint
}
transport := "http"
if config.ServerConfig.TLS.HasValue() {
transport = "https"
}
obsrecv, err := receiverhelper.NewObsReport(receiverhelper.ObsReportSettings{
ReceiverID: settings.ID,
Transport: transport,
ReceiverCreateSettings: settings,
})
if err != nil {
return nil, err
}
r := &splunkReceiver{
settings: settings,
config: &config,
obsrecv: obsrecv,
gzipReaderPool: &sync.Pool{New: func() any { return new(gzip.Reader) }},
}
return r, nil
}
// Start tells the receiver to start its processing.
// By convention the consumer of the received data is set when the receiver
// instance is created.
func (r *splunkReceiver) Start(ctx context.Context, host component.Host) error {
// server.Handler will be nil on initial call, otherwise noop.
if r.server != nil && r.server.Handler != nil {
return nil
}
mx := http.NewServeMux()
// set up the ack API handler if the ack extension is present
if r.config.Ack.Extension != nil {
ext, found := host.GetExtensions()[*r.config.Ack.Extension]
if !found {
return fmt.Errorf("specified ack extension with id %q could not be found", *r.config.Ack.Extension)
}
r.ackExt = ext.(ackextension.AckExtension)
mx.HandleFunc(r.config.Ack.Path, r.handleAck)
}
mx.HandleFunc(r.config.HealthPath, r.handleHealthReq)
mx.HandleFunc(http.MethodGet+" "+r.config.HealthPath+"/1.0", r.handleHealthReq)
if r.logsConsumer != nil {
mx.HandleFunc(r.config.RawPath, r.handleRawReq)
}
mx.HandleFunc("/", r.handleReq)
// set up the listener
ln, err := r.config.ServerConfig.ToListener(ctx)
if err != nil {
return fmt.Errorf("failed to bind to address %s: %w", r.config.ServerConfig.NetAddr.Endpoint, err)
}
r.server, err = r.config.ServerConfig.ToServer(ctx, host.GetExtensions(), r.settings.TelemetrySettings, mx)
if err != nil {
return err
}
r.shutdownWG.Go(func() {
if errHTTP := r.server.Serve(ln); !errors.Is(errHTTP, http.ErrServerClosed) && errHTTP != nil {
componentstatus.ReportStatus(host, componentstatus.NewFatalErrorEvent(errHTTP))
}
})
return err
}
// Shutdown tells the receiver that should stop reception,
// giving it a chance to perform any necessary clean-up.
func (r *splunkReceiver) Shutdown(context.Context) error {
if r.server == nil {
return nil
}
err := r.server.Close()
r.shutdownWG.Wait()
return err
}
func (r *splunkReceiver) processSuccessResponseWithAck(resp http.ResponseWriter, channelID string) error {
if r.ackExt == nil {
panic("writing response with ack when ack extension is not configured")
}
ackID := r.ackExt.ProcessEvent(channelID)
r.ackExt.Ack(channelID, ackID)
return r.processSuccessResponse(resp, fmt.Appendf(nil, responseOKWithAckID, ackID))
}
func (*splunkReceiver) processSuccessResponse(resp http.ResponseWriter, bodyContent []byte) error {
resp.Header().Set(httpContentTypeHeader, httpJSONTypeHeader)
resp.WriteHeader(http.StatusOK)
_, err := resp.Write(bodyContent)
return err
}
func (r *splunkReceiver) handleAck(resp http.ResponseWriter, req *http.Request) {
if req.Method != http.MethodPost {
r.failRequest(resp, http.StatusBadRequest, invalidMethodRespBodyPostOnly, errInvalidMethod)
return
}
// shouldn't run into this case since we only enable this handler IF ackExt exists. But we have this check just in case
if r.ackExt == nil {
r.failRequest(resp, http.StatusInternalServerError, errInternalServerError, errExtensionMissing)
return
}
var channelID string
var extracted bool
channelID, extracted = r.extractChannel(req)
if !extracted {
r.failRequest(resp, http.StatusBadRequest, requiredDataChannelHeader, nil)
return
}
if channelErr := r.validateChannelHeader(channelID); channelErr != nil {
r.failRequest(resp, http.StatusBadRequest, []byte(channelErr.Error()), channelErr)
return
}
dec := json.NewDecoder(req.Body)
var ackRequest splunk.AckRequest
err := dec.Decode(&ackRequest)
if err != nil {
r.failRequest(resp, http.StatusBadRequest, invalidFormatRespBody, err)
return
}
if len(ackRequest.Acks) == 0 {
r.failRequest(resp, http.StatusBadRequest, invalidFormatRespBody, errors.New("request body must include at least one ackID to be queried"))
return
}
queriedAcks := r.ackExt.QueryAcks(channelID, ackRequest.Acks)
ackString, _ := json.Marshal(queriedAcks)
if err := r.processSuccessResponse(resp, fmt.Appendf(nil, ackResponse, ackString)); err != nil {
r.failRequest(resp, http.StatusInternalServerError, errInternalServerError, err)
}
}
func (r *splunkReceiver) handleRawReq(resp http.ResponseWriter, req *http.Request) {
ctx := req.Context()
ctx = r.obsrecv.StartLogsOp(ctx)
if req.Method != http.MethodPost {
r.failRequest(resp, http.StatusBadRequest, invalidMethodRespBodyPostOnly, errInvalidMethod)
return
}
encoding := req.Header.Get(httpContentEncodingHeader)
if encoding != "" && encoding != gzipEncoding {
r.failRequest(resp, http.StatusUnsupportedMediaType, invalidEncodingRespBody, errInvalidEncoding)
return
}
var channelID string
var extracted bool
if channelID, extracted = r.extractChannel(req); extracted {
if channelErr := r.validateChannelHeader(channelID); channelErr != nil {
r.failRequest(resp, http.StatusBadRequest, []byte(channelErr.Error()), channelErr)
return
}
}
if req.ContentLength == 0 {
r.obsrecv.EndLogsOp(ctx, metadata.Type.String(), 0, nil)
r.failRequest(resp, http.StatusBadRequest, noDataRespBody, nil)
return
}
bodyReader := req.Body
if encoding == gzipEncoding {
reader := r.gzipReaderPool.Get().(*gzip.Reader)
err := reader.Reset(bodyReader)
if err != nil {
r.failRequest(resp, http.StatusBadRequest, errGzipReaderRespBody, err)
_, _ = io.ReadAll(req.Body)
_ = req.Body.Close()
return
}
bodyReader = reader
defer r.gzipReaderPool.Put(reader)
}
resourceCustomizer := r.createResourceCustomizer(req)
query := req.URL.Query()
var timestamp pcommon.Timestamp
if query.Has(queryTime) {
t, err := strconv.ParseInt(query.Get(queryTime), 10, 64)
if t < 0 {
err = errors.New("time cannot be less than 0")
}
if err != nil {
r.failRequest(resp, http.StatusBadRequest, invalidFormatRespBody, err)
return
}
timestamp = pcommon.NewTimestampFromTime(time.Unix(t, 0))
}
ld, slLen, err := splunkHecRawToLogData(r.progressReader(resp, bodyReader), query, resourceCustomizer, r.config, timestamp)
if err != nil {
r.failRequest(resp, http.StatusInternalServerError, errInternalServerError, err)
return
}
r.extendWriteDeadline(resp)
consumerErr := r.logsConsumer.ConsumeLogs(ctx, ld)
_ = bodyReader.Close()
if consumerErr != nil {
r.failRequest(resp, http.StatusInternalServerError, errInternalServerError, consumerErr)
} else {
var ackErr error
if channelID != "" && r.ackExt != nil {
ackErr = r.processSuccessResponseWithAck(resp, channelID)
} else {
ackErr = r.processSuccessResponse(resp, okRespBody)
}
if ackErr != nil {
r.failRequest(resp, http.StatusInternalServerError, errInternalServerError, err)
} else {
r.obsrecv.EndLogsOp(ctx, metadata.Type.String(), slLen, nil)
}
}
}
func (*splunkReceiver) extractChannel(req *http.Request) (string, bool) {
// check header
for k, v := range req.Header {
if strings.EqualFold(k, splunk.HTTPSplunkChannelHeader) {
return strings.ToUpper(v[0]), true
}
}
// check query param
for k, v := range req.URL.Query() {
if strings.EqualFold(k, "channel") {
return strings.ToUpper(v[0]), true
}
}
return "", false
}
func (*splunkReceiver) validateChannelHeader(channelID string) error {
if channelID == "" {
return errors.New(responseErrDataChannelMissing)
}
// channel id must be a valid uuid
// https://docs.splunk.com/Documentation/Splunk/9.2.1/Data/AboutHECIDXAck#:~:text=close%20the%20file.-,About%20channels%20and%20sending%20data,-Sending%20events%20to
_, err := uuid.Parse(channelID)
if err != nil {
return errors.New(responseErrInvalidDataChannel)
}
return nil
}
// setWriteDeadline pushes the write deadline out by timeout. Failure means the
// ResponseController could not Unwrap to a deadline-capable connection (a middleware
// drops Unwrap); logged at debug rather than swallowed so the broken chain stays diagnosable.
func setWriteDeadline(logger *zap.Logger, rc *http.ResponseController, timeout time.Duration) {
if err := rc.SetWriteDeadline(time.Now().Add(timeout)); err != nil {
logger.Debug("failed to extend write deadline; a slow or large request may be reset at WriteTimeout despite progress", zap.Error(err))
}
}
// progressDeadlineReader resets the write deadline on every read so an actively
// transferring body is not cut off by the server's WriteTimeout, which HTTP/2 arms
// once at handler start and never extends. Effectively a body-read idle timeout.
type progressDeadlineReader struct {
reader io.Reader
rc *http.ResponseController
logger *zap.Logger
timeout time.Duration
}
func (p *progressDeadlineReader) Read(b []byte) (int, error) {
setWriteDeadline(p.logger, p.rc, p.timeout)
return p.reader.Read(b)
}
// progressReader wraps body so the write deadline is extended as it is read. Returns
// body unchanged when WriteTimeout is 0. Primes the deadline once so the read starts
// with a full window.
func (r *splunkReceiver) progressReader(resp http.ResponseWriter, body io.Reader) io.Reader {
timeout := r.config.ServerConfig.WriteTimeout
if timeout <= 0 {
return body
}
rc := http.NewResponseController(resp)
setWriteDeadline(r.settings.Logger, rc, timeout)
return &progressDeadlineReader{reader: body, rc: rc, logger: r.settings.Logger, timeout: timeout}
}
// extendWriteDeadline resets the write deadline to a full window once before handing
// data to the pipeline, so consume starts fresh rather than with whatever the body read
// left. Not extended during consume, so a consumer blocking past WriteTimeout is still reset.
func (r *splunkReceiver) extendWriteDeadline(resp http.ResponseWriter) {
if timeout := r.config.ServerConfig.WriteTimeout; timeout > 0 {
setWriteDeadline(r.settings.Logger, http.NewResponseController(resp), timeout)
}
}
func (r *splunkReceiver) handleReq(resp http.ResponseWriter, req *http.Request) {
ctx := req.Context()
if req.Method != http.MethodPost {
r.failRequest(resp, http.StatusBadRequest, invalidMethodRespBodyPostOnly, errInvalidMethod)
return
}
encoding := req.Header.Get(httpContentEncodingHeader)
if encoding != "" && encoding != gzipEncoding {
r.failRequest(resp, http.StatusUnsupportedMediaType, invalidEncodingRespBody, errInvalidEncoding)
return
}
channelID, extracted := r.extractChannel(req)
if extracted {
if channelErr := r.validateChannelHeader(channelID); channelErr != nil {
r.failRequest(resp, http.StatusBadRequest, []byte(channelErr.Error()), channelErr)
return
}
}
bodyReader := req.Body
if encoding == gzipEncoding {
reader := r.gzipReaderPool.Get().(*gzip.Reader)
err := reader.Reset(bodyReader)
if err != nil {
r.failRequest(resp, http.StatusBadRequest, errGzipReaderRespBody, err)
return
}
bodyReader = reader
defer r.gzipReaderPool.Put(reader)
}
if req.ContentLength == 0 {
r.failRequest(resp, http.StatusBadRequest, noDataRespBody, nil)
return
}
dec := json.NewDecoder(r.progressReader(resp, bodyReader))
var unfiltered []*translator.Event
var firstEvent any
firstErr := dec.Decode(&firstEvent)
if firstErr != nil {
r.failRequest(resp, http.StatusBadRequest, invalidFormatRespBody, firstErr)
return
}
switch v := firstEvent.(type) {
case []any:
// Array of events
eventBytes, _ := json.Marshal(v)
var events []translator.Event
if err := json.Unmarshal(eventBytes, &events); err != nil {
r.failRequest(resp, http.StatusBadRequest, invalidFormatRespBody, err)
return
}
if len(events) == 0 {
r.failRequest(resp, http.StatusBadRequest, noDataRespBody, nil)
return
}
// If event is JSON array, there should be no more tokens after the first one
if dec.More() {
r.failRequest(resp, http.StatusBadRequest, invalidFormatRespBody, nil)
return
}
for _, item := range events {
unfiltered = append(unfiltered, &item)
}
default:
// Single event
eventBytes, _ := json.Marshal(v)
var event translator.Event
if err := json.Unmarshal(eventBytes, &event); err != nil {
r.failRequest(resp, http.StatusBadRequest, invalidFormatRespBody, err)
return
}
unfiltered = append(unfiltered, &event)
}
var events []*translator.Event
var metricEvents []*translator.Event
for len(unfiltered) > 0 {
msg := unfiltered[0]
for _, v := range msg.Fields {
if !isFlatJSONField(v) {
r.failRequest(resp, http.StatusBadRequest, fmt.Appendf(nil, responseErrHandlingIndexedFields, len(events)+len(metricEvents)), nil)
return
}
}
if msg.IsMetric() {
if r.metricsConsumer == nil {
r.failRequest(resp, http.StatusBadRequest, errUnsupportedMetricEvent, nil)
return
}
metricEvents = append(metricEvents, msg)
} else {
if msg.Event == nil {
r.failRequest(resp, http.StatusBadRequest, eventRequiredRespBody, nil)
return
}
if msg.Event == "" {
r.failRequest(resp, http.StatusBadRequest, eventBlankRespBody, nil)
return
}
if r.logsConsumer == nil {
r.failRequest(resp, http.StatusBadRequest, errUnsupportedLogEvent, nil)
return
}
events = append(events, msg)
}
if dec.More() {
var msg translator.Event
err := dec.Decode(&msg)
if err != nil {
r.failRequest(resp, http.StatusBadRequest, invalidFormatRespBody, err)
return
}
unfiltered = append(unfiltered, &msg)
}
unfiltered = unfiltered[1:]
}
resourceCustomizer := r.createResourceCustomizer(req)
if r.logsConsumer != nil && len(events) > 0 {
ld, err := splunkHecToLogData(r.settings.Logger, events, resourceCustomizer, r.config)
if err != nil {
r.failRequest(resp, http.StatusBadRequest, errUnmarshalBodyRespBody, err)
return
}
r.extendWriteDeadline(resp)
ctx = r.obsrecv.StartLogsOp(ctx)
decodeErr := r.logsConsumer.ConsumeLogs(ctx, ld)
r.obsrecv.EndLogsOp(ctx, metadata.Type.String(), len(events), nil)
if decodeErr != nil {
r.failRequest(resp, http.StatusInternalServerError, errInternalServerError, decodeErr)
return
}
}
if r.metricsConsumer != nil && len(metricEvents) > 0 {
md, _ := splunkHecToMetricsData(r.settings.Logger, metricEvents, resourceCustomizer, r.config)
r.extendWriteDeadline(resp)
ctx = r.obsrecv.StartMetricsOp(ctx)
decodeErr := r.metricsConsumer.ConsumeMetrics(ctx, md)
r.obsrecv.EndMetricsOp(ctx, metadata.Type.String(), len(metricEvents), nil)
if decodeErr != nil {
r.failRequest(resp, http.StatusInternalServerError, errInternalServerError, decodeErr)
return
}
}
var ackErr error
if channelID != "" && r.ackExt != nil {
ackErr = r.processSuccessResponseWithAck(resp, channelID)
} else {
ackErr = r.processSuccessResponse(resp, okRespBody)
}
if ackErr != nil {
r.failRequest(resp, http.StatusInternalServerError, errInternalServerError, ackErr)
}
}
func (r *splunkReceiver) createResourceCustomizer(req *http.Request) func(resource pcommon.Resource) {
if r.config.AccessTokenPassthroughConfig.AccessTokenPassthrough {
accessToken := req.Header.Get("Authorization")
if strings.HasPrefix(accessToken, splunk.HECTokenHeader+" ") {
accessTokenValue := accessToken[len(splunk.HECTokenHeader)+1:]
return func(resource pcommon.Resource) {
resource.Attributes().PutStr(splunk.HecTokenLabel, accessTokenValue)
}
}
}
return nil
}
func (r *splunkReceiver) failRequest(
resp http.ResponseWriter,
httpStatusCode int,
jsonResponse []byte,
err error,
) {
resp.WriteHeader(httpStatusCode)
if len(jsonResponse) > 0 {
// The response needs to be written as a JSON string.
resp.Header().Add("Content-Type", "application/json")
_, writeErr := resp.Write(jsonResponse)
if writeErr != nil {
r.settings.Logger.Warn("Error writing HTTP response message", zap.Error(writeErr))
}
}
if r.settings.Logger.Core().Enabled(zap.DebugLevel) {
msg := string(jsonResponse)
r.settings.Logger.Debug(
"Splunk HEC receiver request failed",
zap.Int("http_status_code", httpStatusCode),
zap.String("msg", msg),
zap.Error(err), // It handles nil error
)
}
}
func (*splunkReceiver) handleHealthReq(writer http.ResponseWriter, _ *http.Request) {
writer.Header().Add("Content-Type", "application/json")
writer.WriteHeader(http.StatusOK)
_, _ = writer.Write([]byte(responseHecHealthy))
}
func isFlatJSONField(field any) bool {
switch value := field.(type) {
case map[string]any:
return false
case []any:
for _, v := range value {
switch v.(type) {
case map[string]any, []any:
return false
}
}
}
return true
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package splunkhecreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/splunkhecreceiver"
import (
"bufio"
"errors"
"io"
"net/url"
"sort"
"time"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.uber.org/zap"
translator "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/splunk"
)
const (
// splunk metadata
index = "index"
source = "source"
sourcetype = "sourcetype"
host = "host"
queryTime = "time"
)
var errCannotConvertValue = errors.New("cannot convert field value to attribute")
// splunkHecToLogData transforms splunk events into logs
func splunkHecToLogData(logger *zap.Logger, events []*translator.Event, resourceCustomizer func(pcommon.Resource), config *Config) (plog.Logs, error) {
ld := plog.NewLogs()
observedTimestamp := pcommon.NewTimestampFromTime(time.Now())
scopeLogsMap := make(map[[4]string]plog.ScopeLogs)
for _, event := range events {
key := [4]string{event.Host, event.Source, event.SourceType, event.Index}
var sl plog.ScopeLogs
var found bool
if sl, found = scopeLogsMap[key]; !found {
rl := ld.ResourceLogs().AppendEmpty()
sl = rl.ScopeLogs().AppendEmpty()
scopeLogsMap[key] = sl
appendSplunkMetadata(rl, config.HecToOtelAttrs, event.Host, event.Source, event.SourceType, event.Index)
if resourceCustomizer != nil {
resourceCustomizer(rl.Resource())
}
}
// The SourceType field is the most logical "name" of the event.
logRecord := sl.LogRecords().AppendEmpty()
if err := convertToValue(logger, event.Event, logRecord.Body()); err != nil {
return ld, err
}
logRecord.SetTimestamp(convertTimestamp(event.Time))
logRecord.SetObservedTimestamp(observedTimestamp)
// Set event fields first, so the specialized attributes overwrite them if needed.
keys := make([]string, 0, len(event.Fields))
for k := range event.Fields {
keys = append(keys, k)
}
sort.Strings(keys)
for _, key := range keys {
val := event.Fields[key]
err := convertToValue(logger, val, logRecord.Attributes().PutEmpty(key))
if err != nil {
return ld, err
}
}
}
return ld, nil
}
// splunkHecRawToLogData transforms raw splunk event into log
func splunkHecRawToLogData(bodyReader io.Reader, query url.Values, resourceCustomizer func(pcommon.Resource), config *Config, timestamp pcommon.Timestamp) (plog.Logs, int, error) {
ld := plog.NewLogs()
observedTimestamp := pcommon.NewTimestampFromTime(time.Now())
rl := ld.ResourceLogs().AppendEmpty()
appendSplunkMetadata(rl, config.HecToOtelAttrs, query.Get(host), query.Get(source), query.Get(sourcetype), query.Get(index))
if resourceCustomizer != nil {
resourceCustomizer(rl.Resource())
}
sl := rl.ScopeLogs().AppendEmpty()
if config.Splitting == SplittingStrategyNone {
b, err := io.ReadAll(bodyReader)
if err != nil {
return ld, 0, err
}
logRecord := sl.LogRecords().AppendEmpty()
logRecord.Body().SetStr(string(b))
logRecord.SetTimestamp(timestamp)
logRecord.SetObservedTimestamp(observedTimestamp)
} else {
sc := bufio.NewScanner(bodyReader)
for sc.Scan() {
logRecord := sl.LogRecords().AppendEmpty()
logLine := sc.Text()
logRecord.Body().SetStr(logLine)
logRecord.SetTimestamp(timestamp)
logRecord.SetObservedTimestamp(observedTimestamp)
}
}
return ld, sl.LogRecords().Len(), nil
}
func appendSplunkMetadata(rl plog.ResourceLogs, attrs translator.HecToOtelAttrs, host, source, sourceType, index string) {
if host != "" {
rl.Resource().Attributes().PutStr(attrs.Host, host)
}
if source != "" {
rl.Resource().Attributes().PutStr(attrs.Source, source)
}
if sourceType != "" {
rl.Resource().Attributes().PutStr(attrs.SourceType, sourceType)
}
if index != "" {
rl.Resource().Attributes().PutStr(attrs.Index, index)
}
}
func convertToValue(logger *zap.Logger, src any, dest pcommon.Value) error {
switch value := src.(type) {
case nil:
case string:
dest.SetStr(value)
case int64:
dest.SetInt(value)
case float64:
dest.SetDouble(value)
case bool:
dest.SetBool(value)
case map[string]any:
return convertToAttributeMap(logger, value, dest)
case []any:
return convertToSliceVal(logger, value, dest)
default:
logger.Debug("Unsupported value conversion", zap.Any("value", src))
return errCannotConvertValue
}
return nil
}
func convertToSliceVal(logger *zap.Logger, value []any, dest pcommon.Value) error {
arr := dest.SetEmptySlice()
for _, elt := range value {
err := convertToValue(logger, elt, arr.AppendEmpty())
if err != nil {
return err
}
}
return nil
}
func convertToAttributeMap(logger *zap.Logger, value map[string]any, dest pcommon.Value) error {
attrMap := dest.SetEmptyMap()
keys := make([]string, 0, len(value))
for k := range value {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
v := value[k]
if err := convertToValue(logger, v, attrMap.PutEmpty(k)); err != nil {
return err
}
}
return nil
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package splunkhecreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/splunkhecreceiver"
import (
"fmt"
"strconv"
"strings"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.uber.org/zap"
translator "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/splunk"
)
// splunkHecToMetricsData converts Splunk HEC metric points to
// pmetric.Metrics. Returning the converted data and the number of
// dropped time series.
func splunkHecToMetricsData(logger *zap.Logger, events []*translator.Event, resourceCustomizer func(pcommon.Resource), config *Config) (pmetric.Metrics, int) {
numDroppedTimeSeries := 0
md := pmetric.NewMetrics()
scopeMetricsMap := make(map[[4]string]pmetric.ScopeMetrics)
for _, event := range events {
values := event.GetMetricValues()
labels := buildAttributes(event.Fields)
metrics := pmetric.NewMetricSlice()
for metricName, metricValue := range values {
pointTimestamp := convertTimestamp(event.Time)
metric := pmetric.NewMetric()
metric.SetName(metricName)
switch v := metricValue.(type) {
case int64:
addIntGauge(metrics, metricName, v, pointTimestamp, labels)
case *int64:
addIntGauge(metrics, metricName, *v, pointTimestamp, labels)
case float64:
addDoubleGauge(metrics, metricName, v, pointTimestamp, labels)
case *float64:
addDoubleGauge(metrics, metricName, *v, pointTimestamp, labels)
case string:
convertString(logger, &numDroppedTimeSeries, metrics, metricName, pointTimestamp, v, labels)
case *string:
convertString(logger, &numDroppedTimeSeries, metrics, metricName, pointTimestamp, *v, labels)
default:
// drop this point as we do not know how to extract a value from it
numDroppedTimeSeries++
logger.Debug("Cannot convert metric, unknown input type",
zap.String("metric", metricName))
}
}
if metrics.Len() == 0 {
continue
}
key := [4]string{event.Host, event.Source, event.SourceType, event.Index}
var sm pmetric.ScopeMetrics
var found bool
if sm, found = scopeMetricsMap[key]; !found {
resourceMetrics := md.ResourceMetrics().AppendEmpty()
sm = resourceMetrics.ScopeMetrics().AppendEmpty()
scopeMetricsMap[key] = sm
attrs := resourceMetrics.Resource().Attributes()
if event.Host != "" {
attrs.PutStr(config.HecToOtelAttrs.Host, event.Host)
}
if event.Source != "" {
attrs.PutStr(config.HecToOtelAttrs.Source, event.Source)
}
if event.SourceType != "" {
attrs.PutStr(config.HecToOtelAttrs.SourceType, event.SourceType)
}
if event.Index != "" {
attrs.PutStr(config.HecToOtelAttrs.Index, event.Index)
}
if resourceCustomizer != nil {
resourceCustomizer(resourceMetrics.Resource())
}
}
metrics.MoveAndAppendTo(sm.Metrics())
}
return md, numDroppedTimeSeries
}
func convertString(logger *zap.Logger, numDroppedTimeSeries *int, metrics pmetric.MetricSlice, metricName string, pointTimestamp pcommon.Timestamp, s string, attributes pcommon.Map) {
// best effort, cast to string and turn into a number
dbl, err := strconv.ParseFloat(s, 64)
if err != nil {
*numDroppedTimeSeries++
logger.Debug("Cannot convert metric value from string to number",
zap.String("metric", metricName))
} else {
addDoubleGauge(metrics, metricName, dbl, pointTimestamp, attributes)
}
}
func addIntGauge(metrics pmetric.MetricSlice, metricName string, value int64, ts pcommon.Timestamp, attributes pcommon.Map) {
metric := metrics.AppendEmpty()
metric.SetName(metricName)
intPt := metric.SetEmptyGauge().DataPoints().AppendEmpty()
intPt.SetTimestamp(ts)
intPt.SetIntValue(value)
attributes.CopyTo(intPt.Attributes())
}
func addDoubleGauge(metrics pmetric.MetricSlice, metricName string, value float64, ts pcommon.Timestamp, attributes pcommon.Map) {
metric := metrics.AppendEmpty()
metric.SetName(metricName)
doublePt := metric.SetEmptyGauge().DataPoints().AppendEmpty()
doublePt.SetTimestamp(ts)
doublePt.SetDoubleValue(value)
attributes.CopyTo(doublePt.Attributes())
}
/*
Splunk HEC timestamps can be in nanoseconds, microseconds, milliseconds, and seconds epoch.
Example:
- 1234567890
- 1234567890123
- 1234567890123456
- 1234567890123456789
The format can also be <second>.<sub-second>.
Example:
- 1234567890.000
- 1234567890.123
- 1234567890.123456
- 1234567890.123456789
*/
func convertTimestamp(t float64) pcommon.Timestamp {
if t >= 10_000_000_000_000_000 {
// nano
return pcommon.Timestamp(t)
}
if t >= 10_000_000_000_000 {
// micro
return pcommon.Timestamp(t * 1e3)
}
if t >= 10_000_000_000 {
// milli
return pcommon.Timestamp(t * 1e6)
}
// second
return pcommon.Timestamp(t * 1e9)
}
// Extract dimensions from the Splunk event fields to populate metric data point attributes.
func buildAttributes(dimensions map[string]any) pcommon.Map {
attributes := pcommon.NewMap()
attributes.EnsureCapacity(len(dimensions))
for key, val := range dimensions {
if strings.HasPrefix(key, "metric_name") || key == "_value" {
continue
}
if key == "" || val == nil {
// TODO: Log or metric for this odd ball?
continue
}
attributes.PutStr(key, fmt.Sprintf("%v", val))
}
return attributes
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package webhookeventreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/webhookeventreceiver"
import (
"errors"
"regexp"
"strings"
"time"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/config/configopaque"
"go.uber.org/multierr"
)
var (
errMissingEndpointFromConfig = errors.New("missing receiver server endpoint from config")
errReadTimeoutExceedsMaxValue = errors.New("the duration specified for read_timeout exceeds the maximum allowed value of 10s")
errWriteTimeoutExceedsMaxValue = errors.New("the duration specified for write_timeout exceeds the maximum allowed value of 10s")
errRequiredHeader = errors.New("both key and value are required to assign a required_header")
errHeaderAttributeRegexCompile = errors.New("regex for header_attribute_regex failed to compile")
errHMACMissingSecret = errors.New("hmac_signature.secret is required when hmac_signature is configured")
errHMACMissingHeader = errors.New("hmac_signature.header is required when hmac_signature is configured")
errHMACMissingPrefix = errors.New("hmac_signature.prefix is required when hmac_signature is configured")
errPathMissingLeadingSlash = errors.New("path must begin with a leading '/'")
errHealthPathMissingSlash = errors.New("health_path must begin with a leading '/'")
)
// Config defines configuration for the Generic Webhook receiver.
type Config struct {
ServerConfig confighttp.ServerConfig `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct
ReadTimeout string `mapstructure:"read_timeout"` // wait time for reading request headers in ms. Default is 500ms.
WriteTimeout string `mapstructure:"write_timeout"` // wait time for writing request response in ms. Default is 500ms.
Path string `mapstructure:"path"` // path for data collection. Default is /events
HealthPath string `mapstructure:"health_path"` // path for health check api. Default is /health_check
RequiredHeader RequiredHeader `mapstructure:"required_header"` // optional setting to set a required header for all requests to have
SplitLogsAtNewLine bool `mapstructure:"split_logs_at_newline"` // optional setting to split logs into multiple log records
SplitLogsAtJSONBoundary bool `mapstructure:"split_logs_at_json_boundary"` // optional setting to split logs at JSON object boundaries
ConvertHeadersToAttributes bool `mapstructure:"convert_headers_to_attributes"` // optional to convert all headers to attributes
HeaderAttributeRegex string `mapstructure:"header_attribute_regex"` // optional to convert headers matching a regex to log attributes
HMACSignature HMACSignature `mapstructure:"hmac_signature"` // optional HMAC hex digest signature verification
}
type RequiredHeader struct {
Key string `mapstructure:"key"`
Value string `mapstructure:"value"`
}
// HMACSignature defines configuration for HMAC hex digest signature verification.
// This is compatible with webhook signature schemes used by GitHub (X-Hub-Signature-256: sha256=<hex>)
// and Fingerprint (fpjs-event-signature: v1=<hex>).
type HMACSignature struct {
// Secret is the shared secret used to compute the HMAC.
Secret configopaque.String `mapstructure:"secret"`
// Header is the HTTP header name containing the signature (e.g. "X-Hub-Signature-256").
Header string `mapstructure:"header"`
// Prefix is the prefix before the hex digest in the header value (e.g. "sha256=" or "v1=").
Prefix string `mapstructure:"prefix"`
}
func (cfg *Config) Validate() error {
var errs error
maxReadWriteTimeout, _ := time.ParseDuration("10s")
if cfg.ServerConfig.NetAddr.Endpoint == "" {
errs = multierr.Append(errs, errMissingEndpointFromConfig)
}
// httprouter panics at Start() if a registered path does not begin with '/'.
// Catch it here so it surfaces as a config error instead of a runtime panic.
if !strings.HasPrefix(cfg.Path, "/") {
errs = multierr.Append(errs, errPathMissingLeadingSlash)
}
if !strings.HasPrefix(cfg.HealthPath, "/") {
errs = multierr.Append(errs, errHealthPathMissingSlash)
}
// If a user defines a custom read/write timeout there is a maximum value
// of 10s imposed here.
if cfg.ReadTimeout != "" {
readTimeout, err := time.ParseDuration(cfg.ReadTimeout)
if err != nil {
errs = multierr.Append(errs, err)
}
if readTimeout > maxReadWriteTimeout {
errs = multierr.Append(errs, errReadTimeoutExceedsMaxValue)
}
}
if cfg.WriteTimeout != "" {
writeTimeout, err := time.ParseDuration(cfg.WriteTimeout)
if err != nil {
errs = multierr.Append(errs, err)
}
if writeTimeout > maxReadWriteTimeout {
errs = multierr.Append(errs, errWriteTimeoutExceedsMaxValue)
}
}
// Set default MaxRequestBodySize if not configured
if cfg.ServerConfig.MaxRequestBodySize == 0 {
cfg.ServerConfig.MaxRequestBodySize = int64(20 * 1024 * 1024) // 20MiB
// to match default value http://github.com/open-telemetry/opentelemetry-collector/blob/release/v0.139.x/config/confighttp/server.go#L31
}
if (cfg.RequiredHeader.Key != "" && cfg.RequiredHeader.Value == "") || (cfg.RequiredHeader.Value != "" && cfg.RequiredHeader.Key == "") {
errs = multierr.Append(errs, errRequiredHeader)
}
if cfg.SplitLogsAtNewLine && cfg.SplitLogsAtJSONBoundary {
errs = multierr.Append(errs, errors.New("split_logs_at_new_line and split_logs_at_json_boundary cannot be enabled at the same time"))
}
if cfg.HMACSignature.Secret != "" || cfg.HMACSignature.Header != "" || cfg.HMACSignature.Prefix != "" {
if cfg.HMACSignature.Secret == "" {
errs = multierr.Append(errs, errHMACMissingSecret)
}
if cfg.HMACSignature.Header == "" {
errs = multierr.Append(errs, errHMACMissingHeader)
}
if cfg.HMACSignature.Prefix == "" {
errs = multierr.Append(errs, errHMACMissingPrefix)
}
}
if cfg.HeaderAttributeRegex != "" {
_, err := regexp.Compile(cfg.HeaderAttributeRegex)
if err != nil {
errs = multierr.Append(errs, errHeaderAttributeRegexCompile)
errs = multierr.Append(errs, err)
}
}
return errs
}
func (cfg *Config) ShouldSplitLogsAtJSONBoundary() bool {
return cfg.SplitLogsAtJSONBoundary
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package webhookeventreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/webhookeventreceiver"
import (
"context"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/config/confignet"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/xreceiver"
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/webhookeventreceiver/internal/metadata"
)
var scopeLogName = "otlp/" + metadata.Type.String()
const (
// might add this later, for now I wish to require a valid
// endpoint to be declared by the user.
// Default endpoints to bind to.
// defaultEndpoint = "localhost:8080"
defaultReadTimeout = "500ms"
defaultWriteTimeout = "500ms"
defaultPath = "/events"
defaultHealthPath = "/health_check"
defaultMaxRequestBodySize = 100 * 1024 // 100KB
)
// NewFactory creates a factory for Generic Webhook Receiver.
func NewFactory() receiver.Factory {
return xreceiver.NewFactory(
metadata.Type,
createDefaultConfig,
xreceiver.WithLogs(createLogsReceiver, metadata.LogsStability),
xreceiver.WithDeprecatedTypeAlias(metadata.DeprecatedType),
)
}
// Default configuration for the generic webhook receiver
func createDefaultConfig() component.Config {
netAddr := confignet.NewDefaultAddrConfig()
netAddr.Transport = confignet.TransportTypeTCP
serverConfig := confighttp.NewDefaultServerConfig()
serverConfig.NetAddr = netAddr
serverConfig.MaxRequestBodySize = defaultMaxRequestBodySize
return &Config{
ServerConfig: serverConfig,
Path: defaultPath,
HealthPath: defaultHealthPath,
ReadTimeout: defaultReadTimeout,
WriteTimeout: defaultWriteTimeout,
ConvertHeadersToAttributes: false, // optional, off by default
SplitLogsAtNewLine: false,
SplitLogsAtJSONBoundary: false,
}
}
// createLogsReceiver creates a logs receiver based on provided config.
func createLogsReceiver(
_ context.Context,
params receiver.Settings,
cfg component.Config,
consumer consumer.Logs,
) (receiver.Logs, error) {
conf := cfg.(*Config)
return newLogsReceiver(params, *conf, consumer)
}
// Code generated by mdatagen. DO NOT EDIT.
package metadata
import (
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/receiver"
)
// LogsBuilder provides an interface for scrapers to report logs while taking care of all the transformations
// required to produce log representation defined in metadata and user config.
type LogsBuilder struct {
logsBuffer plog.Logs
logRecordsBuffer plog.LogRecordSlice
buildInfo component.BuildInfo // contains version information.
}
// LogBuilderOption applies changes to default logs builder.
type LogBuilderOption interface {
apply(*LogsBuilder)
}
func NewLogsBuilder(settings receiver.Settings) *LogsBuilder {
lb := &LogsBuilder{
logsBuffer: plog.NewLogs(),
logRecordsBuffer: plog.NewLogRecordSlice(),
buildInfo: settings.BuildInfo,
}
return lb
}
// ResourceLogsOption applies changes to provided resource logs.
type ResourceLogsOption interface {
apply(plog.ResourceLogs)
}
type resourceLogsOptionFunc func(plog.ResourceLogs)
func (rlof resourceLogsOptionFunc) apply(rl plog.ResourceLogs) {
rlof(rl)
}
// WithLogsResource sets the provided resource on the emitted ResourceLogs.
// It's recommended to use ResourceBuilder to create the resource.
func WithLogsResource(res pcommon.Resource) ResourceLogsOption {
return resourceLogsOptionFunc(func(rl plog.ResourceLogs) {
res.CopyTo(rl.Resource())
})
}
// AppendLogRecord adds a log record to the logs builder.
func (lb *LogsBuilder) AppendLogRecord(lr plog.LogRecord) {
lr.MoveTo(lb.logRecordsBuffer.AppendEmpty())
}
// EmitForResource saves all the generated logs under a new resource and updates the internal state to be ready for
// recording another set of log records as part of another resource. This function can be helpful when one scraper
// needs to emit logs from several resources. Otherwise calling this function is not required,
// just `Emit` function can be called instead.
// Resource attributes should be provided as ResourceLogsOption arguments.
func (lb *LogsBuilder) EmitForResource(options ...ResourceLogsOption) {
rl := plog.NewResourceLogs()
ils := rl.ScopeLogs().AppendEmpty()
ils.Scope().SetName(ScopeName)
ils.Scope().SetVersion(lb.buildInfo.Version)
for _, op := range options {
op.apply(rl)
}
if lb.logRecordsBuffer.Len() > 0 {
lb.logRecordsBuffer.MoveAndAppendTo(ils.LogRecords())
lb.logRecordsBuffer = plog.NewLogRecordSlice()
}
if ils.LogRecords().Len() > 0 {
rl.MoveTo(lb.logsBuffer.ResourceLogs().AppendEmpty())
}
}
// Emit returns all the logs accumulated by the logs builder and updates the internal state to be ready for
// recording another set of logs. This function will be responsible for applying all the transformations required to
// produce logs representation defined in metadata and user config.
func (lb *LogsBuilder) Emit(options ...ResourceLogsOption) plog.Logs {
lb.EmitForResource(options...)
logs := lb.logsBuffer
lb.logsBuffer = plog.NewLogs()
return logs
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package webhookeventreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/webhookeventreceiver"
import (
"bufio"
"bytes"
"compress/gzip"
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"io"
"net/http"
"regexp"
"strings"
"sync"
"time"
"github.com/goccy/go-json"
"github.com/julienschmidt/httprouter"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componentstatus"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/receiverhelper"
"go.uber.org/zap"
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/webhookeventreceiver/internal/metadata"
)
var (
errNilLogsConsumer = errors.New("missing a logs consumer")
errInvalidRequestMethod = errors.New("invalid method. Valid method is POST")
errInvalidEncodingType = errors.New("invalid encoding type")
errEmptyResponseBody = errors.New("request body content length is zero")
errMissingRequiredHeader = errors.New("request was missing required header or incorrect header value")
errMissingSignatureHeader = errors.New("missing HMAC signature header")
errInvalidSignaturePrefix = errors.New("HMAC signature header has invalid prefix")
errInvalidSignatureEncoding = errors.New("HMAC signature hex encoding is invalid")
errSignatureMismatch = errors.New("HMAC signature does not match payload")
)
const healthyResponse = `{"text": "Webhookevent receiver is healthy"}`
type eventReceiver struct {
settings receiver.Settings
cfg *Config
logConsumer consumer.Logs
server *http.Server
shutdownWG sync.WaitGroup
obsrecv *receiverhelper.ObsReport
gzipPool *sync.Pool
includeHeadersRegex *regexp.Regexp
maxRequestBodySize int // Computed max token size for scanner (minimum 64KB)
}
func newLogsReceiver(params receiver.Settings, cfg Config, consumer consumer.Logs) (receiver.Logs, error) {
if consumer == nil {
return nil, errNilLogsConsumer
}
if err := cfg.Validate(); err != nil {
return nil, err
}
var includeHeaderRegex *regexp.Regexp
if cfg.HeaderAttributeRegex != "" {
// Valdiate() call above has already ensured this will compile
includeHeaderRegex, _ = regexp.Compile(cfg.HeaderAttributeRegex)
}
transport := "http"
if cfg.ServerConfig.TLS.HasValue() {
transport = "https"
}
obsrecv, err := receiverhelper.NewObsReport(receiverhelper.ObsReportSettings{
ReceiverID: params.ID,
Transport: transport,
ReceiverCreateSettings: params,
})
if err != nil {
return nil, err
}
// create eventReceiver instance
er := &eventReceiver{
settings: params,
cfg: &cfg,
logConsumer: consumer,
obsrecv: obsrecv,
gzipPool: &sync.Pool{New: func() any { return new(gzip.Reader) }},
includeHeadersRegex: includeHeaderRegex,
maxRequestBodySize: int(cfg.ServerConfig.MaxRequestBodySize),
}
return er, nil
}
// Start function manages receiver startup tasks. part of the receiver.Logs interface.
func (er *eventReceiver) Start(ctx context.Context, host component.Host) error {
// noop if not nil. if start has not been called before these values should be nil.
if er.server != nil && er.server.Handler != nil {
return nil
}
// create listener from config
ln, err := er.cfg.ServerConfig.ToListener(ctx)
if err != nil {
return err
}
// set up router.
router := httprouter.New()
router.POST(er.cfg.Path, er.handleReq)
router.GET(er.cfg.HealthPath, er.handleHealthCheck)
// webhook server standup and configuration
er.server, err = er.cfg.ServerConfig.ToServer(ctx, host.GetExtensions(), er.settings.TelemetrySettings, router)
if err != nil {
return err
}
readTimeout, err := time.ParseDuration(er.cfg.ReadTimeout)
if err != nil {
return err
}
writeTimeout, err := time.ParseDuration(er.cfg.WriteTimeout)
if err != nil {
return err
}
// set timeouts
er.server.ReadHeaderTimeout = readTimeout
er.server.WriteTimeout = writeTimeout
// shutdown
er.shutdownWG.Go(func() {
if errHTTP := er.server.Serve(ln); !errors.Is(errHTTP, http.ErrServerClosed) && errHTTP != nil {
componentstatus.ReportStatus(host, componentstatus.NewFatalErrorEvent(errHTTP))
}
})
return nil
}
// Shutdown function manages receiver shutdown tasks. part of the receiver.Logs interface.
func (er *eventReceiver) Shutdown(context.Context) error {
// server must exist to be closed.
if er.server == nil {
return nil
}
err := er.server.Close()
er.shutdownWG.Wait()
return err
}
// handleReq handles incoming request from webhook. On success returns a 200 response code to the webhook
func (er *eventReceiver) handleReq(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
ctx := r.Context()
ctx = er.obsrecv.StartLogsOp(ctx)
if r.Method != http.MethodPost {
er.failBadReq(ctx, w, http.StatusBadRequest, errInvalidRequestMethod)
er.obsrecv.EndLogsOp(ctx, metadata.Type.String(), 0, errInvalidRequestMethod)
return
}
if er.cfg.RequiredHeader.Key != "" {
requiredHeaderValue := r.Header.Get(er.cfg.RequiredHeader.Key)
if requiredHeaderValue != er.cfg.RequiredHeader.Value {
er.failBadReq(ctx, w, http.StatusUnauthorized, errMissingRequiredHeader)
er.obsrecv.EndLogsOp(ctx, metadata.Type.String(), 0, errMissingRequiredHeader)
return
}
}
encoding := r.Header.Get("Content-Encoding")
// only support gzip if encoding header is set.
if encoding != "" && encoding != "gzip" {
er.failBadReq(ctx, w, http.StatusUnsupportedMediaType, errInvalidEncodingType)
er.obsrecv.EndLogsOp(ctx, metadata.Type.String(), 0, errInvalidEncodingType)
return
}
if r.ContentLength == 0 {
er.failBadReq(ctx, w, http.StatusBadRequest, errEmptyResponseBody)
er.obsrecv.EndLogsOp(ctx, metadata.Type.String(), 0, errEmptyResponseBody)
return
}
// If HMAC signature verification is configured, read the raw body for verification
// before any decompression, since webhook providers sign the raw payload.
if er.cfg.HMACSignature.Secret != "" {
limitedBody := io.LimitReader(r.Body, int64(er.maxRequestBodySize)+1)
rawBody, readErr := io.ReadAll(limitedBody)
_ = r.Body.Close()
if readErr != nil {
er.failBadReq(ctx, w, http.StatusBadRequest, readErr)
er.obsrecv.EndLogsOp(ctx, metadata.Type.String(), 0, readErr)
return
}
if len(rawBody) > er.maxRequestBodySize {
tooLargeErr := fmt.Errorf("%w: limit is %d bytes", errRequestBodyTooLarge, er.maxRequestBodySize)
er.failBadReq(ctx, w, http.StatusBadRequest, tooLargeErr)
er.obsrecv.EndLogsOp(ctx, metadata.Type.String(), 0, tooLargeErr)
return
}
if verifyErr := er.verifyHMACSignature(rawBody, r.Header.Get(er.cfg.HMACSignature.Header)); verifyErr != nil {
er.failBadReq(ctx, w, http.StatusUnauthorized, verifyErr)
er.obsrecv.EndLogsOp(ctx, metadata.Type.String(), 0, verifyErr)
return
}
// Replace the body with a reader over the raw bytes for subsequent processing
r.Body = io.NopCloser(bytes.NewReader(rawBody))
}
bodyReader := r.Body
// gzip encoded case
if encoding == "gzip" || encoding == "x-gzip" {
reader := er.gzipPool.Get().(*gzip.Reader)
err := reader.Reset(bodyReader)
if err != nil {
er.failBadReq(ctx, w, http.StatusBadRequest, err)
er.obsrecv.EndLogsOp(ctx, metadata.Type.String(), 0, err)
_, _ = io.ReadAll(r.Body)
_ = r.Body.Close()
return
}
bodyReader = reader
defer er.gzipPool.Put(reader)
}
// send body into a scanner and then convert the request body into a log
sc := bufio.NewScanner(bodyReader)
ld, numLogs, err := er.reqToLog(sc, r.Header, r.URL.Query())
_ = bodyReader.Close()
if err != nil {
er.failBadReq(ctx, w, http.StatusBadRequest, err)
er.obsrecv.EndLogsOp(ctx, metadata.Type.String(), numLogs, err)
return
}
consumerErr := er.logConsumer.ConsumeLogs(ctx, ld)
if consumerErr != nil {
er.failBadReq(ctx, w, http.StatusInternalServerError, consumerErr)
} else {
w.WriteHeader(http.StatusOK)
}
er.obsrecv.EndLogsOp(ctx, metadata.Type.String(), numLogs, consumerErr)
}
// Simple healthcheck endpoint.
func (*eventReceiver) handleHealthCheck(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(healthyResponse))
}
// verifyHMACSignature verifies the HMAC-SHA256 hex digest signature of the request body.
// The signature header value is expected to be in the format "<prefix><hex-digest>",
// e.g. "sha256=abc123..." (GitHub) or "v1=abc123..." (Fingerprint).
func (er *eventReceiver) verifyHMACSignature(payload []byte, signatureHeader string) error {
if signatureHeader == "" {
return errMissingSignatureHeader
}
prefix := er.cfg.HMACSignature.Prefix
if !strings.HasPrefix(signatureHeader, prefix) {
return fmt.Errorf("%w: expected prefix %q", errInvalidSignaturePrefix, prefix)
}
hexDigest := strings.TrimPrefix(signatureHeader, prefix)
sigBytes, err := hex.DecodeString(hexDigest)
if err != nil {
return fmt.Errorf("%w: %w", errInvalidSignatureEncoding, err)
}
mac := hmac.New(sha256.New, []byte(er.cfg.HMACSignature.Secret))
mac.Write(payload)
expectedMAC := mac.Sum(nil)
if !hmac.Equal(sigBytes, expectedMAC) {
return errSignatureMismatch
}
return nil
}
// write response on a failed/bad request. Generates a small json body based on the thrown by
// the handle func and the appropriate http status code. many webhooks will either log these responses or
// notify webhook users should a none 2xx code be detected.
func (er *eventReceiver) failBadReq(_ context.Context,
w http.ResponseWriter,
httpStatusCode int,
err error,
) {
jsonResp, err := json.Marshal(err.Error())
if err != nil {
er.settings.Logger.Warn("failed to marshall error to json")
}
// write response to webhook
w.WriteHeader(httpStatusCode)
if len(jsonResp) > 0 {
w.Header().Add("Content-Type", "application/json")
_, err = w.Write(jsonResp)
if err != nil {
er.settings.Logger.Warn("failed to write json response", zap.Error(err))
}
}
// log bad webhook request if debug is enabled
if er.settings.Logger.Core().Enabled(zap.DebugLevel) {
msg := string(jsonResp)
er.settings.Logger.Debug(msg, zap.Int("http_status_code", httpStatusCode), zap.Error(err))
}
}
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package webhookeventreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/webhookeventreceiver"
import (
"bufio"
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"regexp"
"strings"
"time"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/webhookeventreceiver/internal/metadata"
)
const (
headerNamespace = "header"
)
var errRequestBodyTooLarge = errors.New("request body exceeds maximum allowed size")
func (er *eventReceiver) reqToLog(sc *bufio.Scanner,
headers http.Header,
query url.Values,
) (plog.Logs, int, error) {
// we simply dont split the data passed into scan (i.e. scan the whole thing)
// the downside to this approach is that only 1 log per request can be handled.
// NOTE: logs will contain these newline characters which could have formatting
// consequences downstream.
split := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
if !atEOF {
return 0, nil, nil
}
return 0, data, bufio.ErrFinalToken
}
sc.Split(split)
// Increase max token size from default 64KB to configured value
sc.Buffer(make([]byte, bufio.MaxScanTokenSize), er.maxRequestBodySize)
log := plog.NewLogs()
resourceLog := log.ResourceLogs().AppendEmpty()
appendMetadata(resourceLog, query)
scopeLog := resourceLog.ScopeLogs().AppendEmpty()
scopeLog.Scope().SetName(scopeLogName)
scopeLog.Scope().SetVersion(er.settings.BuildInfo.Version)
scopeLog.Scope().Attributes().PutStr("source", er.settings.ID.String())
scopeLog.Scope().Attributes().PutStr("receiver", metadata.Type.String())
for sc.Scan() {
lines := []string{sc.Text()}
if er.cfg.SplitLogsAtNewLine {
lines = strings.Split(sc.Text(), "\n")
} else if er.cfg.ShouldSplitLogsAtJSONBoundary() {
lines = splitJSONObjects(sc.Text())
}
for _, line := range lines {
logRecord := scopeLog.LogRecords().AppendEmpty()
logRecord.SetObservedTimestamp(pcommon.NewTimestampFromTime(time.Now()))
logRecord.Body().SetStr(line)
if er.includeHeadersRegex != nil {
appendHeaders(headers, logRecord, er.includeHeadersRegex)
}
}
}
if err := sc.Err(); err != nil {
if errors.Is(err, bufio.ErrTooLong) {
return log, scopeLog.LogRecords().Len(), fmt.Errorf("%w: limit is %d bytes", errRequestBodyTooLarge, er.maxRequestBodySize)
}
return log, scopeLog.LogRecords().Len(), fmt.Errorf("failed to scan request body: %w", err)
}
return log, scopeLog.LogRecords().Len(), nil
}
// append query parameters and webhook source as resource attributes
func appendMetadata(resourceLog plog.ResourceLogs, query url.Values) {
for k := range query {
if query.Get(k) != "" {
resourceLog.Resource().Attributes().PutStr(k, query.Get(k))
}
}
}
// append headers as logRecord attributes if they match supplied regex
func appendHeaders(h http.Header, l plog.LogRecord, r *regexp.Regexp) {
for k := range h {
// Skip the required header used for authentication
if r.MatchString(k) {
slice := l.Attributes().PutEmptySlice(headerAttributeKey(k))
for _, v := range h.Values(k) {
slice.AppendEmpty().SetStr(v)
}
}
}
}
// prepend the header key with the "header." namespace
func headerAttributeKey(header string) string {
return headerNamespace + "." + header
}
func splitJSONObjects(data string) []string {
var result []string
decoder := json.NewDecoder(bytes.NewReader([]byte(data)))
for {
var obj json.RawMessage
err := decoder.Decode(&obj)
if err != nil {
// If we hit EOF or any other error, we're done
break
}
result = append(result, string(obj))
}
// If no valid JSON objects were found, return the original data
if len(result) == 0 {
return []string{data}
}
return result
}