This is an automated email from the ASF dual-hosted git repository.
adoroszlai pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/master by this push:
new b2707d37e14 HDDS-15245. Increase code coverage for tracing code
(#10273)
b2707d37e14 is described below
commit b2707d37e14c6a1b7c00ab9f4e882a22f6081d54
Author: sravani <[email protected]>
AuthorDate: Sun May 17 00:12:26 2026 +0530
HDDS-15245. Increase code coverage for tracing code (#10273)
---
.../hadoop/hdds/tracing/TestLoopSampler.java | 89 +++++++++++++
.../hadoop/hdds/tracing/TestSpanSampling.java | 82 ++++++++++++
.../hadoop/hdds/tracing/TestTracingConfig.java | 100 ++++++++++++++
.../hadoop/hdds/tracing/TestTracingUtil.java | 146 ++++++++++++++++++---
4 files changed, 399 insertions(+), 18 deletions(-)
diff --git
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/tracing/TestLoopSampler.java
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/tracing/TestLoopSampler.java
new file mode 100644
index 00000000000..93ae9d97725
--- /dev/null
+++
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/tracing/TestLoopSampler.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.hadoop.hdds.tracing;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for {@link LoopSampler}: invalid ratios, fixed outcomes at 0 and
1 (and above 1),
+ * and approximate behavior at 50%.
+ */
+public class TestLoopSampler {
+
+ /**
+ * negative sampling ration must throw error.
+ */
+ @Test
+ public void negativeRatioThrows() {
+ assertThrows(IllegalArgumentException.class, () -> new LoopSampler(-0.01));
+ }
+
+ /**
+ * Test to check if given a ratio of zero for a span, that it should never
be sampled.
+ */
+ @Test
+ public void zeroNeverSamples() {
+ LoopSampler s = new LoopSampler(0.0);
+ for (int i = 0; i < 50; i++) {
+ assertFalse(s.shouldSample());
+ }
+ }
+
+ /**
+ * Ration of one , indicates that span should always be sampled.
+ */
+ @Test
+ public void oneAlwaysSamples() {
+ LoopSampler s = new LoopSampler(1.0);
+ for (int i = 0; i < 50; i++) {
+ assertTrue(s.shouldSample());
+ }
+ }
+
+ /**
+ * Ration above one is taken as , every span should be sampled for that
value.
+ */
+ @Test
+ public void aboveOneIsCappedToAlwaysSample() {
+ LoopSampler s = new LoopSampler(2.0);
+ for (int i = 0; i < 50; i++) {
+ assertTrue(s.shouldSample());
+ }
+ }
+
+ /**
+ * Test to check if ratio of half gives approximately half spans as selected.
+ */
+ @Test
+ public void halfSamplesStatistically() {
+ LoopSampler s = new LoopSampler(0.5);
+ int hits = 0;
+ int n = 20_000;
+ for (int i = 0; i < n; i++) {
+ if (s.shouldSample()) {
+ hits++;
+ }
+ }
+ assertTrue(hits > n * 0.45 && hits < n * 0.55,
+ "expected ~50% samples, got " + hits + " / " + n);
+ }
+}
diff --git
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/tracing/TestSpanSampling.java
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/tracing/TestSpanSampling.java
index f46eb2855bb..09836dea1ed 100644
---
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/tracing/TestSpanSampling.java
+++
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/tracing/TestSpanSampling.java
@@ -21,6 +21,8 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.SpanContext;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.TraceFlags;
import io.opentelemetry.api.trace.TraceState;
@@ -141,4 +143,84 @@ public void testChildDropsWhenParentIsNotSampled() {
assertEquals(SamplingDecision.DROP, result.getDecision());
}
+
+ @Test
+ public void testParseSpanSamplingConfigNullOrEmpty() {
+ assertThat(TracingUtil.parseSpanSamplingConfig(null)).isEmpty();
+ assertThat(TracingUtil.parseSpanSamplingConfig("")).isEmpty();
+ }
+
+ @Test
+ public void testParseSpanSamplingConfigSkipsMalformedEntries() {
+ String config = "badnocolon, :0.5, nameonly:, :1.0, good:0.5";
+ Map<String, LoopSampler> result =
TracingUtil.parseSpanSamplingConfig(config);
+ assertThat(result).containsOnlyKeys("good");
+ }
+
+ @Test
+ public void testParseSpanSamplingConfigCapsRateAboveOne() {
+ Map<String, LoopSampler> result =
+ TracingUtil.parseSpanSamplingConfig("heavy:2.0");
+ assertThat(result).hasSize(1).containsKey("heavy");
+ assertThat(result.get("heavy").shouldSample()).isTrue();
+ }
+
+ @Test
+ public void testSpanSamplerGetDescription() {
+ Map<String, LoopSampler> spanMap = new HashMap<>();
+ spanMap.put("a", new LoopSampler(1.0));
+ SpanSampler sampler = new SpanSampler(Sampler.alwaysOn(), spanMap);
+ assertThat(sampler.getDescription()).contains("SpanSampler").contains("a");
+ }
+
+ /**
+ * Test to check Child span has entry in map with ratio as 0.
+ * It must not be sampled even if parent flag is set to sampled.
+ */
+ @Test
+ public void testChildWithConfiguredSpanAndZeroLoopSamplerDrops() {
+ Map<String, LoopSampler> spanMap = new HashMap<>();
+ spanMap.put("rpc", new LoopSampler(0.0));
+ SpanSampler customSampler = new SpanSampler(Sampler.alwaysOn(), spanMap);
+
+ Span parentSpan = Span.wrap(
+ SpanContext.create(
+ "ff000000000000000000000000000041",
+ "ff00000000000042",
+ TraceFlags.getSampled(),
+ TraceState.getDefault()));
+
+ Context parentContext = Context.root().with(parentSpan);
+
+ SamplingResult result = customSampler.shouldSample(
+ parentContext, "ff000000000000000000000000000041", "rpc",
+ SpanKind.INTERNAL, Attributes.empty(), Collections.emptyList());
+
+ assertThat(result.getDecision()).isEqualTo(SamplingDecision.DROP);
+ }
+
+ /**
+ * Test to check a child span with no entry in map will be sampled if parent
is sampled.
+ */
+ @Test
+ public void testChildSampledParentNotInSpanMapIsRecorded() {
+ Map<String, LoopSampler> spanMap = new HashMap<>();
+ spanMap.put("other", new LoopSampler(1.0));
+ SpanSampler customSampler = new SpanSampler(Sampler.alwaysOn(), spanMap);
+
+ Span parentSpan = Span.wrap(
+ SpanContext.create(
+ "ff000000000000000000000000000041",
+ "ff00000000000042",
+ TraceFlags.getSampled(),
+ TraceState.getDefault()));
+
+ Context parentContext = Context.root().with(parentSpan);
+
+ SamplingResult result = customSampler.shouldSample(
+ parentContext, "ff000000000000000000000000000041", "unlistedSpan",
+ SpanKind.INTERNAL, Attributes.empty(), Collections.emptyList());
+
+
assertThat(result.getDecision()).isEqualTo(SamplingDecision.RECORD_AND_SAMPLE);
+ }
}
diff --git
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/tracing/TestTracingConfig.java
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/tracing/TestTracingConfig.java
new file mode 100644
index 00000000000..f3692a6edeb
--- /dev/null
+++
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/tracing/TestTracingConfig.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.hadoop.hdds.tracing;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.apache.hadoop.hdds.conf.InMemoryConfigurationForTesting;
+import org.apache.hadoop.hdds.conf.MutableConfigurationSource;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Class to test configurations for Tracing.
+ */
+public class TestTracingConfig {
+
+ /**
+ * Assert that sampler ratio is clamped to 1 , as that is the highest.
+ */
+ @Test
+ public void testTraceSamplerRatioFromConfigClampedAboveOne() {
+ MutableConfigurationSource conf = new InMemoryConfigurationForTesting();
+ conf.setBoolean("ozone.tracing.enabled", true);
+ conf.setDouble("ozone.tracing.sampler", 1.75);
+
+ TracingConfig tracingConfig = conf.getObject(TracingConfig.class);
+
+ assertEquals(1.0, tracingConfig.getTraceSamplerRatio());
+ }
+
+ /**
+ * Test to Assert that sampler ratio is set correct and matches config.
+ */
+ @Test
+ public void testTraceSamplerRatioValidFromConfig() {
+ MutableConfigurationSource conf = new InMemoryConfigurationForTesting();
+ conf.setBoolean("ozone.tracing.enabled", true);
+ conf.setDouble("ozone.tracing.sampler", 0.25);
+
+ TracingConfig tracingConfig = conf.getObject(TracingConfig.class);
+
+ assertEquals(0.25, tracingConfig.getTraceSamplerRatio());
+ }
+
+ /**
+ * Test to check negative sampler ratio is set to 1.
+ */
+ @Test
+ public void testTraceSamplerRatioNegativeClampedToOne() {
+ MutableConfigurationSource conf = new InMemoryConfigurationForTesting();
+ conf.setBoolean("ozone.tracing.enabled", true);
+ conf.setDouble("ozone.tracing.sampler", -0.5);
+
+ TracingConfig tracingConfig = conf.getObject(TracingConfig.class);
+
+ assertEquals(1.0, tracingConfig.getTraceSamplerRatio());
+ }
+
+ /**
+ * Test to Assert that endpoint is set correct and matches config.
+ */
+ @Test
+ public void testExplicitTracingEndpoint() {
+ MutableConfigurationSource conf = new InMemoryConfigurationForTesting();
+ conf.setBoolean("ozone.tracing.enabled", true);
+ conf.set("ozone.tracing.endpoint", "http://collector.example:4317");
+
+ TracingConfig tracingConfig = conf.getObject(TracingConfig.class);
+
+ assertEquals("http://collector.example:4317",
tracingConfig.getTracingEndpoint());
+ }
+
+ /**
+ * Test to Assert that span sampling is set correct and matches config.
+ */
+ @Test
+ public void testExplicitSpanSampling() {
+ MutableConfigurationSource conf = new InMemoryConfigurationForTesting();
+ conf.setBoolean("ozone.tracing.enabled", true);
+ conf.set("ozone.tracing.span.sampling", "createKey:0.5");
+
+ TracingConfig tracingConfig = conf.getObject(TracingConfig.class);
+
+ assertEquals("createKey:0.5", tracingConfig.getSpanSampling());
+ }
+}
diff --git
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/tracing/TestTracingUtil.java
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/tracing/TestTracingUtil.java
index d0e58d76665..4474e8a42c4 100644
---
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/tracing/TestTracingUtil.java
+++
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/tracing/TestTracingUtil.java
@@ -21,10 +21,15 @@
import static org.apache.hadoop.hdds.tracing.TracingUtil.exportCurrentSpan;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.SpanContext;
+import io.opentelemetry.context.Scope;
import java.io.IOException;
import org.apache.hadoop.hdds.conf.InMemoryConfigurationForTesting;
import org.apache.hadoop.hdds.conf.MutableConfigurationSource;
@@ -37,6 +42,31 @@
*/
public class TestTracingUtil {
+ private static MutableConfigurationSource tracingEnabled() {
+ MutableConfigurationSource config = new InMemoryConfigurationForTesting();
+ config.setBoolean("ozone.tracing.enabled", true);
+ return config;
+ }
+
+ private static String traceIdFromExportedCarrier(String parentCarrier) {
+ TracingUtil.TextExtractor extractor = new TracingUtil.TextExtractor();
+ String traceparent = extractor.get(parentCarrier, "traceparent");
+ assertNotNull(traceparent, "carrier missing traceparent: " +
parentCarrier);
+ // W3C: 00-<traceId 32 hex>-<spanId 16 hex>-<flags 2 hex>
+ String[] parts = traceparent.split("-", 4);
+ assertEquals(4, parts.length, "bad traceparent: " + traceparent);
+ return parts[1];
+ }
+
+ private static String parentSpanIdFromExportedCarrier(String parentCarrier) {
+ TracingUtil.TextExtractor extractor = new TracingUtil.TextExtractor();
+ String traceparent = extractor.get(parentCarrier, "traceparent");
+ assertNotNull(traceparent, "carrier missing traceparent: " +
parentCarrier);
+ String[] parts = traceparent.split("-", 4);
+ assertEquals(4, parts.length, "bad traceparent: " + traceparent);
+ return parts[2];
+ }
+
@Test
public void testDefaultMethod() {
Service subject = createProxy(new ServiceImpl(), Service.class,
@@ -55,16 +85,6 @@ public void testInitTracing() {
}
}
- private static MutableConfigurationSource tracingEnabled() {
- MutableConfigurationSource config = new InMemoryConfigurationForTesting();
- config.setBoolean("ozone.tracing.enabled", true);
- return config;
- }
-
- /**
- * Test for checking if span was not created when a regular method
- * in Service implementation has @SkipTracing.
- */
@Test
public void testSkipTracingNoSpan() {
TracingUtil.initTracing("TestService", tracingEnabled());
@@ -75,10 +95,6 @@ public void testSkipTracingNoSpan() {
assertFalse(impl.wasSpanActive(), "Span should NOT be created for
@SkipTracing methods.");
}
- /**
- * Test for checking if span was not created when a method throws exception
- * in Service implementation and has @SkipTracing.
- */
@Test
public void testSkipTracingExceptionUnwrapped() {
TracingUtil.initTracing("TestService", tracingEnabled());
@@ -91,10 +107,6 @@ public void testSkipTracingExceptionUnwrapped() {
assertFalse(impl.wasSpanActive(), "Span should NOT have been created for a
@SkipTracing throwing method.");
}
- /**
- * Test for checking if span is created when a method in Service
implementation
- * does not have @SkipTracing.
- */
@Test
public void testProxyNormalVsSkipped() {
TracingUtil.initTracing("TestService", tracingEnabled());
@@ -104,4 +116,102 @@ public void testProxyNormalVsSkipped() {
serviceProxy.normalMethod();
assertTrue(impl.wasSpanActive(), "Normal method should have an active
span.");
}
+
+ @Test
+ public void testImportAndCreateSpanNullOrEmptyParent() {
+ TracingUtil.initTracing("NoParentService",
tracingEnabled().getObject(TracingConfig.class));
+ for (String parent : new String[] {null, ""}) {
+ Span span = TracingUtil.importAndCreateSpan("root-child", parent);
+ try (Scope ignored = span.makeCurrent()) {
+ assertTrue(Span.current().getSpanContext().isValid());
+ } finally {
+ span.end();
+ }
+ }
+ }
+
+ @Test
+ public void testImportAndCreateSpanWithExportedParentContext() {
+ TracingUtil.initTracing("import-w3c",
tracingEnabled().getObject(TracingConfig.class));
+ String parentCarrier;
+ try (TracingUtil.TraceCloseable ignored =
TracingUtil.createActivatedSpan("parent")) {
+ parentCarrier = TracingUtil.exportCurrentSpan();
+ }
+ assertFalse(parentCarrier.isEmpty(), "exported trace context should not be
empty");
+ Span child = TracingUtil.importAndCreateSpan("child", parentCarrier);
+ try (Scope s = child.makeCurrent()) {
+ assertTrue(Span.current().getSpanContext().isValid());
+ } finally {
+ child.end();
+ }
+ }
+
+ @Test
+ public void testExecuteInNewSpanUsesParentWhenContextHasActiveSpan() {
+ TracingUtil.initTracing("nested-span",
tracingEnabled().getObject(TracingConfig.class));
+ try (TracingUtil.TraceCloseable ignored =
TracingUtil.createActivatedSpan("outer")) {
+ TracingUtil.executeInNewSpan("inner", () ->
+ assertTrue(Span.current().getSpanContext().isValid()));
+ }
+ }
+
+ @Test
+ public void testExecuteAsChildSpanUsesImportedParentContext() throws
Exception {
+ TracingUtil.initTracing("child-span",
tracingEnabled().getObject(TracingConfig.class));
+ String parentCarrier;
+ try (TracingUtil.TraceCloseable ignored =
TracingUtil.createActivatedSpan("parent")) {
+ parentCarrier = TracingUtil.exportCurrentSpan();
+ }
+
+ String expectedTraceId = traceIdFromExportedCarrier(parentCarrier);
+ String exportedParentSpanId =
parentSpanIdFromExportedCarrier(parentCarrier);
+
+ TracingUtil.executeAsChildSpan("as-child", parentCarrier, () -> {
+ SpanContext ctx = Span.current().getSpanContext();
+ assertTrue(ctx.isValid());
+ assertEquals(expectedTraceId, ctx.getTraceId(),
+ "child should stay on the same trace as the exported parent
context");
+ assertNotEquals(exportedParentSpanId, ctx.getSpanId(),
+ "child span id should differ from exported parent span id");
+ });
+ }
+
+ @Test
+ public void testExecuteAsChildSpanPropagatesException() throws Exception {
+ TracingUtil.initTracing("child-ex",
tracingEnabled().getObject(TracingConfig.class));
+ String parentCarrier;
+ try (TracingUtil.TraceCloseable ignored =
TracingUtil.createActivatedSpan("parent")) {
+ parentCarrier = TracingUtil.exportCurrentSpan();
+ }
+ IOException thrown = assertThrows(IOException.class,
+ () -> TracingUtil.executeAsChildSpan("failing", parentCarrier,
+ () -> {
+ throw new IOException("expected");
+ }));
+ assertEquals("expected", thrown.getMessage());
+ }
+
+ @Test
+ public void testTextExtractorAsTextMapGetter() {
+ TracingUtil.TextExtractor getter = new TracingUtil.TextExtractor();
+ String carrier =
+ "traceparent=00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01;";
+ assertTrue(getter.keys(carrier).iterator().hasNext());
+ assertEquals(
+ "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01",
+ getter.get(carrier, "traceparent"));
+ }
+
+ @Test
+ public void testTextExtractorEmptyAndMalformedEntries() {
+ TracingUtil.TextExtractor ex = new TracingUtil.TextExtractor();
+ ex.keys("");
+ assertFalse(ex.keys("").iterator().hasNext());
+
+ TracingUtil.TextExtractor ex2 = new TracingUtil.TextExtractor();
+ String carrier = "notkeyvalue;traceparent=00-a-b-01;orphan=";
+ assertTrue(ex2.keys(carrier).iterator().hasNext());
+ assertEquals("00-a-b-01", ex2.get(carrier, "traceparent"));
+ assertEquals("00-a-b-01", ex2.get(carrier, "traceparent"));
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]