Skip to content

Commit 78ed6a5

Browse files
committed
ecs-metadata: support dimensionToUpdate config field with container_<name|id> values
1 parent ff7b84a commit 78ed6a5

File tree

3 files changed

+136
-7
lines changed

3 files changed

+136
-7
lines changed

internal/signalfx-agent/pkg/monitors/ecs/ecs.go

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ import (
2727
"github.com/signalfx/signalfx-agent/pkg/utils/filter"
2828
)
2929

30+
const (
31+
ctrNameDim = "container_name"
32+
ctrIDDim = "container_id"
33+
)
34+
3035
var logger = log.WithFields(log.Fields{"monitorType": monitorType})
3136

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

65+
type dimensionValueFn func(ctr ecs.Container) (string, string)
66+
5867
// Monitor for ECS Metadata
5968
type Monitor struct {
6069
Output types.FilteringOutput
@@ -68,9 +77,10 @@ type Monitor struct {
6877
// shouldIgnore - key : container docker id, tells if stats for the container should be ignored.
6978
// Usually the container was filtered out by excludedImages
7079
// or container metadata is not received.
71-
shouldIgnore map[string]bool
72-
imageFilter filter.StringFilter
73-
logger log.FieldLogger
80+
shouldIgnore map[string]bool
81+
imageFilter filter.StringFilter
82+
logger log.FieldLogger
83+
dimensionToUpdate dimensionValueFn
7484
}
7585

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

95+
if conf.DimensionToUpdate == ctrNameDim {
96+
m.dimensionToUpdate = func(ctr ecs.Container) (string, string) { return ctrNameDim, ctr.Name }
97+
} else if conf.DimensionToUpdate == ctrIDDim {
98+
m.dimensionToUpdate = func(ctr ecs.Container) (string, string) { return ctrIDDim, ctr.DockerID }
99+
} else {
100+
return fmt.Errorf("unsupported `dimensionToUpdate` %q. Must be one of %q or %q", conf.DimensionToUpdate, ctrNameDim, ctrIDDim)
101+
}
102+
85103
m.conf = conf
86104
m.timeout = time.Duration(conf.TimeoutSeconds) * time.Second
87105
m.client = &http.Client{
@@ -202,9 +220,10 @@ func (m *Monitor) fetchStatsForAll(enhancedMetricsConfig dmonitor.EnhancedMetric
202220

203221
m.Output.SendDatapoints(dps...)
204222

223+
name, value := m.dimensionToUpdate(container)
205224
containerProps := &types.Dimension{
206-
Name: "container_name",
207-
Value: container.Name,
225+
Name: name,
226+
Value: value,
208227
Properties: map[string]string{"known_status": container.KnownStatus},
209228
Tags: nil,
210229
}
@@ -301,10 +320,10 @@ func getTaskLimitMetrics(container ecs.Container, enhancedMetricsConfig dmonitor
301320
cpuDp.Dimensions = map[string]string{}
302321
cpuDp.Dimensions["plugin"] = "ecs"
303322
name := strings.TrimPrefix(container.Name, "/")
304-
cpuDp.Dimensions["container_name"] = name
323+
cpuDp.Dimensions[ctrNameDim] = name
305324
cpuDp.Dimensions["plugin_instance"] = name
306325
cpuDp.Dimensions["container_image"] = container.Image
307-
cpuDp.Dimensions["container_id"] = container.DockerID
326+
cpuDp.Dimensions[ctrNameDim] = container.DockerID
308327
cpuDp.Dimensions["container_hostname"] = container.Networks[0].IPAddresses[0]
309328

310329
taskLimitDps = append(taskLimitDps, cpuDp)
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Copyright Splunk, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
package ecs
15+
16+
import (
17+
"testing"
18+
19+
"github.com/signalfx/defaults"
20+
"github.com/stretchr/testify/assert"
21+
"github.com/stretchr/testify/require"
22+
23+
"github.com/signalfx/signalfx-agent/pkg/core/common/ecs"
24+
"github.com/signalfx/signalfx-agent/pkg/core/config"
25+
"github.com/signalfx/signalfx-agent/pkg/neotest"
26+
)
27+
28+
func TestDimensionToUpdate(t *testing.T) {
29+
ctr := ecs.Container{
30+
Name: "some.container.name",
31+
DockerID: "some.container.id",
32+
}
33+
34+
for _, test := range []struct {
35+
configValue string
36+
expectedDimKey string
37+
expectedDimValue string
38+
expectedError string
39+
}{
40+
{
41+
configValue: "container_name",
42+
expectedDimKey: "container_name",
43+
expectedDimValue: "some.container.name",
44+
},
45+
{
46+
configValue: "container_id",
47+
expectedDimKey: "container_id",
48+
expectedDimValue: "some.container.id",
49+
},
50+
{
51+
configValue: "default",
52+
expectedDimKey: "container_name",
53+
expectedDimValue: "some.container.name",
54+
},
55+
{
56+
configValue: "invalid",
57+
expectedError: "unsupported `dimensionToUpdate` \"invalid\". Must be one of \"container_name\" or \"container_id\"",
58+
},
59+
} {
60+
test := test
61+
t.Run(test.configValue, func(t *testing.T) {
62+
cfg := &Config{
63+
MetadataEndpoint: "http://localhost:0/not/real",
64+
MonitorConfig: config.MonitorConfig{
65+
IntervalSeconds: 1000,
66+
},
67+
}
68+
if test.configValue != "default" {
69+
cfg.DimensionToUpdate = test.configValue
70+
}
71+
require.NoError(t, defaults.Set(cfg))
72+
monitor := &Monitor{
73+
Output: neotest.NewTestOutput(),
74+
}
75+
76+
err := monitor.Configure(cfg)
77+
t.Cleanup(func() {
78+
if monitor.cancel != nil {
79+
monitor.cancel()
80+
}
81+
})
82+
if test.expectedError != "" {
83+
require.EqualError(t, err, test.expectedError)
84+
return
85+
}
86+
require.NoError(t, err)
87+
key, value := monitor.dimensionToUpdate(ctr)
88+
assert.Equal(t, test.expectedDimKey, key)
89+
assert.Equal(t, test.expectedDimValue, value)
90+
})
91+
}
92+
}

internal/signalfx-agent/pkg/neotest/output.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/signalfx/golib/v3/datapoint"
77
"github.com/signalfx/golib/v3/event"
88
"github.com/signalfx/golib/v3/trace"
9+
910
"github.com/signalfx/signalfx-agent/pkg/core/dpfilters"
1011
"github.com/signalfx/signalfx-agent/pkg/monitors/types"
1112
)
@@ -19,6 +20,11 @@ type TestOutput struct {
1920
dimChan chan *types.Dimension
2021
}
2122

23+
var _ types.Output = (*TestOutput)(nil)
24+
25+
// Currently just satisfying the interface (noop implementation)
26+
var _ types.FilteringOutput = (*TestOutput)(nil)
27+
2228
// NewTestOutput creates a new initialized TestOutput instance
2329
func NewTestOutput() *TestOutput {
2430
return &TestOutput{
@@ -167,3 +173,15 @@ loop:
167173
// AddDatapointExclusionFilter is a noop here.
168174
func (to *TestOutput) AddDatapointExclusionFilter(f dpfilters.DatapointFilter) {
169175
}
176+
177+
func (to *TestOutput) EnabledMetrics() []string {
178+
return nil
179+
}
180+
181+
func (to *TestOutput) HasEnabledMetricInGroup(group string) bool {
182+
return false
183+
}
184+
185+
func (to *TestOutput) HasAnyExtraMetrics() bool {
186+
return false
187+
}

0 commit comments

Comments
 (0)