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 a5f4ad8 Move docs to docs/ directory, add website menu, configuration
reference, and README
a5f4ad8 is described below
commit a5f4ad8e20dc6ae1b51c9e6a88179ccbf1fbe178
Author: Wu Sheng <[email protected]>
AuthorDate: Fri Feb 27 08:41:17 2026 +0800
Move docs to docs/ directory, add website menu, configuration reference,
and README
- Move 5 root-level doc files (DISTRO-POLICY,
OAL/MAL/LAL/CONFIG-INIT-IMMIGRATION)
into docs/ with lowercase names as the canonical location
- Add docs/menu.yml for website generation
- Add docs/README.md as the website landing page
- Add docs/configuration.md with full module/setting/env-var reference
- Slim down root README.md to a brief overview linking to docs/
- Update CLAUDE.md references to point to docs/ paths
---
CLAUDE.md | 10 +-
CONFIG-INIT-IMMIGRATION.md | 253 --------------
DISTRO-POLICY.md | 322 ------------------
LAL-IMMIGRATION.md | 352 -------------------
MAL-IMMIGRATION.md | 817 ---------------------------------------------
OAL-IMMIGRATION.md | 162 ---------
README.md | 78 +----
docs/README.md | 111 ++++++
docs/configuration.md | 628 ++++++++++++++++++++++++++++++++++
docs/menu.yml | 34 ++
10 files changed, 788 insertions(+), 1979 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index ce04263..397b0ea 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -8,13 +8,13 @@
- `oap-graalvm-server/` — GraalVM-ready OAP server module (JVM distro) with
same-FQCN replacement classes and comprehensive test suites.
- `oap-graalvm-native/` — Native image module: `native-maven-plugin`
configuration, native-specific `log4j2.xml`, `log4j2-reflect-config.json`, and
native distribution assembly.
- `docker/` — `Dockerfile.native` (runtime image) and `docker-compose.yml`
(BanyanDB + OAP native).
-- `DISTRO-POLICY.md` — Build plan with phase tracking and module selection.
+- `docs/` — Documentation: distro-policy, configuration, OAL/MAL/LAL
immigration, config initialization.
- Root-level Maven + Makefile — Orchestrates building on top of the submodule.
## Key Principles
1. **Minimize upstream changes.** SkyWalking is a submodule. Changes to it
require separate upstream PRs and syncing back.
2. **Build-time class export.** All runtime code generation (OAL via
Javassist, MAL/LAL via Groovy) runs at build time. Export `.class` files into
native-image classpath.
-3. **Fixed module wiring.** Module/provider selection is hardcoded in this
distro — no SPI discovery. See DISTRO-POLICY.md for the full module table.
+3. **Fixed module wiring.** Module/provider selection is hardcoded in this
distro — no SPI discovery. See docs/distro-policy.md for the full module table.
4. **JDK 25.** Already compiles and runs.
## Technical Notes
@@ -24,13 +24,13 @@
- **Combination pattern**: Multiple YAML files from different data sources
(otel, telegraf, zabbix) may define metrics with the same name. The precompiler
assigns deterministic suffixes (`_1`, `_2`, etc.) and tracks expression hashes
for unambiguous resolution.
- **Same-FQCN replacement**: Classes in
`oap-libs-for-graalvm/*/src/main/java/` with the same fully-qualified class
name as upstream classes are repackaged via `maven-shade-plugin` (original
`.class` excluded). Used for `DSL.java`, `SampleFamily.java`,
`MeterSystem.java`, etc.
- **Classpath scanning**: Guava `ClassPath.from()` used in multiple places.
Run at build-time pre-compilation as verification gate, export static class
index.
-- **Config loading**: `YamlConfigLoaderUtils.copyProperties()` replaced with
same-FQCN version that uses Lombok setters instead of `Field.setAccessible()`.
See [CONFIG-INIT-IMMIGRATION.md](CONFIG-INIT-IMMIGRATION.md).
+- **Config loading**: `YamlConfigLoaderUtils.copyProperties()` replaced with
same-FQCN version that uses Lombok setters instead of `Field.setAccessible()`.
See [docs/config-init-immigration.md](docs/config-init-immigration.md).
- **Reflection metadata**: Precompiler auto-generates `reflect-config.json` by
scanning Armeria HTTP handlers, GraphQL resolvers/types, config POJOs, and
OAL/MAL/LAL manifests. `log4j2-reflect-config.json` is manually maintained for
Log4j2 plugin classes.
- **Native image**: `oap-graalvm-native` uses `native-maven-plugin` with
`-Pnative` profile. Console-only `log4j2.xml` avoids RollingFile reflection
chain. ~203MB binary, boots to full module init.
## Test Suites
- **MAL**: 71 YAML files covered by 73 test classes (1,281 assertions). See
`oap-graalvm-server/src/test/CLAUDE.md` for test generation instructions and
`oap-graalvm-server/src/test/MAL-COVERAGE.md` for coverage tracking.
-- **LAL**: 8 YAML files covered by 5 test classes (19 assertions). See
[LAL-IMMIGRATION.md](LAL-IMMIGRATION.md) for details.
+- **LAL**: 8 YAML files covered by 5 test classes (19 assertions). See
[docs/lal-immigration.md](docs/lal-immigration.md) for details.
Both suites use dual-path comparison: Path A (fresh GroovyShell compilation)
vs Path B (pre-compiled class from build-time JAR). Both paths must produce
identical results.
@@ -65,4 +65,4 @@ docker compose -f docker/docker-compose.yml up
- **Storage**: BanyanDB
- **Cluster**: Standalone, Kubernetes
- **Configuration**: Kubernetes
-- **Receivers/Query/Analyzers/Alarm/Telemetry/Other**: Full feature set (see
DISTRO-POLICY.md for details)
\ No newline at end of file
+- **Receivers/Query/Analyzers/Alarm/Telemetry/Other**: Full feature set (see
docs/distro-policy.md for details)
\ No newline at end of file
diff --git a/CONFIG-INIT-IMMIGRATION.md b/CONFIG-INIT-IMMIGRATION.md
deleted file mode 100644
index 3b96b61..0000000
--- a/CONFIG-INIT-IMMIGRATION.md
+++ /dev/null
@@ -1,253 +0,0 @@
-# Config Initialization — Eliminate Reflection in Config Loading
-
-## Context
-
-`YamlConfigLoaderUtils.copyProperties()` uses `Field.setAccessible(true)` +
-`field.set()` to populate `ModuleConfig` objects from YAML properties. This
-reflection pattern is problematic for GraalVM native image — every config field
-would require `reflect-config.json` entries, and `setAccessible()` is
restricted.
-
-Since our module/provider set is fixed (37 modules, see DISTRO-POLICY.md), we
-can generate hardcoded field-setting code at build time that eliminates all
-config-related reflection.
-
----
-
-## Problem
-
-In `ModuleDefine.prepare()` and in `BanyanDBConfigLoader`,
-`copyProperties()` iterates property names, looks up fields by name via
-reflection, and sets them:
-
-```java
-Field field = getDeclaredField(destClass, propertyName);
-field.setAccessible(true); // restricted in native image
-field.set(dest, value); // needs reflect-config.json
-```
-
-This requires:
-- `getDeclaredField()` with class hierarchy walk
-- `field.setAccessible(true)` to bypass private access
-- `field.set()` for every property
-
----
-
-## Solution: Same-FQCN Replacement of YamlConfigLoaderUtils
-
-Generate a replacement `YamlConfigLoaderUtils.java` with the same FQCN
-(`org.apache.skywalking.oap.server.library.util.YamlConfigLoaderUtils`) that
-dispatches by config object type and sets fields directly — no
-`Field.setAccessible()`, no `getDeclaredField()` scan.
-
-This is one of 23 same-FQCN replacement classes in the distro (see
DISTRO-POLICY.md for full list).
-
-### Field Access Strategy (per field)
-
-| Strategy | Condition | Example |
-|---|---|---|
-| **Lombok setter** | All non-final fields (class-level `@Setter` added via
`-for-graalvm` modules) | `cfg.setRole((String) value)` |
-| **Getter + clear + addAll** | Final collection field |
`cfg.getDownsampling().clear(); cfg.getDownsampling().addAll((List) value)` |
-| **Error** | Unknown config type | `throw new
IllegalArgumentException("Unknown config type: ...")` |
-
-All config classes that previously lacked `@Setter` now have it added via
-same-FQCN replacement classes in the `-for-graalvm` modules. No VarHandle, no
-reflection fallback. The generator fails at build time if any non-final field
-lacks a setter.
-
-### Generated Code Structure
-
-```java
-package org.apache.skywalking.oap.server.library.util;
-
-public class YamlConfigLoaderUtils {
- // No VarHandle, no reflection. Pure setter-based.
-
- // Type-dispatch: check instanceof, delegate to type-specific method
- public static void copyProperties(Object dest, Properties src,
- String moduleName, String providerName)
- throws IllegalAccessException {
- if (dest instanceof CoreModuleConfig) {
- copyToCoreModuleConfig((CoreModuleConfig) dest, src, moduleName,
providerName);
- } else if (dest instanceof ClusterModuleKubernetesConfig) {
-
copyToClusterModuleKubernetesConfig((ClusterModuleKubernetesConfig) dest, src,
moduleName, providerName);
- }
- // ... all config types ...
- else {
- throw new IllegalArgumentException("Unknown config type: " +
dest.getClass().getName());
- }
- }
-
- // Per-type: switch on property name, set via Lombok setter
- private static void copyToCoreModuleConfig(
- CoreModuleConfig cfg, Properties src,
- String moduleName, String providerName) {
- // iterate properties
- switch (key) {
- case "role":
- cfg.setRole((String) value);
- break;
- case "persistentPeriod":
- cfg.setPersistentPeriod((int) value);
- break;
- // ... all fields via setters ...
- default:
- log.warn("{} setting is not supported in {} provider of {}
module",
- key, providerName, moduleName);
- break;
- }
- }
- // ... one method per config type ...
-}
-```
-
----
-
-## Build Tool
-
-**Module**: `build-tools/config-generator`
-
-**Main class**: `ConfigInitializerGenerator.java`
-
-### Input
-- Classpath with all SkyWalking module JARs (same dependencies as
`oap-graalvm-server`)
-- Provider class list — derived from the fixed module table in
`GraalVMOAPServerStartUp`
-- Extra config class list — BanyanDB nested configs used by
`BanyanDBConfigLoader`
-
-### Process
-1. For each provider, call `newConfigCreator().type()` to discover the config
class
-2. For each config class, use Java reflection to scan all declared fields
- (walking up to `ModuleConfig` superclass)
-3. For each field, check if a setter method exists (`set` + capitalize(name))
-4. **Fail if any non-final field lacks a setter** (all config classes must
have `@Setter`)
-5. Generate `YamlConfigLoaderUtils.java` with type-dispatch + switch-based
field assignment
-
-The generator depends on `-for-graalvm` modules (not upstream JARs) so it sees
-config classes with `@Setter` added. Original upstream JARs are forced to
-`provided` scope to prevent classpath shadowing.
-
-### Output
--
`oap-graalvm-server/src/main/java/org/apache/skywalking/oap/server/library/util/YamlConfigLoaderUtils.java`
- (same-FQCN replacement)
-
-### Running the generator
-```bash
-JAVA_HOME=/Users/wusheng/.sdkman/candidates/java/25-graal \
- mvn -pl build-tools/config-generator exec:java \
-
-Dexec.args="oap-graalvm-server/src/main/java/org/apache/skywalking/oap/server/library/util/YamlConfigLoaderUtils.java"
-```
-
----
-
-## Config Classes Inventory
-
-37 modules registered in `GraalVMOAPServerStartUp`. 1 (`AlarmModule`) has null
-ConfigCreator. The rest need coverage.
-
-### Provider Config Classes
-
-| Provider | Config Class | Fields | Setter Status |
-|---|---|---|---|
-| CoreModuleProvider | `CoreModuleConfig` | ~40 | `@Getter` class-level,
`@Setter` on ~12 fields, rest need VarHandle |
-| BanyanDBStorageProvider | `BanyanDBStorageConfig` | ~5 | `@Getter @Setter` |
-| ClusterModuleStandaloneProvider | (empty config) | 0 | — |
-| ClusterModuleKubernetesProvider | `ClusterModuleKubernetesConfig` | 3 |
`@Setter` |
-| ConfigmapConfigurationProvider | `ConfigmapConfigurationSettings` | 3 |
`@Setter` |
-| PrometheusTelemetryProvider | `PrometheusConfig` | 5 | `@Setter` |
-| AnalyzerModuleProvider | `AnalyzerModuleConfig` | ~10 | `@Getter @Setter` |
-| LogAnalyzerModuleProvider | `LogAnalyzerModuleConfig` | 2 | `@Setter` |
-| SharingServerModuleProvider | `SharingServerConfig` | ~15 | Needs check |
-| EnvoyMetricReceiverProvider | `EnvoyMetricReceiverConfig` | ~15 | Needs
check |
-| KafkaFetcherProvider | `KafkaFetcherConfig` | ~20 | `@Data` (all setters) |
-| ZipkinReceiverProvider | `ZipkinReceiverConfig` | ~20 | Needs check |
-| GraphQLQueryProvider | `GraphQLQueryConfig` | 4 | `@Setter` |
-| HealthCheckerProvider | `HealthCheckerConfig` | 1 | `@Setter` |
-| + others | (simple configs) | 0-5 | Various |
-
-### BanyanDB Config Loading (BanyanDBConfigLoader)
-
-`BanyanDBConfigLoader` (in `storage-banyandb-plugin`) loads config from
`bydb.yml`
-and `bydb-topn.yml` independently of the standard YAML config loading path. It
-calls `copyProperties()` on nested config objects, so the generated
-`YamlConfigLoaderUtils` must handle all BanyanDB inner classes.
-
-**`loadBaseConfig()`** — reads `bydb.yml`, calls `copyProperties()` 11 times:
-
-| Call target | Type | Handler | Fields |
-|---|---|---|---|
-| `config.getGlobal()` | `Global` | `copyToGlobal()` | 18 (targets,
maxBulkSize, flushInterval, ...) |
-| `config.getRecordsNormal()` | `RecordsNormal` | `copyToRecordsNormal()` | 8
(inherited from GroupResource) |
-| `config.getRecordsLog()` | `RecordsLog` | `copyToRecordsLog()` | 8 |
-| `config.getTrace()` | `Trace` | `copyToTrace()` | 8 |
-| `config.getZipkinTrace()` | `ZipkinTrace` | `copyToZipkinTrace()` | 8 |
-| `config.getRecordsBrowserErrorLog()` | `RecordsBrowserErrorLog` |
`copyToRecordsBrowserErrorLog()` | 8 |
-| `config.getMetricsMin()` | `MetricsMin` | `copyToMetricsMin()` | 8 |
-| `config.getMetricsHour()` | `MetricsHour` | `copyToMetricsHour()` | 8 |
-| `config.getMetricsDay()` | `MetricsDay` | `copyToMetricsDay()` | 8 |
-| `config.getMetadata()` | `Metadata` | `copyToMetadata()` | 8 |
-| `config.getProperty()` | `Property` | `copyToProperty()` | 8 |
-
-**`copyStages()`** — creates warm/cold `Stage` objects, calls
`copyProperties()` on each:
-
-| Call target | Type | Handler | Fields |
-|---|---|---|---|
-| warm Stage | `Stage` | `copyToStage()` | 7 (name, nodeSelector, shardNum,
segmentInterval, ttl, replicas, close) |
-| cold Stage | `Stage` | `copyToStage()` | 7 |
-
-**`loadTopNConfig()`** — reads `bydb-topn.yml`, populates `TopN` objects via
direct
-setter calls (`topN.setName()`, `topN.setGroupByTagNames()`, etc.). Does NOT
call
-`copyProperties()` — no handler needed.
-
-**Class hierarchy note**: All group config classes (`RecordsNormal`, `Trace`,
-`MetricsMin`, etc.) extend `GroupResource`. The generator walks the class
hierarchy
-from each subclass up to `GroupResource` and generates setter calls for all 8
-inherited fields (`shardNum`, `segmentInterval`, `ttl`, `replicas`,
-`enableWarmStage`, `enableColdStage`, `defaultQueryStages`,
-`additionalLifecycleStages`). `Trace` doesn't have its own `@Getter @Setter`
but
-inherits all setters from `GroupResource`.
-
-**`instanceof` ordering**: Inner classes (`Global`, `RecordsNormal`, `Trace`,
etc.)
-are static inner classes that do NOT extend `BanyanDBStorageConfig`, so the
-`instanceof BanyanDBStorageConfig` check matches only the top-level config.
Inner
-class checks come after and work independently.
-
-Two extra inner classes (`RecordsTrace`, `RecordsZipkinTrace`) are included in
-EXTRA_CONFIG_CLASSES for completeness, even though `BanyanDBConfigLoader`
doesn't
-currently call `copyProperties()` on them.
-
----
-
-## Same-FQCN Replacements (Config Initialization)
-
-| Upstream Class | Upstream Location | Replacement Location | What Changed |
-|---|---|---|---|
-| `YamlConfigLoaderUtils` |
`server-library/library-util/.../util/YamlConfigLoaderUtils.java` |
`oap-graalvm-server/` (not in library-util-for-graalvm due to 30+ cross-module
imports) | Complete rewrite. Uses type-dispatch with Lombok `@Setter` methods
instead of `Field.setAccessible()` + `field.set()`. |
-| `CoreModuleConfig` | `server-core/.../core/CoreModuleConfig.java` |
`oap-libs-for-graalvm/server-core-for-graalvm/` | Added `@Setter` at class
level. Upstream only has `@Getter`. |
-| `AnalyzerModuleConfig` |
`analyzer/agent-analyzer/.../provider/AnalyzerModuleConfig.java` |
`oap-libs-for-graalvm/agent-analyzer-for-graalvm/` | Added `@Setter` at class
level. |
-| `LogAnalyzerModuleConfig` |
`analyzer/log-analyzer/.../provider/LogAnalyzerModuleConfig.java` |
`oap-libs-for-graalvm/log-analyzer-for-graalvm/` | Added `@Setter` at class
level. |
-| `EnvoyMetricReceiverConfig` |
`server-receiver-plugin/envoy-metrics-receiver-plugin/.../EnvoyMetricReceiverConfig.java`
| `oap-libs-for-graalvm/envoy-metrics-receiver-for-graalvm/` | Added `@Setter`
at class level. |
-| `OtelMetricReceiverConfig` |
`server-receiver-plugin/otel-receiver-plugin/.../OtelMetricReceiverConfig.java`
| `oap-libs-for-graalvm/otel-receiver-for-graalvm/` | Added `@Setter` at class
level. |
-| `EBPFReceiverModuleConfig` |
`server-receiver-plugin/skywalking-ebpf-receiver-plugin/.../EBPFReceiverModuleConfig.java`
| `oap-libs-for-graalvm/ebpf-receiver-for-graalvm/` | Added `@Setter` at class
level. |
-| `AWSFirehoseReceiverModuleConfig` |
`server-receiver-plugin/aws-firehose-receiver/.../AWSFirehoseReceiverModuleConfig.java`
| `oap-libs-for-graalvm/aws-firehose-receiver-for-graalvm/` | Added `@Setter`
at class level. |
-| `CiliumFetcherConfig` |
`server-fetcher-plugin/cilium-fetcher-plugin/.../CiliumFetcherConfig.java` |
`oap-libs-for-graalvm/cilium-fetcher-for-graalvm/` | Added `@Setter` at class
level. |
-| `StatusQueryConfig` |
`server-query-plugin/status-query-plugin/.../StatusQueryConfig.java` |
`oap-libs-for-graalvm/status-query-for-graalvm/` | Added `@Setter` at class
level. |
-| `HealthCheckerConfig` | `server-health-checker/.../HealthCheckerConfig.java`
| `oap-libs-for-graalvm/health-checker-for-graalvm/` | Added `@Setter` at class
level. |
-
-Config replacements (except `YamlConfigLoaderUtils`) are repackaged into their
respective `-for-graalvm` modules via `maven-shade-plugin`.
`YamlConfigLoaderUtils` lives in `oap-graalvm-server` because it imports types
from 30+ modules; the original `.class` is excluded from
`library-util-for-graalvm` via shade filter.
-
-## Same-FQCN Packaging
-
-The original `YamlConfigLoaderUtils.class` is excluded from the
`library-util-for-graalvm` shaded JAR. The replacement in
`oap-graalvm-server.jar` is the only copy on the classpath.
-
----
-
-## Verification
-
-```bash
-# Run generator
-JAVA_HOME=/Users/wusheng/.sdkman/candidates/java/25-graal \
- mvn -pl build-tools/config-generator exec:java \
-
-Dexec.args="oap-graalvm-server/src/main/java/org/apache/skywalking/oap/server/library/util/YamlConfigLoaderUtils.java"
-
-# Full build (compile + test + package)
-JAVA_HOME=/Users/wusheng/.sdkman/candidates/java/25-graal make build-distro
-```
diff --git a/DISTRO-POLICY.md b/DISTRO-POLICY.md
deleted file mode 100644
index c192373..0000000
--- a/DISTRO-POLICY.md
+++ /dev/null
@@ -1,322 +0,0 @@
-# SkyWalking GraalVM Distro - Distribution Policy
-
-## Goal
-Build and package Apache SkyWalking OAP server as a GraalVM native image on
JDK 25.
-
-## Architecture Constraints
-- **Submodule**: `skywalking/` is a git submodule of `apache/skywalking.git`.
All SkyWalking source changes go through upstream PRs. **Minimize upstream
changes.**
-- **This repo**: Maven + Makefile to orchestrate building on top of the
submodule. Pre-compilation, GraalVM config, native-image wiring, and the fixed
module manager live here.
-- **JDK 25**: Already compiles and runs. Not an issue.
-
-## Module Selection (Fixed at Build Time)
-
-| Category | Module | Provider |
-|----------|--------|----------|
-| **Core** | CoreModule | default |
-| **Storage** | StorageModule | BanyanDB |
-| **Cluster** | ClusterModule | Standalone, Kubernetes |
-| **Configuration** | ConfigurationModule | Kubernetes |
-| **Receivers** | SharingServerModule, TraceModule, JVMModule,
MeterReceiverModule, LogModule, RegisterModule, ProfileModule, BrowserModule,
EventModule, OtelMetricReceiverModule, MeshReceiverModule,
EnvoyMetricReceiverModule, ZipkinReceiverModule, ZabbixReceiverModule,
TelegrafReceiverModule, AWSFirehoseReceiverModule, CiliumFetcherModule,
EBPFReceiverModule, AsyncProfilerModule, PprofModule, CLRModule,
ConfigurationDiscoveryModule, KafkaFetcherModule | default providers |
-| **Analyzers** | AnalyzerModule, LogAnalyzerModule, EventAnalyzerModule |
default providers |
-| **Query** | QueryModule (GraphQL), PromQLModule, LogQLModule,
ZipkinQueryModule, StatusQueryModule | default providers |
-| **Alarm** | AlarmModule | default |
-| **Telemetry** | TelemetryModule | Prometheus |
-| **Other** | ExporterModule, HealthCheckerModule, AIPipelineModule | default
providers |
-
-**Full feature set.** Work around issues as they arise.
-
----
-
-## Core Strategy
-
-1. **Build-Time Class Export**: All runtime code generation (OAL via
Javassist, MAL/LAL via Groovy) runs at build time. Export `.class` files and
package into native-image classpath. Classpath scanning also runs here as a
verification gate.
-
-2. **Fixed Module Wiring**: Module/provider selection is hardcoded in this
distro (no SPI discovery). Simplified config file for selected providers only.
-
-3. **Separation**: SkyWalking upstream changes tracked separately, go through
upstream PRs.
-
----
-
-## OAL Runtime Class Generation (Javassist)
-
-### What Happens
-OAL V2 generates metrics/builder/dispatcher classes at startup via Javassist
(`ClassPool.makeClass()` → `CtClass.toClass()`). Already has
`writeGeneratedFile()` for debug export.
-
-### Approach (this repo)
-All `.oal` scripts are known. Run OAL engine at build time, export `.class`
files, load them directly at runtime from manifests.
-
-**Details**: [OAL-IMMIGRATION.md](OAL-IMMIGRATION.md)
-
-### What Was Built
-- `OALClassExporter` processes all 9 OAL defines, exports ~620 metrics
classes, ~620 builder classes, ~45 dispatchers
-- 3 manifest files: `oal-metrics-classes.txt`, `oal-dispatcher-classes.txt`,
`oal-disabled-sources.txt`
-- Same-FQCN replacement `OALEngineLoaderService` loads pre-compiled classes
from manifests instead of running Javassist
-
-### Upstream Changes Needed
-- None. Build-time class export works via existing debug API
(`setOpenEngineDebug(true)` + `setGeneratedFilePath()`)
-
----
-
-## MAL and LAL (Groovy + Javassist)
-
-### What Happens
-- MAL uses `GroovyShell` + `DelegatingScript` for meter rule expressions
(~1250 rules across 71 YAML files). Also, `MeterSystem.create()` uses Javassist
to dynamically generate one meter subclass per metric rule.
-- LAL uses `GroovyShell` + `@CompileStatic` + `LALPrecompiledExtension` for
log analysis scripts (10 rules).
-
-### Approach (this repo)
-Run full MAL/LAL initialization at build time via `build-tools/precompiler`
(unified tool). Export Javassist-generated `.class` files. Transpile all Groovy
expressions to pure Java at build time — zero Groovy at runtime.
-
-**Details**: [MAL-IMMIGRATION.md](MAL-IMMIGRATION.md) |
[LAL-IMMIGRATION.md](LAL-IMMIGRATION.md)
-
-### What Was Built
-- **Unified precompiler** (`build-tools/precompiler`): Replaced separate
`oal-exporter` and `mal-compiler` modules. Compiles all 71 MAL YAML rule files
(meter-analyzer-config, otel-rules, log-mal-rules, envoy-metrics-rules,
telegraf-rules, zabbix-rules) producing 1209 meter classes.
-- **MAL-to-Java transpiler**: 1250+ MAL expressions transpiled from Groovy AST
to pure Java `MalExpression` implementations. 29 filter expressions transpiled
to `MalFilter` implementations. Zero Groovy at runtime.
-- **LAL-to-Java transpiler**: 10 LAL scripts (6 unique) transpiled to pure
Java `LalExpression` implementations. Spec classes enhanced with `Consumer`
overloads for transpiled code.
-- **Groovy stubs module**: Minimal `groovy.lang.*` types (Binding, Closure,
etc.) for class loading. No `org.codehaus.groovy.*` — prevents GraalVM
`GroovyIndyInterfaceFeature` from activating.
-- **Manifests**: `META-INF/mal-expressions.txt` (transpiled Java classes),
`META-INF/mal-groovy-expression-hashes.txt` (SHA-256 for combination pattern
resolution), `META-INF/mal-meter-classes.txt` (Javassist-generated classes),
`META-INF/lal-expressions.txt` (transpiled LAL classes),
`META-INF/annotation-scan/MeterFunction.txt` (16 function classes).
-- **Combination pattern**: Multiple YAML files from different data sources
(otel, telegraf, zabbix) may define metrics with the same name. Deterministic
suffixes (`_1`, `_2`) with expression hash tracking enable unambiguous
resolution.
-- **Same-FQCN replacements**: `DSL.java` (MAL), `DSL.java` (LAL),
`FilterExpression.java`, `MeterSystem.java`, `Expression.java`,
`SampleFamily.java` — all use pure Java, no Groovy.
-- **Comparison test suite**: 73 MAL test classes (1281 assertions) + 5 LAL
test classes (19 assertions) covering all 79 YAML files. Tests require data
flow through full pipeline (no vacuous agreements). Dual-path: fresh Groovy
compilation (Path A) vs transpiled Java (Path B).
-
-### Groovy Elimination
-- MAL: `MalExpression` interface replaces `DelegatingScript`. `SampleFamily`
uses Java functional interfaces (`TagFunction`, `SampleFilter`,
`ForEachFunction`, `DecorateFunction`, `PropertiesExtractor`) instead of
`groovy.lang.Closure`.
-- LAL: `LalExpression` interface replaces `DelegatingScript`. Spec classes
have `Consumer` overloads.
-- No `groovy.lang.Closure` in any production source code. Groovy is test-only
dependency.
-
----
-
-## Classpath Scanning (Guava ClassPath)
-
-### What Happens
-`ClassPath.from()` used in `SourceReceiverImpl.scan()`, `AnnotationScan`,
`MeterSystem`, `DefaultMetricsFunctionRegistry`, `FilterMatchers`,
`MetricsHolder`.
-
-### What Was Solved
-- `AnnotationScan` and `SourceReceiverImpl` replaced with same-FQCN classes
that read from build-time manifests. 6 annotation/interface manifests under
`META-INF/annotation-scan/`: `ScopeDeclaration`, `Stream`, `Disable`,
`MultipleDisable`, `SourceDispatcher`, `ISourceDecorator`.
-- `DefaultMetricsFunctionRegistry`, `FilterMatchers`, `MetricsHolder` — these
only run inside the OAL engine at build time, not at runtime. Automatically
solved.
-- `MeterSystem` replaced with same-FQCN class that reads from
`META-INF/annotation-scan/MeterFunction.txt` manifest (16 meter function
classes). Solved as part of MAL immigration.
-
----
-
-## Module System & Configuration
-
-### Current Behavior
-`ModuleManager` uses `ServiceLoader` (SPI). `application.yml` selects
providers. Config loaded via reflection (`Field.setAccessible` + `field.set` in
`YamlConfigLoaderUtils.copyProperties`).
-
-### Approach (this repo)
-1. **New module manager**: Directly constructs chosen
`ModuleDefine`/`ModuleProvider` — no SPI
-2. **Simplified config file**: Only knobs for selected providers
-3. **Config loading**: **No reflection.** Build-time tool scans all
`ModuleConfig` subclass fields → generates same-FQCN replacement of
`YamlConfigLoaderUtils` that uses Lombok setters and VarHandle to set config
fields directly. Eliminates `Field.setAccessible`/`field.set` and the need for
`reflect-config.json` for config classes.
-
-**Details**: [CONFIG-INIT-IMMIGRATION.md](CONFIG-INIT-IMMIGRATION.md)
-
-### What Was Built
-- `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 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
-
----
-
-## Same-FQCN Packaging (Repackaged Modules)
-
-### Problem
-
-Same-FQCN replacement classes need to shadow upstream originals. Classpath
ordering tricks confuse developers and AI tools.
-
-### Solution: Per-JAR Repackaged Modules (`oap-libs-for-graalvm`)
-
-Each upstream JAR that has replacement classes gets a corresponding
`*-for-graalvm` module under `oap-libs-for-graalvm/`. The module uses
`maven-shade-plugin` to:
-1. Include only the upstream JAR in the shade
-2. Exclude the specific `.class` files being replaced
-3. Produce a JAR containing: all upstream classes MINUS replaced ones PLUS our
replacements
-
-`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.
-
-### 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`, `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):**
-
-| Module | Replacement Class |
-|---|---|
-| `envoy-metrics-receiver-for-graalvm` | `EnvoyMetricReceiverConfig` |
-| `otel-receiver-for-graalvm` | `OtelMetricReceiverConfig` |
-| `ebpf-receiver-for-graalvm` | `EBPFReceiverModuleConfig` |
-| `aws-firehose-receiver-for-graalvm` | `AWSFirehoseReceiverModuleConfig` |
-| `cilium-fetcher-for-graalvm` | `CiliumFetcherConfig` |
-| `status-query-for-graalvm` | `StatusQueryConfig` |
-| `health-checker-for-graalvm` | `HealthCheckerConfig` |
-
-### No Classpath Ordering Required
-
-No duplicate FQCNs on the classpath. The startup script (`oapService.sh`) uses
a simple flat classpath. The `oap-graalvm-native` uber JAR also has no FQCN
conflicts.
-
-### Adding New Replacements
-
-To add a new same-FQCN replacement:
-1. Create a new `*-for-graalvm` module under `oap-libs-for-graalvm/` (or add
to existing one)
-2. Add the replacement `.java` file with the same FQCN
-3. Configure shade plugin to exclude the original `.class` from the upstream
JAR
-4. Add the `-for-graalvm` artifact to root `pom.xml` `<dependencyManagement>`
-5. In `oap-graalvm-server/pom.xml`: add the original JAR to
`<dependencyManagement>` as `provided`, add `-for-graalvm` to `<dependencies>`
-6. Add the original JAR to `distribution.xml` `<excludes>`
-
----
-
-## Additional GraalVM Risks
-
-| Risk | Status | Mitigation |
-|------|--------|------------|
-| **Reflection** (annotations, OAL enricher, HTTP handlers, GraphQL types) |
SOLVED | Auto-generated by precompiler from manifests;
`log4j2-reflect-config.json` for Log4j2 plugins |
-| **gRPC / Netty / Armeria** | SOLVED | GraalVM reachability metadata repo
handles these automatically |
-| **Resource loading** (`ResourceUtils`, config files) | SOLVED |
`resource-config.json` via tracing agent |
-| **Log4j2** | SOLVED | Console-only `log4j2.xml` avoids RollingFile
reflection chain; Log4j2 plugin classes in `log4j2-reflect-config.json` |
-| **Kafka client** (for Kafka fetcher) | Untested | Known GraalVM support, may
need config |
-| **Kubernetes client 6.7.1** (for cluster + config) | Untested | Has GraalVM
support, may need config at runtime |
-
----
-
-## Distro Resource Files
-
-Upstream `server-starter/src/main/resources/` contains 236 files. They fall
into
-two categories: files included directly in the distro `config/` directory
(loaded
-at runtime via file I/O), and files consumed by the precompiler at build time
-(not needed at runtime — their logic is baked into pre-compiled `.class`
files).
-
-### Directly Included in Distro (`config/`)
-
-These files are loaded at runtime via `ResourceUtils.read()`, `Files.walk()`,
or
-YAML parsing. No reflection involved — safe for GraalVM native image as-is.
-
-| File / Directory | Count | Loaded By | Purpose |
-|---|---|---|---|
-| `application.yml` | 1 | Custom (distro's own, not upstream) |
Module/provider config |
-| `bydb.yml` | 1 | `BanyanDBConfigLoader` | BanyanDB storage base config |
-| `bydb-topn.yml` | 1 | `BanyanDBConfigLoader` | BanyanDB TopN aggregation
config |
-| `log4j2.xml` | 1 | Log4j2 framework | Logging configuration |
-| `alarm-settings.yml` | 1 | `AlarmModuleProvider` via `ResourceUtils.read()`
| Alarm rules |
-| `component-libraries.yml` | 1 | `ComponentLibraryCatalogService` via
`ResourceUtils.read()` | Component ID mapping |
-| `endpoint-name-grouping.yml` | 1 | `EndpointNameGroupingRuleWatcher` via
`ResourceUtils.read()` | Endpoint grouping rules |
-| `gateways.yml` | 1 | `UninstrumentedGatewaysConfig` via
`ResourceUtils.read()` | Gateway definitions |
-| `hierarchy-definition.yml` | 1 | `HierarchyDefinitionService` via
`ResourceUtils.read()` | Layer hierarchy |
-| `metadata-service-mapping.yaml` | 1 | `ResourceUtils.read()` | Metadata
service mapping |
-| `service-apdex-threshold.yml` | 1 | `ApdexThresholdConfig` via
`ResourceUtils.read()` | APDEX thresholds |
-| `trace-sampling-policy-settings.yml` | 1 | `TraceSamplingPolicyWatcher` via
`ResourceUtils.read()` | Trace sampling |
-| `ui-initialized-templates/**` | 131 | `UITemplateInitializer` via
`Files.walk()` | UI dashboard JSON templates |
-| `cilium-rules/**` | 2 | `CiliumFetcherProvider` via
`ResourceUtils.getPathFiles()` | Cilium flow rules |
-| `openapi-definitions/**` | 1 | `EndpointNameGrouping` via
`ResourceUtils.getPathFiles()` | OpenAPI grouping definitions |
-
-**Total: 146 files** included in the distro `config/` directory.
-
-### Pre-compiled at Build Time (NOT in distro)
-
-These files are consumed by `build-tools/precompiler` during the build. Their
-expressions, scripts, and metric definitions are compiled into `.class` files
-packaged in JARs. The YAML source files are not needed at runtime.
-
-| Category | Count | Pre-compiled Into | Tool |
-|---|---|---|---|
-| `oal/*.oal` | 9 | ~620 metrics classes + ~620 builders + ~45 dispatchers
(Javassist) | `OALClassExporter` |
-| `meter-analyzer-config/*.yaml` | 11 | 147 Groovy scripts + Javassist meter
classes | `MALPrecompiler` |
-| `otel-rules/**/*.yaml` | 55 | 1044 Groovy scripts + Javassist meter classes
| `MALPrecompiler` |
-| `log-mal-rules/*.yaml` | 2 | 2 Groovy scripts | `MALPrecompiler` |
-| `envoy-metrics-rules/*.yaml` | 2 | 26 Groovy scripts + Javassist meter
classes | `MALPrecompiler` |
-| `telegraf-rules/*.yaml` | 1 | 20 Groovy scripts + Javassist meter classes |
`MALPrecompiler` |
-| `zabbix-rules/*.yaml` | 1 | 15 Groovy scripts + Javassist meter classes |
`MALPrecompiler` |
-| `lal/*.yaml` | 8 | 6 unique `@CompileStatic` Groovy classes |
`LALPrecompiler` |
-
-**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 |
-|---|---|
-| `application.yml` (upstream) | Replaced by distro's own simplified
`application.yml` |
-
----
-
-## Build Workflow
-
-### Build System
-- Maven + Makefile orchestrates building on top of the skywalking submodule
-- GraalVM JDK 25 in CI (`.github/workflows/ci.yml`)
-- JVM-mode starter with fixed module wiring (`FixedModuleManager` +
`GraalVMOAPServerStartUp`)
-- Simplified config file for selected modules (`application.yml`)
-
-### Build-Time Pre-Compilation
-
-**OAL**: OAL engine exports `.class` files (9 defines, ~620 metrics, ~620
builders, ~45 dispatchers). 7 annotation/interface manifests. 3 same-FQCN
replacement classes (`OALEngineLoaderService`, `AnnotationScan`,
`SourceReceiverImpl`).
-
-**MAL**: Unified precompiler (`build-tools/precompiler`) processes 71 YAML
files → 1250 expressions transpiled to pure Java `MalExpression` + 1209
Javassist meter classes. Combination pattern with deterministic suffixes +
expression hash tracking. Same-FQCN replacements: `DSL.java`,
`FilterExpression.java`, `MeterSystem.java`, `Expression.java`,
`SampleFamily.java`. 73 comparison test classes, 1281 assertions (100% YAML
coverage).
-
-**LAL**: 8 YAML files → 10 rules → 6 unique transpiled Java `LalExpression`
classes. Same-FQCN `DSL.java` loads via SHA-256 hash lookup. 5 comparison test
classes, 19 assertions (100% branch coverage).
-
-**Config initialization**: `ConfigInitializerGenerator` generates same-FQCN
`YamlConfigLoaderUtils` using Lombok setters — zero `Field.setAccessible` at
runtime.
-
-**Config data serialization**: Precompiler serializes parsed config POJOs to
`META-INF/config-data/*.json` (7 JSON files). 3 same-FQCN replacement loaders
(`MeterConfigs`, `Rules`, `LALConfigs`) deserialize from JSON instead of
filesystem YAML.
-
-**Module system**: `ModuleDefine` replacement with direct `prepare()` overload
(bypasses ServiceLoader). `GraalVMOAPServerStartUp` with `configuration.has()`
guards for 6 optional modules.
-
-**Distro resource packaging**: 146 runtime files → distro `config/`, 89
pre-compiled files → JARs. Assembly descriptor (`distribution.xml`) packages
runtime config files from upstream.
-
-### Groovy Elimination
-
-- MAL-to-Java transpiler: 1250+ expressions → pure Java `MalExpression` (no
Groovy MOP/ExpandoMetaClass)
-- LAL-to-Java transpiler: 10 scripts → pure Java `LalExpression` (no
DelegatingScript)
-- `SampleFamily` Closure parameters → Java functional interfaces (zero
`groovy.lang.Closure` in production)
-- Groovy stubs module for class loading (no `org.codehaus.groovy.*`)
-- HierarchyDefinitionService: same-FQCN replacement with Java-backed closures
-- Real Groovy (`groovy-5.0.3.jar`) is test-only;
`groovy-stubs-1.0.0-SNAPSHOT.jar` on runtime classpath
-- 1303 tests require actual data flow (no vacuous empty-result agreements)
-
-### Native Image Build
-
-- `native-maven-plugin` (GraalVM buildtools 0.10.4) in `oap-graalvm-native`
with `-Pnative` profile
-- `reflect-config.json` auto-generated by precompiler from manifests (OAL,
MAL, LAL, meter, HTTP handlers, GraphQL types)
-- `log4j2-reflect-config.json` for Log4j2 plugin classes; console-only
`log4j2.xml` with `SW_LOG_LEVEL` env var
-- gRPC/Netty/Protobuf/Armeria via GraalVM reachability metadata repository
-- Auto-scanned reflection metadata: Armeria HTTP handlers (~19), GraphQL
resolvers (~32), GraphQL types (~182), config POJOs (8)
-- Native binary: ~203MB, boots to full module init with all HTTP endpoints
functional
-
-### Native Distro Packaging
-
-- Assembly descriptor (`native-distribution.xml`) packages native binary +
config files
-- `Dockerfile.native` packages native distro into `debian:bookworm-slim`
-- `docker-compose.yml` with BanyanDB + OAP native services
-- CI pipeline: multi-arch native build (amd64 + arm64) with Docker manifest
push to GHCR
-
-### Remaining Verification
-- Verify all receiver plugins work (gRPC + HTTP endpoints)
-- Verify all query APIs work (GraphQL, PromQL, LogQL, Zipkin)
-- Verify cluster mode (K8s)
-- Verify alarm module
-- Performance benchmarking vs JVM
-
----
-
-## Upstream Changes Tracker
-
-No upstream changes needed. All GraalVM incompatibilities are resolved in this
distro via same-FQCN replacement and build-time pre-compilation:
-- OAL: build-time class export works via existing debug API
-- MAL: transpiled to pure Java, bypasses Groovy entirely
-- LAL: transpiled to pure Java, bypasses Groovy entirely
-- Dynamic Groovy MOP: transpiled to pure Java, no ExpandoMetaClass/MOP at
runtime
diff --git a/LAL-IMMIGRATION.md b/LAL-IMMIGRATION.md
deleted file mode 100644
index d22704f..0000000
--- a/LAL-IMMIGRATION.md
+++ /dev/null
@@ -1,352 +0,0 @@
-# LAL Immigration: Build-Time Pre-Compilation + Groovy Elimination
-
-## Context
-
-LAL (Log Analysis Language) uses Groovy with `@CompileStatic` +
`LALPrecompiledExtension` for log analysis scripts. At startup,
`GroovyShell.parse()` compiles each LAL DSL script into a
`LALDelegatingScript`. GraalVM native image cannot compile Groovy at runtime.
-
-LAL also enforces security constraints via `SecureASTCustomizer` — `while`,
`do-while`, and `for` loops are disallowed. Branch coverage focuses on
`if`/`else if`/`else` chains.
-
-**Solution**: Compile all LAL scripts at build time via the unified
precompiler. Export `.class` files + manifests. At runtime, load pre-compiled
classes via SHA-256 hash lookup — no Groovy compilation.
-
----
-
-## Rule File Inventory
-
-**8 LAL YAML files, 10 rules, 6 unique pre-compiled classes:**
-
-| File | Rule Name | DSL Features |
-|---|---|---|
-| `default.yaml` | default | Empty sink (trivial passthrough) |
-| `nginx.yaml` | nginx-access-log | `tag()` guard, `text { regexp }`,
conditional tag extraction |
-| `nginx.yaml` | nginx-error-log | `tag()` guard, `text { regexp }`, timestamp
with format, `metrics {}` |
-| `mysql-slowsql.yaml` | mysql-slowsql | `json {}`, conditional `slowSql {}` |
-| `pgsql-slowsql.yaml` | pgsql-slowsql | Identical DSL to mysql-slowsql |
-| `redis-slowsql.yaml` | redis-slowsql | Identical DSL to mysql-slowsql |
-| `envoy-als.yaml` | envoy-als | `parsed?.` navigation, conditional `abort
{}`, tag extraction, `rateLimit` sampler |
-| `envoy-als.yaml` | network-profiling-slow-trace | `json {}`, `tag()` guard,
`sampledTrace {}` with 3-way if/else chains |
-| `mesh-dp.yaml` | network-profiling-slow-trace | Identical DSL to envoy-als's
2nd rule |
-| `k8s-service.yaml` | network-profiling-slow-trace | Identical DSL to
envoy-als's 2nd rule |
-
-**SHA-256 deduplication**: mysql/pgsql/redis share identical DSL (1 class).
The 3 network-profiling rules share identical DSL (1 class). Total unique
pre-compiled classes: **6**.
-
----
-
-## Build-Time Compilation
-
-The unified precompiler (`build-tools/precompiler`) handles LAL alongside OAL
and MAL:
-
-1. Loads all 8 LAL YAML files via `LALConfigs.load()`
-2. For each rule's DSL string, compiles with the same `CompilerConfiguration`
as upstream:
- - `@CompileStatic` with `LALPrecompiledExtension` for type checking
- - `SecureASTCustomizer` disallowing loops
- - `ImportCustomizer` for `ProcessRegistry`
- - Script base class: `LALDelegatingScript`
-3. Exports compiled `.class` files to the output directory
-4. Writes three manifest files:
-
-**`META-INF/lal-expressions.txt`** — SHA-256 hash → transpiled Java class
(used at runtime):
-```
-a1b2c3d4...=org.apache.skywalking.oap.server.core.source.oal.rt.lal.LalExpr_0
-e5f6a7b8...=org.apache.skywalking.oap.server.core.source.oal.rt.lal.LalExpr_1
-...
-```
-
-**`META-INF/lal-scripts-by-hash.txt`** — SHA-256 hash → Groovy class
(build-time artifact):
-```
-a1b2c3d4...=network_profiling_slow_trace
-...
-```
-
-**`META-INF/lal-scripts.txt`** — rule name → Groovy class (build-time
artifact):
-```
-default=default
-nginx-access-log=nginx_access_log
-...
-```
-
-The `lal-expressions.txt` manifest is the runtime manifest for the transpiled
Java expressions.
-The other two manifests are build-time artifacts for verification and
debugging.
-
----
-
-## Runtime Replacement
-
-**Same-FQCN class**: `oap-graalvm-server/.../log/analyzer/dsl/DSL.java`
-
-Same FQCN as upstream `org.apache.skywalking.oap.log.analyzer.dsl.DSL`. The
`of()` method:
-
-1. Computes SHA-256 of the DSL string
-2. Loads `META-INF/lal-scripts-by-hash.txt` manifest (lazy, thread-safe,
cached)
-3. Looks up the pre-compiled class name by hash
-4. `Class.forName(className)` → `newInstance()` → cast to `DelegatingScript`
-5. Creates `FilterSpec`, sets delegate, returns `DSL` instance
-
-No `GroovyShell`, no compilation. The pre-compiled class already contains the
statically-compiled bytecode with all type checking baked in.
-
----
-
-## Key Difference from MAL
-
-| Aspect | MAL | LAL |
-|---|---|---|
-| Groovy mode | Dynamic (MOP, `propertyMissing`, `ExpandoMetaClass`) |
`@CompileStatic` with extension |
-| Loop support | No restriction | Loops disallowed (`SecureASTCustomizer`) |
-| Script base class | `DelegatingScript` | `LALDelegatingScript` |
-| Manifest lookup | By metric name | By SHA-256 hash of DSL content |
-| GraalVM risk | High (dynamic Groovy MOP) | Low (statically compiled) |
-
-LAL's `@CompileStatic` compilation means the pre-compiled classes are fully
statically typed — no runtime metaclass manipulation needed. This makes LAL
significantly more native-image-friendly than MAL.
-
----
-
-## Comparison Test Suite
-
-**5 test classes, 19 assertions** covering all 8 YAML files and 10 rules.
-
-Each test runs both paths and asserts identical `Binding` state:
-
-```
- LAL YAML file
- |
- Load rules (name, dsl)
- |
- For each rule's DSL:
- / \
- Path A (Fresh Groovy) Path B (Pre-compiled)
- GroovyShell.parse() SHA-256 → manifest lookup
- same CompilerConfig Class.forName() → newInstance()
- \ /
- Create FilterSpec (mocked ModuleManager)
- script.setDelegate(filterSpec)
- filterSpec.bind(binding)
- script.run()
- \ /
- Assert identical Binding state
-```
-
-**What is compared after evaluation:**
-- `binding.shouldAbort()` — did `abort {}` fire?
-- `binding.shouldSave()` — log persistence flag
-- `binding.log()` — LogData.Builder state (service, serviceInstance, endpoint,
layer, timestamp, tags)
-- `binding.metricsContainer()` — SampleFamily objects (for nginx-error-log
`metrics {}`)
-- `binding.databaseSlowStatement()` — builder state (for slowSql rules)
-- `binding.sampledTraceBuilder()` — builder state (for network-profiling
sampledTrace)
-
-### Test Classes
-
-| Test Class | YAML File(s) | Tests | Coverage |
-|---|---|---|---|
-| `LALDefaultTest` | default.yaml | 2 | Trivial passthrough + manifest
verification |
-| `LALNginxTest` | nginx.yaml | 5 | Access log: matching/non-matching tag +
non-matching regex. Error log: matching/non-matching tag |
-| `LALSlowSqlTest` | mysql/pgsql/redis-slowsql.yaml | 3 | SLOW_SQL tag guard
(match/skip) + 3-file manifest verification |
-| `LALEnvoyAlsTest` | envoy-als.yaml (1st rule) | 3 | Abort path (low code, no
flags), non-abort with flags, non-abort with high code |
-| `LALNetworkProfilingTest` | envoy-als/mesh-dp/k8s-service.yaml | 6 | 4
componentId branches (http/tcp x ssl/no-ssl), LOG_KIND guard, 3-file manifest
verification |
-
-### Branch Coverage
-
-- **`tag()` guard**: All rules with `if (tag("LOG_KIND") == ...)` tested with
matching and non-matching values
-- **`abort {}`**: envoy-als tested with conditions that trigger and skip abort
-- **`slowSql {}`**: Tested with SLOW_SQL tag match (block executed) and
non-match (block skipped)
-- **`sampledTrace {}`**: componentId 4-way if/else chain fully covered
(HTTP=49, HTTPS=129, TLS=130, TCP=110)
-- **`text { regexp }`**: nginx access log tested with matching and
non-matching text patterns
-- **`json {}`**: Tested via slowSql and network-profiling rules
-- **`rateLimit` sampler**: envoy-als tested with responseFlags present/absent
(if/else branch)
-- **`parsed?.` navigation**: envoy-als tested with nested Map traversal
-
----
-
-## 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
transpiled `LalExpression` from `META-INF/lal-expressions.txt` manifest (keyed
by SHA-256 hash). No Groovy runtime. |
-| `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()`. |
-| `AbstractSpec` | `analyzer/log-analyzer/.../dsl/spec/AbstractSpec.java` |
`oap-libs-for-graalvm/log-analyzer-for-graalvm/` | Added `abort()` no-arg
overload for transpiled code. |
-| `FilterSpec` | `analyzer/log-analyzer/.../dsl/spec/filter/FilterSpec.java` |
`oap-libs-for-graalvm/log-analyzer-for-graalvm/` | Added `Consumer` overloads:
`json()`, `text(Consumer)`, `extractor(Consumer)`, `sink(Consumer)`,
`filter(Runnable)`. |
-| `ExtractorSpec` |
`analyzer/log-analyzer/.../dsl/spec/extractor/ExtractorSpec.java` |
`oap-libs-for-graalvm/log-analyzer-for-graalvm/` | Added `Consumer` overloads:
`metrics(Consumer)`, `slowSql(Consumer)`, `sampledTrace(Consumer)`. |
-| `SinkSpec` | `analyzer/log-analyzer/.../dsl/spec/sink/SinkSpec.java` |
`oap-libs-for-graalvm/log-analyzer-for-graalvm/` | Added `Consumer` overloads:
`sampler(Consumer)`, `enforcer()`, `dropper()`. |
-| `SamplerSpec` | `analyzer/log-analyzer/.../dsl/spec/sink/SamplerSpec.java` |
`oap-libs-for-graalvm/log-analyzer-for-graalvm/` | Added `rateLimit(String,
Consumer)`, `possibility(int, Consumer)` for String-keyed samplers. |
-
-All replacements are repackaged into `log-analyzer-for-graalvm` via
`maven-shade-plugin` — the original `.class` files are excluded from the shaded
JAR.
-
----
-
-## Files Created
-
-1.
**`oap-libs-for-graalvm/log-analyzer-for-graalvm/src/main/java/.../log/analyzer/dsl/DSL.java`**
- Same-FQCN replacement: loads pre-compiled LAL scripts from manifest via
SHA-256 hash
-
-2.
**`oap-graalvm-server/src/test/java/.../graalvm/lal/LALScriptComparisonBase.java`**
- Abstract base class: ModuleManager mock setup, dual-path compilation,
Binding state comparison
-
-3. **`oap-graalvm-server/src/test/java/.../graalvm/lal/LALDefaultTest.java`**
- Tests for default.yaml (2 tests)
-
-4. **`oap-graalvm-server/src/test/java/.../graalvm/lal/LALNginxTest.java`**
- Tests for nginx.yaml access-log and error-log rules (5 tests)
-
-5. **`oap-graalvm-server/src/test/java/.../graalvm/lal/LALSlowSqlTest.java`**
- Tests for mysql/pgsql/redis-slowsql.yaml (3 tests)
-
-6. **`oap-graalvm-server/src/test/java/.../graalvm/lal/LALEnvoyAlsTest.java`**
- Tests for envoy-als.yaml envoy-als rule (3 tests)
-
-7.
**`oap-graalvm-server/src/test/java/.../graalvm/lal/LALNetworkProfilingTest.java`**
- Tests for network-profiling-slow-trace rule across 3 files (6 tests)
-
----
-
-## Verification
-
-```bash
-# Build precompiler first (generates LAL classes + manifests)
-JAVA_HOME=/Users/wusheng/.sdkman/candidates/java/25-graal \
- mvn -pl build-tools/precompiler install -DskipTests
-
-# Run LAL tests only
-JAVA_HOME=/Users/wusheng/.sdkman/candidates/java/25-graal \
- mvn -pl oap-graalvm-server test \
- -Dtest="org.apache.skywalking.oap.server.graalvm.lal.*"
-
-# Full build (all tests)
-JAVA_HOME=/Users/wusheng/.sdkman/candidates/java/25-graal make build-distro
-```
-
-Expected: 19 comparison tests across 5 test classes, all passing.
-
----
-
-## Pure Java LAL Transpiler
-
-LAL transpilation is complete. All 10 LAL scripts (6 unique after SHA-256
dedup)
-are transpiled from Groovy AST to pure Java source at build time, compiled to
-`.class` files, and loaded at runtime via `LalExpression` interface — no Groovy
-runtime needed.
-
-**Approach**: Both options from the plan were combined:
-- **Option A (Transpiler)**: `LalToJavaTranspiler` converts Groovy AST to Java
source
-- **Option B (Groovy Stubs)**: `groovy-stubs` module provides minimal
`groovy.lang.*`
- types for class loading (no `org.codehaus.groovy.*`)
-
----
-
-## What Was Built
-
-### 1. LalExpression Interface
-`oap-libs-for-graalvm/log-analyzer-for-graalvm/.../dsl/LalExpression.java`
-
-```java
-@FunctionalInterface
-public interface LalExpression {
- void execute(FilterSpec filterSpec, Binding binding);
-}
-```
-
-### 2. Groovy Stubs Module
-`oap-libs-for-graalvm/groovy-stubs/` — Minimal stub classes:
-- `groovy.lang.Binding`, `Closure`, `GString`, `GroovyObject`,
`GroovyObjectSupport`
-- `groovy.lang.Script`, `groovy.util.DelegatingScript`
-- `groovy.lang.DelegatesTo`, `MetaClass`, `MissingPropertyException`,
`GroovyRuntimeException`
-
-Key: **No `org.codehaus.groovy.*` packages** — prevents GraalVM
`GroovyIndyInterfaceFeature` from activating.
-
-### 3. Spec Class Consumer Overloads
-Same-FQCN replacements in `oap-libs-for-graalvm/log-analyzer-for-graalvm/`:
-- `AbstractSpec` — `abort()` no-arg
-- `FilterSpec` — `json()` no-arg, `text(Consumer)`, `extractor(Consumer)`,
`sink(Consumer)`, `filter(Runnable)`
-- `ExtractorSpec` — `metrics(Consumer)`, `slowSql(Consumer)`,
`sampledTrace(Consumer)`
-- `SinkSpec` — `sampler(Consumer)`, `enforcer()`, `dropper()`
-- `SamplerSpec` — `rateLimit(String, Consumer)`, `possibility(int, Consumer)`
-
-### 4. LalToJavaTranspiler
-`build-tools/precompiler/.../LalToJavaTranspiler.java` (~650 lines)
-
-Groovy AST → Java source transpilation:
-- Statement-based emission with delegation context tracking
-- If/else if/else chains
-- Property access via `getAt()`
-- Cast handling (`as String/Long/Boolean/Integer`)
-- GString interpolation → string concatenation
-- Null-safe navigation (`?.` → ternary null checks)
-- Static method calls (`ProcessRegistry`)
-- Map expression handling (named args)
-- Embedded helper methods (`getAt`, `toLong`, `toInt`, `toBoolean`, `isTruthy`)
-- JavaCompiler batch compilation
-- Manifest writing (`META-INF/lal-expressions.txt`)
-
-### 5. Runtime DSL.java
-Updated `oap-libs-for-graalvm/log-analyzer-for-graalvm/.../dsl/DSL.java`:
-- Loads `LalExpression` from `META-INF/lal-expressions.txt` (not
`DelegatingScript`)
-- `evaluate()` calls `expression.execute(filterSpec, binding)` (not
`script.run()`)
-
-### 6. Dual-Path Tests Updated
-`LALScriptComparisonBase.java` updated:
-- Path A: Fresh Groovy compilation → `DelegatingScript.run()`
-- Path B: Transpiled `LalExpression` from manifest →
`expression.execute(filterSpec, binding)`
-- 19 tests across 5 classes, all passing
-
----
-
-## Generated Code Example
-
-Input (network-profiling-slow-trace, Groovy):
-```groovy
-filter {
- json{}
- extractor{
- if (tag("LOG_KIND") == "NET_PROFILING_SAMPLED_TRACE") {
- sampledTrace {
- latency parsed.latency as Long
- componentId 49 // simplified
- }
- }
- }
-}
-```
-
-Output (LalExpr_0.java):
-```java
-public class LalExpr_0 implements LalExpression {
- private static Object getAt(Object obj, String key) { ... }
- private static long toLong(Object obj) { ... }
-
- @Override
- public void execute(FilterSpec filterSpec, Binding binding) {
- filterSpec.json();
- filterSpec.extractor(ext -> {
- if
("NET_PROFILING_SAMPLED_TRACE".equals(filterSpec.tag("LOG_KIND"))) {
- ext.sampledTrace(st -> {
- st.latency(toLong(getAt(binding.parsed(), "latency")));
- st.componentId(49);
- });
- }
- });
- }
-}
-```
-
----
-
-## Groovy Runtime Removal
-
-- `groovy-stubs` wired as runtime dependency
-- Real Groovy (`groovy-5.0.3.jar`) moved to test-only scope
-- Native image builds and boots without `org.codehaus.groovy.*` on classpath
-- `GroovyIndyInterfaceFeature` stays dormant (no `org.codehaus.groovy`
packages)
diff --git a/MAL-IMMIGRATION.md b/MAL-IMMIGRATION.md
deleted file mode 100644
index 5506e39..0000000
--- a/MAL-IMMIGRATION.md
+++ /dev/null
@@ -1,817 +0,0 @@
-# MAL Immigration: Build-Time Pre-Compilation + Groovy Elimination
-
-## Context
-
-MAL (Meter Analysis Language) and LAL (Log Analysis Language) have three
GraalVM-incompatible runtime patterns:
-
-1. **MeterSystem ClassPath scanning**: `MeterSystem` constructor uses Guava
`ClassPath.from()` to discover `@MeterFunction`-annotated classes (16 meter
functions).
-2. **MeterSystem Javassist dynamic class generation**: `MeterSystem.create()`
uses Javassist `ClassPool.makeClass()` to create one dynamic meter subclass per
metric rule at runtime (~1188 rules across all MAL sources). Classes live in
`org.apache.skywalking.oap.server.core.analysis.meter.dynamic.*`.
-3. **Groovy runtime compilation**: MAL uses `GroovyShell.parse()` with dynamic
Groovy features. LAL uses `GroovyShell.parse()` with `@CompileStatic`. Both
compile scripts at startup.
-
-**Key architectural difference from OAL**: MAL initialization is a tightly
coupled pipeline where Groovy compilation, static analysis, and Javassist
generation are interleaved:
-
-```
-Rule YAML → MetricConvert constructor → Analyzer.build()
- → DSL.parse() [Groovy compilation]
- → e.parse() [static analysis → scopeType, functionName,
metricType]
- → meterSystem.create() [Javassist class generation +
MetricsStreamProcessor registration]
-```
-
-The build tool must execute this entire chain — Groovy pre-compilation and
Javassist pre-generation cannot be separated.
-
-**Solution**: Run the full MAL/LAL initialization at build time. Export
Javassist-generated `.class` files + compiled Groovy script bytecode. At
runtime, load pre-generated classes from manifests — no ClassPath scanning, no
Javassist, no Groovy compilation.
-
----
-
-## Rule file inventory
-
-| Source | Path | Loader | Files | Metric Rules |
-|--------|------|--------|-------|--------------|
-| Agent meter | `meter-analyzer-config/` | `MeterConfigs.loadConfig()` | 11 |
~147 |
-| OTel metrics | `otel-rules/` | `Rules.loadRules()` | 55 | ~1039 |
-| Log MAL | `log-mal-rules/` | `Rules.loadRules()` | 2 | ~2 |
-| LAL scripts | `lal/` | `LALConfigs.load()` | 8 | 10 |
-| **Total** | | | **76** | **~1198** |
-
-Each MAL metric rule generates one Groovy script + one Javassist dynamic meter
class. Each LAL rule generates one Groovy script.
-
----
-
-## MeterFunction Manifest + Same-FQCN MeterSystem
-
-### Problem
-
-`MeterSystem` constructor (`MeterSystem.java:75-96`) scans the classpath:
-
-```java
-ClassPath classpath = ClassPath.from(MeterSystem.class.getClassLoader());
-ImmutableSet<ClassPath.ClassInfo> classes =
classpath.getTopLevelClassesRecursive("org.apache.skywalking");
-for (ClassPath.ClassInfo classInfo : classes) {
- Class<?> functionClass = classInfo.load();
- if (functionClass.isAnnotationPresent(MeterFunction.class)) {
- functionRegister.put(metricsFunction.functionName(), functionClass);
- }
-}
-```
-
-### 16 Meter Function classes
-
-| Function Name | Class | Accept Type |
-|---|---|---|
-| `avg` | `AvgFunction` | `Long` |
-| `avgLabeled` | `AvgLabeledFunction` | `DataTable` |
-| `avgHistogram` | `AvgHistogramFunction` | `BucketedValues` |
-| `avgHistogramPercentile` | `AvgHistogramPercentileFunction` |
`PercentileArgument` |
-| `latest` | `LatestFunction` | `Long` |
-| `latestLabeled` | `LatestLabeledFunction` | `DataTable` |
-| `max` | `MaxFunction` | `Long` |
-| `maxLabeled` | `MaxLabeledFunction` | `DataTable` |
-| `min` | `MinFunction` | `Long` |
-| `minLabeled` | `MinLabeledFunction` | `DataTable` |
-| `sum` | `SumFunction` | `Long` |
-| `sumLabeled` | `SumLabeledFunction` | `DataTable` |
-| `sumHistogram` | `HistogramFunction` | `BucketedValues` |
-| `sumHistogramPercentile` | `SumHistogramPercentileFunction` |
`PercentileArgument` |
-| `sumPerMin` | `SumPerMinFunction` | `Long` |
-| `sumPerMinLabeled` | `SumPerMinLabeledFunction` | `DataTable` |
-
-### Approach
-
-1. Extend `OALClassExporter` to scan `@MeterFunction` annotation at build
time. Write `META-INF/annotation-scan/MeterFunction.txt` with format
`functionName=FQCN` (one entry per line).
-
-2. Create same-FQCN replacement `MeterSystem` in `oap-graalvm-server` that
reads function registry from the manifest instead of `ClassPath.from()`:
-
-```java
-// Pseudocode for replacement MeterSystem constructor
-public MeterSystem(ModuleManager manager) {
- this.manager = manager;
- this.classPool = ClassPool.getDefault();
-
- // Read from manifest instead of ClassPath.from()
- for (String line :
readManifest("META-INF/annotation-scan/MeterFunction.txt")) {
- String[] parts = line.split("=", 2);
- String functionName = parts[0];
- Class<?> functionClass = Class.forName(parts[1]);
- functionRegister.put(functionName, (Class<? extends AcceptableValue>)
functionClass);
- }
-}
-```
-
----
-
-## Build-Time MAL/LAL Pre-Compilation + MeterSystem Class Generation
-
-**Module**: `build-tools/precompiler` (unified tool)
-
-### MALCompiler.java — main build tool
-
-The tool executes the full initialization pipeline at build time:
-
-#### Initialize infrastructure
-
-```java
-// 1. Initialize scope registry (same as OALClassExporter)
-DefaultScopeDefine.reset();
-AnnotationScan scopeScan = new AnnotationScan();
-scopeScan.registerListener(new DefaultScopeDefine.Listener());
-scopeScan.scan();
-
-// 2. Create ExportingMeterSystem — intercepts Javassist to export .class files
-ExportingMeterSystem meterSystem = new ExportingMeterSystem(outputDir);
-```
-
-#### Load and compile all MAL rules
-
-```java
-// 3. Agent meter rules (meter-analyzer-config/)
-List<MeterConfig> agentConfigs =
MeterConfigs.loadConfig("meter-analyzer-config", activeFiles);
-for (MeterConfig config : agentConfigs) {
- new MetricConvert(config, meterSystem); // triggers: Groovy compile +
parse + Javassist
-}
-
-// 4. OTel rules (otel-rules/)
-List<Rule> otelRules = Rules.loadRules("otel-rules", enabledOtelRules);
-for (Rule rule : otelRules) {
- new MetricConvert(rule, meterSystem);
-}
-
-// 5. Log MAL rules (log-mal-rules/)
-List<Rule> logMalRules = Rules.loadRules("log-mal-rules", enabledLogMalRules);
-for (Rule rule : logMalRules) {
- new MetricConvert(rule, meterSystem);
-}
-```
-
-#### Load and compile all LAL rules
-
-```java
-// 6. LAL rules (lal/)
-List<LALConfigs> lalConfigs = LALConfigs.load("lal", lalFiles);
-for (LALConfig config : flattenedConfigs) {
- DSL.of(moduleManager, logConfig, config.getDsl()); // Groovy compile with
@CompileStatic
-}
-```
-
-#### Export bytecode + write manifests
-
-The tool intercepts both Groovy and Javassist class generation to capture
bytecode:
-
-**Javassist interception** (`ExportingMeterSystem`): Override `create()` to
call `ctClass.toBytecode()` and write `.class` files to the output directory.
Also records metadata for the manifest.
-
-**Groovy script capture**: Use Groovy's `CompilationUnit` API or intercept
`GroovyShell.parse()` to extract compiled script bytecode. Each MAL expression
compiles to a unique script class (name based on hash or sequential index).
-
-**Manifest files**:
-
-`META-INF/mal-meter-classes.txt` — Javassist-generated meter classes:
-```
-# format: metricsName|scopeId|functionName|dataType|FQCN
-meter_java_agent_created_tracing_context_count|1|sum|java.lang.Long|org.apache.skywalking.oap.server.core.analysis.meter.dynamic.meter_java_agent_created_tracing_context_count
-...
-```
-
-`META-INF/mal-groovy-scripts.txt` — Pre-compiled MAL Groovy scripts:
-```
-# format: metricName=scriptClassName
-meter_java_agent_created_tracing_context_count=org.apache.skywalking.oap.server.core.analysis.meter.script.Script0001
-...
-```
-
-`META-INF/lal-scripts.txt` — Pre-compiled LAL Groovy scripts:
-```
-# format: layer:ruleName=scriptClassName
-GENERAL:default=org.apache.skywalking.oap.server.core.analysis.lal.script.LALScript0001
-NGINX:nginx-access-log=org.apache.skywalking.oap.server.core.analysis.lal.script.LALScript0002
-...
-```
-
-### ExportingMeterSystem — Javassist interception
-
-This is a build-time variant of `MeterSystem` that exports bytecode instead of
loading classes:
-
-```java
-// Pseudocode
-public class ExportingMeterSystem extends MeterSystem {
- private final Path outputDir;
- private final List<MeterClassMetadata> metadata = new ArrayList<>();
-
- @Override
- public synchronized <T> void create(String metricsName, String
functionName,
- ScopeType type, Class<T> dataType) {
- // Same Javassist logic as upstream MeterSystem.create()
- CtClass metricsClass = classPool.makeClass(METER_CLASS_PACKAGE +
className, parentClass);
- // ... add constructor and createNew() method ...
-
- // Instead of toClass(), write bytecode to disk
- byte[] bytecode = metricsClass.toBytecode();
- Path classFile = outputDir.resolve(packageToPath(METER_CLASS_PACKAGE +
className) + ".class");
- Files.createDirectories(classFile.getParent());
- Files.write(classFile, bytecode);
-
- // Record metadata for manifest
- metadata.add(new MeterClassMetadata(metricsName, type.getScopeId(),
- functionName, dataType.getName(), METER_CLASS_PACKAGE +
className));
- }
-}
-```
-
-### Same-FQCN replacement classes
-
-**1. MAL `DSL`** (`oap-graalvm-server/.../meter/analyzer/dsl/DSL.java`)
-
-Same FQCN as `org.apache.skywalking.oap.meter.analyzer.dsl.DSL`. `parse()`
loads a pre-compiled Groovy script class instead of calling
`GroovyShell.parse()`:
-
-```java
-// Pseudocode
-public static Expression parse(String metricName, String expression) {
- // Look up pre-compiled script class from manifest
- String scriptClassName = lookupScript(metricName);
- Class<?> scriptClass = Class.forName(scriptClassName);
- DelegatingScript script = (DelegatingScript)
scriptClass.getDeclaredConstructor().newInstance();
-
- // Same CompilerConfiguration setup (imports, security) is baked into the
pre-compiled class
- return new Expression(metricName, expression, script);
-}
-```
-
-The `Expression.empower()` method still runs at runtime — it calls
`setDelegate()` and `ExpandoMetaClass` registration. These are Java API calls
on the Groovy runtime, not compilation.
-
-**2. `FilterExpression`**
(`oap-graalvm-server/.../meter/analyzer/dsl/FilterExpression.java`)
-
-Same FQCN. Loads pre-compiled filter closure class instead of
`GroovyShell.evaluate()`.
-
-**3. LAL `DSL`** (`oap-graalvm-server/.../log/analyzer/dsl/DSL.java`)
-
-Same FQCN as `org.apache.skywalking.oap.log.analyzer.dsl.DSL`. `of()` loads
pre-compiled LAL script class (already `@CompileStatic`) instead of calling
`GroovyShell.parse()`:
-
-```java
-// Pseudocode
-public static DSL of(ModuleManager moduleManager, LogAnalyzerModuleConfig
config, String dsl) {
- String scriptClassName = lookupLALScript(layer, ruleName);
- Class<?> scriptClass = Class.forName(scriptClassName);
- DelegatingScript script = (DelegatingScript)
scriptClass.getDeclaredConstructor().newInstance();
- FilterSpec filterSpec = new FilterSpec(moduleManager, config);
- script.setDelegate(filterSpec);
- return new DSL(script, filterSpec);
-}
-```
-
-**4. `MeterSystem`** (enhanced from Step 1)
-
-The `create()` method becomes a manifest lookup + `MetricsStreamProcessor`
registration:
-
-```java
-// Pseudocode
-public synchronized <T> void create(String metricsName, String functionName,
- ScopeType type, Class<T> dataType) {
- if (meterPrototypes.containsKey(metricsName)) {
- return; // already registered
- }
-
- // Load pre-generated class from classpath
- MeterClassMetadata meta = lookupMeterClass(metricsName);
- Class<?> targetClass = Class.forName(meta.fqcn);
- AcceptableValue prototype = (AcceptableValue)
targetClass.getDeclaredConstructor().newInstance();
- meterPrototypes.put(metricsName, new MeterDefinition(type, prototype,
dataType));
-
- // Register with stream processor (same as upstream)
- MetricsStreamProcessor.getInstance().create(
- manager,
- new StreamDefinition(metricsName, type.getScopeId(),
prototype.builder(),
- MetricsStreamProcessor.class),
- targetClass
- );
-}
-```
-
-`buildMetrics()` and `doStreamingCalculation()` work unchanged — they use the
prototype map.
-
-### MAL Groovy: Why @CompileStatic is NOT possible
-
-MAL expressions rely on three dynamic Groovy features:
-
-1. **`propertyMissing(String)`** (`Expression.java:126`): When an expression
references `counter` or `jvm_memory_bytes_used`, Groovy calls
`ExpressionDelegate.propertyMissing(sampleName)` which looks up the sample
family from a `ThreadLocal<Map<String, SampleFamily>>`. Static compilation
cannot resolve these properties.
-
-2. **`ExpandoMetaClass` on `Number`** (`Expression.java:104-111`): The
`empower()` method registers `plus`, `minus`, `multiply`, `div` closures on
`Number.class` to allow expressions like `100 * server_cpu_seconds`. This is
runtime metaclass manipulation.
-
-3. **Closure arguments** in DSL methods: Expressions like `.tag({tags ->
tags.gc = 'young_gc'})` and `.filter({tags -> tags.job_name == 'mysql'})` pass
Groovy closures with dynamic property access.
-
-**Approach**: Pre-compile using standard dynamic Groovy (same
`CompilerConfiguration` as upstream — `DelegatingScript` base class,
`SecureASTCustomizer`, `ImportCustomizer`). The compiled `.class` files contain
the same bytecode that `GroovyShell.parse()` would produce. At runtime,
`Class.forName()` + `newInstance()` loads the pre-compiled script, and
`Expression.empower()` sets up the delegate and `ExpandoMetaClass`.
-
-**Native image solution**: Dynamic Groovy was eliminated entirely via the
MAL-to-Java transpiler (see below). All MAL expressions are transpiled to pure
Java at build time — no Groovy MOP, no `ExpandoMetaClass`, no `invokedynamic`
at runtime.
-
-### LAL Groovy: Already @CompileStatic
-
-LAL already uses `@CompileStatic` with `LALPrecompiledExtension` for type
checking. The `CompilationUnit` approach works directly. Compiled scripts
extend `LALDelegatingScript` and are fully statically typed.
-
----
-
-## Verification Tests
-
-### MALCompilerTest (in `build-tools/mal-compiler/src/test/`)
-
-```
-- allRuleYamlFilesLoadable:
- Verify all 76 rule files exist on classpath
-
-- fullCompilationGeneratesExpectedCounts:
- Run MALCompiler.main(), verify:
- - Generated meter .class count > 0 for each rule source
- - Groovy script .class count matches rule count
- - All 3 manifest files exist and are non-empty
-
-- manifestEntriesAreWellFormed:
- Parse manifest files, verify format and field count per line
-```
-
-### MALPrecompiledRegistrationTest (in `oap-graalvm-server/src/test/`)
-
-```
-- meterFunctionManifestMatchesClasspath:
- Compare manifest against Guava ClassPath scan of @MeterFunction
- (same pattern as PrecompiledRegistrationTest for OAL)
-
-- all16MeterFunctionsInManifest:
- Verify all 16 known function names appear
-
-- precompiledMeterClassesLoadable:
- For each entry in mal-meter-classes.txt:
- - Class.forName() succeeds
- - Class extends the correct meter function parent
-
-- precompiledGroovyScriptsLoadable:
- For each entry in mal-groovy-scripts.txt:
- - Class.forName() succeeds
- - Class is assignable to DelegatingScript
-
-- precompiledLALScriptsLoadable:
- For each entry in lal-scripts.txt:
- - Class.forName() succeeds
- - Class is assignable to LALDelegatingScript
-```
-
----
-
-## 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 |
-|---|---|---|---|
-| `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.
-
----
-
-## Files Created
-
-1. **`build-tools/precompiler/`** (unified, replaces separate `oal-exporter` +
`mal-compiler`)
- - Main build tool: loads all MAL/LAL rules, runs full initialization
pipeline, exports .class files + manifests
-
-2.
**`oap-libs-for-graalvm/server-core-for-graalvm/src/main/java/.../core/analysis/meter/MeterSystem.java`**
- - Same-FQCN replacement: reads function registry from manifest, loads
pre-generated classes in `create()`
-
-3.
**`oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/.../meter/analyzer/dsl/DSL.java`**
- - Same-FQCN replacement: loads pre-compiled MAL Groovy scripts from manifest
-
-4.
**`oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/.../meter/analyzer/dsl/FilterExpression.java`**
- - Same-FQCN replacement: loads pre-compiled filter closures from manifest
-
-5.
**`oap-graalvm-server/src/test/java/.../graalvm/PrecompiledMALExecutionTest.java`**
- - Runtime registration and loading tests
-
-6. **`oap-graalvm-server/src/test/java/.../graalvm/mal/`** — 73 comparison
test classes covering all 71 MAL YAML files
-
-## Key Upstream Files (read-only)
-
-- `MeterSystem.java` — ClassPath scan (`constructor:75-96`) + Javassist class
gen (`create():180-259`)
-- `DSL.java` (meter-analyzer) — MAL Groovy compilation: `GroovyShell.parse()`
with `DelegatingScript`, `SecureASTCustomizer`
-- `Expression.java` — Script execution: `DelegatingScript.run()`,
`ExpandoMetaClass` on Number, `ExpressionDelegate.propertyMissing()`
-- `FilterExpression.java` — Filter closure compilation:
`GroovyShell.evaluate()`
-- `Analyzer.java` — Initialization chain: `build()` → `DSL.parse()` →
`e.parse()` → `meterSystem.create()`
-- `MetricConvert.java` — Rule → Analyzer creation: `new MetricConvert(rule,
meterSystem)` triggers full pipeline
-- `DSL.java` (log-analyzer) — LAL Groovy compilation: `@CompileStatic` +
`LALPrecompiledExtension`
-- `LALDelegatingScript.java` — LAL script base class: `filter()`, `json()`,
`text()`, `extractor()`, `sink()`
-- `LogFilterListener.java` — LAL DSL factory: `DSL.of()` for each rule, stores
in `Map<Layer, Map<String, DSL>>`
-- `Rules.java` — OTel/log-MAL rule loading: `loadRules(path, enabledRules)`
-- `MeterConfigs.java` — Agent meter rule loading: `loadConfig(path, fileNames)`
-- `LALConfigs.java` — LAL rule loading: `load(path, files)`
-- `MeterFunction.java` — Annotation: `@MeterFunction(functionName = "...")` on
meter function classes
-- `AcceptableValue.java` — Interface all meter functions implement
-- `MeterClassPackageHolder.java` — Package anchor for Javassist-generated
classes
-
----
-
-## Verification
-
-```bash
-# 1. Build everything
-make build-distro
-
-# 2. Check generated meter classes exist
-ls
build-tools/precompiler/target/generated-classes/org/apache/skywalking/oap/server/core/analysis/meter/dynamic/
-
-# 3. Check transpiled Java expression classes exist
-ls
build-tools/precompiler/target/generated-classes/org/apache/skywalking/oap/server/core/source/oal/rt/mal/
-
-# 4. Check manifest files
-wc -l
build-tools/precompiler/target/generated-classes/META-INF/mal-meter-classes.txt
-wc -l
build-tools/precompiler/target/generated-classes/META-INF/mal-expressions.txt
-wc -l
build-tools/precompiler/target/generated-classes/META-INF/lal-expressions.txt
-
-# 5. Check MeterFunction manifest
-cat
build-tools/precompiler/target/generated-classes/META-INF/annotation-scan/MeterFunction.txt
-
-# 6. Verify tests pass
-make build-distro # runs all comparison tests (1300+ assertions)
-```
-
----
-
-## Pure Java MAL Transpiler
-
-### Why: Groovy + GraalVM Native Image is Incompatible
-
-The first native-image build (`make native-image`) failed with:
-
-```
-Error: Could not find target method:
- protected static void com.oracle.svm.polyglot.groovy
-
.Target_org_codehaus_groovy_vmplugin_v7_IndyInterface_invalidateSwitchPoints
- .invalidateSwitchPoints()
-```
-
-**Root cause**: GraalVM's built-in `GroovyIndyInterfaceFeature` (in
`library-support.jar`)
-targets `IndyInterface.invalidateSwitchPoints()` — a method removed in Groovy
5.0.3.
-The Groovy 5.x runtime still uses `org.codehaus.groovy` packages and
`invokedynamic`
-bootstrapping, but the internal API changed. GraalVM's substitution layer
cannot find
-the target method.
-
-**Why fixing Groovy is not worth it**: Even if the substitution were patched,
Groovy's
-dynamic runtime (ExpandoMetaClass, MOP, IndyInterface, MetaClassRegistry)
requires
-extensive GraalVM reflection/substitution configuration. The MAL pre-compiled
scripts
-use `invokedynamic` + Groovy MOP for method resolution. Making this work
reliably in
-native-image would be fragile and complex.
-
-**Solution**: Eliminate Groovy from runtime entirely. Generate pure Java code
at build
-time that implements the same MAL expression logic. The existing 1303 UTs
validate
-that the generated Java code produces identical results to the Groovy-compiled
scripts.
-
----
-
-### Approach: MAL-to-Java Transpiler
-
-**Key insight**: MAL expressions are method chains on `SampleFamily` with a
well-defined
-API. The precompiler already parses all 1250+ MAL rules. Instead of compiling
them to
-Groovy bytecode (which needs Groovy runtime), we parse the Groovy AST at build
time and
-generate equivalent Java source code. Zero Groovy at runtime.
-
-### What the Transpiler Changes
-
-| Aspect | Before (Groovy pre-compilation) | After (Java transpiler) |
-|--------|----------------------------------|--------------------------|
-| Build output | Groovy `.class` bytecode | Pure Java `.class` files |
-| Runtime dependency | Groovy runtime (MOP, ExpandoMetaClass) | None |
-| Expression execution | `DelegatingScript.run()` + `ExpandoMetaClass` |
`MalExpression.run(Map<String, SampleFamily>)` |
-| Closure parameters | `groovy.lang.Closure` | Java functional interfaces |
-| `propertyMissing()` | Groovy MOP resolves metric names |
`samples.get("metricName")` |
-| `Number * SampleFamily` | `ExpandoMetaClass` on `Number` |
`sf.multiply(number)` (transpiler swaps operands) |
-| Filter expressions | Groovy `Script.run()` → `Closure<Boolean>` |
`SampleFilter.test(Map<String, String>)` |
-
-### What Stays the Same
-
-- **MeterSystem Javassist generation**: Pre-compiled at build time, unchanged
-- **Config data serialization**: JSON manifests for rule configs, unchanged
-- **Manifest-based loading**: Same pattern, new manifest format for Java
expressions
-- **MetricConvert pipeline**: Still runs at build time for Javassist class
generation
-
----
-
-### Java Functional Interfaces for Closure Replacements
-
-5 `SampleFamily` methods accept `groovy.lang.Closure` parameters. Replace with
Java
-functional interfaces:
-
-| Method | Closure Type | Java Interface |
-|--------|-------------|----------------|
-| `tag(Closure<?> cl)` | `Map<String,String> → Map<String,String>` |
`TagFunction extends Function<Map, Map>` |
-| `filter(Closure<Boolean> f)` | `Map<String,String> → Boolean` |
`SampleFilter extends Predicate<Map>` |
-| `forEach(List, Closure each)` | `(String, Map) → void` | `ForEachFunction
extends BiConsumer<String, Map>` |
-| `decorate(Closure c)` | `MeterEntity → void` | `DecorateFunction extends
Consumer<MeterEntity>` |
-| `instance(..., Closure extractor)` | `Map<String,String> →
Map<String,String>` | `PropertiesExtractor extends Function<Map, Map>` |
-
-**Files created**:
-- `oap-libs-for-graalvm/meter-analyzer-for-graalvm/.../dsl/MalExpression.java`
--
`oap-libs-for-graalvm/meter-analyzer-for-graalvm/.../dsl/SampleFamilyFunctions.java`
-
-### MalExpression Interface
-
-```java
-public interface MalExpression {
- SampleFamily run(Map<String, SampleFamily> samples);
-}
-```
-
-Each generated MAL expression class implements this interface. The `samples`
map
-replaces Groovy's `propertyMissing()` delegate — metric names are resolved via
-`samples.get("metricName")`.
-
-### MalFilter Interface
-
-```java
-public interface MalFilter {
- boolean test(Map<String, String> tags);
-}
-```
-
-Each generated filter expression class implements this interface.
-
----
-
-### Same-FQCN SampleFamily Replacement
-
-**File**:
`oap-libs-for-graalvm/meter-analyzer-for-graalvm/.../dsl/SampleFamily.java`
-
-Copy upstream `SampleFamily.java` and change ONLY the 5 Closure-based method
signatures:
-
-```java
-// Before (upstream)
-public SampleFamily tag(Closure<?> cl) { ... cl.rehydrate(...).call(arg) ... }
-public SampleFamily filter(Closure<Boolean> filter) { ...
filter.call(it.labels) ... }
-public SampleFamily forEach(List<String> array, Closure<Void> each) { ...
each.call(element, labels) ... }
-public SampleFamily decorate(Closure<Void> c) { ... c.call(meterEntity) ... }
-public SampleFamily instance(..., Closure<Map<String,String>>
propertiesExtractor) { ... }
-
-// After (replacement)
-public SampleFamily tag(TagFunction fn) { ... fn.apply(arg) ... }
-public SampleFamily filter(SampleFilter f) { ... f.test(it.labels) ... }
-public SampleFamily forEach(List<String> array, ForEachFunction each) { ...
each.accept(element, labels) ... }
-public SampleFamily decorate(DecorateFunction fn) { ... fn.accept(meterEntity)
... }
-public SampleFamily instance(..., PropertiesExtractor extractor) { ... }
-```
-
-All other methods (tagEqual, sum, avg, histogram, service, endpoint, etc.)
remain
-identical — they don't use Groovy types.
-
-**Shade config**: Add `SampleFamily.class` and `SampleFamily$*.class` to shade
excludes.
-
----
-
-### MAL-to-Java Transpiler Implementation
-
-**File**: `build-tools/precompiler/.../MalToJavaTranspiler.java`
-
-Parses MAL expression strings using Groovy's parser (available at build time,
not needed
-at runtime) and generates pure Java source code.
-
-### Input → Output Example
-
-**Input** (MAL expression string from apisix.yaml):
-```
-apisix_http_status.tagNotEqual('route','').tag({tags->tags.route='route/'+tags['route']})
-
.sum(['code','service_name','route','node']).rate('PT1M').endpoint(['service_name'],['route'],
Layer.APISIX)
-```
-
-**Output** (generated Java class):
-```java
-package org.apache.skywalking.oap.server.core.source.oal.rt.mal;
-
-import java.util.*;
-import org.apache.skywalking.oap.meter.analyzer.dsl.*;
-import org.apache.skywalking.oap.server.core.analysis.Layer;
-
-public class MalExpr_meter_apisix_endpoint_http_status implements
MalExpression {
- @Override
- public SampleFamily run(Map<String, SampleFamily> samples) {
- SampleFamily v0 = samples.get("apisix_http_status");
- if (v0 == null) return SampleFamily.EMPTY;
- return v0
- .tagNotEqual("route", "")
- .tag(tags -> { tags.put("route", "route/" + tags.get("route"));
return tags; })
- .sum(List.of("code", "service_name", "route", "node"))
- .rate("PT1M")
- .endpoint(List.of("service_name"), List.of("route"), Layer.APISIX);
- }
-}
-```
-
-### AST Node → Java Output Mapping
-
-| Groovy AST Node | Java Output |
-|-----------------|-------------|
-| Bare property access (`metric_name`) | `samples.get("metric_name")` |
-| Method call on SampleFamily | Direct method call (same name) |
-| `Closure` in `.tag()` | Lambda: `tags -> { tags.put(...); return tags; }` |
-| `Closure` in `.filter()` | Lambda: `labels -> booleanExpr` |
-| `Closure` in `.forEach()` | Lambda: `(element, labels) -> { ... }` |
-| `tags.name` inside closure | `tags.get("name")` |
-| `tags.name = val` inside closure | `tags.put("name", val)` |
-| `tags['key']` inside closure | `tags.get("key")` |
-| Binary `SF + SF` | `left.plus(right)` |
-| Binary `SF - SF` | `left.minus(right)` |
-| Binary `SF * SF` | `left.multiply(right)` |
-| Binary `SF / SF` | `left.div(right)` |
-| Binary `Number * SF` | `sf.multiply(number)` (swap operands) |
-| Binary `Number + SF` | `sf.plus(number)` (swap operands) |
-| Binary `Number - SF` | `sf.minus(number).negative()` (swap operands) |
-| Binary `Number / SF` | `sf.newValue(v -> number / v)` (swap operands) |
-| Unary `-SF` | `sf.negative()` |
-| `['a','b','c']` (list literal) | `List.of("a", "b", "c")` |
-| `[50,75,90,95,99]` (int list) | `List.of(50, 75, 90, 95, 99)` |
-| String literal `'foo'` | `"foo"` |
-| Enum constant `Layer.APISIX` | `Layer.APISIX` |
-| Enum constant `DetectPoint.SERVER` | `DetectPoint.SERVER` |
-| `DownsamplingType.LATEST` / `LATEST` | `DownsamplingType.LATEST` |
-| `time()` function | `java.time.Instant.now().getEpochSecond()` |
-| `a?.b` (safe navigation) | `a != null ? a.b : null` |
-| `a ?: b` (elvis) | `a != null ? a : b` |
-
-### Filter Expression Transpilation
-
-Filter expressions are simpler — they are boolean closures:
-
-**Input**: `{ tags -> tags.job_name == 'apisix-monitoring' }`
-
-**Output**:
-```java
-public class MalFilter_apisix implements MalFilter {
- @Override
- public boolean test(Map<String, String> tags) {
- return "apisix-monitoring".equals(tags.get("job_name"));
- }
-}
-```
-
----
-
-### Same-FQCN Expression.java Replacement
-
-**File**:
`oap-libs-for-graalvm/meter-analyzer-for-graalvm/.../dsl/Expression.java`
-
-Simplified — no DelegatingScript, no ExpandoMetaClass, no ExpressionDelegate:
-
-```java
-public class Expression {
- private final String metricName;
- private final String literal;
- private final MalExpression expression; // Pure Java
-
- public Result run(Map<String, SampleFamily> sampleFamilies) {
- SampleFamily sf = expression.run(sampleFamilies);
- if (sf == SampleFamily.EMPTY) return Result.fail("EMPTY");
- return Result.success(sf);
- }
-
- // parse() still provides ExpressionParsingContext for static analysis
- // (scopeType, functionName, etc.) — this is unchanged
-}
-```
-
----
-
-### Updated DSL.java and FilterExpression.java
-
-### DSL.java (already exists, update)
-
-Change from loading `DelegatingScript` to loading `MalExpression`:
-
-```java
-public static Expression parse(String metricName, String expression) {
- MalExpression malExpr = loadFromManifest(metricName); // Class.forName +
newInstance
- return new Expression(metricName, expression, malExpr);
-}
-```
-
-Manifest: `META-INF/mal-expressions.txt` (replaces `mal-groovy-scripts.txt`):
-```
-meter_apisix_endpoint_http_status=org.apache.skywalking.oap.server.core.source.oal.rt.mal.MalExpr_meter_apisix_endpoint_http_status
-```
-
-### FilterExpression.java (already exists, update)
-
-Change from loading Groovy `Script` → `Closure<Boolean>` to loading
`MalFilter`:
-
-```java
-public class FilterExpression {
- private final MalFilter filter;
-
- public FilterExpression(String literal) {
- this.filter = loadFromManifest(literal); // Class.forName +
newInstance
- }
-
- public Map<String, SampleFamily> filter(Map<String, SampleFamily>
sampleFamilies) {
- // Apply filter.test(labels) to each sample family
- }
-}
-```
-
----
-
-### Precompiler Integration
-
-Replace `compileMAL()` in `Precompiler.java` to use the transpiler instead of
Groovy
-compilation:
-
-```java
-private static void compileMAL(String outputDir, ...) {
- MalToJavaTranspiler transpiler = new MalToJavaTranspiler(outputDir);
-
- for (Rule rule : allRules) {
- for (MetricsRule mr : rule.getMetricsRules()) {
- String metricName = formatMetricName(rule, mr.getName());
- String expression = formatExp(rule.getExpPrefix(),
rule.getExpSuffix(), mr.getExp());
- transpiler.transpile(metricName, expression);
- }
- if (rule.getFilter() != null) {
- transpiler.transpileFilter(rule.getName(), rule.getFilter());
- }
- }
-
- transpiler.writeManifest(); // mal-expressions.txt
- transpiler.compileAll(); // javac on generated .java files
-}
-```
-
-The Javassist meter class generation is still run via `MetricConvert` pipeline
(unchanged
-from Phase 2). Only Groovy compilation is replaced by transpilation.
-
----
-
-### Same-FQCN Replacements (Transpiler)
-
-| Upstream Class | Replacement Location | Change |
-|---|---|---|
-| `SampleFamily` | `meter-analyzer-for-graalvm/` | **New.** Closure parameters
→ functional interfaces |
-| `Expression` | `meter-analyzer-for-graalvm/` | **New.** No Groovy: uses
`MalExpression` instead of `DelegatingScript` |
-| `DSL` (MAL) | `meter-analyzer-for-graalvm/` | **Updated.** Loads
`MalExpression` instead of Groovy `DelegatingScript` |
-| `FilterExpression` | `meter-analyzer-for-graalvm/` | **Updated.** Loads
`MalFilter` instead of Groovy `Closure<Boolean>` |
-
----
-
-### Files Created (Transpiler)
-
-| File | Purpose |
-|------|---------|
-| `.../dsl/MalExpression.java` | Interface for generated MAL expression
classes |
-| `.../dsl/MalFilter.java` | Interface for generated MAL filter classes |
-| `.../dsl/SampleFamilyFunctions.java` | Functional interfaces: TagFunction,
SampleFilter, ForEachFunction, DecorateFunction, PropertiesExtractor |
-| `.../meter-analyzer-for-graalvm/SampleFamily.java` | Same-FQCN: Closure →
functional interfaces |
-| `.../meter-analyzer-for-graalvm/Expression.java` | Same-FQCN: no Groovy,
uses MalExpression |
-| `.../precompiler/MalToJavaTranspiler.java` | AST-walking transpiler: Groovy
AST → Java source |
-| Generated `MalExpr_*.java` files | ~1250 pure Java expression classes |
-| Generated `MalFilter_*.java` files | ~29 pure Java filter classes |
-
-### Files Modified (Transpiler)
-
-| File | Change |
-|------|--------|
-| `Precompiler.java` | `compileMAL()` uses transpiler instead of Groovy
compilation |
-| `meter-analyzer-for-graalvm/pom.xml` | Add SampleFamily, Expression to shade
excludes |
-| `meter-analyzer-for-graalvm/DSL.java` | Load MalExpression instead of
DelegatingScript |
-| `meter-analyzer-for-graalvm/FilterExpression.java` | Load MalFilter instead
of Groovy Closure |
-
----
-
-### Transpiler Verification
-
-```bash
-# 1. Rebuild with transpiler
-make compile
-
-# 2. Run ALL existing tests (1303 UTs validate identical MAL behavior)
-make test
-
-# 3. Boot JVM distro
-make shutdown && make boot
-
-# 4. Verify no Groovy at runtime (after LAL is also transpiled)
-jar tf
oap-graalvm-server/target/oap-graalvm-jvm-distro/oap-graalvm-jvm-distro/libs/*.jar
| grep groovy
-# Should find NOTHING
-
-# 5. Attempt native-image build (no more Groovy substitution error)
-make native-image
-```
diff --git a/OAL-IMMIGRATION.md b/OAL-IMMIGRATION.md
deleted file mode 100644
index 6366bee..0000000
--- a/OAL-IMMIGRATION.md
+++ /dev/null
@@ -1,162 +0,0 @@
-# OAL Build-Time Pre-Compilation
-
-## Context
-
-OAL engine generates metrics, builder, and dispatcher classes at runtime via
Javassist (`ClassPool.makeClass()` → `CtClass.toClass()`). GraalVM native image
doesn't support runtime bytecode generation. Additionally, Guava's
`ClassPath.from()` — used by `AnnotationScan.scan()` and
`SourceReceiverImpl.scan()` — doesn't work in native image (no JAR-based
classpath).
-
-**Solution**: Run OAL engine at build time, export `.class` files + manifests.
Replace upstream classes with same-FQCN versions that load from manifests
instead of scanning or generating code.
-
----
-
-## Build-Time OAL Class Export Tool
-
-**Module**: `build-tools/precompiler` (originally `build-tools/oal-exporter`,
merged into unified precompiler)
-
-**Created**: `OALClassExporter.java` — main class that:
-
-1. Validates all 9 OAL script files are on the classpath
-2. Initializes `DefaultScopeDefine` by scanning `@ScopeDeclaration`
annotations (OAL enricher needs scope metadata)
-3. For each of the 9 `OALDefine` configs: instantiates `OALEngineV2`, enables
debug output (`setOpenEngineDebug(true)` + `setGeneratedFilePath()`), calls
`engine.start()` which parses OAL → enriches → generates `.class` files via
Javassist
-4. Scans the output directory for generated `.class` files and writes OAL
manifests:
- - `META-INF/oal-metrics-classes.txt` — ~620 fully-qualified class names
- - `META-INF/oal-dispatcher-classes.txt` — ~45 fully-qualified class names
- - `META-INF/oal-disabled-sources.txt` — disabled source names from
`disable.oal`
-5. Runs Guava `ClassPath.from()` scan at build time to produce 6
annotation/interface manifests under `META-INF/annotation-scan/`:
- - `ScopeDeclaration.txt` — classes annotated with `@ScopeDeclaration`
- - `Stream.txt` — classes annotated with `@Stream` (hardcoded only, not
OAL-generated)
- - `Disable.txt` — classes annotated with `@Disable`
- - `MultipleDisable.txt` — classes annotated with `@MultipleDisable`
- - `SourceDispatcher.txt` — concrete implementations of `SourceDispatcher`
interface (hardcoded only)
- - `ISourceDecorator.txt` — concrete implementations of `ISourceDecorator`
interface
-
-**Key difference from original plan**: No "collecting listeners" needed.
`engine.start()` generates `.class` files directly to disk via the debug API.
We scan the output directory for class files rather than hooking into engine
callbacks.
-
-### 9 OAL Defines processed
-
-| Define | Config File | Source Package | Catalog |
-|--------|-------------|----------------|---------|
-| `DisableOALDefine` | `oal/disable.oal` | `core.source` | — |
-| `CoreOALDefine` | `oal/core.oal` | `core.source` | — |
-| `JVMOALDefine` | `oal/java-agent.oal` | `core.source` | — |
-| `CLROALDefine` | `oal/dotnet-agent.oal` | `core.source` | — |
-| `BrowserOALDefine` | `oal/browser.oal` | `core.browser.source` | — |
-| `MeshOALDefine` | `oal/mesh.oal` | `core.source` | `ServiceMesh` |
-| `EBPFOALDefine` | `oal/ebpf.oal` | `core.source` | — |
-| `TCPOALDefine` | `oal/tcp.oal` | `core.source` | `EnvoyTCP` |
-| `CiliumOALDefine` | `oal/cilium.oal` | `core.source` | — |
-
-### Generated class packages
-
-- Metrics:
`org.apache.skywalking.oap.server.core.source.oal.rt.metrics.*Metrics`
-- Builders:
`org.apache.skywalking.oap.server.core.source.oal.rt.metrics.builder.*MetricsBuilder`
-- Dispatchers:
`org.apache.skywalking.oap.server.core.source.oal.rt.dispatcher.[catalog].*Dispatcher`
-
----
-
-## Runtime Registration via Same-FQCN Replacement Classes
-
-Instead of extending upstream classes or hooking via `ModuleWiringBridge`, we
use **same-FQCN replacement**: create classes in `oap-graalvm-server` with the
exact same fully-qualified class name as the upstream class. Maven classpath
precedence ensures our version is loaded instead of the upstream version.
-
-### 3 replacement classes created:
-
-**1. `OALEngineLoaderService`**
(`oap-graalvm-server/.../core/oal/rt/OALEngineLoaderService.java`)
-
-Same FQCN as upstream
`org.apache.skywalking.oap.server.core.oal.rt.OALEngineLoaderService`. On first
`load()` call:
-- Reads `META-INF/oal-disabled-sources.txt` → registers with `DisableRegister`
-- Reads `META-INF/oal-metrics-classes.txt` → `Class.forName()` →
`StreamAnnotationListener.notify()`
-- Reads `META-INF/oal-dispatcher-classes.txt` → `Class.forName()` →
`DispatcherDetectorListener.addIfAsSourceDispatcher()`
-- All subsequent `load()` calls are no-ops (all classes registered on first
call regardless of which `OALDefine` triggered it)
-
-**2. `AnnotationScan`**
(`oap-graalvm-server/.../core/annotation/AnnotationScan.java`)
-
-Same FQCN as upstream
`org.apache.skywalking.oap.server.core.annotation.AnnotationScan`. Instead of
Guava `ClassPath.from()` scanning, reads manifest files from
`META-INF/annotation-scan/{AnnotationSimpleName}.txt`. Each registered
`AnnotationListener` is matched against its corresponding manifest.
-
-**3. `SourceReceiverImpl`**
(`oap-graalvm-server/.../core/source/SourceReceiverImpl.java`)
-
-Same FQCN as upstream
`org.apache.skywalking.oap.server.core.source.SourceReceiverImpl`. `scan()`
reads from `META-INF/annotation-scan/SourceDispatcher.txt` and
`META-INF/annotation-scan/ISourceDecorator.txt` instead of Guava classpath
scanning.
-
-### Key differences from original plan:
-- **No extending** — same-FQCN replacement instead of subclassing
-- **No `ModuleWiringBridge` changes** — classpath precedence handles the swap
automatically
-- **3 replacement classes, not 1** — `AnnotationScan` and `SourceReceiverImpl`
also needed replacement
-- **Classpath scanning fully eliminated** — annotation manifests replace Guava
scanning
-
----
-
-## Class Loading and Remaining Scans
-
-### `Class.forName()` in native image
-`Class.forName()` is supported in GraalVM native image when classes are
registered in `reflect-config.json`. Since all pre-generated classes are on the
classpath at native-image build time, the GraalVM compiler includes them in the
binary. The `reflect-config.json` entries enable runtime `Class.forName()`
lookup.
-
-### OAL-internal scans — build-time only
-The 3 OAL-internal scans (`MetricsHolder`, `DefaultMetricsFunctionRegistry`,
`FilterMatchers`) only run inside the OAL engine during `engine.start()`. They
happen at **build time** in `OALClassExporter`, not at runtime. Automatically
solved.
-
-### `MeterSystem` — solved in MAL immigration
-`MeterSystem` uses Guava `ClassPath.from()` to discover meter function classes
at runtime. Replaced with manifest-based loading. See
[MAL-IMMIGRATION.md](MAL-IMMIGRATION.md).
-
-### `reflect-config.json`
-GraalVM reflection configuration for `Class.forName()` calls on OAL-generated
and manifest-listed classes is auto-generated by the precompiler from manifests.
-
----
-
-## Same-FQCN Replacements (OAL)
-
-| Upstream Class | Upstream Location | Replacement Location | What Changed |
-|---|---|---|---|
-| `OALEngineLoaderService` |
`server-core/.../oal/rt/OALEngineLoaderService.java` |
`oap-libs-for-graalvm/server-core-for-graalvm/` | Complete rewrite. Loads
pre-compiled OAL classes from build-time manifests instead of running ANTLR4 +
FreeMarker + Javassist at runtime. |
-| `AnnotationScan` | `server-core/.../annotation/AnnotationScan.java` |
`oap-libs-for-graalvm/server-core-for-graalvm/` | Complete rewrite. Reads
`META-INF/annotation-scan/{name}.txt` manifests instead of Guava
`ClassPath.from()` scanning. |
-| `SourceReceiverImpl` | `server-core/.../source/SourceReceiverImpl.java` |
`oap-libs-for-graalvm/server-core-for-graalvm/` | Complete rewrite. Reads
dispatcher/decorator manifests instead of Guava `ClassPath.from()` scanning. |
-
-All three replacements are repackaged into `server-core-for-graalvm` via
`maven-shade-plugin` — the original `.class` files are excluded from the shaded
JAR.
-
----
-
-## Files Created
-
-1. **`build-tools/precompiler/src/main/java/.../Precompiler.java`** (unified,
originally `oal-exporter`)
- - Build-time tool: runs 9 OAL defines, exports `.class` files, writes OAL
manifests + annotation/interface manifests + reflection metadata
-
-2.
**`oap-libs-for-graalvm/server-core-for-graalvm/src/main/java/.../core/oal/rt/OALEngineLoaderService.java`**
- - Same-FQCN replacement: loads pre-compiled OAL classes from manifests
-
-3.
**`oap-libs-for-graalvm/server-core-for-graalvm/src/main/java/.../core/annotation/AnnotationScan.java`**
- - Same-FQCN replacement: reads annotation manifests instead of Guava
classpath scanning
-
-4.
**`oap-libs-for-graalvm/server-core-for-graalvm/src/main/java/.../core/source/SourceReceiverImpl.java`**
- - Same-FQCN replacement: reads dispatcher/decorator manifests instead of
Guava classpath scanning
-
-5. **`oap-graalvm-server/src/test/java/.../PrecompiledRegistrationTest.java`**
- - 12 tests: manifest vs Guava scan comparison, OAL class loading, scope
registration, source→dispatcher→metrics chain consistency
-
-## Key Upstream Files (read-only)
-
-- `OALEngineV2.java` — `start()` (parse → enrich → generate),
`notifyAllListeners()` (register)
-- `OALClassGeneratorV2.java` — `setOpenEngineDebug(true)`,
`setGeneratedFilePath()`, `writeGeneratedFile()` exports via
`ctClass.toBytecode()`
-- `OALEngineLoaderService.java` (upstream) — `load()` creates engine, sets
listeners, calls `start()`+`notifyAllListeners()`
-- `StorageBuilderFactory.java:67-78` — `Default` impl uses `metrics-builder`
template path
-- `StreamAnnotationListener.java` — `notify(Class)` reads `@Stream`, routes to
`MetricsStreamProcessor.create()`
-- `CoreModuleProvider.java:356-357` — registers `OALEngineLoaderService` in
`prepare()`
-- `CoreModuleProvider.java:417-421` — `start()` calls `load(DisableOALDefine)`
then `scan()`
-
----
-
-## Verification
-
-```bash
-# 1. Build everything
-make build-distro
-
-# 2. Check generated classes exist
-ls
build-tools/precompiler/target/generated-classes/org/apache/skywalking/oap/server/core/source/oal/rt/metrics/
-ls
build-tools/precompiler/target/generated-classes/org/apache/skywalking/oap/server/core/source/oal/rt/dispatcher/
-
-# 3. Check manifest files
-cat
build-tools/precompiler/target/generated-classes/META-INF/oal-metrics-classes.txt
-cat
build-tools/precompiler/target/generated-classes/META-INF/oal-dispatcher-classes.txt
-
-# 4. Check annotation scan manifests
-ls build-tools/precompiler/target/generated-classes/META-INF/annotation-scan/
-
-# 5. Verify tests pass
-make build-distro # runs PrecompiledRegistrationTest
-```
diff --git a/README.md b/README.md
index 6c2c669..534602e 100644
--- a/README.md
+++ b/README.md
@@ -3,89 +3,31 @@
SkyWalking GraalVM Distro is a re-distribution of the official Apache
SkyWalking OAP server, targeting GraalVM native image on JDK 25.
-## Why a Re-Distribution?
+This distro moves all dynamic code generation (Javassist, Groovy, classpath
scanning) from runtime to build time, producing a ~203MB native binary with
full OAP feature set. No upstream source modifications required.
-SkyWalking OAP server relies heavily on runtime code generation and dynamic
class loading — patterns that are fundamentally incompatible with GraalVM
native image's closed-world assumption. This includes Javassist bytecode
generation (~1,850 classes), Groovy dynamic compilation (~1,260 scripts), Guava
classpath scanning, ServiceLoader discovery, and reflection-based configuration.
-
-None of these work in a GraalVM native image out of the box.
-
-This distro moves all dynamic code generation from runtime to build time. At
build time, the full OAL/MAL/LAL initialization pipeline runs, captures all
generated bytecode, and packages it into the native image classpath. At
runtime, pre-compiled classes are loaded from manifests — no Javassist, no
GroovyShell, no classpath scanning.
-
-The **same-FQCN replacement** technique makes this transparent: classes in
this distro share the exact fully-qualified name with their upstream
counterparts. Maven classpath ordering ensures the replacement is loaded. No
forking, no patching.
-
-## Module Selection
-
-This distro targets a **full-feature OAP server** with fixed module/provider
selection:
-
-- **Storage**: BanyanDB
-- **Cluster**: Standalone, Kubernetes
-- **Configuration**: Kubernetes
-- **Receivers**: All (trace, meter, log, profile, browser, OTel, mesh, envoy,
Zipkin, Zabbix, Telegraf, etc.)
-- **Query**: GraphQL, PromQL, LogQL, Zipkin
-- **Alarm, Telemetry, Exporter**: Enabled
-
-See [DISTRO-POLICY.md](DISTRO-POLICY.md) for the full module table and build
plan.
-
-## Build
-
-Requires GraalVM JDK 25.
+## Quick Start
```bash
-# Initialize submodule
-git submodule update --init --recursive
+git clone --recurse-submodules
https://github.com/apache/skywalking-graalvm-distro.git
+cd skywalking-graalvm-distro
-# Init upstream SkyWalking submodule and install to Maven cache (first time
only)
+# First time: install upstream SkyWalking to Maven cache
JAVA_HOME=/path/to/graalvm-jdk-25 make init-skywalking
-# Compile distro (precompiler + tests + server)
+# Build distro (precompiler + tests + server)
JAVA_HOME=/path/to/graalvm-jdk-25 make build-distro
-# Build native image (requires GraalVM with native-image)
+# Build native image
JAVA_HOME=/path/to/graalvm-jdk-25 make native-image
-# Build native image for Docker on macOS (cross-compiles via Docker)
-make native-image-macos
-
-# Package into Docker image
-make docker-native
-
-# Run with docker-compose (BanyanDB + OAP native)
+# Run with Docker Compose (BanyanDB + OAP native)
docker compose -f docker/docker-compose.yml up
```
-## Project Structure
-
-```
-skywalking-graalvm-distro/
-├── skywalking/ # Git submodule — DO NOT MODIFY
-├── build-tools/
-│ ├── precompiler/ # Build-time OAL + MAL + LAL
pre-compilation + reflection metadata
-│ └── config-generator/ # Build-time config code generation
(YamlConfigLoaderUtils)
-├── oap-libs-for-graalvm/ # Per-JAR repackaged modules (same-FQCN
replacements via shade)
-├── oap-graalvm-server/ # GraalVM-ready OAP server (JVM distro)
-│ └── src/
-│ ├── main/java/ # Same-FQCN replacement classes
-│ └── test/java/ # 1,300+ comparison tests
-├── oap-graalvm-native/ # Native image module (native-maven-plugin
+ assembly)
-├── docker/
-│ ├── Dockerfile.native # Runtime image (debian:bookworm-slim +
native binary)
-│ └── docker-compose.yml # BanyanDB + OAP native for local testing
-├── DISTRO-POLICY.md # Build plan with phase tracking
-├── OAL-IMMIGRATION.md # OAL pre-compilation design
-├── MAL-IMMIGRATION.md # MAL pre-compilation design
-├── LAL-IMMIGRATION.md # LAL pre-compilation design
-└── CONFIG-INIT-IMMIGRATION.md # Config initialization design
-```
-
## Documentation
-| Document | Description |
-|---|---|
-| [DISTRO-POLICY.md](DISTRO-POLICY.md) | Build plan, module selection, phase
tracking, risk assessment |
-| [OAL-IMMIGRATION.md](OAL-IMMIGRATION.md) | OAL (Observability Analysis
Language) pre-compilation: Javassist class export, annotation scanning, runtime
manifests |
-| [MAL-IMMIGRATION.md](MAL-IMMIGRATION.md) | MAL (Meter Analysis Language)
pre-compilation: Groovy-to-Java transpilation, Javassist meter classes,
combination pattern |
-| [LAL-IMMIGRATION.md](LAL-IMMIGRATION.md) | LAL (Log Analysis Language)
pre-compilation: Groovy-to-Java transpilation, SHA-256 manifest lookup |
-| [CONFIG-INIT-IMMIGRATION.md](CONFIG-INIT-IMMIGRATION.md) | Config
initialization: reflection-free config loading via generated code |
+Full documentation is available in [docs/](docs/) and published on the project
website.
## License
+
Apache 2.0
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..eec7fff
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,111 @@
+# SkyWalking GraalVM Distro
+
+**GraalVM native-image distribution of Apache SkyWalking OAP Server.**
+
+This project produces a self-contained native binary of the SkyWalking OAP
backend.
+It wraps the upstream [Apache
SkyWalking](https://github.com/apache/skywalking) repository
+as a git submodule and applies build-time transformations to eliminate all
GraalVM-incompatible
+patterns — without modifying upstream source code.
+
+## Why Native Image
+
+- **Fast startup** — native binary boots directly to full module initialization
+- **Lower memory footprint** — no JIT compiler, no class-loading overhead
+- **Single binary deployment** — ~203MB self-contained executable, ideal for
containers and cloud-native environments
+
+## How It Works
+
+SkyWalking OAP relies on runtime code generation and dynamic class loading in
several subsystems.
+This distro moves all of that to build time:
+
+| Subsystem | Runtime Pattern | Build-Time Solution |
+|-----------|----------------|---------------------|
+| **OAL** (metrics) | Javassist generates ~1285 classes at startup |
`OALClassExporter` runs the OAL engine at build time, exports `.class` files
and manifests |
+| **MAL** (meters) | Groovy compiles 1250+ expressions at startup |
`MalToJavaTranspiler` converts Groovy AST to pure Java `MalExpression` classes |
+| **LAL** (logs) | Groovy compiles 10 scripts at startup |
`LalToJavaTranspiler` converts Groovy AST to pure Java `LalExpression` classes |
+| **Config loading** | `Field.setAccessible()` + reflection |
`ConfigInitializerGenerator` produces setter-based `YamlConfigLoaderUtils` |
+| **Classpath scanning** | Guava `ClassPath.from()` at startup | Build-time
manifests under `META-INF/annotation-scan/` |
+| **Module wiring** | ServiceLoader SPI discovery | `FixedModuleManager` with
hardcoded module/provider construction |
+
+All replacement classes use the **same-FQCN** (fully-qualified class name)
technique: a replacement
+class with the identical package and name is repackaged via
`maven-shade-plugin`, excluding the
+original from the upstream JAR. No classpath ordering tricks needed.
+
+## Module Selection
+
+The distro ships with a full feature set. Module/provider selection is fixed
at build time:
+
+- **Storage**: BanyanDB
+- **Cluster**: Standalone, Kubernetes
+- **Configuration**: Kubernetes
+- **Receivers**: All (trace, JVM, meter, log, browser, OTel, mesh, Envoy,
Zipkin, Zabbix, Telegraf, AWS Firehose, Cilium, eBPF, async-profiler, pprof,
CLR, Kafka fetcher)
+- **Analyzers**: Trace, Log, Event
+- **Query**: GraphQL, PromQL, LogQL, Zipkin, Status
+- **Alarm, Telemetry, Exporter, Health Check, AI Pipeline**: All enabled
+
+See [Distribution Policy](distro-policy.md) for the complete module table and
architecture details.
+
+## Prerequisites
+
+- **GraalVM JDK 25** with `native-image` installed
+- **Maven 3.9+**
+- **Docker** (for container builds and docker-compose)
+
+## Build Commands
+
+```bash
+# Full build (precompiler + tests + JVM distro)
+JAVA_HOME=$GRAALVM_HOME make build-distro
+
+# Precompiler only
+JAVA_HOME=$GRAALVM_HOME mvn -pl build-tools/precompiler install -DskipTests
+
+# Run tests only
+JAVA_HOME=$GRAALVM_HOME mvn -pl oap-graalvm-server test
+
+# Native image
+JAVA_HOME=$GRAALVM_HOME make native-image
+
+# Docker native image (cross-compile on macOS)
+make native-image-macos
+
+# Package into Docker image
+make docker-native
+
+# Run with Docker Compose (BanyanDB + OAP native)
+docker compose -f docker/docker-compose.yml up
+```
+
+## Project Structure
+
+```
+skywalking-graalvm-distro/
+├── skywalking/ # Git submodule — apache/skywalking (read-only)
+├── build-tools/
+│ ├── precompiler/ # OAL + MAL + LAL build-time compilation
+│ └── config-generator/ # Config code generator (YamlConfigLoaderUtils)
+├── oap-libs-for-graalvm/ # Per-JAR same-FQCN replacement modules (shade
plugin)
+├── oap-graalvm-server/ # GraalVM-ready OAP server (JVM distro)
+├── oap-graalvm-native/ # Native image build (native-maven-plugin)
+├── docker/ # Dockerfile.native + docker-compose.yml
+└── docs/ # Documentation
+```
+
+## Test Suites
+
+All build-time transpilations are validated by dual-path comparison tests:
+
+- **MAL**: 73 test classes, 1281 assertions — covers all 71 YAML rule files
+- **LAL**: 5 test classes, 19 assertions — covers all 8 YAML rule files
+
+Each test compiles the expression via both paths (fresh Groovy compilation vs
pre-compiled Java class)
+and asserts identical results. Tests require actual data flow — no vacuous
empty-result agreements.
+
+## Further Reading
+
+- [Distribution Policy](distro-policy.md) — full module table, architecture
constraints, build workflow
+- [Configuration](configuration.md) — all available settings, environment
variables, and differences from upstream
+- [OAL Pre-Compilation](oal-immigration.md) — Javassist class export,
annotation scan manifests
+- [MAL Transpilation](mal-immigration.md) — Groovy-to-Java transpiler,
combination pattern, functional interfaces
+- [LAL Transpilation](lal-immigration.md) — Groovy-to-Java transpiler, SHA-256
deduplication, spec class overloads
+- [Config Initialization](config-init-immigration.md) — reflection-free config
loading via generated setters
diff --git a/docs/configuration.md b/docs/configuration.md
new file mode 100644
index 0000000..3f9de36
--- /dev/null
+++ b/docs/configuration.md
@@ -0,0 +1,628 @@
+# Configuration
+
+The SkyWalking GraalVM Distro uses a simplified `application.yml` with a fixed
set of modules and providers.
+All settings are configurable via **system environment variables** at runtime,
following the same
+`${SW_ENV_VAR:default}` pattern as upstream SkyWalking.
+
+For the full upstream configuration reference, see the
+[SkyWalking Configuration
Vocabulary](https://skywalking.apache.org/docs/main/next/en/setup/backend/configuration-vocabulary/).
+
+## Differences from Upstream
+
+This distro selects a fixed subset of modules/providers at build time. Modules
and providers **not listed
+below are not available** — they are not compiled into the distro binary.
+
+### Removed Providers (vs upstream)
+
+| Module | Removed Providers | Reason |
+|--------|-------------------|--------|
+| **cluster** | zookeeper, consul, etcd, nacos | Only `standalone` and
`kubernetes` are included |
+| **storage** | elasticsearch, h2, mysql, postgresql, opensearch | Only
`banyandb` is included |
+| **configuration** | apollo, consul, etcd, nacos, zookeeper, grpc | Only
`k8s-configmap` (and `none`) are included |
+| **telemetry** | none (upstream default) | Only `prometheus` is included |
+
+All other modules (receivers, analyzers, query, alarm, exporter, etc.) retain
their default providers
+with the same configuration options as upstream.
+
+### Optional Modules
+
+Several modules are **disabled by default** (empty selector `:-`). Enable them
by setting the
+corresponding environment variable:
+
+| Module | Enable Via | Default |
+|--------|-----------|---------|
+| `receiver-zabbix` | `SW_RECEIVER_ZABBIX=default` | disabled |
+| `receiver-zipkin` | `SW_RECEIVER_ZIPKIN=default` | disabled |
+| `query-zipkin` | `SW_QUERY_ZIPKIN=default` | disabled |
+| `kafka-fetcher` | `SW_KAFKA_FETCHER=default` | disabled |
+| `cilium-fetcher` | `SW_CILIUM_FETCHER=default` | disabled |
+| `exporter` | `SW_EXPORTER=default` | disabled |
+
+## Module Configuration Reference
+
+Below is the complete list of modules, their fixed providers, and available
settings.
+Each setting shows its environment variable and default value.
+
+---
+
+### cluster
+
+**Selector**: `SW_CLUSTER` (default: `standalone`)
+
+Available providers: `standalone`, `kubernetes`
+
+| Setting | Environment Variable | Default |
+|---------|---------------------|---------|
+| *(selector)* | `SW_CLUSTER` | `standalone` |
+
+**kubernetes** provider:
+
+| Setting | Environment Variable | Default |
+|---------|---------------------|---------|
+| namespace | `SW_CLUSTER_K8S_NAMESPACE` | `default` |
+| labelSelector | `SW_CLUSTER_K8S_LABEL` | `app=collector,release=skywalking` |
+| uidEnvName | `SW_CLUSTER_K8S_UID` | `SKYWALKING_COLLECTOR_UID` |
+
+---
+
+### core
+
+**Selector**: `SW_CORE` (default: `default`)
+
+| Setting | Environment Variable | Default |
+|---------|---------------------|---------|
+| role | `SW_CORE_ROLE` | `Mixed` |
+| restHost | `SW_CORE_REST_HOST` | `0.0.0.0` |
+| restPort | `SW_CORE_REST_PORT` | `12800` |
+| restContextPath | `SW_CORE_REST_CONTEXT_PATH` | `/` |
+| restIdleTimeOut | `SW_CORE_REST_IDLE_TIMEOUT` | `30000` |
+| restAcceptQueueSize | `SW_CORE_REST_QUEUE_SIZE` | `0` |
+| httpMaxRequestHeaderSize | `SW_CORE_HTTP_MAX_REQUEST_HEADER_SIZE` | `8192` |
+| gRPCHost | `SW_CORE_GRPC_HOST` | `0.0.0.0` |
+| gRPCPort | `SW_CORE_GRPC_PORT` | `11800` |
+| maxConcurrentCallsPerConnection | `SW_CORE_GRPC_MAX_CONCURRENT_CALL` | `0` |
+| maxMessageSize | `SW_CORE_GRPC_MAX_MESSAGE_SIZE` | `52428800` |
+| gRPCThreadPoolSize | `SW_CORE_GRPC_THREAD_POOL_SIZE` | `-1` |
+| gRPCSslEnabled | `SW_CORE_GRPC_SSL_ENABLED` | `false` |
+| gRPCSslKeyPath | `SW_CORE_GRPC_SSL_KEY_PATH` | `""` |
+| gRPCSslCertChainPath | `SW_CORE_GRPC_SSL_CERT_CHAIN_PATH` | `""` |
+| gRPCSslTrustedCAPath | `SW_CORE_GRPC_SSL_TRUSTED_CA_PATH` | `""` |
+| downsampling | *(not overridable)* | `Hour, Day` |
+| enableDataKeeperExecutor | `SW_CORE_ENABLE_DATA_KEEPER_EXECUTOR` | `true` |
+| dataKeeperExecutePeriod | `SW_CORE_DATA_KEEPER_EXECUTE_PERIOD` | `5` |
+| recordDataTTL | `SW_CORE_RECORD_DATA_TTL` | `3` |
+| metricsDataTTL | `SW_CORE_METRICS_DATA_TTL` | `7` |
+| l1FlushPeriod | `SW_CORE_L1_AGGREGATION_FLUSH_PERIOD` | `500` |
+| storageSessionTimeout | `SW_CORE_STORAGE_SESSION_TIMEOUT` | `70000` |
+| persistentPeriod | `SW_CORE_PERSISTENT_PERIOD` | `25` |
+| topNReportPeriod | `SW_CORE_TOPN_REPORT_PERIOD` | `10` |
+| activeExtraModelColumns | `SW_CORE_ACTIVE_EXTRA_MODEL_COLUMNS` | `false` |
+| serviceNameMaxLength | `SW_SERVICE_NAME_MAX_LENGTH` | `70` |
+| serviceCacheRefreshInterval | `SW_SERVICE_CACHE_REFRESH_INTERVAL` | `10` |
+| instanceNameMaxLength | `SW_INSTANCE_NAME_MAX_LENGTH` | `70` |
+| endpointNameMaxLength | `SW_ENDPOINT_NAME_MAX_LENGTH` | `150` |
+| searchableTracesTags | `SW_SEARCHABLE_TAG_KEYS` |
`http.method,http.status_code,rpc.status_code,db.type,db.instance,mq.queue,mq.topic,mq.broker`
|
+| searchableLogsTags | `SW_SEARCHABLE_LOGS_TAG_KEYS` |
`level,http.status_code` |
+| searchableAlarmTags | `SW_SEARCHABLE_ALARM_TAG_KEYS` | `level` |
+| autocompleteTagKeysQueryMaxSize | `SW_AUTOCOMPLETE_TAG_KEYS_QUERY_MAX_SIZE`
| `100` |
+| autocompleteTagValuesQueryMaxSize |
`SW_AUTOCOMPLETE_TAG_VALUES_QUERY_MAX_SIZE` | `100` |
+| prepareThreads | `SW_CORE_PREPARE_THREADS` | `2` |
+| enableEndpointNameGroupingByOpenapi |
`SW_CORE_ENABLE_ENDPOINT_NAME_GROUPING_BY_OPENAPI` | `true` |
+| syncPeriodHttpUriRecognitionPattern |
`SW_CORE_SYNC_PERIOD_HTTP_URI_RECOGNITION_PATTERN` | `10` |
+| trainingPeriodHttpUriRecognitionPattern |
`SW_CORE_TRAINING_PERIOD_HTTP_URI_RECOGNITION_PATTERN` | `60` |
+| maxHttpUrisNumberPerService | `SW_CORE_MAX_HTTP_URIS_NUMBER_PER_SVR` |
`3000` |
+| enableHierarchy | `SW_CORE_ENABLE_HIERARCHY` | `true` |
+| maxHeapMemoryUsagePercent | `SW_CORE_MAX_HEAP_MEMORY_USAGE_PERCENT` | `96` |
+| maxDirectMemoryUsage | `SW_CORE_MAX_DIRECT_MEMORY_USAGE` | `-1` |
+
+---
+
+### storage
+
+**Selector**: `SW_STORAGE` (default: `banyandb`)
+
+Only the `banyandb` provider is available. BanyanDB storage configuration is
loaded from
+`bydb.yml` and `bydb-topn.yml` at runtime. See the
+[upstream BanyanDB storage
documentation](https://skywalking.apache.org/docs/main/next/en/setup/backend/storages/banyandb/)
+for details on those files.
+
+---
+
+### agent-analyzer
+
+**Selector**: `SW_AGENT_ANALYZER` (default: `default`)
+
+| Setting | Environment Variable | Default |
+|---------|---------------------|---------|
+| traceSamplingPolicySettingsFile | `SW_TRACE_SAMPLING_POLICY_SETTINGS_FILE` |
`trace-sampling-policy-settings.yml` |
+| slowDBAccessThreshold | `SW_SLOW_DB_THRESHOLD` | `default:200,mongodb:100` |
+| forceSampleErrorSegment | `SW_FORCE_SAMPLE_ERROR_SEGMENT` | `true` |
+| segmentStatusAnalysisStrategy | `SW_SEGMENT_STATUS_ANALYSIS_STRATEGY` |
`FROM_SPAN_STATUS` |
+| noUpstreamRealAddressAgents | `SW_NO_UPSTREAM_REAL_ADDRESS` | `6000,9000` |
+| meterAnalyzerActiveFiles | `SW_METER_ANALYZER_ACTIVE_FILES` |
`datasource,threadpool,satellite,go-runtime,python-runtime,continuous-profiling,java-agent,go-agent,ruby-runtime`
|
+| slowCacheReadThreshold | `SW_SLOW_CACHE_SLOW_READ_THRESHOLD` |
`default:20,redis:10` |
+| slowCacheWriteThreshold | `SW_SLOW_CACHE_SLOW_WRITE_THRESHOLD` |
`default:20,redis:10` |
+
+---
+
+### log-analyzer
+
+**Selector**: `SW_LOG_ANALYZER` (default: `default`)
+
+| Setting | Environment Variable | Default |
+|---------|---------------------|---------|
+| lalFiles | `SW_LOG_LAL_FILES` |
`envoy-als,mesh-dp,mysql-slowsql,pgsql-slowsql,redis-slowsql,k8s-service,nginx,default`
|
+| malFiles | `SW_LOG_MAL_FILES` | `nginx` |
+
+---
+
+### event-analyzer
+
+**Selector**: `SW_EVENT_ANALYZER` (default: `default`)
+
+No additional settings.
+
+---
+
+### receiver-sharing-server
+
+**Selector**: `SW_RECEIVER_SHARING_SERVER` (default: `default`)
+
+| Setting | Environment Variable | Default |
+|---------|---------------------|---------|
+| restHost | `SW_RECEIVER_SHARING_REST_HOST` | `0.0.0.0` |
+| restPort | `SW_RECEIVER_SHARING_REST_PORT` | `0` |
+| restContextPath | `SW_RECEIVER_SHARING_REST_CONTEXT_PATH` | `/` |
+| restIdleTimeOut | `SW_RECEIVER_SHARING_REST_IDLE_TIMEOUT` | `30000` |
+| restAcceptQueueSize | `SW_RECEIVER_SHARING_REST_QUEUE_SIZE` | `0` |
+| httpMaxRequestHeaderSize |
`SW_RECEIVER_SHARING_HTTP_MAX_REQUEST_HEADER_SIZE` | `8192` |
+| gRPCHost | `SW_RECEIVER_GRPC_HOST` | `0.0.0.0` |
+| gRPCPort | `SW_RECEIVER_GRPC_PORT` | `0` |
+| maxConcurrentCallsPerConnection | `SW_RECEIVER_GRPC_MAX_CONCURRENT_CALL` |
`0` |
+| maxMessageSize | `SW_RECEIVER_GRPC_MAX_MESSAGE_SIZE` | `52428800` |
+| gRPCThreadPoolSize | `SW_RECEIVER_GRPC_THREAD_POOL_SIZE` | `0` |
+| gRPCSslEnabled | `SW_RECEIVER_GRPC_SSL_ENABLED` | `false` |
+| gRPCSslKeyPath | `SW_RECEIVER_GRPC_SSL_KEY_PATH` | `""` |
+| gRPCSslCertChainPath | `SW_RECEIVER_GRPC_SSL_CERT_CHAIN_PATH` | `""` |
+| gRPCSslTrustedCAsPath | `SW_RECEIVER_GRPC_SSL_TRUSTED_CAS_PATH` | `""` |
+| authentication | `SW_AUTHENTICATION` | `""` |
+
+---
+
+### receiver-register
+
+**Selector**: `SW_RECEIVER_REGISTER` (default: `default`)
+
+No additional settings.
+
+---
+
+### receiver-trace
+
+**Selector**: `SW_RECEIVER_TRACE` (default: `default`)
+
+No additional settings.
+
+---
+
+### receiver-jvm
+
+**Selector**: `SW_RECEIVER_JVM` (default: `default`)
+
+No additional settings.
+
+---
+
+### receiver-clr
+
+**Selector**: `SW_RECEIVER_CLR` (default: `default`)
+
+No additional settings.
+
+---
+
+### receiver-profile
+
+**Selector**: `SW_RECEIVER_PROFILE` (default: `default`)
+
+No additional settings.
+
+---
+
+### receiver-async-profiler
+
+**Selector**: `SW_RECEIVER_ASYNC_PROFILER` (default: `default`)
+
+| Setting | Environment Variable | Default |
+|---------|---------------------|---------|
+| jfrMaxSize | `SW_RECEIVER_ASYNC_PROFILER_JFR_MAX_SIZE` | `31457280` |
+| memoryParserEnabled | `SW_RECEIVER_ASYNC_PROFILER_MEMORY_PARSER_ENABLED` |
`true` |
+
+---
+
+### receiver-pprof
+
+**Selector**: `SW_RECEIVER_PPROF` (default: `default`)
+
+| Setting | Environment Variable | Default |
+|---------|---------------------|---------|
+| pprofMaxSize | `SW_RECEIVER_PPROF_MAX_SIZE` | `31457280` |
+| memoryParserEnabled | `SW_RECEIVER_PPROF_MEMORY_PARSER_ENABLED` | `true` |
+
+---
+
+### receiver-zabbix (disabled by default)
+
+**Selector**: `SW_RECEIVER_ZABBIX` (default: disabled)
+
+Set `SW_RECEIVER_ZABBIX=default` to enable.
+
+| Setting | Environment Variable | Default |
+|---------|---------------------|---------|
+| port | `SW_RECEIVER_ZABBIX_PORT` | `10051` |
+| host | `SW_RECEIVER_ZABBIX_HOST` | `0.0.0.0` |
+| activeFiles | `SW_RECEIVER_ZABBIX_ACTIVE_FILES` | `agent` |
+
+---
+
+### service-mesh
+
+**Selector**: `SW_SERVICE_MESH` (default: `default`)
+
+No additional settings.
+
+---
+
+### envoy-metric
+
+**Selector**: `SW_ENVOY_METRIC` (default: `default`)
+
+| Setting | Environment Variable | Default |
+|---------|---------------------|---------|
+| acceptMetricsService | `SW_ENVOY_METRIC_SERVICE` | `true` |
+| enabledEnvoyMetricsRules | `SW_ENVOY_METRIC_ENABLED_RULES` |
`envoy,envoy-svc-relation` |
+| alsHTTPAnalysis | `SW_ENVOY_METRIC_ALS_HTTP_ANALYSIS` | `""` |
+| alsTCPAnalysis | `SW_ENVOY_METRIC_ALS_TCP_ANALYSIS` | `""` |
+| k8sServiceNameRule | `K8S_SERVICE_NAME_RULE` |
`${pod.metadata.labels.(service.istio.io/canonical-name)}.${pod.metadata.namespace}`
|
+| istioServiceNameRule | `ISTIO_SERVICE_NAME_RULE` |
`${serviceEntry.metadata.name}.${serviceEntry.metadata.namespace}` |
+| istioServiceEntryIgnoredNamespaces |
`SW_ISTIO_SERVICE_ENTRY_IGNORED_NAMESPACES` | `""` |
+| gRPCHost | `SW_ALS_GRPC_HOST` | `0.0.0.0` |
+| gRPCPort | `SW_ALS_GRPC_PORT` | `0` |
+| maxConcurrentCallsPerConnection | `SW_ALS_GRPC_MAX_CONCURRENT_CALL` | `0` |
+| maxMessageSize | `SW_ALS_GRPC_MAX_MESSAGE_SIZE` | `0` |
+| gRPCThreadPoolSize | `SW_ALS_GRPC_THREAD_POOL_SIZE` | `0` |
+| gRPCSslEnabled | `SW_ALS_GRPC_SSL_ENABLED` | `false` |
+| gRPCSslKeyPath | `SW_ALS_GRPC_SSL_KEY_PATH` | `""` |
+| gRPCSslCertChainPath | `SW_ALS_GRPC_SSL_CERT_CHAIN_PATH` | `""` |
+| gRPCSslTrustedCAsPath | `SW_ALS_GRPC_SSL_TRUSTED_CAS_PATH` | `""` |
+
+---
+
+### kafka-fetcher (disabled by default)
+
+**Selector**: `SW_KAFKA_FETCHER` (default: disabled)
+
+Set `SW_KAFKA_FETCHER=default` to enable.
+
+| Setting | Environment Variable | Default |
+|---------|---------------------|---------|
+| bootstrapServers | `SW_KAFKA_FETCHER_SERVERS` | `localhost:9092` |
+| namespace | `SW_NAMESPACE` | `""` |
+| partitions | `SW_KAFKA_FETCHER_PARTITIONS` | `3` |
+| replicationFactor | `SW_KAFKA_FETCHER_PARTITIONS_FACTOR` | `2` |
+| enableNativeProtoLog | `SW_KAFKA_FETCHER_ENABLE_NATIVE_PROTO_LOG` | `true` |
+| enableNativeJsonLog | `SW_KAFKA_FETCHER_ENABLE_NATIVE_JSON_LOG` | `true` |
+| consumers | `SW_KAFKA_FETCHER_CONSUMERS` | `1` |
+| kafkaHandlerThreadPoolSize | `SW_KAFKA_HANDLER_THREAD_POOL_SIZE` | `-1` |
+| kafkaHandlerThreadPoolQueueSize | `SW_KAFKA_HANDLER_THREAD_POOL_QUEUE_SIZE`
| `-1` |
+
+---
+
+### cilium-fetcher (disabled by default)
+
+**Selector**: `SW_CILIUM_FETCHER` (default: disabled)
+
+Set `SW_CILIUM_FETCHER=default` to enable.
+
+| Setting | Environment Variable | Default |
+|---------|---------------------|---------|
+| peerHost | `SW_CILIUM_FETCHER_PEER_HOST` |
`hubble-peer.kube-system.svc.cluster.local` |
+| peerPort | `SW_CILIUM_FETCHER_PEER_PORT` | `80` |
+| fetchFailureRetrySecond | `SW_CILIUM_FETCHER_FETCH_FAILURE_RETRY_SECOND` |
`10` |
+| sslConnection | `SW_CILIUM_FETCHER_SSL_CONNECTION` | `false` |
+| sslPrivateKeyFile | `SW_CILIUM_FETCHER_PRIVATE_KEY_FILE_PATH` | *(empty)* |
+| sslCertChainFile | `SW_CILIUM_FETCHER_CERT_CHAIN_FILE_PATH` | *(empty)* |
+| sslCaFile | `SW_CILIUM_FETCHER_CA_FILE_PATH` | *(empty)* |
+| convertClientAsServerTraffic |
`SW_CILIUM_FETCHER_CONVERT_CLIENT_AS_SERVER_TRAFFIC` | `true` |
+
+---
+
+### receiver-meter
+
+**Selector**: `SW_RECEIVER_METER` (default: `default`)
+
+No additional settings.
+
+---
+
+### receiver-otel
+
+**Selector**: `SW_OTEL_RECEIVER` (default: `default`)
+
+| Setting | Environment Variable | Default |
+|---------|---------------------|---------|
+| enabledHandlers | `SW_OTEL_RECEIVER_ENABLED_HANDLERS` |
`otlp-metrics,otlp-logs` |
+| enabledOtelMetricsRules | `SW_OTEL_RECEIVER_ENABLED_OTEL_METRICS_RULES` |
`apisix,nginx/*,k8s/*,istio-controlplane,vm,mysql/*,postgresql/*,oap,aws-eks/*,windows,aws-s3/*,aws-dynamodb/*,aws-gateway/*,redis/*,elasticsearch/*,rabbitmq/*,mongodb/*,kafka/*,pulsar/*,bookkeeper/*,rocketmq/*,clickhouse/*,activemq/*,kong/*,flink/*,banyandb/*`
|
+
+---
+
+### receiver-zipkin (disabled by default)
+
+**Selector**: `SW_RECEIVER_ZIPKIN` (default: disabled)
+
+Set `SW_RECEIVER_ZIPKIN=default` to enable.
+
+| Setting | Environment Variable | Default |
+|---------|---------------------|---------|
+| searchableTracesTags | `SW_ZIPKIN_SEARCHABLE_TAG_KEYS` | `http.method` |
+| sampleRate | `SW_ZIPKIN_SAMPLE_RATE` | `10000` |
+| maxSpansPerSecond | `SW_ZIPKIN_MAX_SPANS_PER_SECOND` | `0` |
+| enableHttpCollector | `SW_ZIPKIN_HTTP_COLLECTOR_ENABLED` | `true` |
+| restHost | `SW_RECEIVER_ZIPKIN_REST_HOST` | `0.0.0.0` |
+| restPort | `SW_RECEIVER_ZIPKIN_REST_PORT` | `9411` |
+| restContextPath | `SW_RECEIVER_ZIPKIN_REST_CONTEXT_PATH` | `/` |
+| restIdleTimeOut | `SW_RECEIVER_ZIPKIN_REST_IDLE_TIMEOUT` | `30000` |
+| restAcceptQueueSize | `SW_RECEIVER_ZIPKIN_REST_QUEUE_SIZE` | `0` |
+| enableKafkaCollector | `SW_ZIPKIN_KAFKA_COLLECTOR_ENABLED` | `false` |
+| kafkaBootstrapServers | `SW_ZIPKIN_KAFKA_SERVERS` | `localhost:9092` |
+| kafkaGroupId | `SW_ZIPKIN_KAFKA_GROUP_ID` | `zipkin` |
+| kafkaTopic | `SW_ZIPKIN_KAFKA_TOPIC` | `zipkin` |
+| kafkaConsumerConfig | `SW_ZIPKIN_KAFKA_CONSUMER_CONFIG` |
`{"auto.offset.reset":"earliest","enable.auto.commit":true}` |
+| kafkaConsumers | `SW_ZIPKIN_KAFKA_CONSUMERS` | `1` |
+| kafkaHandlerThreadPoolSize | `SW_ZIPKIN_KAFKA_HANDLER_THREAD_POOL_SIZE` |
`-1` |
+| kafkaHandlerThreadPoolQueueSize |
`SW_ZIPKIN_KAFKA_HANDLER_THREAD_POOL_QUEUE_SIZE` | `-1` |
+
+---
+
+### receiver-browser
+
+**Selector**: `SW_RECEIVER_BROWSER` (default: `default`)
+
+| Setting | Environment Variable | Default |
+|---------|---------------------|---------|
+| sampleRate | `SW_RECEIVER_BROWSER_SAMPLE_RATE` | `10000` |
+
+---
+
+### receiver-log
+
+**Selector**: `SW_RECEIVER_LOG` (default: `default`)
+
+No additional settings.
+
+---
+
+### query
+
+**Selector**: `SW_QUERY` (default: `graphql`)
+
+| Setting | Environment Variable | Default |
+|---------|---------------------|---------|
+| enableLogTestTool | `SW_QUERY_GRAPHQL_ENABLE_LOG_TEST_TOOL` | `false` |
+| maxQueryComplexity | `SW_QUERY_MAX_QUERY_COMPLEXITY` | `3000` |
+| enableUpdateUITemplate | `SW_ENABLE_UPDATE_UI_TEMPLATE` | `false` |
+| enableOnDemandPodLog | `SW_ENABLE_ON_DEMAND_POD_LOG` | `false` |
+
+---
+
+### query-zipkin (disabled by default)
+
+**Selector**: `SW_QUERY_ZIPKIN` (default: disabled)
+
+Set `SW_QUERY_ZIPKIN=default` to enable.
+
+| Setting | Environment Variable | Default |
+|---------|---------------------|---------|
+| restHost | `SW_QUERY_ZIPKIN_REST_HOST` | `0.0.0.0` |
+| restPort | `SW_QUERY_ZIPKIN_REST_PORT` | `9412` |
+| restContextPath | `SW_QUERY_ZIPKIN_REST_CONTEXT_PATH` | `/zipkin` |
+| restIdleTimeOut | `SW_QUERY_ZIPKIN_REST_IDLE_TIMEOUT` | `30000` |
+| restAcceptQueueSize | `SW_QUERY_ZIPKIN_REST_QUEUE_SIZE` | `0` |
+| lookback | `SW_QUERY_ZIPKIN_LOOKBACK` | `86400000` |
+| namesMaxAge | `SW_QUERY_ZIPKIN_NAMES_MAX_AGE` | `300` |
+| uiQueryLimit | `SW_QUERY_ZIPKIN_UI_QUERY_LIMIT` | `10` |
+| uiDefaultLookback | `SW_QUERY_ZIPKIN_UI_DEFAULT_LOOKBACK` | `900000` |
+
+---
+
+### promql
+
+**Selector**: `SW_PROMQL` (default: `default`)
+
+| Setting | Environment Variable | Default |
+|---------|---------------------|---------|
+| restHost | `SW_PROMQL_REST_HOST` | `0.0.0.0` |
+| restPort | `SW_PROMQL_REST_PORT` | `9090` |
+| restContextPath | `SW_PROMQL_REST_CONTEXT_PATH` | `/` |
+| restIdleTimeOut | `SW_PROMQL_REST_IDLE_TIMEOUT` | `30000` |
+| restAcceptQueueSize | `SW_PROMQL_REST_QUEUE_SIZE` | `0` |
+| buildInfoVersion | `SW_PROMQL_BUILD_INFO_VERSION` | `2.45.0` |
+| buildInfoRevision | `SW_PROMQL_BUILD_INFO_REVISION` | `""` |
+| buildInfoBranch | `SW_PROMQL_BUILD_INFO_BRANCH` | `""` |
+| buildInfoBuildUser | `SW_PROMQL_BUILD_INFO_BUILD_USER` | `""` |
+| buildInfoBuildDate | `SW_PROMQL_BUILD_INFO_BUILD_DATE` | `""` |
+| buildInfoGoVersion | `SW_PROMQL_BUILD_INFO_GO_VERSION` | `""` |
+
+---
+
+### logql
+
+**Selector**: `SW_LOGQL` (default: `default`)
+
+| Setting | Environment Variable | Default |
+|---------|---------------------|---------|
+| restHost | `SW_LOGQL_REST_HOST` | `0.0.0.0` |
+| restPort | `SW_LOGQL_REST_PORT` | `3100` |
+| restContextPath | `SW_LOGQL_REST_CONTEXT_PATH` | `/` |
+| restIdleTimeOut | `SW_LOGQL_REST_IDLE_TIMEOUT` | `30000` |
+| restAcceptQueueSize | `SW_LOGQL_REST_QUEUE_SIZE` | `0` |
+
+---
+
+### alarm
+
+**Selector**: `SW_ALARM` (default: `default`)
+
+No additional settings in `application.yml`. Alarm rules are configured via
`alarm-settings.yml`.
+
+---
+
+### telemetry
+
+**Selector**: `SW_TELEMETRY` (default: `prometheus`)
+
+Only the `prometheus` provider is available.
+
+| Setting | Environment Variable | Default |
+|---------|---------------------|---------|
+| host | `SW_TELEMETRY_PROMETHEUS_HOST` | `0.0.0.0` |
+| port | `SW_TELEMETRY_PROMETHEUS_PORT` | `1234` |
+| sslEnabled | `SW_TELEMETRY_PROMETHEUS_SSL_ENABLED` | `false` |
+| sslKeyPath | `SW_TELEMETRY_PROMETHEUS_SSL_KEY_PATH` | `""` |
+| sslCertChainPath | `SW_TELEMETRY_PROMETHEUS_SSL_CERT_CHAIN_PATH` | `""` |
+
+---
+
+### configuration
+
+**Selector**: `SW_CONFIGURATION` (default: `k8s-configmap`)
+
+Available providers: `none`, `k8s-configmap`
+
+**k8s-configmap** provider:
+
+| Setting | Environment Variable | Default |
+|---------|---------------------|---------|
+| period | `SW_CONFIG_CONFIGMAP_PERIOD` | `60` |
+| namespace | `SW_CLUSTER_K8S_NAMESPACE` | `default` |
+| labelSelector | `SW_CLUSTER_K8S_LABEL` | `app=collector,release=skywalking` |
+
+---
+
+### exporter (disabled by default)
+
+**Selector**: `SW_EXPORTER` (default: disabled)
+
+Set `SW_EXPORTER=default` to enable.
+
+| Setting | Environment Variable | Default |
+|---------|---------------------|---------|
+| enableGRPCMetrics | `SW_EXPORTER_ENABLE_GRPC_METRICS` | `false` |
+| gRPCTargetHost | `SW_EXPORTER_GRPC_HOST` | `127.0.0.1` |
+| gRPCTargetPort | `SW_EXPORTER_GRPC_PORT` | `9870` |
+| enableKafkaTrace | `SW_EXPORTER_ENABLE_KAFKA_TRACE` | `false` |
+| enableKafkaLog | `SW_EXPORTER_ENABLE_KAFKA_LOG` | `false` |
+| kafkaBootstrapServers | `SW_EXPORTER_KAFKA_SERVERS` | `localhost:9092` |
+| kafkaProducerConfig | `SW_EXPORTER_KAFKA_PRODUCER_CONFIG` | `""` |
+| kafkaTopicTrace | `SW_EXPORTER_KAFKA_TOPIC_TRACE` |
`skywalking-export-trace` |
+| kafkaTopicLog | `SW_EXPORTER_KAFKA_TOPIC_LOG` | `skywalking-export-log` |
+| exportErrorStatusTraceOnly | `SW_EXPORTER_KAFKA_TRACE_FILTER_ERROR` |
`false` |
+
+---
+
+### health-checker
+
+**Selector**: `SW_HEALTH_CHECKER` (default: `default`)
+
+| Setting | Environment Variable | Default |
+|---------|---------------------|---------|
+| checkIntervalSeconds | `SW_HEALTH_CHECKER_INTERVAL_SECONDS` | `30` |
+
+---
+
+### status-query
+
+**Selector**: `SW_STATUS_QUERY` (default: `default`)
+
+| Setting | Environment Variable | Default |
+|---------|---------------------|---------|
+| keywords4MaskingSecretsOfConfig |
`SW_DEBUGGING_QUERY_KEYWORDS_FOR_MASKING_SECRETS` |
`user,password,token,accessKey,secretKey,authentication` |
+
+---
+
+### configuration-discovery
+
+**Selector**: `SW_CONFIGURATION_DISCOVERY` (default: `default`)
+
+| Setting | Environment Variable | Default |
+|---------|---------------------|---------|
+| disableMessageDigest | `SW_DISABLE_MESSAGE_DIGEST` | `false` |
+
+---
+
+### receiver-event
+
+**Selector**: `SW_RECEIVER_EVENT` (default: `default`)
+
+No additional settings.
+
+---
+
+### receiver-ebpf
+
+**Selector**: `SW_RECEIVER_EBPF` (default: `default`)
+
+| Setting | Environment Variable | Default |
+|---------|---------------------|---------|
+| continuousPolicyCacheTimeout | `SW_CONTINUOUS_POLICY_CACHE_TIMEOUT` | `60` |
+| gRPCHost | `SW_EBPF_GRPC_HOST` | `0.0.0.0` |
+| gRPCPort | `SW_EBPF_GRPC_PORT` | `0` |
+| maxConcurrentCallsPerConnection | `SW_EBPF_GRPC_MAX_CONCURRENT_CALL` | `0` |
+| maxMessageSize | `SW_EBPF_ALS_GRPC_MAX_MESSAGE_SIZE` | `0` |
+| gRPCThreadPoolSize | `SW_EBPF_GRPC_THREAD_POOL_SIZE` | `0` |
+| gRPCSslEnabled | `SW_EBPF_GRPC_SSL_ENABLED` | `false` |
+| gRPCSslKeyPath | `SW_EBPF_GRPC_SSL_KEY_PATH` | `""` |
+| gRPCSslCertChainPath | `SW_EBPF_GRPC_SSL_CERT_CHAIN_PATH` | `""` |
+| gRPCSslTrustedCAsPath | `SW_EBPF_GRPC_SSL_TRUSTED_CAS_PATH` | `""` |
+
+---
+
+### receiver-telegraf
+
+**Selector**: `SW_RECEIVER_TELEGRAF` (default: `default`)
+
+| Setting | Environment Variable | Default |
+|---------|---------------------|---------|
+| activeFiles | `SW_RECEIVER_TELEGRAF_ACTIVE_FILES` | `vm` |
+
+---
+
+### aws-firehose
+
+**Selector**: `SW_RECEIVER_AWS_FIREHOSE` (default: `default`)
+
+| Setting | Environment Variable | Default |
+|---------|---------------------|---------|
+| host | `SW_RECEIVER_AWS_FIREHOSE_HTTP_HOST` | `0.0.0.0` |
+| port | `SW_RECEIVER_AWS_FIREHOSE_HTTP_PORT` | `12801` |
+| contextPath | `SW_RECEIVER_AWS_FIREHOSE_HTTP_CONTEXT_PATH` | `/` |
+| idleTimeOut | `SW_RECEIVER_AWS_FIREHOSE_HTTP_IDLE_TIME_OUT` | `30000` |
+| acceptQueueSize | `SW_RECEIVER_AWS_FIREHOSE_HTTP_ACCEPT_QUEUE_SIZE` | `0` |
+| maxRequestHeaderSize |
`SW_RECEIVER_AWS_FIREHOSE_HTTP_MAX_REQUEST_HEADER_SIZE` | `8192` |
+| firehoseAccessKey | `SW_RECEIVER_AWS_FIREHOSE_ACCESS_KEY` | *(empty)* |
+| enableTLS | `SW_RECEIVER_AWS_FIREHOSE_HTTP_ENABLE_TLS` | `false` |
+| tlsKeyPath | `SW_RECEIVER_AWS_FIREHOSE_HTTP_TLS_KEY_PATH` | *(empty)* |
+| tlsCertChainPath | `SW_RECEIVER_AWS_FIREHOSE_HTTP_TLS_CERT_CHAIN_PATH` |
*(empty)* |
+
+---
+
+### ai-pipeline
+
+**Selector**: `SW_AI_PIPELINE` (default: `default`)
+
+| Setting | Environment Variable | Default |
+|---------|---------------------|---------|
+| uriRecognitionServerAddr | `SW_AI_PIPELINE_URI_RECOGNITION_SERVER_ADDR` |
*(empty)* |
+| uriRecognitionServerPort | `SW_AI_PIPELINE_URI_RECOGNITION_SERVER_PORT` |
`17128` |
+| baselineServerAddr | `SW_API_PIPELINE_BASELINE_SERVICE_HOST` | *(empty)* |
+| baselineServerPort | `SW_API_PIPELINE_BASELINE_SERVICE_PORT` | `18080` |
diff --git a/docs/menu.yml b/docs/menu.yml
new file mode 100644
index 0000000..4b0af8b
--- /dev/null
+++ b/docs/menu.yml
@@ -0,0 +1,34 @@
+# 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.
+
+
+catalog:
+ - name: "Welcome"
+ path: "/readme"
+ - name: "Distribution Policy"
+ path: "/distro-policy"
+ - name: "Configuration"
+ path: "/configuration"
+ - name: "Build-Time Immigration"
+ catalog:
+ - name: "OAL Pre-Compilation"
+ path: "/oal-immigration"
+ - name: "MAL Transpilation"
+ path: "/mal-immigration"
+ - name: "LAL Transpilation"
+ path: "/lal-immigration"
+ - name: "Config Initialization"
+ path: "/config-init-immigration"