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; + } + } }
