This is an automated email from the ASF dual-hosted git repository.

wu-sheng pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking.git


The following commit(s) were added to refs/heads/master by this push:
     new 0cec0de68b Fix: `avgHistogramPercentile` / `sumHistogramPercentile` 
meter functions reported the smallest finite bucket boundary (#13866)
0cec0de68b is described below

commit 0cec0de68b6767a5328418ecf1115d09458517d7
Author: Wan Kai <[email protected]>
AuthorDate: Sat May 9 23:25:39 2026 +0800

    Fix: `avgHistogramPercentile` / `sumHistogramPercentile` meter functions 
reported the smallest finite bucket boundary (#13866)
---
 docs/en/changes/changes.md                         |  1 +
 .../avg/AvgHistogramPercentileFunction.java        | 17 ++++---
 .../sum/SumHistogramPercentileFunction.java        | 17 ++++---
 .../avg/AvgHistogramPercentileFunctionTest.java    | 53 ++++++++++++++++++++++
 .../sum/SumHistogramPercentileFunctionTest.java    | 53 ++++++++++++++++++++++
 5 files changed, 127 insertions(+), 14 deletions(-)

diff --git a/docs/en/changes/changes.md b/docs/en/changes/changes.md
index e4d7f2debd..c0d15e5a5e 100644
--- a/docs/en/changes/changes.md
+++ b/docs/en/changes/changes.md
@@ -153,6 +153,7 @@
 * Fix: `envoy-ai-gateway` metrics rules, make the metrics value return `0` 
when the divisor is `0`.
 * Custom `Layer`s can be declared without modifying the OAP source — via an 
operator-managed `layer-extensions.yml`, inline `layerDefinitions:` block in a 
MAL or LAL rule file, or a plugin extension. UI dashboard templates for new 
layers are auto-discovered from the `ui-initialized-templates/` directory. 
Recommended ordinal range for external layers is `>= 1000`; conflicting names 
or ordinals are reported at boot.
 * LAL: support full arithmetic (`+`, `-`, `*`, `/`) on numeric operands and 
fix the original bug where `(tag("x") as Integer) + (tag("y") as Integer)` was 
treated as string concatenation — expressions like `input_tokens + 
output_tokens < 10000` produced the concatenated string `"2589115"` rather than 
the integer sum `2704`, so token-threshold conditions never triggered `abort 
{}`. Operand types are now inferred from explicit casts (`as Integer` / `as 
Long` / `as Float` / `as Double`), ty [...]
+* Fix: `avgHistogramPercentile` / `sumHistogramPercentile` meter functions 
reported the smallest finite bucket boundary (e.g. `10` for OTel 
`gen_ai_server_request_duration` whose `le` is rewritten from `0.01s` → `10ms`) 
for every rank when no samples were observed in any bucket. The percentile 
loop's `count >= roof` check matched on the first sorted bucket because both 
sides were `0`. `calculate()` now short-circuits to `0` for every rank when the 
windowed total is `0`.
 
 #### UI
 * Add mobile menu icon and i18n labels for the iOS layer.
diff --git 
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/meter/function/avg/AvgHistogramPercentileFunction.java
 
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/meter/function/avg/AvgHistogramPercentileFunction.java
index 7716febfe1..c018a2e7c1 100644
--- 
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/meter/function/avg/AvgHistogramPercentileFunction.java
+++ 
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/meter/function/avg/AvgHistogramPercentileFunction.java
@@ -235,6 +235,14 @@ public abstract class AvgHistogramPercentileFunction 
extends Meter implements Ac
                        long total;
                        total = subDataset.sumOfValues();
 
+                    if (total <= 0) {
+                        for (int rankIdx = 0; rankIdx < ranks.size(); 
rankIdx++) {
+                            labels.put(PERCENTILE_LABEL_NAME, 
String.valueOf(ranks.get(rankIdx)));
+                            percentileValues.put(labels, 0L);
+                        }
+                        return;
+                    }
+
                     int[] roofs = new int[ranks.size()];
                     for (int i = 0; i < ranks.size(); i++) {
                         roofs[i] = Math.round(total * ranks.get(i) * 1.0f / 
100);
@@ -253,13 +261,8 @@ public abstract class AvgHistogramPercentileFunction 
extends Meter implements Ac
                             int roof = roofs[rankIdx];
 
                             if (count >= roof) {
-                                if (labels.isEmpty()) {
-                                    labels.put(PERCENTILE_LABEL_NAME, 
String.valueOf(ranks.get(rankIdx)));
-                                    percentileValues.put(labels, 
Long.parseLong(key));
-                                } else {
-                                    labels.put(PERCENTILE_LABEL_NAME, 
String.valueOf(ranks.get(rankIdx)));
-                                    percentileValues.put(labels, 
Long.parseLong(key));
-                                }
+                                labels.put(PERCENTILE_LABEL_NAME, 
String.valueOf(ranks.get(rankIdx)));
+                                percentileValues.put(labels, 
Long.parseLong(key));
                                 loopIndex++;
                             } else {
                                 break;
diff --git 
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/meter/function/sum/SumHistogramPercentileFunction.java
 
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/meter/function/sum/SumHistogramPercentileFunction.java
index ac2f4c7d05..5d94a5f55f 100644
--- 
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/meter/function/sum/SumHistogramPercentileFunction.java
+++ 
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/meter/function/sum/SumHistogramPercentileFunction.java
@@ -201,6 +201,14 @@ public abstract class SumHistogramPercentileFunction 
extends Meter implements Ac
                        long total;
                        total = subDataset.sumOfValues();
 
+                    if (total <= 0) {
+                        for (int rankIdx = 0; rankIdx < ranks.size(); 
rankIdx++) {
+                            labels.put(PERCENTILE_LABEL_NAME, 
String.valueOf(ranks.get(rankIdx)));
+                            percentileValues.put(labels, 0L);
+                        }
+                        return;
+                    }
+
                     int[] roofs = new int[ranks.size()];
                     for (int i = 0; i < ranks.size(); i++) {
                         roofs[i] = Math.round(total * ranks.get(i) * 1.0f / 
100);
@@ -219,13 +227,8 @@ public abstract class SumHistogramPercentileFunction 
extends Meter implements Ac
                             int roof = roofs[rankIdx];
 
                             if (count >= roof) {
-                                if (labels.isEmpty()) {
-                                    labels.put(PERCENTILE_LABEL_NAME, 
String.valueOf(ranks.get(rankIdx)));
-                                    percentileValues.put(labels, 
Long.parseLong(key));
-                                } else {
-                                    labels.put(PERCENTILE_LABEL_NAME, 
String.valueOf(ranks.get(rankIdx)));
-                                    percentileValues.put(labels, 
Long.parseLong(key));
-                                }
+                                labels.put(PERCENTILE_LABEL_NAME, 
String.valueOf(ranks.get(rankIdx)));
+                                percentileValues.put(labels, 
Long.parseLong(key));
                                 loopIndex++;
                             } else {
                                 break;
diff --git 
a/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/meter/function/avg/AvgHistogramPercentileFunctionTest.java
 
b/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/meter/function/avg/AvgHistogramPercentileFunctionTest.java
index 60a88f774a..17cdb1ebd6 100644
--- 
a/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/meter/function/avg/AvgHistogramPercentileFunctionTest.java
+++ 
b/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/meter/function/avg/AvgHistogramPercentileFunctionTest.java
@@ -255,6 +255,59 @@ public class AvgHistogramPercentileFunctionTest {
         );
     }
 
+    @Test
+    public void testFunctionWithNoData() {
+        PercentileFunctionInst inst = new PercentileFunctionInst();
+        inst.accept(
+            MeterEntity.newService("service-test", Layer.GENERAL),
+            new PercentileArgument(
+                new BucketedValues(
+                    BUCKETS,
+                    new long[] {
+                        0,
+                        0,
+                        0,
+                        0
+                    }
+                ),
+                RANKS
+            )
+        );
+
+        inst.calculate();
+        // No samples observed in any bucket — every rank should report 0
+        // rather than collapsing to the smallest bucket boundary.
+        Assertions.assertEquals(new DataTable("{p=50},0|{p=90},0"), 
inst.getValue());
+    }
+
+    @Test
+    public void testFunctionWithNoDataAndLabels() {
+        BucketedValues bucketedValues = new BucketedValues(
+            BUCKETS,
+            new long[] {
+                0,
+                0,
+                0,
+                0
+            }
+        );
+        bucketedValues.getLabels().put("service_name", "ai-gateway");
+        PercentileFunctionInst inst = new PercentileFunctionInst();
+        inst.accept(
+            MeterEntity.newService("service-test", Layer.GENERAL),
+            new PercentileArgument(
+                bucketedValues,
+                RANKS
+            )
+        );
+
+        inst.calculate();
+        Assertions.assertEquals(
+            new 
DataTable("{service_name=ai-gateway,p=50},0|{service_name=ai-gateway,p=90},0"),
+            inst.getValue()
+        );
+    }
+
     private static class PercentileFunctionInst extends 
AvgHistogramPercentileFunction {
         @Override
         public AcceptableValue<PercentileArgument> createNew() {
diff --git 
a/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/meter/function/sum/SumHistogramPercentileFunctionTest.java
 
b/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/meter/function/sum/SumHistogramPercentileFunctionTest.java
index 22f89ca29b..e7bd56da5a 100644
--- 
a/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/meter/function/sum/SumHistogramPercentileFunctionTest.java
+++ 
b/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/meter/function/sum/SumHistogramPercentileFunctionTest.java
@@ -245,6 +245,59 @@ public class SumHistogramPercentileFunctionTest {
         );
     }
 
+    @Test
+    public void testFunctionWithNoData() {
+        PercentileFunctionInst inst = new PercentileFunctionInst();
+        inst.accept(
+            MeterEntity.newService("service-test", Layer.GENERAL),
+            new PercentileArgument(
+                new BucketedValues(
+                    BUCKETS,
+                    new long[] {
+                        0,
+                        0,
+                        0,
+                        0
+                    }
+                ),
+                RANKS
+            )
+        );
+
+        inst.calculate();
+        // No samples observed in any bucket — every rank should report 0
+        // rather than collapsing to the smallest bucket boundary.
+        Assertions.assertEquals(new DataTable("{p=50},0|{p=90},0"), 
inst.getValue());
+    }
+
+    @Test
+    public void testFunctionWithNoDataAndLabels() {
+        BucketedValues bucketedValues = new BucketedValues(
+            BUCKETS,
+            new long[] {
+                0,
+                0,
+                0,
+                0
+            }
+        );
+        bucketedValues.getLabels().put("service_name", "ai-gateway");
+        PercentileFunctionInst inst = new PercentileFunctionInst();
+        inst.accept(
+            MeterEntity.newService("service-test", Layer.GENERAL),
+            new PercentileArgument(
+                bucketedValues,
+                RANKS
+            )
+        );
+
+        inst.calculate();
+        Assertions.assertEquals(
+            new 
DataTable("{service_name=ai-gateway,p=50},0|{service_name=ai-gateway,p=90},0"),
+            inst.getValue()
+        );
+    }
+
     private static class PercentileFunctionInst extends 
SumHistogramPercentileFunction {
         @Override
         public AcceptableValue<PercentileArgument> createNew() {

Reply via email to