From 957ecc2f30b3b33b1a7c987d10d21191affc4c74 Mon Sep 17 00:00:00 2001 From: Giovanni Liva Date: Tue, 14 Apr 2020 07:43:37 +0200 Subject: [PATCH] Implement Configuration for BatchSpanProcessor --- .../sdk/common/ConfigBuilder.java | 52 ---- .../sdk/trace/export/BatchSpansProcessor.java | 234 ++++++++++++------ .../sdk/trace/export/package-info.java | 40 +++ .../trace/export/BatchSpansProcessorTest.java | 112 +++++++++ 4 files changed, 317 insertions(+), 121 deletions(-) delete mode 100644 sdk/src/main/java/io/opentelemetry/sdk/common/ConfigBuilder.java create mode 100644 sdk/src/main/java/io/opentelemetry/sdk/trace/export/package-info.java diff --git a/sdk/src/main/java/io/opentelemetry/sdk/common/ConfigBuilder.java b/sdk/src/main/java/io/opentelemetry/sdk/common/ConfigBuilder.java deleted file mode 100644 index 2a67d981a02..00000000000 --- a/sdk/src/main/java/io/opentelemetry/sdk/common/ConfigBuilder.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2020, OpenTelemetry Authors - * - * 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 io.opentelemetry.sdk.common; - -import java.util.Map; - -public abstract class ConfigBuilder { - - protected ConfigBuilder() {} - - public abstract ConfigBuilder fromConfigMap(Map configMap); - - public abstract ConfigBuilder fromEnv(); - - protected boolean getProperty(String name, Map map, boolean defaultValue) { - try { - return Boolean.parseBoolean(System.getProperty(name, map.get(name))); - } catch (NumberFormatException ex) { - return defaultValue; - } - } - - protected int getProperty(String name, Map map, int defaultValue) { - try { - return Integer.parseInt(System.getProperty(name, map.get(name))); - } catch (NumberFormatException ex) { - return defaultValue; - } - } - - protected long getProperty(String name, Map map, long defaultValue) { - try { - return Long.parseLong(System.getProperty(name, map.get(name))); - } catch (NumberFormatException ex) { - return defaultValue; - } - } -} diff --git a/sdk/src/main/java/io/opentelemetry/sdk/trace/export/BatchSpansProcessor.java b/sdk/src/main/java/io/opentelemetry/sdk/trace/export/BatchSpansProcessor.java index 356f14ce8d6..e5eb9003481 100644 --- a/sdk/src/main/java/io/opentelemetry/sdk/trace/export/BatchSpansProcessor.java +++ b/sdk/src/main/java/io/opentelemetry/sdk/trace/export/BatchSpansProcessor.java @@ -16,18 +16,20 @@ package io.opentelemetry.sdk.trace.export; +import com.google.auto.value.AutoValue; +import com.google.common.collect.Maps; import com.google.common.util.concurrent.MoreExecutors; import io.opentelemetry.OpenTelemetry; import io.opentelemetry.internal.Utils; import io.opentelemetry.metrics.LongCounter; import io.opentelemetry.metrics.LongCounter.BoundLongCounter; import io.opentelemetry.metrics.Meter; -import io.opentelemetry.sdk.common.ConfigBuilder; import io.opentelemetry.sdk.trace.ReadableSpan; import io.opentelemetry.sdk.trace.SpanProcessor; import io.opentelemetry.sdk.trace.data.SpanData; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; @@ -39,6 +41,7 @@ import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.Logger; +import javax.annotation.Nonnull; import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.Immutable; @@ -106,11 +109,11 @@ public static BatchSpansProcessor create(SpanExporter spanExporter) { public static BatchSpansProcessor create(SpanExporter spanExporter, Config config) { return new BatchSpansProcessor( spanExporter, - config.sampled, - config.scheduleDelayMillis, - config.maxQueueSize, - config.maxExportBatchSize, - config.exporterTimeoutMillis); + config.isReportOnlySampled(), + config.getScheduleDelayMillis(), + config.getMaxQueueSize(), + config.getMaxExportBatchSize(), + config.getExporterTimeoutMillis()); } @Override @@ -323,101 +326,165 @@ public void run() { } @Immutable - public static final class Config { - private final long scheduleDelayMillis; - private final int maxQueueSize; - private final int maxExportBatchSize; - private final int exporterTimeoutMillis; - private final boolean sampled; + @AutoValue + public abstract static class Config { - private Config( - boolean sampled, - long scheduleDelayMillis, - int maxQueueSize, - int maxExportBatchSize, - int exporterTimeoutMillis) { - this.sampled = sampled; - this.scheduleDelayMillis = scheduleDelayMillis; - this.maxQueueSize = maxQueueSize; - this.maxExportBatchSize = maxExportBatchSize; - this.exporterTimeoutMillis = exporterTimeoutMillis; - } + public abstract boolean isReportOnlySampled(); - public long getScheduleDelayMillis() { - return scheduleDelayMillis; - } - - public int getMaxQueueSize() { - return maxQueueSize; - } + public abstract long getScheduleDelayMillis(); - public int getMaxExportBatchSize() { - return maxExportBatchSize; - } + public abstract int getMaxQueueSize(); - public int getExporterTimeoutMillis() { - return exporterTimeoutMillis; - } + public abstract int getMaxExportBatchSize(); - public boolean isReportOnlySampled() { - return sampled; - } + public abstract int getExporterTimeoutMillis(); public static Config getDefault() { return new Builder().build(); } + /** + * Creates a {@link Config} object reading the configuration values first from the environment + * and then from system properties. If a configuration value is missing, it uses the default + * value. + * + * @return The {@link Config} object. + */ + public static Config loadFromDefaultSources() { + return new Builder().readEnv().readSystemProperties().build(); + } + public static Builder newBuilder() { return new Builder(); } - public static class Builder extends ConfigBuilder { + public static final class Builder { + + private static final String KEY_SCHEDULE_DELAY_MILLIS = "otel.bsp.schedule.delay"; + private static final String KEY_MAX_QUEUE_SIZE = "otel.bsp.max.queue"; + private static final String KEY_MAX_EXPORT_BATCH_SIZE = "otel.bsp.max.export.batch"; + private static final String KEY_EXPORT_TIMEOUT_MILLIS = "otel.bsp.export.timeout"; + private static final String KEY_SAMPLED = "otel.bsp.report.sampled"; + + private static final long DEFAULT_SCHEDULE_DELAY_MILLIS = 5000; + private static final int DEFAULT_MAX_QUEUE_SIZE = 2048; + private static final int DEFAULT_MAX_EXPORT_BATCH_SIZE = 512; + private static final int DEFAULT_EXPORT_TIMEOUT_MILLIS = 30_000; + private static final boolean DEFAULT_REPORT_ONLY_SAMPLED = true; + + private long scheduleDelayMillis = DEFAULT_SCHEDULE_DELAY_MILLIS; + private int maxQueueSize = DEFAULT_MAX_QUEUE_SIZE; + private int maxExportBatchSize = DEFAULT_MAX_EXPORT_BATCH_SIZE; + private int exporterTimeoutMillis = DEFAULT_EXPORT_TIMEOUT_MILLIS; + private boolean sampled = DEFAULT_REPORT_ONLY_SAMPLED; + + enum NamingConvention { + DOT { + @Override + protected char getSeparator() { + return '.'; + } + + @Nonnull + @Override + public String normalize(@Nonnull String key) { + return key.toLowerCase(); + } + }, + ENV_VAR { + @Override + protected char getSeparator() { + return '_'; + } + }; - private static final String KEY_SCHEDULE_DELAY_MILLIS = "OTEL_BSP_SCHEDULE_DELAY"; - private static final String KEY_MAX_QUEUE_SIZE = "OTEL_BSP_MAX_QUEUE"; - private static final String KEY_MAX_EXPORT_BATCH_SIZE = "OTEL_BSP_MAX_EXPORT_BATCH"; - private static final String KEY_EXPORT_TIMEOUT_MILLIS = "OTEL_BSP_EXPORT_TIMEOUT"; - private static final String KEY_SAMPLED = "OTEL_BSP_REPORT_SAMPLED"; + @Nonnull + public String normalize(@Nonnull String key) { + return key.toLowerCase().replace(getSeparator(), DOT.getSeparator()); + } - private static final long SCHEDULE_DELAY_MILLIS = 5000; - private static final int MAX_QUEUE_SIZE = 2048; - private static final int MAX_EXPORT_BATCH_SIZE = 512; - private static final int EXPORT_TIMEOUT_MILLIS = 30_000; - private static final boolean REPORT_ONLY_SAMPLED = true; + public Map normalize(@Nonnull Map map) { + Map properties = new HashMap<>(); + for (String key : map.keySet()) { + properties.put(normalize(key), map.get(key)); + } + return Collections.unmodifiableMap(properties); + } - private long scheduleDelayMillis = SCHEDULE_DELAY_MILLIS; - private int maxQueueSize = MAX_QUEUE_SIZE; - private int maxExportBatchSize = MAX_EXPORT_BATCH_SIZE; - private int exporterTimeoutMillis = EXPORT_TIMEOUT_MILLIS; - private boolean sampled = REPORT_ONLY_SAMPLED; + protected abstract char getSeparator(); + } /** - * Sets the configuration values from the given configuration map. + * Sets the configuration values from the given configuration map. This method looks for the + * following keys: + * + *
    + *
  • {@code otel.bsp.schedule.delay}: to set the delay interval between two consecutive + * exports. + *
  • {@code otel.bsp.max.queue}: to set the maximum queue size. + *
  • {@code otel.bsp.max.export.batch}: to set the maximum batch size. + *
  • {@code otel.bsp.export.timeout}: to set the maximum allowed time to export data. + *
  • {@code otel.bsp.report.sampled}: to set whether only sampled spans should be + * reported. + *
* * @param configMap {@link Map} holding the configuration values. * @return this. */ - @Override - public Builder fromConfigMap(Map configMap) { + public Builder fromConfigMap( + Map configMap, NamingConvention namingConvention) { + configMap = namingConvention.normalize(configMap); this.setScheduleDelayMillis( - getProperty(KEY_SCHEDULE_DELAY_MILLIS, configMap, SCHEDULE_DELAY_MILLIS)); - this.setMaxQueueSize(getProperty(KEY_MAX_QUEUE_SIZE, configMap, MAX_QUEUE_SIZE)); + getProperty(KEY_SCHEDULE_DELAY_MILLIS, configMap, this.scheduleDelayMillis)); + this.setMaxQueueSize( + getProperty( + namingConvention.normalize(KEY_MAX_QUEUE_SIZE), configMap, this.maxQueueSize)); this.setMaxExportBatchSize( - getProperty(KEY_MAX_EXPORT_BATCH_SIZE, configMap, MAX_EXPORT_BATCH_SIZE)); + getProperty(KEY_MAX_EXPORT_BATCH_SIZE, configMap, this.maxExportBatchSize)); this.setExporterTimeoutMillis( - getProperty(KEY_EXPORT_TIMEOUT_MILLIS, configMap, EXPORT_TIMEOUT_MILLIS)); - this.reportOnlySampled(getProperty(KEY_SAMPLED, configMap, REPORT_ONLY_SAMPLED)); + getProperty(KEY_EXPORT_TIMEOUT_MILLIS, configMap, this.exporterTimeoutMillis)); + this.reportOnlySampled(getProperty(KEY_SAMPLED, configMap, this.sampled)); return this; } /** - * Sets the configuration values from environment variables. + * Sets the configuration values from environment variables. This method looks for the + * following keys: + * + *
    + *
  • {@code OTEL_BSP_SCHEDULE_DELAY}: to set the delay interval between two consecutive + * exports. + *
  • {@code OTEL_BSP_MAX_QUEUE}: to set the maximum queue size. + *
  • {@code OTEL_BSP_MAX_EXPORT_BATCH}: to set the maximum batch size. + *
  • {@code OTEL_BSP_EXPORT_TIMEOUT}: to set the maximum allowed time to export data. + *
  • {@code OTEL_BSP_REPORT_SAMPLED}: to set whether only sampled spans should be + * reported. + *
+ * + * @return this. + */ + public Builder readEnv() { + return fromConfigMap(System.getenv(), NamingConvention.ENV_VAR); + } + + /** + * Sets the configuration values from system properties. This method looks for the + * following keys: + * + *
    + *
  • {@code otel.bsp.schedule.delay}: to set the delay interval between two consecutive + * exports. + *
  • {@code otel.bsp.max.queue}: to set the maximum queue size. + *
  • {@code otel.bsp.max.export.batch}: to set the maximum batch size. + *
  • {@code otel.bsp.export.timeout}: to set the maximum allowed time to export data. + *
  • {@code otel.bsp.report.sampled}: to set whether only sampled spans should be + * reported. + *
* * @return this. */ - @Override - public Builder fromEnv() { - return fromConfigMap(System.getenv()); + public Builder readSystemProperties() { + return fromConfigMap(Maps.fromProperties(System.getProperties()), NamingConvention.DOT); } /** @@ -495,8 +562,37 @@ public Builder setMaxExportBatchSize(int maxExportBatchSize) { return this; } + private static boolean getProperty( + String name, Map map, boolean defaultValue) { + if (map.containsKey(name)) { + return Boolean.parseBoolean(map.get(name)); + } + return defaultValue; + } + + private static int getProperty(String name, Map map, int defaultValue) { + try { + return Integer.parseInt(map.get(name)); + } catch (NumberFormatException ex) { + return defaultValue; + } + } + + private static long getProperty(String name, Map map, long defaultValue) { + try { + return Long.parseLong(map.get(name)); + } catch (NumberFormatException ex) { + return defaultValue; + } + } + + /** + * Builds the {@link Config} object. + * + * @return the {@link Config} object. + */ public Config build() { - return new Config( + return new AutoValue_BatchSpansProcessor_Config( sampled, scheduleDelayMillis, maxQueueSize, maxExportBatchSize, exporterTimeoutMillis); } } diff --git a/sdk/src/main/java/io/opentelemetry/sdk/trace/export/package-info.java b/sdk/src/main/java/io/opentelemetry/sdk/trace/export/package-info.java new file mode 100644 index 00000000000..49fe3116196 --- /dev/null +++ b/sdk/src/main/java/io/opentelemetry/sdk/trace/export/package-info.java @@ -0,0 +1,40 @@ +/* + * Copyright 2019, OpenTelemetry Authors + * + * 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. + */ + +/** + * Utilities that allows different tracing services to export recorded data for sampled spans in + * their own format. + * + *

Contents

+ * + *
    + *
  • {@link io.opentelemetry.sdk.trace.export.SpanExporter} + *
  • {@link io.opentelemetry.sdk.trace.export.SimpleSpansProcessor} + *
  • {@link io.opentelemetry.sdk.trace.export.BatchSpansProcessor} + *
  • {@link io.opentelemetry.sdk.trace.export.MultiSpanExporter} + *
+ * + *

Default values for {@link io.opentelemetry.sdk.trace.export.BatchSpansProcessor}

+ * + *
    + *
  • {@code SCHEDULE_DELAY_MILLIS: 5000} + *
  • {@code MAX_QUEUE_SIZE: 2048} + *
  • {@code MAX_EXPORT_BATCH_SIZE: 512} + *
  • {@code EXPORT_TIMEOUT_MILLIS: 30_000} + *
  • {@code REPORT_ONLY_SAMPLED: true} + *
+ */ +package io.opentelemetry.sdk.trace.export; diff --git a/sdk/src/test/java/io/opentelemetry/sdk/trace/export/BatchSpansProcessorTest.java b/sdk/src/test/java/io/opentelemetry/sdk/trace/export/BatchSpansProcessorTest.java index fa643c865cf..301d8077d01 100644 --- a/sdk/src/test/java/io/opentelemetry/sdk/trace/export/BatchSpansProcessorTest.java +++ b/sdk/src/test/java/io/opentelemetry/sdk/trace/export/BatchSpansProcessorTest.java @@ -28,7 +28,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -73,6 +75,116 @@ private ReadableSpan createSampledEndedSpan(String spanName) { return (ReadableSpan) span; } + @Test + public void testConfiguration() { + BatchSpansProcessor.Config config; + BatchSpansProcessor.Config defConfig = BatchSpansProcessor.Config.getDefault(); + + config = BatchSpansProcessor.Config.newBuilder().build(); + // check defaults + assertThat(config.getScheduleDelayMillis()).isEqualTo(defConfig.getScheduleDelayMillis()); + assertThat(config.getMaxQueueSize()).isEqualTo(defConfig.getMaxQueueSize()); + assertThat(config.getMaxExportBatchSize()).isEqualTo(defConfig.getMaxExportBatchSize()); + assertThat(config.getExporterTimeoutMillis()).isEqualTo(defConfig.getExporterTimeoutMillis()); + assertThat(config.isReportOnlySampled()).isEqualTo(defConfig.isReportOnlySampled()); + + // check system configuration + Map configMap = new HashMap<>(); + configMap.put("otel.bsp.schedule.delay", "1"); + configMap.put("otel.bsp.max.queue", "1"); + configMap.put("otel.bsp.max.export.batch", "1"); + configMap.put("otel.bsp.export.timeout", "1"); + configMap.put("otel.bsp.report.sampled", "false"); + config = + BatchSpansProcessor.Config.newBuilder() + .fromConfigMap(configMap, BatchSpansProcessor.Config.Builder.NamingConvention.DOT) + .build(); + assertThat(config.getScheduleDelayMillis()).isEqualTo(1); + assertThat(config.getMaxQueueSize()).isEqualTo(1); + assertThat(config.getMaxExportBatchSize()).isEqualTo(1); + assertThat(config.getExporterTimeoutMillis()).isEqualTo(1); + assertThat(config.isReportOnlySampled()).isEqualTo(false); + + // Check env vars + configMap.clear(); + configMap.put("OTEL_BSP_SCHEDULE_DELAY", "2"); + configMap.put("OTEL_BSP_MAX_QUEUE", "2"); + configMap.put("OTEL_BSP_MAX_EXPORT_BATCH", "2"); + configMap.put("OTEL_BSP_EXPORT_TIMEOUT", "2"); + configMap.put("OTEL_BSP_REPORT_SAMPLED", "true"); + config = + BatchSpansProcessor.Config.newBuilder() + .fromConfigMap(configMap, BatchSpansProcessor.Config.Builder.NamingConvention.ENV_VAR) + .build(); + assertThat(config.getScheduleDelayMillis()).isEqualTo(2); + assertThat(config.getMaxQueueSize()).isEqualTo(2); + assertThat(config.getMaxExportBatchSize()).isEqualTo(2); + assertThat(config.getExporterTimeoutMillis()).isEqualTo(2); + assertThat(config.isReportOnlySampled()).isEqualTo(true); + + // Check mixing conventions + configMap.clear(); + configMap.put("OTEL_BSP_schedule_DELAY", "3"); + configMap.put("OTEL.BSP.MAX_QUEUE", "3"); + configMap.put("OTEL_bsp.MAX_EXPORT_BATCH", "3"); + configMap.put("OTEL_BSP_ExPoRt_TIMEOUT", "3"); + configMap.put("OTEL_bSp.REPORT.SAmpleD", "false"); + config = + BatchSpansProcessor.Config.newBuilder() + .fromConfigMap(configMap, BatchSpansProcessor.Config.Builder.NamingConvention.ENV_VAR) + .build(); + assertThat(config.getScheduleDelayMillis()).isEqualTo(3); + assertThat(config.getMaxQueueSize()).isEqualTo(3); + assertThat(config.getMaxExportBatchSize()).isEqualTo(3); + assertThat(config.getExporterTimeoutMillis()).isEqualTo(3); + assertThat(config.isReportOnlySampled()).isEqualTo(false); + } + + @Test + public void testOnlySetPropertiesOverrideDefaults() { + BatchSpansProcessor.Config config; + Map configMap = new HashMap<>(); + // check only set values are written + configMap.clear(); + configMap.put("OTEL_BSP_SCHEDULE_DELAY", "1"); + config = + BatchSpansProcessor.Config.newBuilder() + .fromConfigMap(configMap, BatchSpansProcessor.Config.Builder.NamingConvention.ENV_VAR) + .build(); + assertThat(config.getScheduleDelayMillis()).isEqualTo(1); + assertThat(config.getMaxQueueSize()).isEqualTo(2048); + assertThat(config.getMaxExportBatchSize()).isEqualTo(512); + assertThat(config.getExporterTimeoutMillis()).isEqualTo(30_000); + assertThat(config.isReportOnlySampled()).isEqualTo(true); + } + + @Test + public void testUserSetPropertiesOverrideDefaults() { + BatchSpansProcessor.Config config; + Map configMap = new HashMap<>(); + // check only set values are written + configMap.clear(); + configMap.put("otel.bsp.schedule.delay", "1"); + configMap.put("otel.bsp.max.queue", "1"); + configMap.put("otel.bsp.max.export.batch", "1"); + configMap.put("otel.bsp.export.timeout", "1"); + configMap.put("otel.bsp.report.sampled", "false"); + config = + BatchSpansProcessor.Config.newBuilder() + .fromConfigMap(configMap, BatchSpansProcessor.Config.Builder.NamingConvention.DOT) + .setScheduleDelayMillis(2) + .setMaxQueueSize(2) + .setMaxExportBatchSize(2) + .setExporterTimeoutMillis(2) + .reportOnlySampled(true) + .build(); + assertThat(config.getScheduleDelayMillis()).isEqualTo(2); + assertThat(config.getMaxQueueSize()).isEqualTo(2); + assertThat(config.getMaxExportBatchSize()).isEqualTo(2); + assertThat(config.getExporterTimeoutMillis()).isEqualTo(2); + assertThat(config.isReportOnlySampled()).isEqualTo(true); + } + // TODO(bdrutu): Fix this when Sampler return RECORD option. /* private ReadableSpan createNotSampledRecordingEventsEndedSpan(String spanName) {