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]

Reply via email to