This is an automated email from the ASF dual-hosted git repository. wusheng pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/skywalking-graalvm-distro.git
commit daac54d3d8783a748672ce9af94097bf609917b4 Author: Wu Sheng <[email protected]> AuthorDate: Wed Feb 18 22:04:20 2026 +0800 Update PLAN.md and OAL-IMMIGRATION.md for completed OAL immigration PLAN.md: - Phase 1 marked COMPLETE, Phase 2 split into done/remaining items - Challenge 1 (OAL) marked SOLVED with implementation details - Challenge 2 (MAL/LAL) rewritten to reflect actual architecture - Challenge 3 marked PARTIALLY SOLVED (AnnotationScan + SourceReceiverImpl done) - Challenge 4 gains Phase 1 implementation summary - Upstream tracker: OAL done (no upstream change needed) OAL-IMMIGRATION.md: - All 3 steps marked DONE - Step 1 rewritten: no collecting listeners, scans output directory - Step 2 rewritten: same-FQCN replacement (3 classes) instead of extending + hooking via ModuleWiringBridge - Step 3 updated: MeterSystem deferred to MAL, reflect-config to Phase 3 - Files section updated to list all 6 actual files created/modified Co-Authored-By: Claude Opus 4.6 <[email protected]> --- OAL-IMMIGRATION.md | 161 +++++++++++++++++++++++++++-------------------------- PLAN.md | 80 +++++++++++++++++--------- 2 files changed, 137 insertions(+), 104 deletions(-) diff --git a/OAL-IMMIGRATION.md b/OAL-IMMIGRATION.md index 01d70fd..1917177 100644 --- a/OAL-IMMIGRATION.md +++ b/OAL-IMMIGRATION.md @@ -1,39 +1,37 @@ -# Phase 2: OAL Build-Time Pre-Compilation +# Phase 2: OAL Build-Time Pre-Compilation — COMPLETE ## 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). So we cannot rely on classpath scanning to discover pre-generated classes. +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 + a manifest of class names. At runtime, load classes by name from the manifest and register them **directly** with `StreamAnnotationListener` and `DispatcherManager` — no classpath scanning. +**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. --- -## Step 1: Build-Time OAL Class Export Tool +## Step 1: Build-Time OAL Class Export Tool — DONE -**Module**: `build-tools/oal-exporter` (skeleton exists) +**Module**: `build-tools/oal-exporter` -**Create**: `OALClassExporter.java` — main class that: +**Created**: `OALClassExporter.java` — main class that: -1. For each of the 9 `OALDefine` configs: instantiate `OALEngineV2`, set `StorageBuilderFactory.Default`, call `engine.start()` (Javassist generates classes in the build JVM) -2. Export bytecode via existing debug mechanism: `generator.setOpenEngineDebug(true)` + `OALClassGeneratorV2.setGeneratedFilePath(outputDir)` — `writeGeneratedFile()` writes `.class` files using `ctClass.toBytecode(DataOutputStream)` -3. After each engine run, call `engine.notifyAllListeners()` with **collecting listeners** that record class names (not real registration) -4. Write manifest files: - - `META-INF/oal-metrics-classes.txt` — one fully-qualified class name per line - - `META-INF/oal-dispatcher-classes.txt` — one fully-qualified class name per line - - `META-INF/oal-disabled-sources.txt` — one source name per line -5. Package output directory into a JAR artifact +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 -**Note on listeners**: `engine.start()` already generates and loads classes, storing them in internal `metricsClasses`/`dispatcherClasses` lists. `notifyAllListeners()` iterates these lists. We provide lightweight listeners that just collect class names for the manifest, not real stream processors. +**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. -**Dependencies** for `oal-exporter/pom.xml`: -- `oal-rt` — engine, generator, parser, enricher, FreeMarker templates -- `server-core` — `OALDefine` subclasses, source classes, annotations, `StorageBuilderFactory` -- `server-starter` — OAL script resource files (`oal/*.oal`) -- Receiver plugins that define `OALDefine` subclasses (for `BrowserOALDefine`, `MeshOALDefine`, etc.) - -**Build integration**: `exec-maven-plugin` runs `OALClassExporter.main()` during `process-classes` phase. Then `maven-jar-plugin` with a custom classifier packages the generated `.class` files + manifest into `oal-generated-classes.jar`. - -### 9 OAL Defines to process +### 9 OAL Defines processed | Define | Config File | Source Package | Catalog | |--------|-------------|----------------|---------| @@ -55,73 +53,82 @@ OAL engine generates metrics, builder, and dispatcher classes at runtime via Jav --- -## Step 2: Runtime Direct Registration (No Classpath Scanning) +## Step 2: Runtime Registration via Same-FQCN Replacement Classes — DONE -**Create**: `PrecompiledOALEngineLoaderService.java` in `oap-graalvm-server` +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. -Extends `OALEngineLoaderService`. Overrides `load()` to: -1. On first invocation: read manifest files from classpath resources -2. For each metrics class name: `Class.forName(name)` → `streamAnnotationListener.notify(clazz)` (same as `StreamAnnotationListener` — routes to `MetricsStreamProcessor.create()`) -3. For each dispatcher class name: `Class.forName(name)` → `dispatcherDetectorListener.addIfAsSourceDispatcher(clazz)` (same as `DispatcherManager.addIfAsSourceDispatcher()`) -4. For each disabled source: `DisableRegister.INSTANCE.add(name)` -5. All subsequent `load()` calls → no-op (classes already registered) +### 3 replacement classes created: -``` -// Pseudocode -public void load(OALDefine define) throws ModuleStartException { - if (registered) return; - registered = true; - - StreamAnnotationListener streamListener = new StreamAnnotationListener(moduleManager); - DispatcherDetectorListener dispatcherListener = moduleManager.find(CoreModule.NAME) - .provider().getService(SourceReceiver.class).getDispatcherDetectorListener(); - - for (String className : readManifest("META-INF/oal-metrics-classes.txt")) { - streamListener.notify(Class.forName(className)); - } - for (String className : readManifest("META-INF/oal-dispatcher-classes.txt")) { - dispatcherListener.addIfAsSourceDispatcher(Class.forName(className)); - } - for (String source : readManifest("META-INF/oal-disabled-sources.txt")) { - DisableRegister.INSTANCE.add(source); - } -} -``` +**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`) -**Wiring**: Replace the default `OALEngineLoaderService` after `CoreModuleProvider.prepare()`. `registerServiceImplementation()` uses `HashMap.put()` — re-registration overwrites. Add a post-prepare hook in `ModuleWiringBridge` for `CoreModuleProvider` to swap in `PrecompiledOALEngineLoaderService`. +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. -**Timing**: `CoreModuleProvider.start()` calls `oalEngineLoaderService.load(DisableOALDefine.INSTANCE)` first, then `annotationScan.scan()` and `receiver.scan()`. Our `PrecompiledOALEngineLoaderService.load()` runs first and registers ALL pre-generated classes (not just disable.oal). The subsequent `annotationScan.scan()` and `receiver.scan()` will also run, but for Phase 2 (JVM mode), they work fine via `ClassPath.from()` and simply re-discover static classes. The OAL classes are alread [...] +### 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** — not deferred to Phase 3; annotation manifests solve it now --- -## Step 3: Why Class.forName() Works for Native Image +## Step 3: Class Loading and Remaining Scans — DONE -`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 (Phase 3 task) enable runtime `Class.forName()` lookup. For Phase 2 (JVM mode), `Class.forName()` works naturally. +### `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 (Phase 3 task) will enable runtime `Class.forName()` lookup. For Phase 2 (JVM mode), `Class.forName()` works naturally. -The 3 OAL-internal scans (`MetricsHolder`, `DefaultMetricsFunctionRegistry`, `FilterMatchers`) only run during OAL engine execution — they happen at **build time** in our tool, not at runtime. So they're automatically solved. +### 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` — deferred to MAL immigration +`MeterSystem` uses Guava `ClassPath.from()` to discover meter function classes at runtime. This is part of the MAL/meter subsystem, not OAL. Will be addressed as part of MAL immigration (Phase 2 remaining work). + +### `reflect-config.json` — deferred to Phase 3 +GraalVM reflection configuration for `Class.forName()` calls on OAL-generated and manifest-listed classes will be generated in Phase 3. --- -## Files to Create +## Files Created + +1. **`build-tools/oal-exporter/src/main/java/.../OALClassExporter.java`** + - Build-time tool: runs 9 OAL defines, exports `.class` files, writes OAL manifests + annotation/interface manifests + +2. **`oap-graalvm-server/src/main/java/.../core/oal/rt/OALEngineLoaderService.java`** + - Same-FQCN replacement: loads pre-compiled OAL classes from manifests -1. **`build-tools/oal-exporter/src/main/java/org/apache/skywalking/oap/server/buildtools/oal/OALClassExporter.java`** - - Main class: iterates 9 OAL defines, runs engine, exports bytecode + writes manifest files +3. **`oap-graalvm-server/src/main/java/.../core/annotation/AnnotationScan.java`** + - Same-FQCN replacement: reads annotation manifests instead of Guava classpath scanning -2. **`oap-graalvm-server/src/main/java/org/apache/skywalking/oap/server/graalvm/PrecompiledOALEngineLoaderService.java`** - - Extends `OALEngineLoaderService`, loads from manifest + direct registration +4. **`oap-graalvm-server/src/main/java/.../core/source/SourceReceiverImpl.java`** + - Same-FQCN replacement: reads dispatcher/decorator manifests instead of Guava classpath scanning -## Files to Modify +5. **`build-tools/oal-exporter/src/test/java/.../OALClassExporterTest.java`** + - 3 tests: OAL script coverage, classpath availability, full export with manifest verification -1. **`build-tools/oal-exporter/pom.xml`** — add dependencies, configure `exec-maven-plugin` + `maven-jar-plugin` -2. **`oap-graalvm-server/pom.xml`** — add dependency on `oal-exporter` generated JAR -3. **`oap-graalvm-server/.../ModuleWiringBridge.java`** — post-prepare hook to replace `OALEngineLoaderService` with `PrecompiledOALEngineLoaderService` -4. **`Makefile`** — ensure build order: skywalking → oal-exporter → oap-graalvm-server +6. **`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 + +## Files Modified + +1. **`build-tools/oal-exporter/pom.xml`** — dependencies + `exec-maven-plugin` + `maven-jar-plugin` +2. **`oap-graalvm-server/pom.xml`** — dependency on `oal-exporter` generated JAR ## 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` — `load()` creates engine, sets listeners, calls `start()`+`notifyAllListeners()` +- `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()` @@ -136,18 +143,16 @@ The 3 OAL-internal scans (`MetricsHolder`, `DefaultMetricsFunctionRegistry`, `Fi make build-distro # 2. Check generated classes exist -ls build-tools/oal-exporter/target/generated-oal-classes/ -# Should have: org/apache/skywalking/.../oal/rt/metrics/*.class -# org/apache/skywalking/.../oal/rt/metrics/builder/*.class -# org/apache/skywalking/.../oal/rt/dispatcher/*.class +ls build-tools/oal-exporter/target/generated-oal-classes/org/apache/skywalking/oap/server/core/source/oal/rt/metrics/ +ls build-tools/oal-exporter/target/generated-oal-classes/org/apache/skywalking/oap/server/core/source/oal/rt/dispatcher/ # 3. Check manifest files cat build-tools/oal-exporter/target/generated-oal-classes/META-INF/oal-metrics-classes.txt cat build-tools/oal-exporter/target/generated-oal-classes/META-INF/oal-dispatcher-classes.txt -# Should list all generated class names -# 4. Verify JAR packaging -jar tf build-tools/oal-exporter/target/oal-generated-classes.jar +# 4. Check annotation scan manifests +ls build-tools/oal-exporter/target/generated-oal-classes/META-INF/annotation-scan/ -# 5. Verify oap-graalvm-server compiles with the generated JAR dependency -``` \ No newline at end of file +# 5. Verify tests pass +make build-distro # runs both OALClassExporterTest and PrecompiledRegistrationTest +``` diff --git a/PLAN.md b/PLAN.md index 74d0845..a89051a 100644 --- a/PLAN.md +++ b/PLAN.md @@ -37,40 +37,54 @@ Build and package Apache SkyWalking OAP server as a GraalVM native image on JDK --- -## Challenge 1: OAL Runtime Class Generation (Javassist) +## Challenge 1: OAL Runtime Class Generation (Javassist) — SOLVED ### 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 in native-image mode. +All `.oal` scripts are known. Run OAL engine at build time, export `.class` files, load them directly at runtime from manifests. See [OAL-IMMIGRATION.md](OAL-IMMIGRATION.md) for details. + +### 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 -- Potentially expose build-time class export as a stable API (currently debug-only) +- None. Build-time class export works via existing debug API (`setOpenEngineDebug(true)` + `setGeneratedFilePath()`) --- -## Challenge 2: MAL and LAL (Groovy) +## Challenge 2: MAL and LAL (Groovy + Javassist) ### What Happens -MAL uses `GroovyShell` + `Binding` for meter rules. LAL uses `GroovyShell` + `DelegatingScript` + `Closure` + AST customizers. +- MAL uses `GroovyShell` + `DelegatingScript` for meter rule expressions (~1188 rules). 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) -Groovy static compilation (`@CompileStatic`). Pre-compile all MAL/LAL rule files at build time, export `.class` files. +Run full MAL/LAL initialization at build time via `build-tools/mal-compiler`. Export Javassist-generated `.class` files + compiled Groovy script bytecode. At runtime, load from manifests. See [MAL-IMMIGRATION.md](MAL-IMMIGRATION.md) for details. + +### Key finding: MAL cannot use @CompileStatic +MAL expressions rely on `propertyMissing()` for sample name resolution and `ExpandoMetaClass` on `Number` for arithmetic operators — fundamentally dynamic Groovy features. Pre-compilation uses standard dynamic Groovy (same `CompilerConfiguration` as upstream). LAL already uses `@CompileStatic`. ### Risks -- `@CompileStatic` may not cover all dynamic features (track and work around) -- May need DSL adjustments (upstream PRs) +- Dynamic Groovy MOP may not work in GraalVM native image (Phase 3 concern) +- If `ExpandoMetaClass` fails in native image: fallback to upstream DSL changes --- -## Challenge 3: Classpath Scanning (Guava ClassPath) +## Challenge 3: Classpath Scanning (Guava ClassPath) — PARTIALLY SOLVED ### What Happens `ClassPath.from()` used in `SourceReceiverImpl.scan()`, `AnnotationScan`, `MeterSystem`, `DefaultMetricsFunctionRegistry`, `FilterMatchers`, `MetricsHolder`. -### Approach (this repo) -Run classpath scanning during build-time pre-compilation. Verify all metrics/log-processing classes are discovered. Export scan results as static class index. Native-image mode loads from index. +### 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. + +### What Remains +`MeterSystem` uses Guava `ClassPath.from()` to scan for meter function classes at runtime. This needs a manifest or build-time scan as part of MAL immigration. --- @@ -84,6 +98,12 @@ Run classpath scanning during build-time pre-compilation. Verify all metrics/log 2. **Simplified config file**: Only knobs for selected providers 3. **Config loading**: **No reflection.** At build time, read `application.yml` + scan `ModuleConfig` subclass fields → generate Java code that directly sets config fields (e.g. `config.restPort = 12800;`). Eliminates `Field.setAccessible`/`field.set` and the need for `reflect-config.json` for config classes. +### What Was Built (Phase 1) +- `FixedModuleManager` — direct module/provider construction, no SPI +- `ModuleWiringBridge` — wires all selected modules/providers +- `GraalVMOAPServerStartUp` — entry point +- `application.yml` — simplified config for selected providers + --- ## Challenge 5: Additional GraalVM Risks @@ -102,19 +122,26 @@ Run classpath scanning during build-time pre-compilation. Verify all metrics/log ## Proposed Phases -### Phase 1: Build System Setup -- [ ] Set up Maven + Makefile in this repo -- [ ] Build skywalking submodule as a dependency +### Phase 1: Build System Setup — COMPLETE +- [x] Set up Maven + Makefile in this repo +- [x] Build skywalking submodule as a dependency - [ ] Set up GraalVM JDK 25 in CI -- [ ] Create a JVM-mode starter with fixed module wiring (validate approach before native-image) -- [ ] Simplified config file for selected modules - -### Phase 2: Build-Time Pre-Compilation & Verification -- [ ] Makefile/Maven step: run OAL engine → export `.class` files -- [ ] Makefile/Maven step: static-compile MAL Groovy scripts → export `.class` files -- [ ] Makefile/Maven step: static-compile LAL Groovy scripts → export `.class` files -- [ ] Makefile/Maven step: run classpath scanning → export class index -- [ ] Verify all metrics/log-processing classes are correctly discovered +- [x] Create a JVM-mode starter with fixed module wiring (`FixedModuleManager` + `ModuleWiringBridge` + `GraalVMOAPServerStartUp`) +- [x] Simplified config file for selected modules (`application.yml`) + +### Phase 2: Build-Time Pre-Compilation & Verification — IN PROGRESS + +**OAL immigration — COMPLETE:** +- [x] OAL engine → export `.class` files (`OALClassExporter`, 9 defines, ~620 metrics, ~45 dispatchers) +- [x] Classpath scanning → export class index (6 annotation/interface manifests in `oal-exporter`) +- [x] Runtime registration from manifests (3 same-FQCN replacement classes: `OALEngineLoaderService`, `AnnotationScan`, `SourceReceiverImpl`) +- [x] Verification tests (`OALClassExporterTest` — 3 tests, `PrecompiledRegistrationTest` — 12 tests) + +**Remaining:** +- [ ] MAL Groovy pre-compilation (`build-tools/mal-compiler` skeleton exists) +- [ ] LAL Groovy pre-compilation (can be part of mal-compiler) +- [ ] `MeterSystem` classpath scan: uses Guava scanning for meter function classes — needs manifest or build-time scan (part of MAL immigration) +- [ ] Config generator (`build-tools/config-generator` skeleton exists) — eliminate `Field.setAccessible` reflection in config loading - [ ] Package everything into native-image classpath ### Phase 3: Native Image Build @@ -122,6 +149,7 @@ Run classpath scanning during build-time pre-compilation. Verify all metrics/log - [ ] Run tracing agent to capture reflection/resource/JNI metadata - [ ] Configure gRPC/Netty/Protobuf for native image - [ ] GraalVM Feature class for SkyWalking-specific registrations +- [ ] `reflect-config.json` for OAL-generated classes (`Class.forName()` calls) - [ ] Get OAP server booting as native image with BanyanDB ### Phase 4: Harden & Test @@ -135,6 +163,6 @@ Run classpath scanning during build-time pre-compilation. Verify all metrics/log --- ## Upstream Changes Tracker -- [ ] OAL engine: stable build-time class export API -- [ ] MAL/LAL: DSL adjustments for Groovy static compilation (if needed) -- [ ] Other findings during implementation \ No newline at end of file +- [x] OAL engine: build-time class export works via existing debug API (no upstream change needed) +- [ ] MAL/LAL: Groovy static compilation / DSL adjustments (not started) +- [ ] Other findings during implementation
