Skip to content

Commit adc00a6

Browse files
authored
Add SPLUNK_MEMORY_TOTAL_MIB (open-telemetry#48)
Today, SPLUNK_BALLAST_SIZE_MIB needs to be computed and manually configured by a user. Most users do not know or care about the ballast and under normal circumstances they should not need to. Users will understand the total amount of memory allocated to the Collector. As such, introduce a SPLUNK_MEMORY_TOTAL_MIB env var that can be used instead of or in addition to SPLUNK_BALLAST_SIZE_MIB. This has the added bonus that now memory spike and limit env vars are no longer required for non-linux systems. This change is backwards compatible. A few other minor enhancements: - Memory spike/limit env vars are now applied even if SPLUNK_CONFIG is passed - Add logging for custom behavior so configuration is clear - Add accessible endpoints to README
1 parent b8f9aa8 commit adc00a6

File tree

3 files changed

+281
-29
lines changed

3 files changed

+281
-29
lines changed

README.md

+31-15
Original file line numberDiff line numberDiff line change
@@ -41,19 +41,35 @@ which require the following environment variables:
4141

4242
- `SPLUNK_REALM` (no default): Which realm to send the data to (for example: `us0`)
4343
- `SPLUNK_ACCESS_TOKEN` (no default): Access token to authenticate requests
44-
- `SPLUNK_BALLAST_SIZE_MIB` (no default): How much memory to allocate to the ballast. This should be set to 1/3 to 1/2 of configured memory.
44+
- `SPLUNK_MEMORY_TOTAL_MIB` (no default): Total memory allocated to the Collector.
4545

46-
In addition, the following environment variables are optional:
46+
The following environment variables are optional:
4747

4848
- `SPLUNK_CONFIG` (default = `/etc/otel/collector/splunk-config_linux.yaml`): Which configuration to load.
49-
- `SPLUNK_MEMORY_LIMIT_PERCENTAGE` (default = `90`): Maximum total memory to be allocated by the process heap.
50-
- `SPLUNK_MEMORY_SPIKE_PERCENTAGE` (default = `20`): Maximum spike between the measurements of memory usage.
51-
52-
When running on a non-linux system, the following environment variables are required:
53-
54-
- `SPLUNK_CONFIG` (default = `/etc/otel/collector/splunk-config_non_linux.yaml`): Configuration to load.
55-
- `SPLUNK_MEMORY_LIMIT_MIB` (no default): Maximum total memory to be allocated by the process heap.
56-
- `SPLUNK_MEMORY_SPIKE_MIB` (no default): Maximum spike between the measurements of memory usage.
49+
- `SPLUNK_BALLAST_SIZE_MIB` (no default): How much memory to allocate to the ballast.
50+
- For Linux systems:
51+
- `SPLUNK_MEMORY_LIMIT_PERCENTAGE` (default = `90`): Maximum total memory to be allocated by the process heap.
52+
- `SPLUNK_MEMORY_SPIKE_PERCENTAGE` (default = `20`): Maximum spike between the measurements of memory usage.
53+
- For non-Linux systems:
54+
- `SPLUNK_MEMORY_LIMIT_MIB` (no default): Maximum total memory to be allocated by the process heap.
55+
- `SPLUNK_MEMORY_SPIKE_MIB` (no default): Maximum spike between the measurements of memory usage.
56+
57+
> `SPLUNK_MEMORY_TOTAL_MIB` automatically configures the ballast, memory limit,
58+
> and memory spike. If the optional environment variables are defined, they
59+
> will override the value calculated from `SPLUNK_MEMORY_TOTAL_MIB`.
60+
61+
With the Collector configured, the following endpoints are accessible:
62+
63+
- `http(s)://<collectorFQDN>:13133/` Health endpoint useful for load balancer monitoring
64+
- `http(s)://<collectorFQDN>:[14250|14268]` Jaeger [gRPC|Thrift HTTP] receiver
65+
- `http(s)://<collectorFQDN>:55678` OpenCensus gRPC and HTTP receiver
66+
- `http(s)://localhost:55679/debug/[tracez|pipelinez]` zPages monitoring
67+
- `http(s)://<collectorFQDN>:55680` OpenTelemetry gRPC receiver
68+
- `http(s)://<collectorFQDN>:6060` HTTP Forwarder used to receive Smart Agent `apiUrl` data
69+
- `http(s)://<collectorFQDN>:7276` SignalFx Infrastructure Monitoring gRPC receiver
70+
- `http(s)://localhost:8888/metrics` Prometheus metrics for the Collector
71+
- `http(s)://<collectorFQDN>:9411/api/[v1|v2]/spans` Zipkin JSON (can be set to proto)receiver
72+
- `http(s)://<collectorFQDN>:9943/v2/trace` SignalFx APM receiver
5773

5874
The following sections describe how to deploy the Collector in supported environments.
5975

@@ -62,7 +78,7 @@ The following sections describe how to deploy the Collector in supported environ
6278
Deploy from a Docker container. Replace `0.1.0` with the latest stable version number:
6379

6480
```bash
65-
$ docker run --rm -e SPLUNK_ACCESS_TOKEN=12345 -e SPLUNK_BALLAST_SIZE_MIB=683 \
81+
$ docker run --rm -e SPLUNK_ACCESS_TOKEN=12345 -e SPLUNK_MEMORY_TOTAL_MIB=1024 \
6682
-e SPLUNK_REALM=us0 -p 13133:13133 -p 14250:14250 -p 14268:14268 -p 55678-55680:55678-55680 \
6783
-p 6060:6060 -p 7276:7276 -p 8888:8888 -p 9411:9411 -p 9943:9943 \
6884
--name otelcol quay.io/signalfx/splunk-otel-collector:0.1.0
@@ -80,7 +96,7 @@ file on GitHub.
8096

8197
```bash
8298
$ make otelcol
83-
$ SPLUNK_REALM=us0 SPLUNK_ACCESS_TOKEN=12345 SPLUNK_BALLAST_SIZE_MIB=683 \
99+
$ SPLUNK_REALM=us0 SPLUNK_ACCESS_TOKEN=12345 SPLUNK_MEMORY_TOTAL_MIB=1024 \
84100
./bin/otelcol
85101
```
86102

@@ -197,7 +213,7 @@ least a CPU core per Collector. Multiple Collectors can deployed behind a
197213
simple round-robin load balancer. Each Collector runs independently, so scale
198214
increases linearly with the number of Collectors you deploy.
199215

200-
The Collector does not persist data to disk so no disk space is required.
216+
> The Collector does not persist data to disk so no disk space is required.
201217
202218
## Advanced Configuration
203219

@@ -209,7 +225,7 @@ specified. Command line arguments take priority over environment variables.
209225
For example in Docker:
210226

211227
```bash
212-
$ docker run --rm -e SPLUNK_ACCESS_TOKEN=12345 -e SPLUNK_BALLAST_SIZE_MIB=683 \
228+
$ docker run --rm -e SPLUNK_ACCESS_TOKEN=12345 -e SPLUNK_MEMORY_TOTAL_MIB=1024 \
213229
-e SPLUNK_REALM=us0 -p 13133:13133 -p 14250:14250 -p 14268:14268 -p 55678-55680:55678-55680 \
214230
-p 6060:6060 -p 7276:7276 -p 8888:8888 -p 9411:9411 -p 9943:9943 \
215231
--name otelcol quay.io/signalfx/splunk-otel-collector:0.1.0 \
@@ -230,7 +246,7 @@ the `--config` command line argument to provide a custom configuration.
230246
For example in Docker:
231247

232248
```bash
233-
$ docker run --rm -e SPLUNK_ACCESS_TOKEN=12345 -e SPLUNK_BALLAST_SIZE_MIB=683 \
249+
$ docker run --rm -e SPLUNK_ACCESS_TOKEN=12345 -e SPLUNK_MEMORY_TOTAL_MIB=1024 \
234250
-e SPLUNK_REALM=us0 -e SPLUNK_CONFIG=/etc/collector.yaml -p 13133:13133 -p 14250:14250 \
235251
-p 14268:14268 -p 55678-55680:55678-55680 -p 6060:6060 -p 7276:7276 -p 8888:8888 \
236252
-p 9411:9411 -p 9943:9943 -v collector.yaml:/etc/collector.yaml:ro \

cmd/otelcol/main.go

+94-14
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ const (
3939
memLimitMiBEnvVarName = "SPLUNK_MEMORY_LIMIT_MIB"
4040
memSpikeEnvVarName = "SPLUNK_MEMORY_SPIKE_PERCENTAGE"
4141
memSpikeMiBEnvVarName = "SPLUNK_MEMORY_SPIKE_MIB"
42+
memTotalEnvVarName = "SPLUNK_MEMORY_TOTAL_MIB"
4243
realmEnvVarName = "SPLUNK_REALM"
4344
tokenEnvVarName = "SPLUNK_ACCESS_TOKEN"
4445

@@ -50,22 +51,50 @@ const (
5051
defaultLocalSAPMNonLinuxConfig = "cmd/otelcol/config/collector/splunk_config_non_linux.yaml"
5152
defaultLocalOTLPLinuxConfig = "cmd/otelcol/config/collector/otlp_config_linux.yaml"
5253
defaultLocalOTLPNonLinuxConfig = "cmd/otelcol/config/collector/otlp_config_non_linux.yaml"
54+
defaultMemoryBallastPercentage = 50
5355
defaultMemoryLimitPercentage = 90
56+
defaultMemoryLimitMaxMiB = 2048
5457
defaultMemorySpikePercentage = 25
58+
defaultMemorySpikeMaxMiB = 2048
5559
)
5660

5761
func main() {
62+
// TODO: Use same format as the collector
5863
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
5964

65+
// Check if the total memory is specified via the env var.
66+
memTotalEnvVarVal := os.Getenv(memTotalEnvVarName)
67+
memTotalSizeMiB := 0
68+
if memTotalEnvVarVal != "" {
69+
// Check if it is a numeric value.
70+
val, err := strconv.Atoi(memTotalEnvVarVal)
71+
if err != nil {
72+
log.Fatalf("Expected a number in %s env variable but got %s", memTotalEnvVarName, memTotalEnvVarVal)
73+
}
74+
if 10 > val {
75+
log.Fatalf("Expected a number greater than 10 for %s env variable but got %s", memTotalEnvVarName, memTotalEnvVarVal)
76+
}
77+
memTotalSizeMiB = val
78+
}
79+
6080
// Check runtime parameters
6181
// Runtime parameters take priority over environment variables
6282
// Runtime parameters are not validated
6383
args := os.Args[1:]
6484
if !contains(args, "--mem-ballast-size-mib") {
65-
useBallastSizeFromEnvVar()
85+
useMemorySizeFromEnvVar(memTotalSizeMiB)
86+
} else {
87+
log.Printf("Ballast CLI argument found, ignoring %s if set", ballastEnvVarName)
6688
}
6789
if !contains(args, "--config") {
6890
useConfigFromEnvVar()
91+
} else {
92+
log.Printf("Config CLI argument found, please ensure memory_limiter settings are correct")
93+
}
94+
if runtime.GOOS == "linux" {
95+
useMemorySettingsPercentageFromEnvVar()
96+
} else {
97+
useMemorySettingsMiBFromEnvVar(memTotalSizeMiB)
6998
}
7099

71100
factories, err := components.Get()
@@ -98,18 +127,27 @@ func contains(arr []string, str string) bool {
98127
return false
99128
}
100129

101-
func useBallastSizeFromEnvVar() {
130+
func useMemorySizeFromEnvVar(memTotalSizeMiB int) {
102131
// Check if the ballast is specified via the env var.
103132
ballastSize := os.Getenv(ballastEnvVarName)
104133
if ballastSize != "" {
105134
// Check if it is a numeric value.
106-
_, err := strconv.Atoi(ballastSize)
135+
val, err := strconv.Atoi(ballastSize)
107136
if err != nil {
108137
log.Fatalf("Expected a number in %s env variable but got %s", ballastEnvVarName, ballastSize)
109138
}
139+
if 0 > val {
140+
log.Fatalf("Expected a number greater than 0 for %s env variable but got %s", ballastEnvVarName, ballastSize)
141+
}
110142

111143
// Inject the command line flag that controls the ballast size.
112144
os.Args = append(os.Args, "--mem-ballast-size-mib="+ballastSize)
145+
} else if memTotalSizeMiB > 0 {
146+
halfMem := strconv.Itoa(memTotalSizeMiB * defaultMemoryBallastPercentage / 100)
147+
log.Printf("Set ballast to %s MiB", halfMem)
148+
// Inject the command line flag that controls the ballast size.
149+
os.Args = append(os.Args, "--mem-ballast-size-mib="+halfMem)
150+
os.Setenv(ballastEnvVarName, halfMem)
113151
}
114152
}
115153

@@ -130,7 +168,6 @@ func useConfigFromEnvVar() {
130168
if config == "" {
131169
log.Fatalf("Unable to find the default configuration file, ensure %s environment variable is set properly", configEnvVarName)
132170
}
133-
useMemorySettingsPercentageFromEnvVar()
134171
} else {
135172
_, err := os.Stat(defaultDockerSAPMNonLinuxConfig)
136173
if err == nil {
@@ -143,7 +180,6 @@ func useConfigFromEnvVar() {
143180
if config == "" {
144181
log.Fatalf("Unable to find the default configuration file, ensure %s environment variable is set properly", configEnvVarName)
145182
}
146-
useMemorySettingsMiBFromEnvVar()
147183
}
148184
} else {
149185
// Check if file exists.
@@ -165,25 +201,31 @@ func useConfigFromEnvVar() {
165201
defaultLocalOTLPNonLinuxConfig:
166202
// The following environment variables are required.
167203
// If any are missing stop here.
168-
requiredEnvVars := []string{ballastEnvVarName, realmEnvVarName, tokenEnvVarName}
204+
requiredEnvVars := []string{realmEnvVarName, tokenEnvVarName}
169205
for _, v := range requiredEnvVars {
170206
if len(os.Getenv(v)) == 0 {
171-
log.Printf("Usage: %s=12345 %s=us0 %s=684 %s", tokenEnvVarName, realmEnvVarName, ballastEnvVarName, os.Args[0])
207+
log.Printf("Usage: %s=12345 %s=us0 %s=1024 %s", tokenEnvVarName, realmEnvVarName, memTotalEnvVarName, os.Args[0])
172208
log.Fatalf("ERROR: Missing environment variable %s", v)
173209
}
174210
}
211+
// Needed for backwards compatibility
212+
if len(os.Getenv(memTotalEnvVarName)) == 0 && len(os.Getenv(ballastEnvVarName)) == 0 {
213+
log.Printf("Usage: %s=12345 %s=us0 %s=1024 %s", tokenEnvVarName, realmEnvVarName, memTotalEnvVarName, os.Args[0])
214+
log.Fatalf("ERROR: Missing environment variable %s", memTotalEnvVarName)
215+
}
175216
}
176217

177218
// Inject the command line flag that controls the configuration.
178219
os.Args = append(os.Args, "--config="+config)
179220
}
180221

181-
func checkMemorySettingsMiBFromEnvVar(envVar string) int {
222+
func checkMemorySettingsMiBFromEnvVar(envVar string, memTotalSizeMiB int) int {
182223
// Check if the memory limit is specified via the env var
183224
// Ensure memory limit is valid
184-
var envVarResult int
225+
var envVarResult int = 0
185226
envVarVal := os.Getenv(envVar)
186-
if envVarVal != "" {
227+
switch {
228+
case envVarVal != "":
187229
// Check if it is a numeric value.
188230
val, err := strconv.Atoi(envVarVal)
189231
if err != nil {
@@ -193,16 +235,54 @@ func checkMemorySettingsMiBFromEnvVar(envVar string) int {
193235
log.Fatalf("Expected a number greater than 0 for %s env variable but got %s", envVar, envVarVal)
194236
}
195237
envVarResult = val
196-
} else {
238+
case memTotalSizeMiB > 0:
239+
break
240+
default:
197241
log.Printf("Usage: %s=12345 %s=us0 %s=684 %s=1024 %s=256 %s", tokenEnvVarName, realmEnvVarName, ballastEnvVarName, memLimitMiBEnvVarName, memSpikeMiBEnvVarName, os.Args[0])
198242
log.Fatalf("ERROR: Missing environment variable %s", envVar)
199243
}
200244
return envVarResult
201245
}
202246

203-
func useMemorySettingsMiBFromEnvVar() {
204-
memLimit := checkMemorySettingsMiBFromEnvVar(memLimitMiBEnvVarName)
205-
memSpike := checkMemorySettingsMiBFromEnvVar(memSpikeMiBEnvVarName)
247+
func useMemorySettingsMiBFromEnvVar(memTotalSizeMiB int) {
248+
// Check if memory limit is specified via environment variable
249+
memLimit := checkMemorySettingsMiBFromEnvVar(memLimitMiBEnvVarName, memTotalSizeMiB)
250+
// Use if set, otherwise memory total size must be specified
251+
if memLimit == 0 {
252+
if memTotalSizeMiB == 0 {
253+
panic("PANIC: Both memory limit MiB and memory total size are set to zero. This should never happen.")
254+
}
255+
// If not set, compute based on memory total size specified
256+
// and default memory limit percentage const
257+
memLimitMiB := memTotalSizeMiB * defaultMemoryLimitPercentage / 100
258+
// The memory limit should be set to defaultMemoryLimitPercentage of total memory
259+
// while reserving a maximum of defaultMemoryLimitMaxMiB of memory.
260+
if (memTotalSizeMiB - memLimitMiB) < defaultMemoryLimitMaxMiB {
261+
memLimit = memLimitMiB
262+
} else {
263+
memLimit = (memTotalSizeMiB - defaultMemoryLimitMaxMiB)
264+
}
265+
log.Printf("Set memory limit to %d MiB", memLimit)
266+
}
267+
// Check if memory spike is specified via environment variable
268+
memSpike := checkMemorySettingsMiBFromEnvVar(memSpikeMiBEnvVarName, memTotalSizeMiB)
269+
// Use if set, otherwise memory total size must be specified
270+
if memSpike == 0 {
271+
if memTotalSizeMiB == 0 {
272+
panic("PANIC: Both memory limit MiB and memory total size are set to zero. This should never happen.")
273+
}
274+
// If not set, compute based on memory total size specified
275+
// and default memory spike percentage const
276+
memSpikeMiB := memTotalSizeMiB * defaultMemorySpikePercentage / 100
277+
// The memory spike should be set to defaultMemorySpikePercentage of total memory
278+
// while specifying a maximum of defaultMemorySpikeMaxMiB of memory.
279+
if memSpikeMiB < defaultMemorySpikeMaxMiB {
280+
memSpike = memSpikeMiB
281+
} else {
282+
memSpike = defaultMemorySpikeMaxMiB
283+
}
284+
log.Printf("Set memory spike limit to %d MiB", memSpike)
285+
}
206286
setMemorySettingsToEnvVar(memLimit, memLimitMiBEnvVarName, memSpike, memSpikeMiBEnvVarName)
207287
}
208288

0 commit comments

Comments
 (0)