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

wusheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-graalvm-distro.git

commit b9ccc10d589db8b2eb276403cbf69e969b3760f6
Author: Wu Sheng <[email protected]>
AuthorDate: Sun Mar 1 08:29:41 2026 +0800

    Fix MAL combination pattern resolution and add reflection configs
    
    DSL.java: resolve correct suffixed metric variant (e.g., telegraf vs otel)
    by matching expression SHA-256 hash against 
mal-groovy-expression-hashes.txt.
    Without this, all shared metric names (meter_vm_cpu_load1, etc.) always
    resolved to the otel (base) variant, ignoring telegraf/zabbix expressions.
    
    Precompiler: add ZabbixRequest$AgentData and AlarmMessage to reflection
    config for Gson serialization at runtime.
---
 .../server/buildtools/precompiler/Precompiler.java |   6 +-
 .../skywalking/oap/meter/analyzer/dsl/DSL.java     | 121 ++++++++++++++++++++-
 2 files changed, 124 insertions(+), 3 deletions(-)

diff --git 
a/build-tools/precompiler/src/main/java/org/apache/skywalking/oap/server/buildtools/precompiler/Precompiler.java
 
b/build-tools/precompiler/src/main/java/org/apache/skywalking/oap/server/buildtools/precompiler/Precompiler.java
index a8863f2..0dc4e27 100644
--- 
a/build-tools/precompiler/src/main/java/org/apache/skywalking/oap/server/buildtools/precompiler/Precompiler.java
+++ 
b/build-tools/precompiler/src/main/java/org/apache/skywalking/oap/server/buildtools/precompiler/Precompiler.java
@@ -1237,6 +1237,8 @@ public class Precompiler {
             
"org.apache.skywalking.oap.server.receiver.zabbix.provider.config.ZabbixConfig$Metric",
             // Zabbix protocol: Gson serializes ActiveChecks response to agent
             
"org.apache.skywalking.oap.server.receiver.zabbix.provider.protocol.bean.ZabbixResponse$ActiveChecks",
+            // Zabbix protocol: Gson deserializes AgentData from agent push
+            
"org.apache.skywalking.oap.server.receiver.zabbix.provider.protocol.bean.ZabbixRequest$AgentData",
             // Meter base class: attr0-attr5 fields must be discoverable via 
getDeclaredFields()
             // for StorageModels.retrieval() to include them in BanyanDB 
schemas
             "org.apache.skywalking.oap.server.core.analysis.meter.Meter",
@@ -1245,7 +1247,9 @@ public class Precompiler {
             // Searchable tag POJO used in alarm/log query results
             
"org.apache.skywalking.oap.server.core.analysis.manual.searchtag.Tag",
             // Alarm snapshot: Gson serializes/deserializes at runtime
-            "org.apache.skywalking.oap.server.core.alarm.AlarmSnapshotRecord"
+            "org.apache.skywalking.oap.server.core.alarm.AlarmSnapshotRecord",
+            // Alarm webhook: Gson serializes AlarmMessage list to JSON for 
webhook POST
+            "org.apache.skywalking.oap.server.core.alarm.AlarmMessage"
         };
         for (String className : configPojos) {
             entries.add(fullAccessEntry(className));
diff --git 
a/oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/DSL.java
 
b/oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/DSL.java
index c728c4f..00ffda8 100644
--- 
a/oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/DSL.java
+++ 
b/oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/DSL.java
@@ -22,6 +22,8 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -31,11 +33,19 @@ import lombok.extern.slf4j.Slf4j;
  * Same-FQCN replacement for upstream MAL DSL.
  * Loads transpiled MalExpression classes from mal-expressions.txt manifest
  * instead of Groovy DelegatingScript classes — no Groovy runtime needed.
+ *
+ * <p>Handles the <b>combination pattern</b>: multiple YAML rule files from 
different data sources
+ * (otel, telegraf, zabbix) may define metrics with the same name (e.g., 
{@code meter_vm_cpu_load1}).
+ * The precompiler assigns deterministic suffixes ({@code _1}, {@code _2}, 
etc.) and records
+ * expression SHA-256 hashes in {@code mal-groovy-expression-hashes.txt}. At 
runtime, the correct
+ * variant is resolved by matching the expression hash.
  */
 @Slf4j
 public final class DSL {
     private static final String MANIFEST_PATH = "META-INF/mal-expressions.txt";
+    private static final String HASHES_PATH = 
"META-INF/mal-groovy-expression-hashes.txt";
     private static volatile Map<String, String> SCRIPT_MAP;
+    private static volatile Map<String, String> HASH_MAP;
     private static final AtomicInteger LOADED_COUNT = new AtomicInteger();
 
     public static Expression parse(final String metricName, final String 
expression) {
@@ -46,10 +56,14 @@ public final class DSL {
         }
 
         Map<String, String> scriptMap = loadManifest();
-        String className = scriptMap.get(metricName);
+        Map<String, String> hashMap = loadHashes();
+        String resolvedName = resolveMetricName(metricName, expression, 
scriptMap, hashMap);
+
+        String className = scriptMap.get(resolvedName);
         if (className == null) {
             throw new IllegalStateException(
                 "Transpiled MAL expression not found for metric: " + metricName
+                    + " (resolved: " + resolvedName + ")"
                     + ". Available: " + scriptMap.size() + " expressions");
         }
 
@@ -57,7 +71,12 @@ public final class DSL {
             Class<?> exprClass = Class.forName(className);
             MalExpression malExpr = (MalExpression) 
exprClass.getDeclaredConstructor().newInstance();
             int count = LOADED_COUNT.incrementAndGet();
-            log.debug("Loaded transpiled MAL expression [{}/{}]: {}", count, 
scriptMap.size(), metricName);
+            if (!resolvedName.equals(metricName)) {
+                log.debug("Loaded transpiled MAL expression [{}/{}]: {} 
(resolved from {} via hash)",
+                    count, scriptMap.size(), resolvedName, metricName);
+            } else {
+                log.debug("Loaded transpiled MAL expression [{}/{}]: {}", 
count, scriptMap.size(), metricName);
+            }
             return new Expression(metricName, expression, malExpr);
         } catch (ClassNotFoundException e) {
             throw new IllegalStateException(
@@ -68,6 +87,66 @@ public final class DSL {
         }
     }
 
+    /**
+     * Resolve the correct suffixed metric name for combination patterns.
+     *
+     * <p>When multiple rule files define the same metric name (e.g., otel and 
telegraf both define
+     * {@code meter_vm_cpu_load1}), the precompiler creates suffixed variants
+     * ({@code meter_vm_cpu_load1}, {@code meter_vm_cpu_load1_1}, {@code 
meter_vm_cpu_load1_2}).
+     * This method computes the SHA-256 of the expression and matches it 
against the recorded
+     * hashes to find the correct variant.
+     */
+    private static String resolveMetricName(String metricName, String 
expression,
+                                            Map<String, String> scriptMap,
+                                            Map<String, String> hashMap) {
+        // If no hashes available, fall back to direct lookup
+        if (hashMap.isEmpty()) {
+            return metricName;
+        }
+
+        String expressionHash = sha256(expression);
+
+        // Check if the base name's hash matches
+        String baseHash = hashMap.get(metricName);
+        if (baseHash != null && baseHash.equals(expressionHash)) {
+            return metricName;
+        }
+
+        // Try suffixed variants: _1, _2, _3, ...
+        for (int i = 1; i <= 10; i++) {
+            String suffixed = metricName + "_" + i;
+            String suffixedHash = hashMap.get(suffixed);
+            if (suffixedHash != null && suffixedHash.equals(expressionHash)) {
+                return suffixed;
+            }
+            // Stop if no more suffixed variants exist
+            if (!scriptMap.containsKey(suffixed)) {
+                break;
+            }
+        }
+
+        // No hash match found — fall back to base name (will work for 
non-combination metrics)
+        if (scriptMap.containsKey(metricName)) {
+            return metricName;
+        }
+
+        return metricName;
+    }
+
+    private static String sha256(String input) {
+        try {
+            MessageDigest digest = MessageDigest.getInstance("SHA-256");
+            byte[] hash = 
digest.digest(input.getBytes(StandardCharsets.UTF_8));
+            StringBuilder sb = new StringBuilder(64);
+            for (byte b : hash) {
+                sb.append(String.format("%02x", b));
+            }
+            return sb.toString();
+        } catch (NoSuchAlgorithmException e) {
+            throw new IllegalStateException("SHA-256 not available", e);
+        }
+    }
+
     private static Map<String, String> loadManifest() {
         if (SCRIPT_MAP != null) {
             return SCRIPT_MAP;
@@ -108,4 +187,42 @@ public final class DSL {
             return map;
         }
     }
+
+    private static Map<String, String> loadHashes() {
+        if (HASH_MAP != null) {
+            return HASH_MAP;
+        }
+        synchronized (DSL.class) {
+            if (HASH_MAP != null) {
+                return HASH_MAP;
+            }
+            Map<String, String> map = new HashMap<>();
+            try (InputStream is = 
DSL.class.getClassLoader().getResourceAsStream(HASHES_PATH)) {
+                if (is == null) {
+                    log.warn("MAL expression hash manifest not found: {}", 
HASHES_PATH);
+                    HASH_MAP = map;
+                    return map;
+                }
+                try (BufferedReader reader = new BufferedReader(
+                    new InputStreamReader(is, StandardCharsets.UTF_8))) {
+                    String line;
+                    while ((line = reader.readLine()) != null) {
+                        line = line.trim();
+                        if (line.isEmpty() || line.startsWith("#")) {
+                            continue;
+                        }
+                        int eq = line.indexOf('=');
+                        if (eq > 0) {
+                            map.put(line.substring(0, eq), line.substring(eq + 
1));
+                        }
+                    }
+                }
+            } catch (IOException e) {
+                throw new IllegalStateException("Failed to load MAL expression 
hash manifest", e);
+            }
+            log.info("Loaded {} MAL expression hashes from manifest", 
map.size());
+            HASH_MAP = map;
+            return map;
+        }
+    }
 }

Reply via email to