Skip to content

Commit 609dc91

Browse files
smart agent: add extraDimension support (open-telemetry#69)
1 parent 9741e2c commit 609dc91

File tree

4 files changed

+74
-25
lines changed

4 files changed

+74
-25
lines changed

internal/receiver/smartagentreceiver/output.go

+26-13
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/signalfx/golib/v3/trace"
2424
"github.com/signalfx/signalfx-agent/pkg/core/dpfilters"
2525
"github.com/signalfx/signalfx-agent/pkg/monitors/types"
26+
"github.com/signalfx/signalfx-agent/pkg/utils"
2627
"go.opentelemetry.io/collector/consumer"
2728
"go.opentelemetry.io/collector/obsreport"
2829
"go.uber.org/zap"
@@ -34,20 +35,22 @@ const internalTransport = "internal"
3435
// It is what provides metrics to the next MetricsConsumer (to be implemented later). At this stage it is only
3536
// a logging instance.
3637
type Output struct {
37-
receiverName string
38-
nextConsumer consumer.MetricsConsumer
39-
logger *zap.Logger
40-
converter Converter
38+
receiverName string
39+
nextConsumer consumer.MetricsConsumer
40+
logger *zap.Logger
41+
converter Converter
42+
extraDimensions map[string]string
4143
}
4244

4345
var _ types.FilteringOutput = (*Output)(nil)
4446

4547
func NewOutput(config Config, nextConsumer consumer.MetricsConsumer, logger *zap.Logger) *Output {
4648
return &Output{
47-
receiverName: config.Name(),
48-
nextConsumer: nextConsumer,
49-
logger: logger,
50-
converter: Converter{logger: logger},
49+
receiverName: config.Name(),
50+
nextConsumer: nextConsumer,
51+
logger: logger,
52+
converter: Converter{logger: logger},
53+
extraDimensions: map[string]string{},
5154
}
5255
}
5356

@@ -70,15 +73,23 @@ func (output *Output) HasAnyExtraMetrics() bool {
7073
return false
7174
}
7275

76+
// Some monitors will clone their Output to provide to child monitors with their own extraDimensions
7377
func (output *Output) Copy() types.Output {
74-
output.logger.Debug("Copy has been called.")
75-
return output
78+
output.logger.Debug("Copying Output", zap.Any("output", output))
79+
cp := *output
80+
cp.extraDimensions = utils.CloneStringMap(output.extraDimensions)
81+
return &cp
7682
}
7783

7884
func (output *Output) SendDatapoints(datapoints ...*datapoint.Datapoint) {
7985
ctx := obsreport.ReceiverContext(context.Background(), output.receiverName, internalTransport)
8086
ctx = obsreport.StartMetricsReceiveOp(ctx, typeStr, internalTransport)
8187

88+
for _, dp := range datapoints {
89+
// Output's extraDimensions take priority over datapoint's
90+
dp.Dimensions = utils.MergeStringMaps(dp.Dimensions, output.extraDimensions)
91+
}
92+
8293
metrics, numDropped := output.converter.toMetrics(datapoints, time.Now())
8394
if numDropped > 0 {
8495
output.logger.Debug("SendDatapoints has dropped points", zap.Int("numDropped", numDropped))
@@ -98,15 +109,17 @@ func (output *Output) SendSpans(spans ...*trace.Span) {
98109
}
99110

100111
func (output *Output) SendDimensionUpdate(dimension *types.Dimension) {
101-
output.logger.Debug("SendDimensionUpdate has been called.", zap.Any("dimension", dimension))
112+
output.logger.Debug("SendDimensionUpdate has been called.", zap.Any("dimension", dimension.String()))
102113
}
103114

104115
func (output *Output) AddExtraDimension(key, value string) {
105-
output.logger.Debug("AddExtraDimension has been called.", zap.String("key", key), zap.String("value", value))
116+
output.logger.Debug("Adding extra dimension", zap.String("key", key), zap.String("value", value))
117+
output.extraDimensions[key] = value
106118
}
107119

108120
func (output *Output) RemoveExtraDimension(key string) {
109-
output.logger.Debug("RemoveExtraDimension has been called.", zap.String("key", key))
121+
output.logger.Debug("Removing extra dimension", zap.String("key", key))
122+
delete(output.extraDimensions, key)
110123
}
111124

112125
func (output *Output) AddExtraSpanTag(key, value string) {

internal/receiver/smartagentreceiver/output_test.go

+25-6
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,18 @@ import (
2121
"github.com/signalfx/signalfx-agent/pkg/core/dpfilters"
2222
"github.com/signalfx/signalfx-agent/pkg/monitors/types"
2323
"github.com/stretchr/testify/assert"
24+
"github.com/stretchr/testify/require"
2425
"go.opentelemetry.io/collector/consumer/consumertest"
2526
"go.uber.org/zap"
2627
)
2728

2829
func TestOutput(t *testing.T) {
29-
output := Output{
30-
nextConsumer: consumertest.NewMetricsNop(),
31-
logger: zap.NewNop(),
32-
}
33-
30+
output := NewOutput(Config{}, consumertest.NewMetricsNop(), zap.NewNop())
3431
output.AddDatapointExclusionFilter(dpfilters.DatapointFilter(nil))
3532
assert.Empty(t, output.EnabledMetrics())
3633
assert.False(t, output.HasEnabledMetricInGroup(""))
3734
assert.False(t, output.HasAnyExtraMetrics())
38-
assert.Same(t, &output, output.Copy())
35+
assert.NotSame(t, &output, output.Copy())
3936
output.SendDatapoints()
4037
output.SendEvent(new(event.Event))
4138
output.SendSpans()
@@ -47,3 +44,25 @@ func TestOutput(t *testing.T) {
4744
output.AddDefaultSpanTag("", "")
4845
output.RemoveDefaultSpanTag("")
4946
}
47+
48+
func TestExtraDimensions(t *testing.T) {
49+
output := NewOutput(Config{}, consumertest.NewMetricsNop(), zap.NewNop())
50+
assert.Empty(t, output.extraDimensions)
51+
52+
output.RemoveExtraDimension("not_a_known_dimension_name")
53+
54+
output.AddExtraDimension("a_dimension_name", "a_value")
55+
assert.Equal(t, "a_value", output.extraDimensions["a_dimension_name"])
56+
57+
cp, ok := output.Copy().(*Output)
58+
require.True(t, ok)
59+
assert.Equal(t, "a_value", cp.extraDimensions["a_dimension_name"])
60+
61+
cp.RemoveExtraDimension("a_dimension_name")
62+
assert.Empty(t, cp.extraDimensions["a_dimension_name"])
63+
assert.Equal(t, "a_value", output.extraDimensions["a_dimension_name"])
64+
65+
cp.AddExtraDimension("another_dimension_name", "another_dimension_value")
66+
assert.Equal(t, "another_dimension_value", cp.extraDimensions["another_dimension_name"])
67+
assert.Empty(t, output.extraDimensions["another_dimension_name"])
68+
}

internal/receiver/smartagentreceiver/receiver.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,17 @@ func (r *Receiver) createMonitor(monitorType string) (interface{}, error) {
114114
}
115115
}
116116

117+
var output *Output
117118
if monitorOutputValue.IsValid() {
118-
output := NewOutput(*r.config, r.nextConsumer, r.logger)
119+
output = NewOutput(*r.config, r.nextConsumer, r.logger)
119120
monitorOutputValue.Set(reflect.ValueOf(output))
120121
} else {
121122
return nil, fmt.Errorf("invalid monitor instance: %#v", monitor)
122123
}
123124

125+
for k, v := range r.config.monitorConfig.MonitorConfigCore().ExtraDimensions {
126+
output.AddExtraDimension(k, v)
127+
}
128+
124129
return monitor, nil
125130
}

internal/receiver/smartagentreceiver/receiver_test.go

+17-5
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ func newConfig(nameVal, monitorType string, intervalSeconds int) Config {
5757
MonitorConfig: config.MonitorConfig{
5858
Type: monitorType,
5959
IntervalSeconds: intervalSeconds,
60+
ExtraDimensions: map[string]string{
61+
"required_dimension": "required_value",
62+
},
6063
},
6164
ReportPerCPU: true,
6265
},
@@ -67,12 +70,15 @@ func TestSmartAgentReceiver(t *testing.T) {
6770
cfg := newConfig("valid", "cpu", 10)
6871
consumer := new(consumertest.MetricsSink)
6972
receiver := NewReceiver(zap.NewNop(), cfg, consumer)
73+
7074
err := receiver.Start(context.Background(), componenttest.NewNopHost())
7175
require.NoError(t, err)
7276

7377
assert.EqualValues(t, "smartagentvalid", cfg.monitorConfig.MonitorConfigCore().MonitorID)
78+
monitor, isMonitor := receiver.monitor.(*cpu.Monitor)
79+
require.True(t, isMonitor)
7480

75-
monitorOutput := receiver.monitor.(*cpu.Monitor).Output
81+
monitorOutput := monitor.Output
7682
_, isOutput := monitorOutput.(*Output)
7783
assert.True(t, isOutput)
7884

@@ -129,10 +135,16 @@ func TestSmartAgentReceiver(t *testing.T) {
129135
default:
130136
t.Errorf("unexpected type %#v for metric %s", metric.DataType(), name)
131137
}
132-
if labels.Len() == 0 {
133-
assert.False(t, seenTotalMetric[name], "unexpected repeated total metric for %v", name)
134-
seenTotalMetric[name] = true
135-
}
138+
139+
labelVal, ok := labels.Get("required_dimension")
140+
require.True(t, ok)
141+
assert.Equal(t, "required_value", labelVal)
142+
143+
// mark metric as having been seen
144+
cpuNum, _ := labels.Get("cpu")
145+
seenName := fmt.Sprintf("%s%s", name, cpuNum)
146+
assert.False(t, seenTotalMetric[seenName], "unexpectedly repeated metric: %v", seenName)
147+
seenTotalMetric[seenName] = true
136148
}
137149
}
138150
}

0 commit comments

Comments
 (0)