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
The following commit(s) were added to refs/heads/main by this push:
new 3c8cb3c Config data serialization, module system wiring, JVM boot
verification
3c8cb3c is described below
commit 3c8cb3c181f13a6f06456361aeeeeee5606fac6d
Author: Wu Sheng <[email protected]>
AuthorDate: Fri Feb 20 21:58:55 2026 +0800
Config data serialization, module system wiring, JVM boot verification
- Precompiler serializes parsed config POJOs to META-INF/config-data/*.json
- Add 3 replacement config loaders: MeterConfigs, Rules, LALConfigs
(load from JSON instead of filesystem YAML)
- Add library-module-for-graalvm: ModuleDefine replacement with direct
prepare() overload, bypassing ServiceLoader discovery
- Simplify FixedModuleManager to use ModuleDefine.prepare() directly,
delete ModuleWiringBridge
- Add configuration.has() guards for 6 optional modules in startup
- Centralize all dependency versions in root pom.xml
- Add debug logging for per-script MAL/LAL/filter loading ([count/total])
- Add docker-compose with all 4 BanyanDB data paths
- Add make shutdown target, SHA-256 staleness test for replacements
- Verify full JVM boot: 38 modules, 620 OAL + 1250 MAL + 7 LAL scripts
- Update DISTRO-POLICY.md: 23 replacement classes across 13 modules
---
.gitignore | 1 +
CLAUDE.md | 3 +
CONFIG-INIT-IMMIGRATION.md | 2 +-
DISTRO-POLICY.md | 43 +++-
LAL-IMMIGRATION.md | 17 ++
MAL-IMMIGRATION.md | 24 +++
Makefile | 23 ++-
build-tools/precompiler/pom.xml | 12 ++
.../server/buildtools/precompiler/Precompiler.java | 145 ++++++++++++--
docker/docker-compose.yml | 27 +++
oap-graalvm-server/pom.xml | 39 +++-
.../server/graalvm/GraalVMOAPServerStartUp.java | 28 ++-
.../server/library/module/FixedModuleManager.java | 47 +++--
.../server/library/module/ModuleWiringBridge.java | 120 -----------
.../server/library/util/YamlConfigLoaderUtils.java | 34 ++--
.../graalvm/ReplacementClassStalenessTest.java | 125 ++++++++++++
.../resources/replacement-source-sha256.properties | 31 +++
.../agent-analyzer-for-graalvm/pom.xml | 5 +
.../provider/meter/config/MeterConfigs.java | 82 ++++++++
.../pom.xml | 14 +-
.../oap/server/library/module/ModuleDefine.java | 167 ++++++++++++++++
.../log-analyzer-for-graalvm/pom.xml | 5 +
.../skywalking/oap/log/analyzer/dsl/DSL.java | 5 +-
.../oap/log/analyzer/provider/LALConfigs.java | 86 ++++++++
.../meter-analyzer-for-graalvm/pom.xml | 5 +
.../skywalking/oap/meter/analyzer/dsl/DSL.java | 5 +-
.../oap/meter/analyzer/dsl/FilterExpression.java | 5 +-
.../oap/meter/analyzer/prometheus/rule/Rules.java | 113 +++++++++++
oap-libs-for-graalvm/pom.xml | 1 +
.../server-core-for-graalvm/pom.xml | 1 +
.../core/config/HierarchyDefinitionService.java | 68 +++----
.../server/core/hierarchy/HierarchyService.java | 222 +++++++++++++++++++++
pom.xml | 71 +++++++
33 files changed, 1340 insertions(+), 236 deletions(-)
diff --git a/.gitignore b/.gitignore
index 776c98c..310d0b6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
# Build output
target/
dependency-reduced-pom.xml
+*.class
# IDE
.idea/
diff --git a/CLAUDE.md b/CLAUDE.md
index d39fe4b..8dffa54 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -39,6 +39,9 @@ JAVA_HOME=/Users/wusheng/.sdkman/candidates/java/25-graal mvn
-pl build-tools/pr
JAVA_HOME=/Users/wusheng/.sdkman/candidates/java/25-graal mvn -pl
oap-graalvm-server test
```
+## Git Commit Rules
+- **No Co-Authored-By**: Do not add `Co-Authored-By` lines to commit messages.
+
## Selected Modules
- **Storage**: BanyanDB
- **Cluster**: Standalone, Kubernetes
diff --git a/CONFIG-INIT-IMMIGRATION.md b/CONFIG-INIT-IMMIGRATION.md
index d16ac16..49d15d0 100644
--- a/CONFIG-INIT-IMMIGRATION.md
+++ b/CONFIG-INIT-IMMIGRATION.md
@@ -15,7 +15,7 @@ config-related reflection.
## Problem
-In `ModuleWiringBridge.wireAndPrepare()` and in `BanyanDBConfigLoader`,
+In `ModuleDefine.prepare()` and in `BanyanDBConfigLoader`,
`copyProperties()` iterates property names, looks up fields by name via
reflection, and sets them:
diff --git a/DISTRO-POLICY.md b/DISTRO-POLICY.md
index 4e53997..60b5c34 100644
--- a/DISTRO-POLICY.md
+++ b/DISTRO-POLICY.md
@@ -109,12 +109,12 @@ MAL expressions rely on `propertyMissing()` for sample
name resolution and `Expa
**Details**: [CONFIG-INIT-IMMIGRATION.md](CONFIG-INIT-IMMIGRATION.md)
### What Was Built
-- `FixedModuleManager` — direct module/provider construction, no SPI
-- `ModuleWiringBridge` — wires all selected modules/providers
-- `GraalVMOAPServerStartUp` — entry point
+- `FixedModuleManager` — direct module/provider construction via
`ModuleDefine.prepare()` overload, no SPI
+- `GraalVMOAPServerStartUp` — entry point with `configuration.has()` guards
for 6 optional modules
- `application.yml` — simplified config for selected providers
- `ConfigInitializerGenerator` — build-time tool that scans config classes and
generates `YamlConfigLoaderUtils` replacement
-- `YamlConfigLoaderUtils` — same-FQCN replacement (8th replacement class)
using type-dispatch + setter/VarHandle instead of reflection
+- `YamlConfigLoaderUtils` — same-FQCN replacement using type-dispatch +
setter/VarHandle instead of reflection
+- `ModuleDefine` — same-FQCN replacement (`library-module-for-graalvm`) adding
`prepare(ModuleManager, ModuleProvider, ...)` overload for direct provider
wiring without ServiceLoader
---
@@ -133,17 +133,18 @@ Each upstream JAR that has replacement classes gets a
corresponding `*-for-graal
`oap-graalvm-server` depends on `*-for-graalvm` JARs instead of originals.
Original upstream JARs are forced to `provided` scope via
`<dependencyManagement>` to prevent transitive leakage.
-### 19 Same-FQCN Replacement Classes Across 12 Modules
+### 23 Same-FQCN Replacement Classes Across 13 Modules
**Non-trivial replacements (load pre-compiled assets from manifests):**
| Module | Replacement Classes | Purpose |
|---|---|---|
+| `library-module-for-graalvm` | `ModuleDefine` | Add `prepare()` overload for
direct provider wiring (bypasses ServiceLoader) |
| `server-core-for-graalvm` | `OALEngineLoaderService`, `AnnotationScan`,
`SourceReceiverImpl`, `MeterSystem`, `CoreModuleConfig`,
`HierarchyDefinitionService` | Load from manifests instead of
Javassist/ClassPath; config with @Setter; Java-backed closures instead of
GroovyShell |
| `library-util-for-graalvm` | `YamlConfigLoaderUtils` | Set config fields via
setter instead of reflection |
-| `meter-analyzer-for-graalvm` | `DSL`, `FilterExpression` | Load pre-compiled
MAL Groovy scripts from manifest |
-| `log-analyzer-for-graalvm` | `DSL`, `LogAnalyzerModuleConfig` | Load
pre-compiled LAL scripts; config with @Setter |
-| `agent-analyzer-for-graalvm` | `AnalyzerModuleConfig` | Config with @Setter |
+| `meter-analyzer-for-graalvm` | `DSL`, `FilterExpression`, `Rules` | Load
pre-compiled MAL Groovy scripts from manifest; load rule data from JSON
config-data manifests |
+| `log-analyzer-for-graalvm` | `DSL`, `LogAnalyzerModuleConfig`, `LALConfigs`
| Load pre-compiled LAL scripts; config with @Setter; load LAL config data from
JSON config-data manifests |
+| `agent-analyzer-for-graalvm` | `AnalyzerModuleConfig`, `MeterConfigs` |
Config with @Setter; load meter config data from JSON config-data manifests |
**Config-only replacements (add `@Setter` for reflection-free config):**
@@ -239,6 +240,12 @@ packaged in JARs. The YAML source files are not needed at
runtime.
**Total: 89 files** consumed at build time, producing ~1285 pre-compiled
classes
and ~1254 Groovy scripts stored in JARs.
+Additionally, the precompiler serializes parsed config POJOs as JSON manifests
in
+`META-INF/config-data/` (7 JSON files for meter-analyzer-config, otel-rules,
+envoy-metrics-rules, log-mal-rules, telegraf-rules, zabbix-rules, and lal).
These
+provide the runtime "wiring" data (metric prefixes, rule names, expression
lookup
+keys) that replacement loader classes use instead of filesystem YAML access.
+
### Not Included (upstream-only)
| File | Reason |
@@ -253,7 +260,7 @@ and ~1254 Groovy scripts stored in JARs.
- [x] Set up Maven + Makefile in this repo
- [x] Build skywalking submodule as a dependency
- [x] Set up GraalVM JDK 25 in CI (`.github/workflows/ci.yml`)
-- [x] Create a JVM-mode starter with fixed module wiring (`FixedModuleManager`
+ `ModuleWiringBridge` + `GraalVMOAPServerStartUp`)
+- [x] Create a JVM-mode starter with fixed module wiring (`FixedModuleManager`
+ `GraalVMOAPServerStartUp`)
- [x] Simplified config file for selected modules (`application.yml`)
### Phase 2: Build-Time Pre-Compilation & Verification — COMPLETE
@@ -283,11 +290,29 @@ and ~1254 Groovy scripts stored in JARs.
- [x] Generated class uses Lombok setters, VarHandle, and getter+clear+addAll
— zero `Field.setAccessible` at runtime
- [x] Reflective fallback for unknown config types (safety net)
+**Config data serialization — COMPLETE:**
+- [x] Precompiler serializes parsed config POJOs to
`META-INF/config-data/*.json` (7 JSON files)
+- [x] 3 same-FQCN replacement loaders: `MeterConfigs` (meter-analyzer-config),
`Rules` (otel-rules, envoy-metrics-rules, telegraf-rules, log-mal-rules),
`LALConfigs` (lal)
+- [x] Replacement loaders deserialize from JSON instead of filesystem YAML,
with glob matching for enabled rules
+
+**Module system — COMPLETE:**
+- [x] `library-module-for-graalvm`: `ModuleDefine` replacement with direct
`prepare()` overload (bypasses ServiceLoader)
+- [x] `FixedModuleManager` simplified: calls `module.prepare(manager,
provider, config, params)` directly
+- [x] `GraalVMOAPServerStartUp`: `configuration.has()` guards for 6 optional
modules (receiver-zabbix, receiver-zipkin, kafka-fetcher, cilium-fetcher,
query-zipkin, exporter)
+
**Distro resource packaging — COMPLETE:**
- [x] Identified all 236 upstream resource files: 146 runtime files → distro
`config/`, 89 pre-compiled files → JARs
- [x] Assembly descriptor (`distribution.xml`) packages runtime config files
from upstream
- [x] Pre-compiled OAL/MAL/LAL files excluded from distro (not needed at
runtime)
+**JVM boot verification — COMPLETE:**
+- [x] Full boot with BanyanDB storage: 38 modules started (6 optional modules
disabled by default)
+- [x] Pre-compiled assets loaded: 620 OAL metrics + 45 dispatchers, 1250 MAL
scripts, 29 filter scripts, 7 LAL scripts
+- [x] Config data loaded from JSON: 9 meter configs, 55 otel rules, 2 envoy
rules, 1 telegraf rule, 1 log-mal rule, 8 LAL configs
+- [x] HTTP endpoints: gRPC :11800, HTTP :12800, Firehose :12801, PromQL :9090,
LogQL :3100, Prometheus :1234
+- [x] Debug logging for per-script MAL/LAL/filter loading (`[count/total]:
metricName`) for e2e verification
+- [x] Dependency versions centralized in root `pom.xml`
+
### Phase 3: Native Image Build
- [ ] `native-image-maven-plugin` configuration in `oap-graalvm-native`
- [ ] Run tracing agent to capture reflection/resource/JNI metadata
diff --git a/LAL-IMMIGRATION.md b/LAL-IMMIGRATION.md
index 44a4be3..e6c18cc 100644
--- a/LAL-IMMIGRATION.md
+++ b/LAL-IMMIGRATION.md
@@ -148,12 +148,29 @@ Each test runs both paths and asserts identical `Binding`
state:
---
+## Config Data Serialization
+
+At build time, the precompiler serializes parsed LAL config POJOs to a JSON
manifest at
+`META-INF/config-data/lal.json`. This provides the runtime config data (rule
names, DSL
+strings, layers) for `LogFilterListener` to create `DSL` instances — without
requiring
+filesystem access to the original YAML files.
+
+| JSON Manifest | Source Directory | Serialized Type |
+|---|---|---|
+| `lal.json` | `lal/` | `Map<String, LALConfigs>` (filename → configs) |
+
+At runtime, the replacement `LALConfigs.load()` deserializes from this JSON
file instead
+of reading YAML from the filesystem.
+
+---
+
## Same-FQCN Replacements (LAL)
| Upstream Class | Upstream Location | Replacement Location | What Changed |
|---|---|---|---|
| `DSL` (LAL) | `analyzer/log-analyzer/.../dsl/DSL.java` |
`oap-libs-for-graalvm/log-analyzer-for-graalvm/` | Complete rewrite. Loads
pre-compiled `@CompileStatic` Groovy script classes from
`META-INF/lal-scripts-by-hash.txt` manifest (keyed by SHA-256 hash) instead of
`GroovyShell.parse()` runtime compilation. |
| `LogAnalyzerModuleConfig` |
`analyzer/log-analyzer/.../provider/LogAnalyzerModuleConfig.java` |
`oap-libs-for-graalvm/log-analyzer-for-graalvm/` | Added `@Setter` at class
level. Enables reflection-free config loading via Lombok setters. |
+| `LALConfigs` | `analyzer/log-analyzer/.../provider/LALConfigs.java` |
`oap-libs-for-graalvm/log-analyzer-for-graalvm/` | Complete rewrite of static
`load()` method. Loads pre-compiled LAL config data from
`META-INF/config-data/{path}.json` instead of filesystem YAML files via
`ResourceUtils.getPathFiles()`. |
All replacements are repackaged into `log-analyzer-for-graalvm` via
`maven-shade-plugin` — the original `.class` files are excluded from the shaded
JAR.
diff --git a/MAL-IMMIGRATION.md b/MAL-IMMIGRATION.md
index 7a53e94..499df5a 100644
--- a/MAL-IMMIGRATION.md
+++ b/MAL-IMMIGRATION.md
@@ -358,6 +358,28 @@ LAL already uses `@CompileStatic` with
`LALPrecompiledExtension` for type checki
---
+## Config Data Serialization
+
+At build time, the precompiler serializes parsed config POJOs to JSON
manifests in
+`META-INF/config-data/`. This provides the runtime "wiring" data (metric
prefixes,
+rule names, expression lookup keys) that connects pre-compiled Groovy scripts
to
+incoming metrics — without requiring filesystem access to the original YAML
files.
+
+| JSON Manifest | Source Directory | Serialized Type |
+|---|---|---|
+| `meter-analyzer-config.json` | `meter-analyzer-config/` | `Map<String,
MeterConfig>` (filename → config) |
+| `otel-rules.json` | `otel-rules/` | `List<Rule>` |
+| `envoy-metrics-rules.json` | `envoy-metrics-rules/` | `List<Rule>` |
+| `log-mal-rules.json` | `log-mal-rules/` | `List<Rule>` |
+| `telegraf-rules.json` | `telegraf-rules/` | `List<Rule>` |
+| `zabbix-rules.json` | `zabbix-rules/` | `List<Rule>` |
+
+At runtime, replacement loader classes (`MeterConfigs`, `Rules`) deserialize
from
+these JSON files instead of reading YAML from the filesystem. Each logs that
configs
+are loaded from the pre-compiled distro.
+
+---
+
## Same-FQCN Replacements (MAL)
| Upstream Class | Upstream Location | Replacement Location | What Changed |
@@ -365,6 +387,8 @@ LAL already uses `@CompileStatic` with
`LALPrecompiledExtension` for type checki
| `MeterSystem` | `server-core/.../analysis/meter/MeterSystem.java` |
`oap-libs-for-graalvm/server-core-for-graalvm/` | Complete rewrite. Reads
`@MeterFunction` classes from `META-INF/annotation-scan/MeterFunction.txt`
manifest instead of Guava `ClassPath.from()`. Loads pre-generated Javassist
meter classes from classpath instead of runtime `ClassPool.makeClass()`. |
| `DSL` (MAL) | `analyzer/meter-analyzer/.../dsl/DSL.java` |
`oap-libs-for-graalvm/meter-analyzer-for-graalvm/` | Complete rewrite. Loads
pre-compiled Groovy `DelegatingScript` classes from
`META-INF/mal-groovy-scripts.txt` manifest instead of `GroovyShell.parse()`
runtime compilation. |
| `FilterExpression` | `analyzer/meter-analyzer/.../dsl/FilterExpression.java`
| `oap-libs-for-graalvm/meter-analyzer-for-graalvm/` | Complete rewrite. Loads
pre-compiled Groovy filter closure classes from
`META-INF/mal-filter-scripts.properties` manifest instead of
`GroovyShell.evaluate()` runtime compilation. |
+| `Rules` | `analyzer/meter-analyzer/.../prometheus/rule/Rules.java` |
`oap-libs-for-graalvm/meter-analyzer-for-graalvm/` | Complete rewrite. Loads
pre-compiled rule data from `META-INF/config-data/{path}.json` instead of
filesystem YAML files via `ResourceUtils.getPath()` + `Files.walk()`. |
+| `MeterConfigs` |
`analyzer/agent-analyzer/.../meter/config/MeterConfigs.java` |
`oap-libs-for-graalvm/agent-analyzer-for-graalvm/` | Complete rewrite. Loads
pre-compiled meter config data from `META-INF/config-data/{path}.json` instead
of filesystem YAML files via `ResourceUtils.getPathFiles()`. |
All replacements are repackaged into their respective `-for-graalvm` modules
via `maven-shade-plugin` — the original `.class` files are excluded from the
shaded JARs.
diff --git a/Makefile b/Makefile
index 68f3530..27430d3 100644
--- a/Makefile
+++ b/Makefile
@@ -19,7 +19,7 @@ SW_VERSION := $(shell grep '<revision>' skywalking/pom.xml |
head -1 | sed 's/.*
MVN := ./mvnw
MVN_ARGS := -Dskywalking.version=$(SW_VERSION)
-.PHONY: all clean build init-submodules build-skywalking build-distro compile
test javadoc dist info
+.PHONY: all clean build init-submodules build-skywalking build-distro compile
test javadoc dist info docker-up docker-down boot shutdown
all: build
@@ -69,3 +69,24 @@ build: build-skywalking build-distro
clean:
$(MVN) clean $(MVN_ARGS)
cd skywalking && ../mvnw clean
+
+# Start BanyanDB for local development
+docker-up:
+ docker compose -f docker/docker-compose.yml up -d
+ @echo "Waiting for BanyanDB to be ready..."
+ @until docker compose -f docker/docker-compose.yml exec banyandb sh -c
'nc -nz 127.0.0.1 17912' 2>/dev/null; do sleep 1; done
+ @echo "BanyanDB is ready on localhost:17912"
+
+# Stop BanyanDB
+docker-down:
+ docker compose -f docker/docker-compose.yml down
+
+# Stop a previously running OAP server
+shutdown:
+
@oap-graalvm-server/target/oap-graalvm-jvm-distro/oap-graalvm-jvm-distro/bin/oapServiceStop.sh
2>/dev/null || true
+
+# Build distro and boot OAP with BanyanDB
+boot: build-distro docker-up
+ SW_STORAGE_BANYANDB_TARGETS=localhost:17912 \
+ SW_CLUSTER=standalone \
+
oap-graalvm-server/target/oap-graalvm-jvm-distro/oap-graalvm-jvm-distro/bin/oapService.sh
diff --git a/build-tools/precompiler/pom.xml b/build-tools/precompiler/pom.xml
index 2bbe6ad..d912eb8 100644
--- a/build-tools/precompiler/pom.xml
+++ b/build-tools/precompiler/pom.xml
@@ -84,6 +84,18 @@
<artifactId>cilium-fetcher-plugin</artifactId>
</dependency>
+ <!-- Agent analyzer (for MeterConfig POJO used in config
serialization) -->
+ <dependency>
+ <groupId>org.apache.skywalking</groupId>
+ <artifactId>agent-analyzer</artifactId>
+ </dependency>
+
+ <!-- Jackson for config data JSON serialization -->
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
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 c1ba98f..db3b0d2 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
@@ -17,12 +17,16 @@
package org.apache.skywalking.oap.server.buildtools.precompiler;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
import com.google.common.collect.ImmutableSet;
import com.google.common.reflect.ClassPath;
import java.io.File;
+import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.Reader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
@@ -32,6 +36,7 @@ import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
@@ -42,6 +47,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.skywalking.oap.meter.analyzer.MetricConvert;
import org.apache.skywalking.oap.log.analyzer.provider.LALConfig;
import org.apache.skywalking.oap.log.analyzer.provider.LALConfigs;
+import
org.apache.skywalking.oap.server.analyzer.provider.meter.config.MeterConfig;
import org.apache.skywalking.oap.meter.analyzer.dsl.DSL;
import org.apache.skywalking.oap.meter.analyzer.dsl.FilterExpression;
import org.apache.skywalking.oap.meter.analyzer.prometheus.rule.MetricsRule;
@@ -229,27 +235,40 @@ public class Precompiler {
MeterSystem meterSystem = new MeterSystem(null);
int totalRules = 0;
+ Map<String, List<Rule>> rulesByPath = new LinkedHashMap<>();
// 1. Agent meter configs (meter-analyzer-config/*.yaml)
- totalRules += loadAndCompileRules("meter-analyzer-config",
List.of("*"), meterSystem);
+ List<Rule> meterAnalyzerRules =
loadAndCompileRules("meter-analyzer-config", List.of("*"), meterSystem);
+ totalRules += meterAnalyzerRules.size();
+ rulesByPath.put("meter-analyzer-config", meterAnalyzerRules);
// 2. OTel rules (otel-rules/*.yaml + otel-rules/**/*.yaml) — includes
root-level files
- totalRules += loadAndCompileRules("otel-rules", List.of("*", "**/*"),
meterSystem);
+ List<Rule> otelRules = loadAndCompileRules("otel-rules", List.of("*",
"**/*"), meterSystem);
+ totalRules += otelRules.size();
+ rulesByPath.put("otel-rules", otelRules);
// 3. Log MAL rules (log-mal-rules/*.yaml)
- totalRules += loadAndCompileRules("log-mal-rules", List.of("*"),
meterSystem);
+ List<Rule> logMalRules = loadAndCompileRules("log-mal-rules",
List.of("*"), meterSystem);
+ totalRules += logMalRules.size();
+ rulesByPath.put("log-mal-rules", logMalRules);
// 4. Envoy metrics rules (envoy-metrics-rules/*.yaml)
- totalRules += loadAndCompileRules("envoy-metrics-rules", List.of("*"),
meterSystem);
+ List<Rule> envoyRules = loadAndCompileRules("envoy-metrics-rules",
List.of("*"), meterSystem);
+ totalRules += envoyRules.size();
+ rulesByPath.put("envoy-metrics-rules", envoyRules);
// 5. Telegraf rules (telegraf-rules/*.yaml)
// Shares metricPrefix=meter_vm with otel-rules/vm.yaml — combination
pattern:
// multiple expressions from different sources aggregate into the same
metrics.
- totalRules += loadAndCompileRules("telegraf-rules", List.of("*"),
meterSystem);
+ List<Rule> telegrafRules = loadAndCompileRules("telegraf-rules",
List.of("*"), meterSystem);
+ totalRules += telegrafRules.size();
+ rulesByPath.put("telegraf-rules", telegrafRules);
// 6. Zabbix rules (zabbix-rules/*.yaml)
// Uses 'metrics' field instead of 'metricsRules', requires custom
loading.
- totalRules += loadAndCompileZabbixRules("zabbix-rules", meterSystem);
+ List<Rule> zabbixRules = loadAndCompileZabbixRules("zabbix-rules",
meterSystem);
+ totalRules += zabbixRules.size();
+ rulesByPath.put("zabbix-rules", zabbixRules);
// Write manifests
Path metaInf = Path.of(outputDir, "META-INF");
@@ -279,17 +298,22 @@ public class Precompiler {
meterSystem.getExportedClasses().size(),
DSL.getScriptRegistry().size(),
FilterExpression.getScriptRegistry().size());
+
+ // ---- Serialize config data as JSON for runtime loaders ----
+ serializeMALConfigData(outputDir, rulesByPath);
}
/**
* Load rules from a resource directory and compile them through the MAL
pipeline.
+ * Returns the loaded rules for config data serialization.
*/
- private static int loadAndCompileRules(String path, List<String>
enabledPatterns,
- MeterSystem meterSystem) {
+ private static List<Rule> loadAndCompileRules(String path, List<String>
enabledPatterns,
+ MeterSystem meterSystem) {
+ List<Rule> loadedRules = Collections.emptyList();
int count = 0;
try {
- List<Rule> rules = Rules.loadRules(path, enabledPatterns);
- for (Rule rule : rules) {
+ loadedRules = Rules.loadRules(path, enabledPatterns);
+ for (Rule rule : loadedRules) {
try {
new MetricConvert(rule, meterSystem);
count++;
@@ -302,16 +326,18 @@ public class Precompiler {
} catch (Exception e) {
log.warn("Failed to load rules from {}", path, e);
}
- return count;
+ return loadedRules;
}
/**
* Load Zabbix rules which use 'metrics' field instead of 'metricsRules'.
* Parses the YAML manually and maps into a Rule for MetricConvert.
+ * Returns the loaded rules for config data serialization.
*/
@SuppressWarnings("unchecked")
- private static int loadAndCompileZabbixRules(String path,
- MeterSystem meterSystem) {
+ private static List<Rule> loadAndCompileZabbixRules(String path,
+ MeterSystem
meterSystem) {
+ List<Rule> loadedRules = new ArrayList<>();
int count = 0;
try {
File[] files = ResourceUtils.getPathFiles(path);
@@ -350,6 +376,7 @@ public class Precompiler {
}
new MetricConvert(rule, meterSystem);
+ loadedRules.add(rule);
count++;
} catch (Exception e) {
log.warn("Failed to compile Zabbix rule: {}",
resourcePath, e);
@@ -359,7 +386,7 @@ public class Precompiler {
} catch (Exception e) {
log.warn("Failed to load rules from {}", path, e);
}
- return count;
+ return loadedRules;
}
/**
@@ -374,10 +401,13 @@ public class Precompiler {
// Enumerate all LAL YAML files from classpath
File[] lalFiles = ResourceUtils.getPathFiles("lal");
List<String> lalFileNames = new ArrayList<>();
+ Map<String, File> lalFileMap = new LinkedHashMap<>();
for (File f : lalFiles) {
String name = f.getName();
if (name.endsWith(".yaml") || name.endsWith(".yml")) {
- lalFileNames.add(name.substring(0, name.lastIndexOf('.')));
+ String key = name.substring(0, name.lastIndexOf('.'));
+ lalFileNames.add(key);
+ lalFileMap.put(key, f);
}
}
@@ -420,6 +450,9 @@ public class Precompiler {
log.info("LAL pre-compilation: {} rules, {} scripts",
totalRules,
org.apache.skywalking.oap.log.analyzer.dsl.DSL.getScriptRegistry().size());
+
+ // ---- Serialize LAL config data as JSON for runtime loader ----
+ serializeLALConfigData(outputDir, lalFileMap);
}
/**
@@ -560,6 +593,88 @@ public class Precompiler {
return result;
}
+ /**
+ * Serialize MAL config data (Rules and MeterConfigs) as JSON for runtime
loaders.
+ * At runtime, replacement loader classes deserialize from these JSON
files instead
+ * of reading YAML from the filesystem.
+ */
+ private static void serializeMALConfigData(String outputDir,
+ Map<String, List<Rule>>
rulesByPath) throws Exception {
+ ObjectMapper mapper = new
ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
+ Path configDataDir = Path.of(outputDir, "META-INF", "config-data");
+ Files.createDirectories(configDataDir);
+
+ // Serialize MeterConfig objects for meter-analyzer-config (runtime
uses MeterConfigs.loadConfig)
+ List<Rule> meterAnalyzerRules =
rulesByPath.get("meter-analyzer-config");
+ if (meterAnalyzerRules != null) {
+ Map<String, MeterConfig> meterConfigs =
loadMeterConfigs("meter-analyzer-config");
+
mapper.writeValue(configDataDir.resolve("meter-analyzer-config.json").toFile(),
meterConfigs);
+ log.info("Serialized {} MeterConfig entries from
meter-analyzer-config to config-data JSON",
+ meterConfigs.size());
+ }
+
+ // Serialize Rule lists for each path (runtime uses Rules.loadRules)
+ for (Map.Entry<String, List<Rule>> entry : rulesByPath.entrySet()) {
+ String path = entry.getKey();
+ if ("meter-analyzer-config".equals(path)) {
+ // meter-analyzer-config is already serialized as MeterConfig
above
+ continue;
+ }
+ List<Rule> rules = entry.getValue();
+ mapper.writeValue(configDataDir.resolve(path + ".json").toFile(),
rules);
+ log.info("Serialized {} Rule entries from {} to config-data JSON",
rules.size(), path);
+ }
+ }
+
+ /**
+ * Load MeterConfig objects from meter-analyzer-config YAML files.
+ * Returns a Map keyed by filename (without extension) for filtering at
runtime.
+ */
+ private static Map<String, MeterConfig> loadMeterConfigs(String path)
throws Exception {
+ File[] files = ResourceUtils.getPathFiles(path);
+ Map<String, MeterConfig> result = new LinkedHashMap<>();
+ Yaml yaml = new Yaml();
+ for (File file : files) {
+ String name = file.getName();
+ if (!name.endsWith(".yaml") && !name.endsWith(".yml")) {
+ continue;
+ }
+ String key = name.substring(0, name.lastIndexOf('.'));
+ try (Reader r = new FileReader(file)) {
+ MeterConfig config = yaml.loadAs(r, MeterConfig.class);
+ if (config != null) {
+ result.put(key, config);
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Serialize LAL config data as JSON for runtime loader.
+ * At runtime, the replacement LALConfigs.load() deserializes from this
JSON file.
+ */
+ private static void serializeLALConfigData(String outputDir,
+ Map<String, File> lalFileMap)
throws Exception {
+ ObjectMapper mapper = new
ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
+ Path configDataDir = Path.of(outputDir, "META-INF", "config-data");
+ Files.createDirectories(configDataDir);
+
+ Map<String, LALConfigs> lalConfigMap = new LinkedHashMap<>();
+ Yaml yaml = new Yaml();
+ for (Map.Entry<String, File> entry : lalFileMap.entrySet()) {
+ try (Reader r = new FileReader(entry.getValue())) {
+ LALConfigs config = yaml.loadAs(r, LALConfigs.class);
+ if (config != null) {
+ lalConfigMap.put(entry.getKey(), config);
+ }
+ }
+ }
+
+ mapper.writeValue(configDataDir.resolve("lal.json").toFile(),
lalConfigMap);
+ log.info("Serialized {} LALConfigs entries from lal to config-data
JSON", lalConfigMap.size());
+ }
+
private static void writeManifest(Path path, List<String> lines) throws
IOException {
Files.write(path, lines, StandardCharsets.UTF_8);
}
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
new file mode 100644
index 0000000..1774dc0
--- /dev/null
+++ b/docker/docker-compose.yml
@@ -0,0 +1,27 @@
+# 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.
+
+services:
+ banyandb:
+ image:
"ghcr.io/apache/skywalking-banyandb:${SW_BANYANDB_COMMIT:-74334e027b7fad414c9773f803163b5c97a9655b}"
+ ports:
+ - "17912:17912"
+ - "17913:17913"
+ command: standalone --stream-root-path /tmp/stream-data
--measure-root-path /tmp/measure-data --property-root-path /tmp/property-data
--trace-root-path /tmp/trace-data
+ healthcheck:
+ test: [ "CMD", "sh", "-c", "nc -nz 127.0.0.1 17912" ]
+ interval: 5s
+ timeout: 60s
+ retries: 120
diff --git a/oap-graalvm-server/pom.xml b/oap-graalvm-server/pom.xml
index 2718684..56888aa 100644
--- a/oap-graalvm-server/pom.xml
+++ b/oap-graalvm-server/pom.xml
@@ -39,6 +39,12 @@
-->
<dependencyManagement>
<dependencies>
+ <dependency>
+ <groupId>org.apache.skywalking</groupId>
+ <artifactId>library-module</artifactId>
+ <version>${skywalking.version}</version>
+ <scope>provided</scope>
+ </dependency>
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>server-core</artifactId>
@@ -116,6 +122,10 @@
<dependencies>
<!-- Repackaged OAP libs with GraalVM-compatible replacements baked in
-->
+ <dependency>
+ <groupId>org.apache.skywalking</groupId>
+ <artifactId>library-module-for-graalvm</artifactId>
+ </dependency>
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>server-core-for-graalvm</artifactId>
@@ -166,6 +176,10 @@
</dependency>
<!-- Core (non-repackaged) -->
+ <dependency>
+ <groupId>org.apache.skywalking</groupId>
+ <artifactId>receiver-proto</artifactId>
+ </dependency>
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>oal-rt</artifactId>
@@ -253,6 +267,11 @@
<groupId>org.apache.skywalking</groupId>
<artifactId>skywalking-async-profiler-receiver-plugin</artifactId>
</dependency>
+ <!-- Upstream marks this as 'provided'; we need it at runtime for
JFREventType -->
+ <dependency>
+ <groupId>org.apache.skywalking</groupId>
+ <artifactId>library-async-profiler-jfr-parser</artifactId>
+ </dependency>
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>skywalking-pprof-receiver-plugin</artifactId>
@@ -335,6 +354,23 @@
<classifier>generated</classifier>
</dependency>
+ <!--
+ Transitive deps that are excluded by provided-scope overrides above
+ but are needed at runtime.
+ -->
+ <dependency>
+ <groupId>org.apache.groovy</groupId>
+ <artifactId>groovy</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-text</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>joda-time</groupId>
+ <artifactId>joda-time</artifactId>
+ </dependency>
+
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
@@ -350,19 +386,16 @@
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
- <version>5.11.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
- <version>5.11.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-reflect</artifactId>
- <version>2.0.9</version>
<scope>test</scope>
</dependency>
</dependencies>
diff --git
a/oap-graalvm-server/src/main/java/org/apache/skywalking/oap/server/graalvm/GraalVMOAPServerStartUp.java
b/oap-graalvm-server/src/main/java/org/apache/skywalking/oap/server/graalvm/GraalVMOAPServerStartUp.java
index 4771254..a510237 100644
---
a/oap-graalvm-server/src/main/java/org/apache/skywalking/oap/server/graalvm/GraalVMOAPServerStartUp.java
+++
b/oap-graalvm-server/src/main/java/org/apache/skywalking/oap/server/graalvm/GraalVMOAPServerStartUp.java
@@ -200,12 +200,16 @@ public class GraalVMOAPServerStartUp {
manager.register(new ProfileModule(), new ProfileModuleProvider());
manager.register(new AsyncProfilerModule(), new
AsyncProfilerModuleProvider());
manager.register(new PprofModule(), new PprofModuleProvider());
- manager.register(new ZabbixReceiverModule(), new
ZabbixReceiverProvider());
+ if (configuration.has("receiver-zabbix")) {
+ manager.register(new ZabbixReceiverModule(), new
ZabbixReceiverProvider());
+ }
manager.register(new MeshReceiverModule(), new MeshReceiverProvider());
manager.register(new EnvoyMetricReceiverModule(), new
EnvoyMetricReceiverProvider());
manager.register(new MeterReceiverModule(), new
MeterReceiverProvider());
manager.register(new OtelMetricReceiverModule(), new
OtelMetricReceiverProvider());
- manager.register(new ZipkinReceiverModule(), new
ZipkinReceiverProvider());
+ if (configuration.has("receiver-zipkin")) {
+ manager.register(new ZipkinReceiverModule(), new
ZipkinReceiverProvider());
+ }
manager.register(new BrowserModule(), new BrowserModuleProvider());
manager.register(new LogModule(), new LogModuleProvider());
manager.register(new EventModule(), new EventModuleProvider());
@@ -214,13 +218,19 @@ public class GraalVMOAPServerStartUp {
manager.register(new AWSFirehoseReceiverModule(), new
AWSFirehoseReceiverModuleProvider());
manager.register(new ConfigurationDiscoveryModule(), new
ConfigurationDiscoveryProvider());
- // Fetchers
- manager.register(new KafkaFetcherModule(), new KafkaFetcherProvider());
- manager.register(new CiliumFetcherModule(), new
CiliumFetcherProvider());
+ // Fetchers (optional, disabled by default with selector: -)
+ if (configuration.has("kafka-fetcher")) {
+ manager.register(new KafkaFetcherModule(), new
KafkaFetcherProvider());
+ }
+ if (configuration.has("cilium-fetcher")) {
+ manager.register(new CiliumFetcherModule(), new
CiliumFetcherProvider());
+ }
// Query
manager.register(new QueryModule(), new GraphQLQueryProvider());
- manager.register(new ZipkinQueryModule(), new ZipkinQueryProvider());
+ if (configuration.has("query-zipkin")) {
+ manager.register(new ZipkinQueryModule(), new
ZipkinQueryProvider());
+ }
manager.register(new PromQLModule(), new PromQLProvider());
manager.register(new LogQLModule(), new LogQLProvider());
manager.register(new StatusQueryModule(), new StatusQueryProvider());
@@ -228,8 +238,10 @@ public class GraalVMOAPServerStartUp {
// Alarm
manager.register(new AlarmModule(), new AlarmModuleProvider());
- // Exporter
- manager.register(new ExporterModule(), new ExporterProvider());
+ // Exporter (optional, disabled by default with selector: -)
+ if (configuration.has("exporter")) {
+ manager.register(new ExporterModule(), new ExporterProvider());
+ }
// Health Checker
manager.register(new HealthCheckerModule(), new
HealthCheckerProvider());
diff --git
a/oap-graalvm-server/src/main/java/org/apache/skywalking/oap/server/library/module/FixedModuleManager.java
b/oap-graalvm-server/src/main/java/org/apache/skywalking/oap/server/library/module/FixedModuleManager.java
index a4915d9..c4afde3 100644
---
a/oap-graalvm-server/src/main/java/org/apache/skywalking/oap/server/library/module/FixedModuleManager.java
+++
b/oap-graalvm-server/src/main/java/org/apache/skywalking/oap/server/library/module/FixedModuleManager.java
@@ -17,17 +17,20 @@
package org.apache.skywalking.oap.server.library.module;
+import java.util.ArrayList;
import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Map;
/**
* A ModuleManager that uses fixed module wiring instead of ServiceLoader
discovery.
* Modules and providers are explicitly registered before initialization.
+ * Uses the direct-wiring prepare() overload added in the
library-module-for-graalvm
+ * replacement of ModuleDefine.
*/
public class FixedModuleManager extends ModuleManager {
- private final Map<String, ModuleWiringBridge.ModuleBinding> bindings = new
LinkedHashMap<>();
- private final Map<String, ModuleDefine> loadedModules = new
LinkedHashMap<>();
+ private final List<ModuleBinding> bindings = new ArrayList<>();
public FixedModuleManager(String description) {
super(description);
@@ -35,36 +38,56 @@ public class FixedModuleManager extends ModuleManager {
/**
* Register a module with its chosen provider.
- * The module name is derived from {@link ModuleDefine#name()}.
*/
public void register(ModuleDefine moduleDefine, ModuleProvider provider) {
- bindings.put(moduleDefine.name(), new
ModuleWiringBridge.ModuleBinding(moduleDefine, provider));
+ bindings.add(new ModuleBinding(moduleDefine, provider));
}
/**
* Initialize all registered modules using fixed wiring (no ServiceLoader).
+ * Calls the direct-wiring prepare() overload on ModuleDefine, then uses
+ * BootstrapFlow for dependency-ordered start and notify.
*/
public void initFixed(ApplicationConfiguration configuration)
throws ModuleNotFoundException, ProviderNotFoundException,
ServiceNotProvidedException,
CycleDependencyException, ModuleConfigException, ModuleStartException {
- ModuleWiringBridge.initAll(this, bindings, configuration);
- // Populate our loaded modules map for find()/has() lookups
- for (Map.Entry<String, ModuleWiringBridge.ModuleBinding> entry :
bindings.entrySet()) {
- loadedModules.put(entry.getKey(), entry.getValue().moduleDefine());
+
+ Map<String, ModuleDefine> loadedModules = new LinkedHashMap<>();
+ TerminalFriendlyTable bootingParameters = getBootingParameters();
+
+ // Phase 1: Prepare all modules using direct provider wiring
+ for (ModuleBinding binding : bindings) {
+ String moduleName = binding.moduleDefine.name();
+ binding.moduleDefine.prepare(
+ this,
+ binding.provider,
+ configuration.getModuleConfiguration(moduleName),
+ bootingParameters
+ );
+ loadedModules.put(moduleName, binding.moduleDefine);
}
+
+ // Phase 2: Start and notify (using BootstrapFlow for dependency
ordering)
+ BootstrapFlow bootstrapFlow = new BootstrapFlow(loadedModules);
+ bootstrapFlow.start(this);
+ bootstrapFlow.notifyAfterCompleted();
}
@Override
public boolean has(String moduleName) {
- return loadedModules.containsKey(moduleName);
+ return bindings.stream().anyMatch(b ->
b.moduleDefine.name().equals(moduleName));
}
@Override
public ModuleProviderHolder find(String moduleName) throws
ModuleNotFoundRuntimeException {
- ModuleDefine module = loadedModules.get(moduleName);
- if (module != null) {
- return module;
+ for (ModuleBinding binding : bindings) {
+ if (binding.moduleDefine.name().equals(moduleName)) {
+ return binding.moduleDefine;
+ }
}
throw new ModuleNotFoundRuntimeException(moduleName + " missing.");
}
+
+ private record ModuleBinding(ModuleDefine moduleDefine, ModuleProvider
provider) {
+ }
}
diff --git
a/oap-graalvm-server/src/main/java/org/apache/skywalking/oap/server/library/module/ModuleWiringBridge.java
b/oap-graalvm-server/src/main/java/org/apache/skywalking/oap/server/library/module/ModuleWiringBridge.java
deleted file mode 100644
index f0ddb28..0000000
---
a/oap-graalvm-server/src/main/java/org/apache/skywalking/oap/server/library/module/ModuleWiringBridge.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * 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.skywalking.oap.server.library.module;
-
-import java.lang.reflect.InvocationTargetException;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import lombok.extern.slf4j.Slf4j;
-
-import static
org.apache.skywalking.oap.server.library.util.YamlConfigLoaderUtils.copyProperties;
-
-/**
- * Bridge class in the same package as ModuleDefine/ModuleProvider to access
- * package-private APIs. Used by the fixed-wiring starter to bypass SPI
discovery.
- */
-@Slf4j
-public class ModuleWiringBridge {
-
- /**
- * Wire a ModuleDefine with a specific ModuleProvider directly, bypassing
ServiceLoader.
- * Replicates the logic in {@link ModuleDefine#prepare} but with a
pre-selected provider.
- */
- public static void wireAndPrepare(ModuleManager moduleManager,
- ModuleDefine module,
- ModuleProvider provider,
-
ApplicationConfiguration.ModuleConfiguration configuration,
- TerminalFriendlyTable bootingParameters)
- throws ModuleConfigException, ModuleStartException,
ServiceNotProvidedException {
-
- // Wire provider to module (package-private setters)
- provider.setManager(moduleManager);
- provider.setModuleDefine(module);
- provider.setBootingParameters(bootingParameters);
-
- log.info("Prepare the {} provider in {} module.", provider.name(),
module.name());
-
- // Initialize config
- try {
- final ModuleProvider.ConfigCreator creator =
provider.newConfigCreator();
- if (creator != null) {
- final Class typeOfConfig = creator.type();
- if (typeOfConfig != null) {
- final ModuleConfig config = (ModuleConfig)
typeOfConfig.getDeclaredConstructor().newInstance();
- copyProperties(
- config,
-
configuration.getProviderConfiguration(provider.name()),
- module.name(),
- provider.name()
- );
- creator.onInitialized(config);
- }
- }
- } catch (IllegalAccessException | NoSuchMethodException |
InvocationTargetException |
- InstantiationException e) {
- throw new ModuleConfigException(
- module.name() + " module config transport to config bean
failure.", e);
- }
-
- // Prepare the provider
- provider.prepare();
- }
-
- /**
- * Initialize all fixed-wired modules: prepare, start, and notify in
dependency order.
- *
- * @param moduleManager the module manager
- * @param moduleBindings ordered map of module name -> (ModuleDefine,
ModuleProvider) pairs
- * @param configuration the application configuration
- */
- public static void initAll(ModuleManager moduleManager,
- Map<String, ModuleBinding> moduleBindings,
- ApplicationConfiguration configuration)
- throws ModuleNotFoundException, ProviderNotFoundException,
ServiceNotProvidedException,
- CycleDependencyException, ModuleConfigException, ModuleStartException {
-
- Map<String, ModuleDefine> loadedModules = new LinkedHashMap<>();
- TerminalFriendlyTable bootingParameters =
moduleManager.getBootingParameters();
-
- // Phase 1: Prepare all modules
- for (Map.Entry<String, ModuleBinding> entry :
moduleBindings.entrySet()) {
- String moduleName = entry.getKey();
- ModuleBinding binding = entry.getValue();
-
- wireAndPrepare(
- moduleManager,
- binding.moduleDefine(),
- binding.provider(),
- configuration.getModuleConfiguration(moduleName),
- bootingParameters
- );
- loadedModules.put(moduleName, binding.moduleDefine());
- }
-
- // Phase 2: Start and notify (using BootstrapFlow for dependency
ordering)
- BootstrapFlow bootstrapFlow = new BootstrapFlow(loadedModules);
- bootstrapFlow.start(moduleManager);
- bootstrapFlow.notifyAfterCompleted();
- }
-
- /**
- * A binding of a ModuleDefine to its chosen ModuleProvider.
- */
- public record ModuleBinding(ModuleDefine moduleDefine, ModuleProvider
provider) {
- }
-}
diff --git
a/oap-graalvm-server/src/main/java/org/apache/skywalking/oap/server/library/util/YamlConfigLoaderUtils.java
b/oap-graalvm-server/src/main/java/org/apache/skywalking/oap/server/library/util/YamlConfigLoaderUtils.java
index 593339c..de85c64 100644
---
a/oap-graalvm-server/src/main/java/org/apache/skywalking/oap/server/library/util/YamlConfigLoaderUtils.java
+++
b/oap-graalvm-server/src/main/java/org/apache/skywalking/oap/server/library/util/YamlConfigLoaderUtils.java
@@ -237,7 +237,7 @@ public class YamlConfigLoaderUtils {
cfg.setRestMaxThreads((int) value);
break;
case "restIdleTimeOut":
- cfg.setRestIdleTimeOut((long) value);
+ cfg.setRestIdleTimeOut(((Number) value).longValue());
break;
case "restAcceptQueueSize":
cfg.setRestAcceptQueueSize((int) value);
@@ -270,10 +270,10 @@ public class YamlConfigLoaderUtils {
cfg.setTopNReportPeriod((int) value);
break;
case "l1FlushPeriod":
- cfg.setL1FlushPeriod((long) value);
+ cfg.setL1FlushPeriod(((Number) value).longValue());
break;
case "storageSessionTimeout":
- cfg.setStorageSessionTimeout((long) value);
+ cfg.setStorageSessionTimeout(((Number) value).longValue());
break;
case "downsampling":
cfg.getDownsampling().clear();
@@ -301,13 +301,13 @@ public class YamlConfigLoaderUtils {
cfg.setRemoteTimeout((int) value);
break;
case "maxSizeOfNetworkAddressAlias":
- cfg.setMaxSizeOfNetworkAddressAlias((long) value);
+ cfg.setMaxSizeOfNetworkAddressAlias(((Number)
value).longValue());
break;
case "maxSizeOfProfileTask":
- cfg.setMaxSizeOfProfileTask((long) value);
+ cfg.setMaxSizeOfProfileTask(((Number) value).longValue());
break;
case "maxSizeOfPprofTask":
- cfg.setMaxSizeOfPprofTask((long) value);
+ cfg.setMaxSizeOfPprofTask(((Number) value).longValue());
break;
case "maxPageSizeOfQueryProfileSnapshot":
cfg.setMaxPageSizeOfQueryProfileSnapshot((int) value);
@@ -379,10 +379,10 @@ public class YamlConfigLoaderUtils {
cfg.setEnableHierarchy((boolean) value);
break;
case "maxHeapMemoryUsagePercent":
- cfg.setMaxHeapMemoryUsagePercent((long) value);
+ cfg.setMaxHeapMemoryUsagePercent(((Number)
value).longValue());
break;
case "maxDirectMemoryUsage":
- cfg.setMaxDirectMemoryUsage((long) value);
+ cfg.setMaxDirectMemoryUsage(((Number) value).longValue());
break;
default:
log.warn("{} setting is not supported in {} provider of {}
module",
@@ -654,7 +654,7 @@ public class YamlConfigLoaderUtils {
cfg.setRestContextPath((String) value);
break;
case "restIdleTimeOut":
- cfg.setRestIdleTimeOut((long) value);
+ cfg.setRestIdleTimeOut(((Number) value).longValue());
break;
case "restAcceptQueueSize":
cfg.setRestAcceptQueueSize((int) value);
@@ -891,7 +891,7 @@ public class YamlConfigLoaderUtils {
cfg.setRestContextPath((String) value);
break;
case "restIdleTimeOut":
- cfg.setRestIdleTimeOut((long) value);
+ cfg.setRestIdleTimeOut(((Number) value).longValue());
break;
case "restAcceptQueueSize":
cfg.setRestAcceptQueueSize((int) value);
@@ -1043,7 +1043,7 @@ public class YamlConfigLoaderUtils {
cfg.setContextPath((String) value);
break;
case "idleTimeOut":
- cfg.setIdleTimeOut((long) value);
+ cfg.setIdleTimeOut(((Number) value).longValue());
break;
case "acceptQueueSize":
cfg.setAcceptQueueSize((int) value);
@@ -1260,13 +1260,13 @@ public class YamlConfigLoaderUtils {
cfg.setRestContextPath((String) value);
break;
case "restIdleTimeOut":
- cfg.setRestIdleTimeOut((long) value);
+ cfg.setRestIdleTimeOut(((Number) value).longValue());
break;
case "restAcceptQueueSize":
cfg.setRestAcceptQueueSize((int) value);
break;
case "lookback":
- cfg.setLookback((long) value);
+ cfg.setLookback(((Number) value).longValue());
break;
case "namesMaxAge":
cfg.setNamesMaxAge((int) value);
@@ -1278,7 +1278,7 @@ public class YamlConfigLoaderUtils {
cfg.setUiEnvironment((String) value);
break;
case "uiDefaultLookback":
- cfg.setUiDefaultLookback((long) value);
+ cfg.setUiDefaultLookback(((Number) value).longValue());
break;
case "uiSearchEnabled":
cfg.setUiSearchEnabled((boolean) value);
@@ -1310,7 +1310,7 @@ public class YamlConfigLoaderUtils {
cfg.setRestContextPath((String) value);
break;
case "restIdleTimeOut":
- cfg.setRestIdleTimeOut((long) value);
+ cfg.setRestIdleTimeOut(((Number) value).longValue());
break;
case "restAcceptQueueSize":
cfg.setRestAcceptQueueSize((int) value);
@@ -1360,7 +1360,7 @@ public class YamlConfigLoaderUtils {
cfg.setRestContextPath((String) value);
break;
case "restIdleTimeOut":
- cfg.setRestIdleTimeOut((long) value);
+ cfg.setRestIdleTimeOut(((Number) value).longValue());
break;
case "restAcceptQueueSize":
cfg.setRestAcceptQueueSize((int) value);
@@ -1456,7 +1456,7 @@ public class YamlConfigLoaderUtils {
final Object value = src.get(key);
switch (key) {
case "checkIntervalSeconds":
- cfg.setCheckIntervalSeconds((long) value);
+ cfg.setCheckIntervalSeconds(((Number) value).longValue());
break;
default:
log.warn("{} setting is not supported in {} provider of {}
module",
diff --git
a/oap-graalvm-server/src/test/java/org/apache/skywalking/oap/server/graalvm/ReplacementClassStalenessTest.java
b/oap-graalvm-server/src/test/java/org/apache/skywalking/oap/server/graalvm/ReplacementClassStalenessTest.java
new file mode 100644
index 0000000..d49d15f
--- /dev/null
+++
b/oap-graalvm-server/src/test/java/org/apache/skywalking/oap/server/graalvm/ReplacementClassStalenessTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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.skywalking.oap.server.graalvm;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+/**
+ * Staleness detector for same-FQCN replacement classes.
+ *
+ * <p>Tracks the SHA-256 of each upstream source file that has a same-FQCN
+ * replacement in this project. If an upstream file changes (e.g. after a
+ * {@code skywalking/} submodule update), this test fails with a clear message
+ * indicating which replacement(s) need review and update.
+ *
+ * <p>SHA-256 hashes are recorded in {@code
replacement-source-sha256.properties}
+ * (same pattern as {@code precompiled-yaml-sha256.properties}).
+ */
+class ReplacementClassStalenessTest {
+
+ private static final String PROPS_RESOURCE =
"replacement-source-sha256.properties";
+
+ @Test
+ void allReplacementSourcesMatchRecordedSha256() throws Exception {
+ Properties props = loadProperties();
+ assertTrue(!props.isEmpty(),
+ PROPS_RESOURCE + " is empty or not found");
+
+ // Resolve paths relative to project root (oap-graalvm-server ->
parent)
+ Path projectRoot = Path.of(System.getProperty("user.dir")).getParent();
+
+ List<String> mismatches = new ArrayList<>();
+ List<String> missing = new ArrayList<>();
+
+ for (String relativePath : props.stringPropertyNames()) {
+ String expectedSha = props.getProperty(relativePath).trim();
+ Path upstreamPath = projectRoot.resolve(relativePath);
+
+ if (!Files.exists(upstreamPath)) {
+ missing.add(relativePath);
+ } else {
+ String actualSha = computeFileSha256(upstreamPath);
+ if (!expectedSha.equals(actualSha)) {
+ mismatches.add(String.format(
+ " %s%n expected: %s%n actual: %s",
+ relativePath, expectedSha, actualSha));
+ }
+ }
+ }
+
+ StringBuilder msg = new StringBuilder();
+ if (!mismatches.isEmpty()) {
+ msg.append("Upstream source files changed — review and update
these replacements:\n");
+ for (String m : mismatches) {
+ msg.append(m).append('\n');
+ }
+ }
+ if (!missing.isEmpty()) {
+ msg.append("Upstream source files not found:\n");
+ for (String m : missing) {
+ msg.append(" ").append(m).append('\n');
+ }
+ }
+
+ if (msg.length() > 0) {
+ fail(msg.toString());
+ }
+ }
+
+ private Properties loadProperties() throws IOException {
+ Properties props = new Properties();
+ try (InputStream is = getClass().getClassLoader()
+ .getResourceAsStream(PROPS_RESOURCE)) {
+ if (is != null) {
+ props.load(is);
+ }
+ }
+ return props;
+ }
+
+ private static String computeFileSha256(Path path) {
+ try (InputStream is = Files.newInputStream(path)) {
+ MessageDigest digest = MessageDigest.getInstance("SHA-256");
+ byte[] buffer = new byte[8192];
+ int read;
+ while ((read = is.read(buffer)) != -1) {
+ digest.update(buffer, 0, read);
+ }
+ byte[] hash = digest.digest();
+ StringBuilder hex = new StringBuilder(hash.length * 2);
+ for (byte b : hash) {
+ hex.append(String.format("%02x", b));
+ }
+ return hex.toString();
+ } catch (IOException | NoSuchAlgorithmException e) {
+ return null;
+ }
+ }
+}
diff --git
a/oap-graalvm-server/src/test/resources/replacement-source-sha256.properties
b/oap-graalvm-server/src/test/resources/replacement-source-sha256.properties
new file mode 100644
index 0000000..77f02a3
--- /dev/null
+++ b/oap-graalvm-server/src/test/resources/replacement-source-sha256.properties
@@ -0,0 +1,31 @@
+# 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.
+
+# Replacement class upstream source SHA-256 hashes — staleness detector
+# Tracks upstream Java source files that have same-FQCN replacements in this
+# project. When a file's content changes (e.g. after skywalking/ submodule
+# update), ReplacementClassStalenessTest fails, flagging which replacements
+# need review and update.
+#
+# Format: relative/path/to/upstream/Source.java = sha256hex
+# To update: shasum -a 256 <path>
+
+# Module system replacement (direct provider wiring without ServiceLoader)
+skywalking/oap-server/server-library/library-module/src/main/java/org/apache/skywalking/oap/server/library/module/ModuleDefine.java
= 89add3015c4265f50a13a9e5800d68aaa43e73f50c1224343712d2744b6e2437
+
+# Config loader replacements (load from JSON manifests instead of filesystem
YAML)
+skywalking/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/meter/config/MeterConfigs.java
= 979b1d081a7e0aa1b627a525157d9f024f81a3db289e76284c54dbf508c494b1
+skywalking/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/prometheus/rule/Rules.java
= 1f300c978e9dca2464379463b40544d8114500b1f87d2df10005c518dd71db99
+skywalking/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/provider/LALConfigs.java
= ecb7ddbc94bd4073e885e76c472dddd171e9b11155402faabb33923b55e38eee
diff --git a/oap-libs-for-graalvm/agent-analyzer-for-graalvm/pom.xml
b/oap-libs-for-graalvm/agent-analyzer-for-graalvm/pom.xml
index cddbeda..d1c4dd9 100644
--- a/oap-libs-for-graalvm/agent-analyzer-for-graalvm/pom.xml
+++ b/oap-libs-for-graalvm/agent-analyzer-for-graalvm/pom.xml
@@ -36,6 +36,10 @@
<groupId>org.apache.skywalking</groupId>
<artifactId>agent-analyzer</artifactId>
</dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
@@ -58,6 +62,7 @@
<artifact>org.apache.skywalking:agent-analyzer</artifact>
<excludes>
<exclude>org/apache/skywalking/oap/server/analyzer/provider/AnalyzerModuleConfig.class</exclude>
+
<exclude>org/apache/skywalking/oap/server/analyzer/provider/meter/config/MeterConfigs.class</exclude>
</excludes>
</filter>
</filters>
diff --git
a/oap-libs-for-graalvm/agent-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/meter/config/MeterConfigs.java
b/oap-libs-for-graalvm/agent-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/meter/config/MeterConfigs.java
new file mode 100644
index 0000000..0138a04
--- /dev/null
+++
b/oap-libs-for-graalvm/agent-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/meter/config/MeterConfigs.java
@@ -0,0 +1,82 @@
+/*
+ * 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.skywalking.oap.server.analyzer.provider.meter.config;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.oap.server.library.module.ModuleStartException;
+import org.apache.skywalking.oap.server.library.util.CollectionUtils;
+
+/**
+ * GraalVM replacement for upstream MeterConfigs.
+ * Original:
skywalking/oap-server/analyzer/agent-analyzer/src/main/java/.../meter/config/MeterConfigs.java
+ * Repackaged into agent-analyzer-for-graalvm via maven-shade-plugin (replaces
original .class in shaded JAR).
+ *
+ * Change: Complete rewrite. Loads pre-compiled meter config data from JSON
manifests
+ * (META-INF/config-data/{path}.json) instead of filesystem YAML files via
ResourceUtils.getPathFiles().
+ * Why: The distro intentionally excludes raw YAML config directories
(meter-analyzer-config, etc.)
+ * — their Groovy expressions are pre-compiled at build time. Config data
(metric prefixes, rule names)
+ * is serialized as JSON by the precompiler for runtime wiring.
+ */
+@Slf4j
+public class MeterConfigs {
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+
+ /**
+ * Load all configs from pre-compiled JSON manifest.
+ */
+ public static List<MeterConfig> loadConfig(String path, List<String>
fileNames) throws ModuleStartException {
+ if (CollectionUtils.isEmpty(fileNames)) {
+ return Collections.emptyList();
+ }
+
+ log.info("Loading meter configs from pre-compiled distro ({})", path);
+
+ String resourcePath = "META-INF/config-data/" + path + ".json";
+ try (InputStream is =
MeterConfigs.class.getClassLoader().getResourceAsStream(resourcePath)) {
+ if (is == null) {
+ throw new ModuleStartException(
+ "Pre-compiled config data not found: " + resourcePath
+ + ". Ensure the precompiler has been run.");
+ }
+
+ Map<String, MeterConfig> allConfigs = MAPPER.readValue(
+ is, new TypeReference<Map<String, MeterConfig>>() { });
+
+ List<MeterConfig> result = allConfigs.entrySet().stream()
+ .filter(e -> fileNames.contains(e.getKey()))
+ .map(Map.Entry::getValue)
+ .collect(Collectors.toList());
+
+ log.info("Loaded {} pre-compiled meter configs from {} (filtered
from {} available)",
+ result.size(), path, allConfigs.size());
+ return result;
+ } catch (ModuleStartException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ModuleStartException("Load pre-compiled meter configs
failed", e);
+ }
+ }
+}
diff --git a/oap-libs-for-graalvm/agent-analyzer-for-graalvm/pom.xml
b/oap-libs-for-graalvm/library-module-for-graalvm/pom.xml
similarity index 82%
copy from oap-libs-for-graalvm/agent-analyzer-for-graalvm/pom.xml
copy to oap-libs-for-graalvm/library-module-for-graalvm/pom.xml
index cddbeda..9b907fa 100644
--- a/oap-libs-for-graalvm/agent-analyzer-for-graalvm/pom.xml
+++ b/oap-libs-for-graalvm/library-module-for-graalvm/pom.xml
@@ -27,14 +27,14 @@
<version>1.0.0-SNAPSHOT</version>
</parent>
- <artifactId>agent-analyzer-for-graalvm</artifactId>
- <name>Agent Analyzer for GraalVM</name>
- <description>Repackaged agent-analyzer with GraalVM-compatible
config</description>
+ <artifactId>library-module-for-graalvm</artifactId>
+ <name>Library Module for GraalVM</name>
+ <description>Repackaged library-module with ModuleDefine that supports
direct provider wiring</description>
<dependencies>
<dependency>
<groupId>org.apache.skywalking</groupId>
- <artifactId>agent-analyzer</artifactId>
+ <artifactId>library-module</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
@@ -50,14 +50,14 @@
<configuration>
<artifactSet>
<includes>
-
<include>org.apache.skywalking:agent-analyzer</include>
+
<include>org.apache.skywalking:library-module</include>
</includes>
</artifactSet>
<filters>
<filter>
-
<artifact>org.apache.skywalking:agent-analyzer</artifact>
+
<artifact>org.apache.skywalking:library-module</artifact>
<excludes>
-
<exclude>org/apache/skywalking/oap/server/analyzer/provider/AnalyzerModuleConfig.class</exclude>
+
<exclude>org/apache/skywalking/oap/server/library/module/ModuleDefine.class</exclude>
</excludes>
</filter>
</filters>
diff --git
a/oap-libs-for-graalvm/library-module-for-graalvm/src/main/java/org/apache/skywalking/oap/server/library/module/ModuleDefine.java
b/oap-libs-for-graalvm/library-module-for-graalvm/src/main/java/org/apache/skywalking/oap/server/library/module/ModuleDefine.java
new file mode 100644
index 0000000..7c4c049
--- /dev/null
+++
b/oap-libs-for-graalvm/library-module-for-graalvm/src/main/java/org/apache/skywalking/oap/server/library/module/ModuleDefine.java
@@ -0,0 +1,167 @@
+/*
+ * 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.skywalking.oap.server.library.module;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.ServiceLoader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static
org.apache.skywalking.oap.server.library.util.YamlConfigLoaderUtils.copyProperties;
+
+/**
+ * GraalVM replacement for upstream ModuleDefine.
+ * Original:
skywalking/oap-server/server-library/library-module/src/main/java/.../module/ModuleDefine.java
+ * Repackaged into library-module-for-graalvm via maven-shade-plugin (replaces
original .class in shaded JAR).
+ *
+ * Change: Added {@link #prepare(ModuleManager, ModuleProvider,
ApplicationConfiguration.ModuleConfiguration,
+ * TerminalFriendlyTable)} overload that accepts a pre-selected provider
directly, bypassing ServiceLoader
+ * discovery. This enables fixed module wiring for GraalVM without reflection
hacks.
+ * Why: The distro uses FixedModuleManager with directly-constructed modules
and providers.
+ * The upstream prepare() method requires ServiceLoader to find providers,
which we bypass entirely.
+ */
+public abstract class ModuleDefine implements ModuleProviderHolder {
+
+ private static final Logger LOGGER =
LoggerFactory.getLogger(ModuleDefine.class);
+
+ private ModuleProvider loadedProvider = null;
+
+ private final String name;
+
+ public ModuleDefine(String name) {
+ this.name = name;
+ }
+
+ /**
+ * @return the module name
+ */
+ public final String name() {
+ return name;
+ }
+
+ /**
+ * @return the {@link Service} provided by this module.
+ */
+ public abstract Class[] services();
+
+ /**
+ * Run the prepare stage for the module, including finding all potential
providers, and asking them to prepare.
+ *
+ * @param moduleManager of this module
+ * @param configuration of this module
+ * @throws ProviderNotFoundException when even don't find a single one
providers.
+ */
+ void prepare(ModuleManager moduleManager,
+ ApplicationConfiguration.ModuleConfiguration configuration,
+ ServiceLoader<ModuleProvider> moduleProviderLoader,
+ TerminalFriendlyTable bootingParameters)
+ throws ProviderNotFoundException, ServiceNotProvidedException,
ModuleConfigException, ModuleStartException {
+ for (ModuleProvider provider : moduleProviderLoader) {
+ if (!configuration.has(provider.name())) {
+ continue;
+ }
+
+ if (provider.module().equals(getClass())) {
+ if (loadedProvider == null) {
+ loadedProvider = provider;
+ loadedProvider.setManager(moduleManager);
+ loadedProvider.setModuleDefine(this);
+ loadedProvider.setBootingParameters(bootingParameters);
+ } else {
+ throw new DuplicateProviderException(
+ this.name() + " module has one " +
loadedProvider.name() + "[" + loadedProvider
+ .getClass()
+ .getName() + "] provider already, " +
provider.name() + "[" + provider.getClass()
+
.getName() + "] is defined as 2nd provider.");
+ }
+ }
+
+ }
+
+ if (loadedProvider == null) {
+ throw new ProviderNotFoundException(this.name() + " module no
provider found.");
+ }
+
+ LOGGER.info("Prepare the {} provider in {} module.",
loadedProvider.name(), this.name());
+ try {
+ final ModuleProvider.ConfigCreator creator =
loadedProvider.newConfigCreator();
+ if (creator != null) {
+ final Class typeOfConfig = creator.type();
+ if (typeOfConfig != null) {
+ final ModuleConfig config = (ModuleConfig)
typeOfConfig.getDeclaredConstructor().newInstance();
+ copyProperties(
+ config,
+
configuration.getProviderConfiguration(loadedProvider.name()), this.name(),
+ loadedProvider.name()
+ );
+ creator.onInitialized(config);
+ }
+ }
+ } catch (IllegalAccessException | NoSuchMethodException |
InvocationTargetException |
+ InstantiationException e) {
+ throw new ModuleConfigException(this.name() + " module config
transport to config bean failure.", e);
+ }
+ loadedProvider.prepare();
+ }
+
+ /**
+ * Prepare with a pre-selected provider, bypassing ServiceLoader discovery.
+ * Used by FixedModuleManager for direct module wiring.
+ */
+ void prepare(ModuleManager moduleManager,
+ ModuleProvider provider,
+ ApplicationConfiguration.ModuleConfiguration configuration,
+ TerminalFriendlyTable bootingParameters)
+ throws ModuleConfigException, ModuleStartException {
+
+ loadedProvider = provider;
+ loadedProvider.setManager(moduleManager);
+ loadedProvider.setModuleDefine(this);
+ loadedProvider.setBootingParameters(bootingParameters);
+
+ LOGGER.info("Prepare the {} provider in {} module.",
loadedProvider.name(), this.name());
+ try {
+ final ModuleProvider.ConfigCreator creator =
loadedProvider.newConfigCreator();
+ if (creator != null) {
+ final Class typeOfConfig = creator.type();
+ if (typeOfConfig != null) {
+ final ModuleConfig config = (ModuleConfig)
typeOfConfig.getDeclaredConstructor().newInstance();
+ copyProperties(
+ config,
+
configuration.getProviderConfiguration(loadedProvider.name()), this.name(),
+ loadedProvider.name()
+ );
+ creator.onInitialized(config);
+ }
+ }
+ } catch (IllegalAccessException | NoSuchMethodException |
InvocationTargetException |
+ InstantiationException e) {
+ throw new ModuleConfigException(this.name() + " module config
transport to config bean failure.", e);
+ }
+ loadedProvider.prepare();
+ }
+
+ @Override
+ public final ModuleProvider provider() throws DuplicateProviderException,
ProviderNotFoundException {
+ if (loadedProvider == null) {
+ throw new ProviderNotFoundException("There is no module provider
in " + this.name() + " module!");
+ }
+
+ return loadedProvider;
+ }
+}
diff --git a/oap-libs-for-graalvm/log-analyzer-for-graalvm/pom.xml
b/oap-libs-for-graalvm/log-analyzer-for-graalvm/pom.xml
index 505b964..ac3465b 100644
--- a/oap-libs-for-graalvm/log-analyzer-for-graalvm/pom.xml
+++ b/oap-libs-for-graalvm/log-analyzer-for-graalvm/pom.xml
@@ -36,6 +36,10 @@
<groupId>org.apache.skywalking</groupId>
<artifactId>log-analyzer</artifactId>
</dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
@@ -60,6 +64,7 @@
<exclude>org/apache/skywalking/oap/log/analyzer/dsl/DSL.class</exclude>
<exclude>org/apache/skywalking/oap/log/analyzer/dsl/DSL$*.class</exclude>
<exclude>org/apache/skywalking/oap/log/analyzer/provider/LogAnalyzerModuleConfig.class</exclude>
+
<exclude>org/apache/skywalking/oap/log/analyzer/provider/LALConfigs.class</exclude>
</excludes>
</filter>
</filters>
diff --git
a/oap-libs-for-graalvm/log-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/log/analyzer/dsl/DSL.java
b/oap-libs-for-graalvm/log-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/log/analyzer/dsl/DSL.java
index d7efe57..f4d357b 100644
---
a/oap-libs-for-graalvm/log-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/log/analyzer/dsl/DSL.java
+++
b/oap-libs-for-graalvm/log-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/log/analyzer/dsl/DSL.java
@@ -27,6 +27,7 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -49,6 +50,7 @@ import
org.apache.skywalking.oap.server.library.module.ModuleStartException;
public class DSL {
private static final String MANIFEST_PATH =
"META-INF/lal-scripts-by-hash.txt";
private static volatile Map<String, String> SCRIPT_MAP;
+ private static final AtomicInteger LOADED_COUNT = new AtomicInteger();
private final DelegatingScript script;
private final FilterSpec filterSpec;
@@ -70,7 +72,8 @@ public class DSL {
DelegatingScript script = (DelegatingScript)
scriptClass.getDeclaredConstructor().newInstance();
FilterSpec filterSpec = new FilterSpec(moduleManager, config);
script.setDelegate(filterSpec);
- log.debug("Loaded pre-compiled LAL script: {} -> {}",
dslHash.substring(0, 12), className);
+ int count = LOADED_COUNT.incrementAndGet();
+ log.debug("Loaded pre-compiled LAL script [{}/{}]: {}", count,
scriptMap.size(), className);
return new DSL(script, filterSpec);
} catch (ClassNotFoundException e) {
throw new ModuleStartException(
diff --git
a/oap-libs-for-graalvm/log-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/log/analyzer/provider/LALConfigs.java
b/oap-libs-for-graalvm/log-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/log/analyzer/provider/LALConfigs.java
new file mode 100644
index 0000000..04160f0
--- /dev/null
+++
b/oap-libs-for-graalvm/log-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/log/analyzer/provider/LALConfigs.java
@@ -0,0 +1,86 @@
+/*
+ * 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.skywalking.oap.log.analyzer.provider;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.oap.server.library.module.ModuleStartException;
+
+import static
org.apache.skywalking.oap.server.library.util.CollectionUtils.isEmpty;
+import static
org.apache.skywalking.oap.server.library.util.StringUtil.isNotBlank;
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * GraalVM replacement for upstream LALConfigs.
+ * Original:
skywalking/oap-server/analyzer/log-analyzer/src/main/java/.../provider/LALConfigs.java
+ * Repackaged into log-analyzer-for-graalvm via maven-shade-plugin (replaces
original .class in shaded JAR).
+ *
+ * Change: Complete rewrite of the static load() method. Loads pre-compiled
LAL config data from JSON
+ * manifests (META-INF/config-data/{path}.json) instead of filesystem YAML
files via ResourceUtils.getPathFiles().
+ * Why: The distro intentionally excludes raw YAML config directories — their
DSL scripts are pre-compiled
+ * at build time. Config data (rule names, DSL strings, layers) is serialized
as JSON by the precompiler.
+ */
+@Data
+@Slf4j
+public class LALConfigs {
+ private List<LALConfig> rules;
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+
+ public static List<LALConfigs> load(final String path, final List<String>
files) throws Exception {
+ if (isEmpty(files)) {
+ return Collections.emptyList();
+ }
+
+ checkArgument(isNotBlank(path), "path cannot be blank");
+
+ log.info("Loading LAL configs from pre-compiled distro ({})", path);
+
+ String resourcePath = "META-INF/config-data/" + path + ".json";
+ try (InputStream is =
LALConfigs.class.getClassLoader().getResourceAsStream(resourcePath)) {
+ if (is == null) {
+ throw new ModuleStartException(
+ "Pre-compiled config data not found: " + resourcePath
+ + ". Ensure the precompiler has been run.");
+ }
+
+ Map<String, LALConfigs> allConfigs = MAPPER.readValue(
+ is, new TypeReference<Map<String, LALConfigs>>() { });
+
+ List<LALConfigs> result = allConfigs.entrySet().stream()
+ .filter(e -> files.contains(e.getKey()))
+ .map(Map.Entry::getValue)
+ .collect(Collectors.toList());
+
+ log.info("Loaded {} pre-compiled LAL configs from {} (filtered
from {} available)",
+ result.size(), path, allConfigs.size());
+ return result;
+ } catch (ModuleStartException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ModuleStartException("Failed to load pre-compiled LAL
config rules", e);
+ }
+ }
+}
diff --git a/oap-libs-for-graalvm/meter-analyzer-for-graalvm/pom.xml
b/oap-libs-for-graalvm/meter-analyzer-for-graalvm/pom.xml
index 7ecc83a..3441483 100644
--- a/oap-libs-for-graalvm/meter-analyzer-for-graalvm/pom.xml
+++ b/oap-libs-for-graalvm/meter-analyzer-for-graalvm/pom.xml
@@ -36,6 +36,10 @@
<groupId>org.apache.skywalking</groupId>
<artifactId>meter-analyzer</artifactId>
</dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
@@ -61,6 +65,7 @@
<exclude>org/apache/skywalking/oap/meter/analyzer/dsl/DSL$*.class</exclude>
<exclude>org/apache/skywalking/oap/meter/analyzer/dsl/FilterExpression.class</exclude>
<exclude>org/apache/skywalking/oap/meter/analyzer/dsl/FilterExpression$*.class</exclude>
+
<exclude>org/apache/skywalking/oap/meter/analyzer/prometheus/rule/Rules.class</exclude>
</excludes>
</filter>
</filters>
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 cdf328d..d766fcf 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
@@ -25,6 +25,7 @@ import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
import lombok.extern.slf4j.Slf4j;
/**
@@ -40,6 +41,7 @@ import lombok.extern.slf4j.Slf4j;
public final class DSL {
private static final String MANIFEST_PATH =
"META-INF/mal-groovy-scripts.txt";
private static volatile Map<String, String> SCRIPT_MAP;
+ private static final AtomicInteger LOADED_COUNT = new AtomicInteger();
public static Expression parse(final String metricName, final String
expression) {
if (metricName == null) {
@@ -59,7 +61,8 @@ public final class DSL {
try {
Class<?> scriptClass = Class.forName(className);
DelegatingScript script = (DelegatingScript)
scriptClass.getDeclaredConstructor().newInstance();
- log.debug("Loaded pre-compiled MAL script: {} -> {}", metricName,
className);
+ int count = LOADED_COUNT.incrementAndGet();
+ log.debug("Loaded pre-compiled MAL script [{}/{}]: {}", count,
scriptMap.size(), metricName);
return new Expression(metricName, expression, script);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(
diff --git
a/oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/FilterExpression.java
b/oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/FilterExpression.java
index b3c14b9..7e38792 100644
---
a/oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/FilterExpression.java
+++
b/oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/FilterExpression.java
@@ -25,6 +25,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
+import java.util.concurrent.atomic.AtomicInteger;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
@@ -42,6 +43,7 @@ import lombok.extern.slf4j.Slf4j;
public class FilterExpression {
private static final String MANIFEST_PATH =
"META-INF/mal-filter-scripts.properties";
private static volatile Map<String, String> FILTER_MAP;
+ private static final AtomicInteger LOADED_COUNT = new AtomicInteger();
private final String literal;
private final Closure<Boolean> filterClosure;
@@ -62,7 +64,8 @@ public class FilterExpression {
Class<?> scriptClass = Class.forName(className);
Script filterScript = (Script)
scriptClass.getDeclaredConstructor().newInstance();
filterClosure = (Closure<Boolean>) filterScript.run();
- log.debug("Loaded pre-compiled filter script for: {}", literal);
+ int count = LOADED_COUNT.incrementAndGet();
+ log.debug("Loaded pre-compiled filter script [{}/{}]: {}", count,
filterMap.size(), literal);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(
"Pre-compiled filter script class not found: " + className, e);
diff --git
a/oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/meter/analyzer/prometheus/rule/Rules.java
b/oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/meter/analyzer/prometheus/rule/Rules.java
new file mode 100644
index 0000000..6bb802f
--- /dev/null
+++
b/oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/meter/analyzer/prometheus/rule/Rules.java
@@ -0,0 +1,113 @@
+/*
+ * 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.skywalking.oap.meter.analyzer.prometheus.rule;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.FileSystems;
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.oap.server.core.UnexpectedException;
+
+/**
+ * GraalVM replacement for upstream Rules.
+ * Original:
skywalking/oap-server/analyzer/meter-analyzer/src/main/java/.../prometheus/rule/Rules.java
+ * Repackaged into meter-analyzer-for-graalvm via maven-shade-plugin (replaces
original .class in shaded JAR).
+ *
+ * Change: Complete rewrite. Loads pre-compiled rule data from JSON manifests
+ * (META-INF/config-data/{path}.json) instead of filesystem YAML files via
ResourceUtils.getPath() + Files.walk().
+ * Why: The distro intentionally excludes raw YAML config directories — their
Groovy expressions are
+ * pre-compiled at build time. Rule data (metric prefixes, expressions, rule
names) is serialized as JSON
+ * by the precompiler for runtime wiring.
+ */
+@Slf4j
+public class Rules {
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+
+ public static List<Rule> loadRules(final String path) throws IOException {
+ return loadRules(path, Collections.emptyList());
+ }
+
+ public static List<Rule> loadRules(final String path, List<String>
enabledRules) throws IOException {
+ log.info("Loading rules from pre-compiled distro ({})", path);
+
+ String resourcePath = "META-INF/config-data/" + path + ".json";
+ try (InputStream is =
Rules.class.getClassLoader().getResourceAsStream(resourcePath)) {
+ if (is == null) {
+ throw new IOException(
+ "Pre-compiled config data not found: " + resourcePath
+ + ". Ensure the precompiler has been run.");
+ }
+
+ List<Rule> allRules = MAPPER.readValue(is, new
TypeReference<List<Rule>>() { });
+
+ // Apply glob matching on rule names — same logic as upstream
+ Map<String, Boolean> formedEnabledRules = enabledRules.stream()
+ .map(rule -> {
+ rule = rule.trim();
+ if (rule.startsWith("/")) {
+ rule = rule.substring(1);
+ }
+ if (!rule.endsWith(".yaml") && !rule.endsWith(".yml")) {
+ return rule + "{.yaml,.yml}";
+ }
+ return rule;
+ })
+ .collect(Collectors.toMap(rule -> rule, $ -> false, (a, b) ->
a));
+
+ List<Rule> filtered = allRules.stream()
+ .filter(rule -> {
+ // Match rule name (relative path without extension)
against enabled patterns
+ // Add .yaml suffix for glob matching (same as upstream
file-based matching)
+ Path rulePath = Path.of(rule.getName() + ".yaml");
+ return
formedEnabledRules.keySet().stream().anyMatch(pattern -> {
+ PathMatcher matcher = FileSystems.getDefault()
+ .getPathMatcher("glob:" + pattern);
+ boolean matches = matcher.matches(rulePath);
+ if (matches) {
+ formedEnabledRules.put(pattern, true);
+ }
+ return matches;
+ });
+ })
+ .collect(Collectors.toList());
+
+ if (formedEnabledRules.containsValue(false)) {
+ List<String> rulesNotFound =
formedEnabledRules.entrySet().stream()
+ .filter(e -> !e.getValue())
+ .map(Map.Entry::getKey)
+ .collect(Collectors.toList());
+ throw new UnexpectedException(
+ "Some configuration files of enabled rules are not found,
enabled rules: "
+ + rulesNotFound);
+ }
+
+ log.info("Loaded {} pre-compiled rules from {} (filtered from {}
available)",
+ filtered.size(), path, allRules.size());
+ return filtered;
+ }
+ }
+}
diff --git a/oap-libs-for-graalvm/pom.xml b/oap-libs-for-graalvm/pom.xml
index 51bedb6..5466c42 100644
--- a/oap-libs-for-graalvm/pom.xml
+++ b/oap-libs-for-graalvm/pom.xml
@@ -34,6 +34,7 @@
<modules>
<module>server-core-for-graalvm</module>
+ <module>library-module-for-graalvm</module>
<module>library-util-for-graalvm</module>
<module>meter-analyzer-for-graalvm</module>
<module>log-analyzer-for-graalvm</module>
diff --git a/oap-libs-for-graalvm/server-core-for-graalvm/pom.xml
b/oap-libs-for-graalvm/server-core-for-graalvm/pom.xml
index deba20d..4ccd3bd 100644
--- a/oap-libs-for-graalvm/server-core-for-graalvm/pom.xml
+++ b/oap-libs-for-graalvm/server-core-for-graalvm/pom.xml
@@ -66,6 +66,7 @@
<exclude>org/apache/skywalking/oap/server/core/analysis/meter/MeterSystem$*.class</exclude>
<exclude>org/apache/skywalking/oap/server/core/config/HierarchyDefinitionService.class</exclude>
<exclude>org/apache/skywalking/oap/server/core/config/HierarchyDefinitionService$*.class</exclude>
+
<exclude>org/apache/skywalking/oap/server/core/hierarchy/HierarchyService.class</exclude>
</excludes>
</filter>
</filters>
diff --git
a/oap-libs-for-graalvm/server-core-for-graalvm/src/main/java/org/apache/skywalking/oap/server/core/config/HierarchyDefinitionService.java
b/oap-libs-for-graalvm/server-core-for-graalvm/src/main/java/org/apache/skywalking/oap/server/core/config/HierarchyDefinitionService.java
index a946708..335f26c 100644
---
a/oap-libs-for-graalvm/server-core-for-graalvm/src/main/java/org/apache/skywalking/oap/server/core/config/HierarchyDefinitionService.java
+++
b/oap-libs-for-graalvm/server-core-for-graalvm/src/main/java/org/apache/skywalking/oap/server/core/config/HierarchyDefinitionService.java
@@ -17,12 +17,12 @@
package org.apache.skywalking.oap.server.core.config;
-import groovy.lang.Closure;
import java.io.FileNotFoundException;
import java.io.Reader;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
+import java.util.function.BiFunction;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.skywalking.oap.server.core.CoreModuleConfig;
@@ -37,54 +37,44 @@ import static java.util.stream.Collectors.toMap;
/**
* Same-FQCN replacement of upstream HierarchyDefinitionService.
*
- * <p>Replaces {@code GroovyShell.evaluate()} in {@code MatchingRule} with
- * pre-built Java-backed {@link Closure} objects. Eliminates runtime Groovy
- * compilation, which is not available in GraalVM native image.
+ * <p>Replaces {@code GroovyShell.evaluate()} with pre-built Java
+ * {@link BiFunction} implementations. Eliminates runtime Groovy
+ * compilation and the Groovy runtime dependency entirely.
*
- * <p>The 4 matching rules from {@code hierarchy-definition.yml} are
implemented
- * as anonymous {@code Closure<Boolean>} subclasses in {@link #RULE_REGISTRY}.
+ * <p>The 4 matching rules from {@code hierarchy-definition.yml} are
+ * implemented as lambda expressions in {@link #RULE_REGISTRY}.
* Unknown rule names fail fast at startup.
*/
@Slf4j
public class HierarchyDefinitionService implements
org.apache.skywalking.oap.server.library.module.Service {
/**
- * Pre-built matching rule closures keyed by rule name from
hierarchy-definition.yml.
- * Each closure takes two Service arguments (upper, lower) and returns
Boolean.
+ * Pre-built matching rules keyed by rule name from
hierarchy-definition.yml.
+ * Each function takes two Service arguments (upper, lower) and returns
Boolean.
*/
- private static final Map<String, Closure<Boolean>> RULE_REGISTRY;
+ private static final Map<String, BiFunction<Service, Service, Boolean>>
RULE_REGISTRY;
static {
RULE_REGISTRY = new HashMap<>();
// name: "{ (u, l) -> u.name == l.name }"
- RULE_REGISTRY.put("name", new Closure<Boolean>(null) {
- public Boolean doCall(final Service u, final Service l) {
- return Objects.equals(u.getName(), l.getName());
- }
- });
+ RULE_REGISTRY.put("name", (u, l) -> Objects.equals(u.getName(),
l.getName()));
// short-name: "{ (u, l) -> u.shortName == l.shortName }"
- RULE_REGISTRY.put("short-name", new Closure<Boolean>(null) {
- public Boolean doCall(final Service u, final Service l) {
- return Objects.equals(u.getShortName(), l.getShortName());
- }
- });
+ RULE_REGISTRY.put("short-name", (u, l) ->
Objects.equals(u.getShortName(), l.getShortName()));
// lower-short-name-remove-ns:
// "{ (u, l) -> { if(l.shortName.lastIndexOf('.') > 0)
// return u.shortName == l.shortName.substring(0,
l.shortName.lastIndexOf('.'));
// return false; } }"
- RULE_REGISTRY.put("lower-short-name-remove-ns", new
Closure<Boolean>(null) {
- public Boolean doCall(final Service u, final Service l) {
- int dot = l.getShortName().lastIndexOf('.');
- if (dot > 0) {
- return Objects.equals(
- u.getShortName(),
- l.getShortName().substring(0, dot));
- }
- return false;
+ RULE_REGISTRY.put("lower-short-name-remove-ns", (u, l) -> {
+ int dot = l.getShortName().lastIndexOf('.');
+ if (dot > 0) {
+ return Objects.equals(
+ u.getShortName(),
+ l.getShortName().substring(0, dot));
}
+ return false;
});
// lower-short-name-with-fqdn:
@@ -92,16 +82,14 @@ public class HierarchyDefinitionService implements
org.apache.skywalking.oap.ser
// return u.shortName.substring(0, u.shortName.lastIndexOf(':'))
// == l.shortName.concat('.svc.cluster.local');
// return false; } }"
- RULE_REGISTRY.put("lower-short-name-with-fqdn", new
Closure<Boolean>(null) {
- public Boolean doCall(final Service u, final Service l) {
- int colon = u.getShortName().lastIndexOf(':');
- if (colon > 0) {
- return Objects.equals(
- u.getShortName().substring(0, colon),
- l.getShortName() + ".svc.cluster.local");
- }
- return false;
+ RULE_REGISTRY.put("lower-short-name-with-fqdn", (u, l) -> {
+ int colon = u.getShortName().lastIndexOf(':');
+ if (colon > 0) {
+ return Objects.equals(
+ u.getShortName().substring(0, colon),
+ l.getShortName() + ".svc.cluster.local");
}
+ return false;
});
}
@@ -177,13 +165,13 @@ public class HierarchyDefinitionService implements
org.apache.skywalking.oap.ser
public static class MatchingRule {
private final String name;
private final String expression;
- private final Closure<Boolean> closure;
+ private final BiFunction<Service, Service, Boolean> matcher;
public MatchingRule(final String name, final String expression) {
this.name = name;
this.expression = expression;
- this.closure = RULE_REGISTRY.get(name);
- if (this.closure == null) {
+ this.matcher = RULE_REGISTRY.get(name);
+ if (this.matcher == null) {
throw new IllegalArgumentException(
"Unknown hierarchy matching rule: " + name
+ ". Known rules: " + RULE_REGISTRY.keySet());
diff --git
a/oap-libs-for-graalvm/server-core-for-graalvm/src/main/java/org/apache/skywalking/oap/server/core/hierarchy/HierarchyService.java
b/oap-libs-for-graalvm/server-core-for-graalvm/src/main/java/org/apache/skywalking/oap/server/core/hierarchy/HierarchyService.java
new file mode 100644
index 0000000..a2c0b16
--- /dev/null
+++
b/oap-libs-for-graalvm/server-core-for-graalvm/src/main/java/org/apache/skywalking/oap/server/core/hierarchy/HierarchyService.java
@@ -0,0 +1,222 @@
+/*
+ * 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.skywalking.oap.server.core.hierarchy;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.oap.server.core.CoreModule;
+import org.apache.skywalking.oap.server.core.CoreModuleConfig;
+import org.apache.skywalking.oap.server.core.analysis.Layer;
+import org.apache.skywalking.oap.server.core.analysis.TimeBucket;
+import org.apache.skywalking.oap.server.core.config.HierarchyDefinitionService;
+import
org.apache.skywalking.oap.server.core.hierarchy.instance.InstanceHierarchyRelation;
+import
org.apache.skywalking.oap.server.core.hierarchy.service.ServiceHierarchyRelation;
+import org.apache.skywalking.oap.server.core.query.MetadataQueryService;
+import org.apache.skywalking.oap.server.core.query.type.Service;
+import org.apache.skywalking.oap.server.core.source.SourceReceiver;
+import org.apache.skywalking.oap.server.library.module.ModuleManager;
+import
org.apache.skywalking.oap.server.library.util.RunnableWithExceptionProtection;
+
+/**
+ * Same-FQCN replacement of upstream HierarchyService.
+ *
+ * <p>Uses {@code MatchingRule.getMatcher().apply()} (BiFunction) instead of
+ * upstream's {@code getClosure().call()} (Groovy Closure), eliminating the
+ * Groovy runtime dependency.
+ */
+@Slf4j
+public class HierarchyService implements
org.apache.skywalking.oap.server.library.module.Service {
+ private final ModuleManager moduleManager;
+ private final boolean isEnableHierarchy;
+ private SourceReceiver sourceReceiver;
+ private MetadataQueryService metadataQueryService;
+ private Map<String, Map<String, HierarchyDefinitionService.MatchingRule>>
hierarchyDefinition;
+
+ public HierarchyService(ModuleManager moduleManager, CoreModuleConfig
moduleConfig) {
+ this.moduleManager = moduleManager;
+ this.isEnableHierarchy = moduleConfig.isEnableHierarchy();
+ }
+
+ private SourceReceiver getSourceReceiver() {
+ if (sourceReceiver == null) {
+ this.sourceReceiver =
moduleManager.find(CoreModule.NAME).provider().getService(SourceReceiver.class);
+ }
+ return sourceReceiver;
+
+ }
+
+ private Map<String, Map<String, HierarchyDefinitionService.MatchingRule>>
getHierarchyDefinition() {
+ if (hierarchyDefinition == null) {
+ hierarchyDefinition = moduleManager.find(CoreModule.NAME)
+ .provider()
+
.getService(HierarchyDefinitionService.class).getHierarchyDefinition();
+ }
+ return hierarchyDefinition;
+ }
+
+ private MetadataQueryService getMetadataQueryService() {
+ if (metadataQueryService == null) {
+ this.metadataQueryService = moduleManager.find(CoreModule.NAME)
+ .provider()
+
.getService(MetadataQueryService.class);
+ }
+ return metadataQueryService;
+ }
+
+ public void toServiceHierarchyRelation(String upperServiceName,
+ Layer upperServiceLayer,
+ String lowerServiceName,
+ Layer lowerServiceLayer) {
+ if (!this.isEnableHierarchy) {
+ return;
+ }
+ Map<String, HierarchyDefinitionService.MatchingRule> lowerLayers =
getHierarchyDefinition().get(upperServiceLayer.name());
+ if (lowerLayers == null ||
!lowerLayers.containsKey(lowerServiceLayer.name())) {
+ log.error("upperServiceLayer " + upperServiceLayer.name() + " or
lowerServiceLayer " + lowerServiceLayer.name()
+ + " is not defined in hierarchy-definition.yml.");
+ return;
+ }
+ autoMatchingServiceRelation(upperServiceName, upperServiceLayer,
lowerServiceName, lowerServiceLayer);
+ }
+
+ public void toInstanceHierarchyRelation(String upperInstanceName,
+ String upperServiceName,
+ Layer upperServiceLayer,
+ String lowerInstanceName,
+ String lowerServiceName,
+ Layer lowerServiceLayer) {
+ if (!this.isEnableHierarchy) {
+ return;
+ }
+ Map<String, HierarchyDefinitionService.MatchingRule> lowerLayers =
getHierarchyDefinition().get(upperServiceLayer.name());
+ if (lowerLayers == null ||
!lowerLayers.containsKey(lowerServiceLayer.name())) {
+ log.error("upperServiceLayer " + upperServiceLayer.name() + " or
lowerServiceLayer " + lowerServiceLayer.name()
+ + " is not defined in hierarchy-definition.yml.");
+ return;
+ }
+
+ buildInstanceHierarchyRelation(upperInstanceName, upperServiceName,
upperServiceLayer, lowerInstanceName,
+ lowerServiceName,
lowerServiceLayer);
+ }
+
+ public void startAutoMatchingServiceHierarchy() {
+ if (!this.isEnableHierarchy) {
+ return;
+ }
+ Executors.newSingleThreadScheduledExecutor()
+ .scheduleWithFixedDelay(
+ new
RunnableWithExceptionProtection(this::autoMatchingServiceRelation, t ->
log.error(
+ "Scheduled auto matching service hierarchy from
service traffic failure.", t)), 30, 20, TimeUnit.SECONDS);
+ }
+
+ private void autoMatchingServiceRelation(String upperServiceName,
+ Layer upperServiceLayer,
+ String lowerServiceName,
+ Layer lowerServiceLayer) {
+ ServiceHierarchyRelation serviceHierarchy = new
ServiceHierarchyRelation();
+ serviceHierarchy.setServiceName(upperServiceName);
+ serviceHierarchy.setServiceLayer(upperServiceLayer);
+ serviceHierarchy.setRelatedServiceName(lowerServiceName);
+ serviceHierarchy.setRelatedServiceLayer(lowerServiceLayer);
+
serviceHierarchy.setTimeBucket(TimeBucket.getMinuteTimeBucket(System.currentTimeMillis()));
+ this.getSourceReceiver().receive(serviceHierarchy);
+ }
+
+ private void buildInstanceHierarchyRelation(String upperInstanceName,
+ String upperServiceName,
+ Layer upperServiceLayer,
+ String lowerInstanceName,
+ String lowerServiceName,
+ Layer lowerServiceLayer) {
+ InstanceHierarchyRelation instanceHierarchy = new
InstanceHierarchyRelation();
+ instanceHierarchy.setInstanceName(upperInstanceName);
+ instanceHierarchy.setServiceName(upperServiceName);
+ instanceHierarchy.setServiceLayer(upperServiceLayer);
+ instanceHierarchy.setRelatedInstanceName(lowerInstanceName);
+ instanceHierarchy.setRelatedServiceName(lowerServiceName);
+ instanceHierarchy.setRelatedServiceLayer(lowerServiceLayer);
+
instanceHierarchy.setTimeBucket(TimeBucket.getMinuteTimeBucket(System.currentTimeMillis()));
+ this.getSourceReceiver().receive(instanceHierarchy);
+ }
+
+ private void autoMatchingServiceRelation() {
+ List<Service> allServices = getMetadataQueryService().listAllServices()
+ .values()
+ .stream()
+
.flatMap(List::stream)
+
.collect(Collectors.toList());
+ if (allServices.size() > 1) {
+ for (int i = 0; i < allServices.size(); i++) {
+ for (int j = i + 1; j < allServices.size(); j++) {
+ Service service = allServices.get(i);
+ Service comparedService = allServices.get(j);
+ String serviceLayer =
service.getLayers().iterator().next();
+ String comparedServiceLayer =
comparedService.getLayers().iterator().next();
+ Map<String, HierarchyDefinitionService.MatchingRule>
lowerLayers = getHierarchyDefinition().get(
+ serviceLayer);
+ Map<String, HierarchyDefinitionService.MatchingRule>
comparedLowerLayers = getHierarchyDefinition().get(
+ comparedServiceLayer);
+ if (lowerLayers != null &&
lowerLayers.get(comparedServiceLayer) != null) {
+ try {
+ if (lowerLayers.get(comparedServiceLayer)
+ .getMatcher()
+ .apply(service, comparedService)) {
+ autoMatchingServiceRelation(service.getName(),
Layer.nameOf(serviceLayer),
+
comparedService.getName(),
+
Layer.nameOf(comparedServiceLayer)
+ );
+ }
+ } catch (Throwable e) {
+ log.error(
+ "Auto matching service hierarchy from service
traffic failure. Upper layer {}, lower layer {}, expression {}",
+ serviceLayer,
+ comparedServiceLayer,
+
lowerLayers.get(comparedServiceLayer).getExpression(), e
+ );
+ }
+
+ } else if (comparedLowerLayers != null &&
comparedLowerLayers.get(serviceLayer) != null) {
+ try {
+ if (comparedLowerLayers.get(serviceLayer)
+ .getMatcher()
+ .apply(comparedService,
service)) {
+ autoMatchingServiceRelation(
+ comparedService.getName(),
+ Layer.nameOf(comparedServiceLayer),
+ service.getName(),
+ Layer.nameOf(serviceLayer)
+ );
+ }
+ } catch (Throwable e) {
+ log.error(
+ "Auto matching service hierarchy from service
traffic failure. Upper layer {}, lower layer {}, expression {}",
+ comparedServiceLayer,
+ serviceLayer,
+
comparedLowerLayers.get(serviceLayer).getExpression(), e
+ );
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/pom.xml b/pom.xml
index b9f3955..85d9fff 100644
--- a/pom.xml
+++ b/pom.xml
@@ -41,6 +41,12 @@
<checkstyle.version>6.18</checkstyle.version>
<checkstyle.fails.on.error>true</checkstyle.fails.on.error>
<junit.version>5.9.2</junit.version>
+ <!-- Versions aligned with upstream skywalking transitive deps -->
+ <groovy.version>5.0.3</groovy.version>
+ <commons-text.version>1.4</commons-text.version>
+ <joda-time.version>2.10.5</joda-time.version>
+ <mockito.version>5.11.0</mockito.version>
+ <powermock.version>2.0.9</powermock.version>
</properties>
<modules>
@@ -52,6 +58,22 @@
<dependencyManagement>
<dependencies>
+ <!-- Import upstream BOM to align third-party dependency versions
-->
+ <dependency>
+ <groupId>org.apache.skywalking</groupId>
+ <artifactId>oap-server-bom</artifactId>
+ <version>${skywalking.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+
+ <!-- Proto stubs (OpenTelemetry, Envoy, etc.) -->
+ <dependency>
+ <groupId>org.apache.skywalking</groupId>
+ <artifactId>receiver-proto</artifactId>
+ <version>${skywalking.version}</version>
+ </dependency>
+
<!-- Storage: BanyanDB -->
<dependency>
<groupId>org.apache.skywalking</groupId>
@@ -194,6 +216,11 @@
<artifactId>skywalking-async-profiler-receiver-plugin</artifactId>
<version>${skywalking.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.skywalking</groupId>
+ <artifactId>library-async-profiler-jfr-parser</artifactId>
+ <version>${skywalking.version}</version>
+ </dependency>
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>skywalking-pprof-receiver-plugin</artifactId>
@@ -336,6 +363,11 @@
<artifactId>server-core-for-graalvm</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.skywalking</groupId>
+ <artifactId>library-module-for-graalvm</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>library-util-for-graalvm</artifactId>
@@ -400,6 +432,27 @@
<scope>provided</scope>
</dependency>
+ <!--
+ Transitive deps of provided-scope upstream JARs that are
+ still needed at runtime (Groovy for pre-compiled MAL scripts,
+ commons-text for MetricConvert, joda-time for alarm formatting).
+ -->
+ <dependency>
+ <groupId>org.apache.groovy</groupId>
+ <artifactId>groovy</artifactId>
+ <version>${groovy.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-text</artifactId>
+ <version>${commons-text.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>joda-time</groupId>
+ <artifactId>joda-time</artifactId>
+ <version>${joda-time.version}</version>
+ </dependency>
+
<!-- Test -->
<dependency>
<groupId>org.junit.jupiter</groupId>
@@ -407,6 +460,24 @@
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-junit-jupiter</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-reflect</artifactId>
+ <version>${powermock.version}</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</dependencyManagement>