Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ecs-metadata: support dimensionToUpdate config field #4091

Merged
merged 1 commit into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### 🛑 Breaking changes 🛑

- `ecs-metadata` sync the `known_status` property on the `container_id` dimension instead of lower cardinality `container_name`. This can be prevented by configuring `dimensionToUpdate` to `container_name` ([#4091](https://github.com/signalfx/splunk-otel-collector/pull/4091))

## v0.91.1

### 💡 Enhancements 💡
Expand Down
33 changes: 26 additions & 7 deletions internal/signalfx-agent/pkg/monitors/ecs/ecs.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ import (
"github.com/signalfx/signalfx-agent/pkg/utils/filter"
)

const (
ctrNameDim = "container_name"
ctrIDDim = "container_id"
)

var logger = log.WithFields(log.Fields{"monitorType": monitorType})

func init() {
Expand All @@ -53,8 +58,12 @@ type Config struct {
// A list of filters of images to exclude. Supports literals, globs, and
// regex.
ExcludedImages []string `yaml:"excludedImages"`
// The dimension to update with `known_status` property syncing. Supported options are "container_id" (default) and "container_name".
DimensionToUpdate string `yaml:"dimensionToUpdate" default:"container_id"`
}

type dimensionValueFn func(ctr ecs.Container) (string, string)

// Monitor for ECS Metadata
type Monitor struct {
Output types.FilteringOutput
Expand All @@ -68,9 +77,10 @@ type Monitor struct {
// shouldIgnore - key : container docker id, tells if stats for the container should be ignored.
// Usually the container was filtered out by excludedImages
// or container metadata is not received.
shouldIgnore map[string]bool
imageFilter filter.StringFilter
logger log.FieldLogger
shouldIgnore map[string]bool
imageFilter filter.StringFilter
logger log.FieldLogger
dimensionToUpdate dimensionValueFn
}

// Configure the monitor and kick off volume metric syncing
Expand All @@ -82,6 +92,14 @@ func (m *Monitor) Configure(conf *Config) error {
return fmt.Errorf("could not load excluded image filter: %w", err)
}

if conf.DimensionToUpdate == ctrNameDim {
m.dimensionToUpdate = func(ctr ecs.Container) (string, string) { return ctrNameDim, ctr.Name }
} else if conf.DimensionToUpdate == ctrIDDim {
m.dimensionToUpdate = func(ctr ecs.Container) (string, string) { return ctrIDDim, ctr.DockerID }
} else {
return fmt.Errorf("unsupported `dimensionToUpdate` %q. Must be one of %q or %q", conf.DimensionToUpdate, ctrNameDim, ctrIDDim)
}

m.conf = conf
m.timeout = time.Duration(conf.TimeoutSeconds) * time.Second
m.client = &http.Client{
Expand Down Expand Up @@ -202,9 +220,10 @@ func (m *Monitor) fetchStatsForAll(enhancedMetricsConfig dmonitor.EnhancedMetric

m.Output.SendDatapoints(dps...)

name, value := m.dimensionToUpdate(container)
containerProps := &types.Dimension{
Name: "container_name",
Value: container.Name,
Name: name,
Value: value,
Properties: map[string]string{"known_status": container.KnownStatus},
Tags: nil,
}
Expand Down Expand Up @@ -301,10 +320,10 @@ func getTaskLimitMetrics(container ecs.Container, enhancedMetricsConfig dmonitor
cpuDp.Dimensions = map[string]string{}
cpuDp.Dimensions["plugin"] = "ecs"
name := strings.TrimPrefix(container.Name, "/")
cpuDp.Dimensions["container_name"] = name
cpuDp.Dimensions[ctrNameDim] = name
cpuDp.Dimensions["plugin_instance"] = name
cpuDp.Dimensions["container_image"] = container.Image
cpuDp.Dimensions["container_id"] = container.DockerID
cpuDp.Dimensions[ctrNameDim] = container.DockerID
cpuDp.Dimensions["container_hostname"] = container.Networks[0].IPAddresses[0]

taskLimitDps = append(taskLimitDps, cpuDp)
Expand Down
92 changes: 92 additions & 0 deletions internal/signalfx-agent/pkg/monitors/ecs/ecs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright Splunk, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ecs

import (
"testing"

"github.com/signalfx/defaults"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/signalfx/signalfx-agent/pkg/core/common/ecs"
"github.com/signalfx/signalfx-agent/pkg/core/config"
"github.com/signalfx/signalfx-agent/pkg/neotest"
)

func TestDimensionToUpdate(t *testing.T) {
ctr := ecs.Container{
Name: "some.container.name",
DockerID: "some.container.id",
}

for _, test := range []struct {
configValue string
expectedDimKey string
expectedDimValue string
expectedError string
}{
{
configValue: "container_name",
expectedDimKey: "container_name",
expectedDimValue: "some.container.name",
},
{
configValue: "container_id",
expectedDimKey: "container_id",
expectedDimValue: "some.container.id",
},
{
configValue: "default",
expectedDimKey: "container_id",
expectedDimValue: "some.container.id",
},
{
configValue: "invalid",
expectedError: "unsupported `dimensionToUpdate` \"invalid\". Must be one of \"container_name\" or \"container_id\"",
},
} {
test := test
t.Run(test.configValue, func(t *testing.T) {
cfg := &Config{
MetadataEndpoint: "http://localhost:0/not/real",
MonitorConfig: config.MonitorConfig{
IntervalSeconds: 1000,
},
}
if test.configValue != "default" {
cfg.DimensionToUpdate = test.configValue
}
require.NoError(t, defaults.Set(cfg))
monitor := &Monitor{
Output: neotest.NewTestOutput(),
}

err := monitor.Configure(cfg)
t.Cleanup(func() {
if monitor.cancel != nil {
monitor.cancel()
}
})
if test.expectedError != "" {
require.EqualError(t, err, test.expectedError)
return
}
require.NoError(t, err)
key, value := monitor.dimensionToUpdate(ctr)
assert.Equal(t, test.expectedDimKey, key)
assert.Equal(t, test.expectedDimValue, value)
})
}
}
18 changes: 18 additions & 0 deletions internal/signalfx-agent/pkg/neotest/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/signalfx/golib/v3/datapoint"
"github.com/signalfx/golib/v3/event"
"github.com/signalfx/golib/v3/trace"

"github.com/signalfx/signalfx-agent/pkg/core/dpfilters"
"github.com/signalfx/signalfx-agent/pkg/monitors/types"
)
Expand All @@ -19,6 +20,11 @@ type TestOutput struct {
dimChan chan *types.Dimension
}

var _ types.Output = (*TestOutput)(nil)

// Currently just satisfying the interface (noop implementation)
var _ types.FilteringOutput = (*TestOutput)(nil)

// NewTestOutput creates a new initialized TestOutput instance
func NewTestOutput() *TestOutput {
return &TestOutput{
Expand Down Expand Up @@ -167,3 +173,15 @@ loop:
// AddDatapointExclusionFilter is a noop here.
func (to *TestOutput) AddDatapointExclusionFilter(f dpfilters.DatapointFilter) {
}

func (to *TestOutput) EnabledMetrics() []string {
return nil
}

func (to *TestOutput) HasEnabledMetricInGroup(group string) bool {
return false
}

func (to *TestOutput) HasAnyExtraMetrics() bool {
return false
}
Loading