Skip to content

Introduce MAL/LAL/Hierarchy V2 engine — replace Groovy DSL runtime with ANTLR4 + Javassist#13723

Merged
wu-sheng merged 67 commits intomasterfrom
groovy-replace
Mar 6, 2026
Merged

Introduce MAL/LAL/Hierarchy V2 engine — replace Groovy DSL runtime with ANTLR4 + Javassist#13723
wu-sheng merged 67 commits intomasterfrom
groovy-replace

Conversation

@wu-sheng
Copy link
Member

@wu-sheng wu-sheng commented Mar 3, 2026

Introduce MAL/LAL/Hierarchy V2 engine — replace Groovy DSL runtime with ANTLR4 + Javassist

  • This is a non-trivial feature. Design doc: docs/en/academy/dsl-compiler-design.md

  • Documentation updated to include this new feature.

  • Tests (UT, IT, E2E) are added to verify the new feature.

  • If this pull request closes/resolves/fixes an existing issue, replace the issue number. Closes #.

  • Update the CHANGES log.


What this PR does

Introduces the MAL/LAL/Hierarchy V2 engine — replacing the Groovy-based DSL runtime for MAL (Meter Analysis Language), LAL (Log Analysis Language), and Hierarchy matching rules with compile-time ANTLR4 parsing and Javassist bytecode generation — the same approach already used by the OAL V2 engine.

All three DSL compilers follow the same pipeline:

DSL string → ANTLR4 parse → Immutable AST → Javassist bytecode → Direct Java execution

Why

  • Remove Groovy runtime dependency (~7 MB) from the OAP server classpath
  • Eliminate runtime interpretation — generated bytecode uses direct method calls with zero reflection
  • Thread-safe by design — all generated instances are stateless singletons; per-request state passed as parameters (no ThreadLocal, no mutable wrappers)
  • Fail-fast at boot — DSL compilation errors are caught during startup with file/line/column reporting, not at first log/metric arrival
  • Debugger-friendly — all generated methods include LocalVariableTable (LVT) entries with named variables

Architecture

DSL Compiled Interface Runtime Signature State Passing
MAL MalExpression SampleFamily run(Map<String, SampleFamily>) Parameter
LAL LalExpression void execute(FilterSpec, ExecutionContext) Parameter
Hierarchy BiFunction<Service, Service, Boolean> Boolean apply(Service, Service) Parameter

All v2 classes live under .v2. packages to avoid FQCN conflicts with v1 (Groovy) classes, which remain in test/script-cases/script-runtime-with-groovy/ for comparison testing.


File change summary

587 files changed, +64,705 / -1,890 (bulk from new test data files and POM version changes).

Category File count Description
Test scripts (MAL/LAL/Hierarchy YAML + data) 174 Production DSL configs + 76 .data.yaml + 10 .input.data companion files
v1-v2 checker tests 125 Cross-version comparison tests in test/script-cases/script-runtime-with-groovy/
Other (POM, CI, git, LICENSE, misc) 108 Version bumps, dependency changes, misc
LAL compiler 52 New v2 compiler, runtime, module provider
MAL compiler 45 New v2 compiler, runtime, module provider
POM files 28 6 new modules, 22 modified
Documentation 15 Design doc, debugging guide, changelog, config vocabulary
Hierarchy compiler 10 New v2 compiler module
Server core 7 HierarchyDefinitionService, HierarchyService, benchmarks
Claude Code skills 7 compile, test, license, gh-pull-request, ci-e2e-debug, run-e2e, generate-classes
Server library 6 GRPCServer/HTTPServer javadoc, ReflectUtil, benchmarks
CLAUDE.md files 4 Module-level AI assistant guides
Server starter 3 SPI wiring for v2 modules
OAL-RT 1 Rename SW_OAL_ENGINE_DEBUGSW_DYNAMIC_CLASS_ENGINE_DEBUG
CI 1 .github/workflows/skywalking.yaml
.gitignore 1 Add *.generated-classes/ pattern

New Maven modules

Module Path Purpose
hierarchy oap-server/analyzer/hierarchy/ Hierarchy rule v2 compiler + SPI provider
script-runtime-with-groovy (parent) test/script-cases/script-runtime-with-groovy/ Test aggregator
mal-v1-with-groovy .../script-runtime-with-groovy/mal-v1-with-groovy/ Groovy v1 MAL runtime for comparison
lal-v1-with-groovy .../script-runtime-with-groovy/lal-v1-with-groovy/ Groovy v1 LAL runtime for comparison
mal-lal-v1-v2-checker .../script-runtime-with-groovy/mal-lal-v1-v2-checker/ MAL/LAL cross-version comparison tests
hierarchy-v1-v2-checker .../script-runtime-with-groovy/hierarchy-v1-v2-checker/ Hierarchy cross-version comparison tests
hierarchy-v1-with-groovy .../script-runtime-with-groovy/hierarchy-v1-with-groovy/ Groovy v1 Hierarchy runtime for comparison

ANTLR4 grammar files (new)

Grammar Path Lines
MAL Lexer oap-server/analyzer/meter-analyzer/src/main/antlr4/.../MALLexer.g4 132
MAL Parser oap-server/analyzer/meter-analyzer/src/main/antlr4/.../MALParser.g4 282
LAL Lexer oap-server/analyzer/log-analyzer/src/main/antlr4/.../LALLexer.g4 175
LAL Parser oap-server/analyzer/log-analyzer/src/main/antlr4/.../LALParser.g4 476
Hierarchy Rule Lexer oap-server/analyzer/hierarchy/src/main/antlr4/.../HierarchyRuleLexer.g4 105
Hierarchy Rule Parser oap-server/analyzer/hierarchy/src/main/antlr4/.../HierarchyRuleParser.g4 135

Key source files (v2 compiler)

MAL compiler (oap-server/analyzer/meter-analyzer/src/main/java/.../v2/)

File Lines Responsibility
compiler/MALScriptParser.java 888 ANTLR4 visitor → immutable MALExpressionModel AST
compiler/MALExpressionModel.java 638 Immutable AST data classes (expression, closure, condition)
compiler/MALClassGenerator.java 1,334 Public API, run() method codegen, metadata extraction
compiler/MALClosureCodegen.java 783 Closure method codegen (inlined via LambdaMetafactory)
compiler/MALCodegenHelper.java 318 Static utilities (escapeJava, isBooleanExpression, etc.)
compiler/rt/MalRuntimeHelper.java 76 Runtime helper: regex match, String[][] handling
dsl/SampleFamily.java 829 Core MAL data structure (v2 copy, no Groovy deps)
v2/Analyzer.java 438 MAL expression analyzer (wires compiled code to metrics pipeline)
v2/MetricConvert.java 153 Metric conversion from SampleFamily to OAP metrics

LAL compiler (oap-server/analyzer/log-analyzer/src/main/java/.../v2/)

File Lines Responsibility
compiler/LALScriptParser.java 947 ANTLR4 visitor → immutable LALScriptModel AST
compiler/LALScriptModel.java 563 Immutable AST data classes (filter, extractor, sink, condition)
compiler/LALClassGenerator.java 553 Public API, execute() method, class scaffolding
compiler/LALBlockCodegen.java 1,157 Extractor/sink/condition/value-access codegen (all static methods)
compiler/LALCodegenHelper.java 107 Static utilities (escapeJava, boxTypeName, LOG_GETTERS)
compiler/rt/LalRuntimeHelper.java 316 Runtime helper: toStr(), mapVal(), safe-nav, proto field access
dsl/ExecutionContext.java 182 Per-request mutable context (abort flag, parsed map, tags, etc.)
dsl/spec/filter/FilterSpec.java 256 Filter specification (json/yaml/text parsers, abort, sink)
dsl/spec/extractor/ExtractorSpec.java 338 Extractor specification (service, instance, endpoint, tags, etc.)
spi/LALSourceTypeProvider.java 53 SPI for resolving extraLogType proto class at compile time

Hierarchy compiler (oap-server/analyzer/hierarchy/src/main/java/.../v2/compiler/)

File Lines Responsibility
HierarchyRuleScriptParser.java 375 ANTLR4 visitor → immutable HierarchyRuleModel AST
HierarchyRuleModel.java 260 Immutable AST data classes (expression, condition, statement)
HierarchyRuleClassGenerator.java 543 Bytecode generation for BiFunction<Service, Service, Boolean>
CompiledHierarchyRuleProvider.java 73 SPI provider: compiles rules and registers as HierarchyRuleProvider

Generated .class file output

During compilation, generated .class files can be dumped for debugging.

Environment variable: SW_DYNAMIC_CLASS_ENGINE_DEBUG — set to any non-empty value to enable.

Output directories (per DSL, relative to working directory):

DSL Output directory
OAL oal-rt-generated-classes/
MAL mal-generated-classes/
LAL lal-generated-classes/
Hierarchy hierarchy-generated-classes/

During checker tests, generated classes are dumped to:

test/script-cases/scripts/**/*.generated-classes/

These directories are git-ignored via .gitignore.

See docs/en/operation/dynamic-code-generation-debugging.md for full details.


v1-v2 cross-version checker tests

Located in test/script-cases/script-runtime-with-groovy/.

Test classes

Test class Path Lines What it tests
MalComparisonTest .../checker/mal/MalComparisonTest.java 1,107 MAL expression metadata + runtime output comparison
MalFilterComparisonTest .../checker/mal/MalFilterComparisonTest.java 205 MAL filter closure comparison
MalInputDataGeneratorTest .../checker/mal/MalInputDataGeneratorTest.java 65 Auto-generate .data.yaml companion files
MalExpectedDataGeneratorTest .../checker/mal/MalExpectedDataGeneratorTest.java 49 Generate expected output data
LalComparisonTest .../checker/lal/LalComparisonTest.java 792 LAL script execution comparison
HierarchyRuleComparisonTest .../core/config/HierarchyRuleComparisonTest.java 191 Hierarchy rule result comparison

Verification counts

DSL Expressions Source
MAL expressions 1,229 73 YAML files across 6 directories
MAL filter closures 31 Separate MalFilterComparisonTest
LAL scripts 29 test-lal/ directory
Hierarchy rules 4 rules × test pairs test-hierarchy-definition.data.yaml
Total ~1,290+

Test data locations

Type Directory Count Format
MAL companion data test/script-cases/scripts/mal/test-*/ 76 files .data.yaml (SampleFamily mock input)
LAL input data test/script-cases/scripts/lal/test-lal/ 10 files .input.data (proto-json LogData/extraLog)

Unit tests (in v2 compiler modules)

Test class Path Lines Cases
MALScriptParserTest .../meter-analyzer/.../MALScriptParserTest.java 406 22
MALClassGeneratorTest .../meter-analyzer/.../MALClassGeneratorTest.java 453 32
DSLV2Test (MAL) .../meter-analyzer/.../DSLV2Test.java 92 Integration test
LALScriptParserTest .../log-analyzer/.../LALScriptParserTest.java 529 20
LALClassGeneratorTest .../log-analyzer/.../LALClassGeneratorTest.java 672 37
LALExpressionExecutionTest .../log-analyzer/.../LALExpressionExecutionTest.java 585 27
DSLV2Test (LAL) .../log-analyzer/.../DSLV2Test.java 49 Integration test
HierarchyRuleScriptParserTest .../hierarchy/.../HierarchyRuleScriptParserTest.java 159 Parser tests
HierarchyRuleClassGeneratorTest .../hierarchy/.../HierarchyRuleClassGeneratorTest.java 158 Codegen tests

JMH benchmarks

Benchmark Path Lines
MalBenchmark .../mal-lal-v1-v2-checker/.../MalBenchmark.java 342
LalBenchmark .../mal-lal-v1-v2-checker/.../LalBenchmark.java 501
HierarchyBenchmark .../hierarchy-v1-v2-checker/.../HierarchyBenchmark.java 233

Results:

DSL Operation v2 speedup over v1
MAL execute ~6.8x faster
LAL compile ~39x faster
LAL execute ~2.8x faster
Hierarchy execute ~2.6x faster

Compiled code examples

MAL Example — Tag closure (inlined as method on main class)

DSL: metric.tag({tags -> tags.service_name = 'APISIX::' + tags.skywalking_service})

Generated class (single .class file, no separate closure class):

// Closure body compiled as a method on the main class
public Map _tag_apply(Map tags) {
  tags.put("service_name", "APISIX::" + tags.get("skywalking_service"));
  return tags;
}

// _tag field holds a TagFunction instance created via LambdaMetafactory
public SampleFamily run(Map samples) {
  SampleFamily sf;
  sf = ((SampleFamily) samples.getOrDefault("metric", SampleFamily.EMPTY));
  sf = sf.tag(this._tag);
  return sf;
}

Closures are compiled as methods on the main class. At class-load time, LambdaMetafactory wraps each method into a functional interface instance — the same mechanism javac uses for lambda expressions.

MAL Example — Regex match with ternary

DSL: metric.tag({ tags -> def matcher = (tags.metrics_name =~ /\.ssl\.certificate\.([^.]+)\.expiration/); tags.secret_name = matcher ? matcher[0][1] : "unknown" })

Generated _tag_apply() method:

public Map _tag_apply(Map tags) {
  String[][] matcher = MalRuntimeHelper.regexMatch(
      (String) tags.get("metrics_name"),
      "\\.ssl\\.certificate\\.([^.]+)\\.expiration");
  tags.put("secret_name", (((Object)(matcher)) != null ? (matcher[0][1]) : ("unknown")));
  return tags;
}

def type inferred as String[][] from =~ regex match. Ternary compiles to Java ternary with null-check on Object cast.


LAL Example — JSON parser with extractor

DSL:

filter {
  json {}
  extractor {
    service parsed.service as String
    instance parsed.instance as String
  }
  sink {}
}

Generated class:

public void execute(FilterSpec filterSpec, ExecutionContext ctx) {
  LalRuntimeHelper h = new LalRuntimeHelper(ctx);
  filterSpec.json(ctx);
  if (!ctx.shouldAbort()) { _extractor(filterSpec.extractor(), h); }
  filterSpec.sink(ctx);
}

private void _extractor(ExtractorSpec _e, LalRuntimeHelper h) {
  _e.service(h.ctx(), h.toStr(h.mapVal("service")));
  _e.instance(h.ctx(), h.toStr(h.mapVal("instance")));
}

Single class, no closures. h.mapVal() accesses JSON parsed map. h.toStr() preserves null (unlike String.valueOf() which returns "null").

LAL Example — Proto-based with extraLogType (Envoy ALS)

DSL:

filter {
  if (parsed?.response?.responseCode?.value as Integer < 400) { abort {} }
  extractor {
    if (parsed?.response?.responseCode) {
      tag 'status.code': parsed?.response?.responseCode?.value
    }
    tag 'response.flag': parsed?.commonProperties?.responseFlags
  }
  sink {}
}

Generated class (with extraLogType = HTTPAccessLogEntry):

public void execute(FilterSpec filterSpec, ExecutionContext ctx) {
  LalRuntimeHelper h = new LalRuntimeHelper(ctx);
  // Cast once, reuse as _p
  HTTPAccessLogEntry _p = (HTTPAccessLogEntry) h.ctx().extraLog();
  // Safe-nav chain cached in local variables
  HTTPResponseProperties _t0 = _p == null ? null : _p.getResponse();
  UInt32Value _t1 = _t0 == null ? null : _t0.getResponseCode();
  if (_t1 != null && _t1.getValue() < 400) { filterSpec.abort(ctx); }
  if (!ctx.shouldAbort()) { _extractor(filterSpec.extractor(), h); }
  filterSpec.sink(ctx);
}

private void _extractor(ExtractorSpec _e, LalRuntimeHelper h) {
  HTTPAccessLogEntry _p = (HTTPAccessLogEntry) h.ctx().extraLog();
  HTTPResponseProperties _t0 = _p == null ? null : _p.getResponse();
  UInt32Value _t1 = _t0 == null ? null : _t0.getResponseCode();
  if (_t1 != null) {
    _e.tag(h.ctx(), "status.code", h.toStr(Integer.valueOf(_t1.getValue())));
  }
  AccessLogCommon _t2 = _p == null ? null : _p.getCommonProperties();
  _e.tag(h.ctx(), "response.flag", h.toStr(_t2 == null ? null : _t2.getResponseFlags()));
}

Proto getter chains resolved via Java reflection at compile time — at runtime it's direct method calls. ?. safe navigation emits == null ? null : ternaries. Intermediate values cached in _tN local variables for readability and dedup.


Hierarchy Example — Simple name match

DSL: { (u, l) -> u.name == l.name }

Generated class:

public Object apply(Object arg0, Object arg1) {
  Service u = (Service) arg0;
  Service l = (Service) arg1;
  return Boolean.valueOf(java.util.Objects.equals(u.getName(), l.getName()));
}

Hierarchy Example — Block body with if/return

DSL: { (u, l) -> { if (l.shortName.lastIndexOf('.') > 0) { return u.shortName == l.shortName.substring(0, l.shortName.lastIndexOf('.')); } return false; } }

Generated class:

public Object apply(Object arg0, Object arg1) {
  Service u = (Service) arg0;
  Service l = (Service) arg1;
  if (l.getShortName().lastIndexOf(".") > 0) {
    return Boolean.valueOf(java.util.Objects.equals(
        u.getShortName(),
        l.getShortName().substring(0, l.getShortName().lastIndexOf("."))));
  }
  return Boolean.valueOf(false);
}

Property access → getter methods. ==Objects.equals(). Numeric > → direct operator.


SPI registration files

SPI file Purpose
oap-server/analyzer/hierarchy/.../META-INF/services/...HierarchyRuleProvider Registers CompiledHierarchyRuleProvider
oap-server/analyzer/log-analyzer/.../META-INF/services/...ModuleDefine Registers v2 LogAnalyzerModule
oap-server/analyzer/log-analyzer/.../META-INF/services/...ModuleProvider Registers v2 LogAnalyzerModuleProvider
oap-server/analyzer/log-analyzer/.../test/.../META-INF/services/...LALSourceTypeProvider Test SPI for mesh proto types
oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/.../META-INF/services/...LALSourceTypeProvider Envoy ALS proto type provider

Documentation

Document Path Lines Content
DSL Compiler Design docs/en/academy/dsl-compiler-design.md 162 Architecture overview of all 4 DSL compilers
Dynamic Code Debugging docs/en/operation/dynamic-code-generation-debugging.md 187 How to dump .class files, output dirs, env var
Changelog docs/en/changes/changes.md +17 lines Feature summary, benchmark results
Config Vocabulary docs/en/setup/backend/configuration-vocabulary.md +1 line SW_DYNAMIC_CLASS_ENGINE_DEBUG env var
LAL Concept Doc docs/en/concepts-and-designs/lal.md Updated Remove Groovy code block labels
Hierarchy Doc docs/en/concepts-and-designs/service-hierarchy.md Updated Document new expression grammar for matching rules
Hierarchy Config docs/en/concepts-and-designs/service-hierarchy-configuration.md Updated Update rule syntax description
MAL Setup docs/en/setup/backend/backend-meter.md Updated Remove initExp from config
Menu docs/menu.yml +10 lines Add debugging doc to nav

Other notable changes included in this branch

  • Rename SW_OAL_ENGINE_DEBUGSW_DYNAMIC_CLASS_ENGINE_DEBUG — unified env var for all 4 DSL compilers
  • Remove initExp from MAL config — was internal Groovy startup validation, v2 compiler validates at startup natively
  • Remove PowerMock dependency — replaced with ReflectUtil (standard Java reflection + sun.misc.Unsafe for final fields)
  • Upgrade Byte Buddy to 1.18.7 — configure explicit -javaagent for Mockito/Byte Buddy in Surefire for JDK 25+ compatibility
  • JMH benchmarks relocated — moved from oap-server/microbench/ to target modules for better colocation

wu-sheng and others added 29 commits February 28, 2026 14:10
Document the detailed implementation plan for eliminating Groovy from
OAP runtime via build-time transpilers (MAL/LAL) and v1/v2 module
split (hierarchy), based on Discussion #13716 and skywalking-graalvm-distro.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add MalExpression, MalFilter, LalExpression functional interfaces and
SampleFamilyFunctions (TagFunction, SampleFilter, ForEachFunction,
DecorateFunction, PropertiesExtractor). Add Java functional interface
overloads alongside existing Groovy Closure methods in SampleFamily,
FilterSpec, ExtractorSpec, and SinkSpec. Change InstanceEntityDescription
to use Function instead of Closure. All 129 existing tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…hase 2)

Ports MalToJavaTranspiler from skywalking-graalvm-distro into a new
mal-transpiler analyzer submodule. The transpiler parses Groovy MAL
expressions/filters via AST at CONVERSION phase and emits equivalent
Java classes implementing MalExpression/MalFilter interfaces from Phase 1.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…hase 3)

Introduces lal-transpiler module that parses LAL Groovy DSL scripts into
AST at Phases.CONVERSION and emits pure Java classes implementing
LalExpression. Handles filter/text/json/yaml/extractor/sink/abort blocks,
parsed property access, safe navigation, cast expressions, GString
interpolation, and SHA-256 deduplication. Makes MalToJavaTranspiler.escapeJava()
public for cross-module reuse. Includes 37 comprehensive tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ing (Phase 4)

Introduces meter-analyzer-v2 and log-analyzer-v2 modules that provide
same-FQCN replacement classes for DSL.java, Expression.java, and
FilterExpression.java. The v2 classes load transpiled MalExpression/
MalFilter/LalExpression implementations from META-INF manifests via
Class.forName() instead of Groovy GroovyShell/ExpandoMetaClass/
DelegatingScript. Uses maven-shade-plugin to overlay the upstream
Groovy-dependent classes. Includes 7 unit tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… (Phase 5)

Extract hierarchy matching rules from HierarchyDefinitionService into
pluggable HierarchyRuleProvider interface. Remove Groovy imports from
server-core by replacing Closure<Boolean> with BiFunction<Service,Service,Boolean>.

- hierarchy-v1: GroovyHierarchyRuleProvider (for CI checker only)
- hierarchy-v2: JavaHierarchyRuleProvider with 4 built-in rules + 12 tests
- HierarchyDefinitionService: add HierarchyRuleProvider interface, DefaultJavaRuleProvider
- HierarchyService: .getClosure().call() → .match()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ase 6)

Three checker modules verify v1 (Groovy) and v2 (transpiled Java) produce
identical results: hierarchy rules (22 tests), MAL expressions (1187 tests),
MAL filters (29 tests), and LAL scripts (10 tests). Zero behavioral
divergences found when both paths succeed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…L to on-the-fly compilation

- Merge mal-grammar + mal-compiler into meter-analyzer
- Merge lal-grammar + lal-compiler into log-analyzer
- Merge hierarchy-rule-grammar + hierarchy-rule-compiler into hierarchy
- Remove 6 standalone modules (3 grammar + 3 compiler)
- Update DSL.java to compile MAL expressions on-the-fly via MALClassGenerator
  instead of loading from non-existent manifest file
- Add varargs handling for tagEqual/tagNotEqual/tagMatch/tagNotMatch in
  generated Javassist code (wrap String args in new String[]{})
- Update test/script-compiler checker POMs to reference merged module names
- Update CLAUDE.md files with merged file structure and paths

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix MAL sample collection regression: skip downsampling() method
  arguments to prevent enum values (MAX, SUM, MIN) from being
  collected as sample names
- Fix MAL safe navigation (?.): parser now correctly propagates
  safeNav flag to chain segments; code generator uses local
  StringBuilder to avoid corrupting parent buffer
- Fix MAL filter grammar: add closureCondition alternatives to
  closureBody rule for bare conditions like { tags -> tags.x == 'v' }
- Fix MAL downsampling detection for bare identifiers parsed as
  ExprArgument wrapping MetricExpr
- Fix MAL sample ordering: use LinkedHashSet for consistent order
- Fix LAL tag() function call: add functionName rule allowing TAG
  token in functionInvocation for if(tag("LOG_KIND") == ...) patterns
- Fix LAL ProcessRegistry support: add PROCESS_REGISTRY to
  valueAccessPrimary grammar rule
- Fix LAL tag statement code generation: wrap single tag entries in
  Collections.singletonMap() since ExtractorSpec.tag() accepts Map
- Fix LAL makeComparison to handle CondFunctionCallContext properly
- Add debug logging to all three code generators (MAL, LAL, Hierarchy)
  showing AST and generated Java source at DEBUG level
- Add generateFilterSource() to MALClassGenerator for testing
- Add error handling unit tests with demo error comments for MAL (5),
  LAL (4), and Hierarchy (4) generators
- All 1248 checker tests pass: MAL 1187, Filter 29, LAL 10, Hierarchy 22

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ng all four DSL compilers (OAL, MAL, LAL, Hierarchy). Remove Groovy references from docs: LAL code blocks, hierarchy matching rule labels, and stale MeterProcessor comment.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Provides a /run-e2e slash command with prerequisites (e2e CLI,
swctl, yq install instructions), rebuild detection, test execution,
and failure debugging workflow.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…, interpolated sampler IDs

Address five critical gaps in the LAL v2 compiler that broke shipped production rules:

1. tag("LOG_KIND") in conditions now emits tagValue() helper instead of null
2. Safe navigation (?.) for method calls emits safeCall() helper to prevent NPE
3. Metrics, slowSql, sampledTrace, sampler/rateLimit blocks generate proper
   sub-consumer classes with BindingAware wiring
4. else-if chains build nested IfBlock AST nodes instead of dropping
   intermediate branches
5. GString interpolation in rateLimit IDs (e.g. "${log.service}:${parsed.code}")
   parsed into InterpolationPart segments and emitted as string concatenation

Also fixes ProcessRegistry static calls to pass arguments through, and adds
comprehensive tests (55 total: 35 generator + 20 parser) covering all gaps
including production-like envoy-als, nginx, and k8s-service rule patterns.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…cripts and runtime comparison

- Rename test/script-compiler to test/script-cases/script-runtime-with-groovy
- Copy all shipped production configs into test/script-cases/scripts/ as test copies
  (MAL: test-otel-rules, test-meter-analyzer-config, test-log-mal-rules, test-envoy-metrics-rules;
   LAL: test-lal; Hierarchy: test-hierarchy-definition.yml)
- Update all checker tests to load from shared scripts/ directory
- Upgrade LAL checker from compile-only to full runtime execution comparison
  (v1 Groovy vs v2 ANTLR4+Javassist, comparing Binding state: service, layer, tags, abort/save)
- Update Maven coordinates and root pom module path

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move benchmarks from the standalone oap-server/microbench module into
the src/test/ directories of the modules they actually test (server-core
and library-util). Drop AbstractMicrobenchmark base class in favor of
self-contained @test run() methods. Bump JMH 1.21 -> 1.37 and remove
the obsolete -XX:BiasedLockingStartupDelay=0 JVM flag (removed in JDK 18).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
MAL compiler fixes (closes 38 previously failing expressions):
- Add ternary operator (?:) support in closures (grammar, AST, codegen)
- Fix valueEqual() and other primitive-double methods with numeric literal args
- Support double-paren argument syntax: sum((['cluster']))
- Handle NUMBER / SampleFamily via MalRuntimeHelper.divReverse() in v2 package
- Add variable declarations, map literals, forEach/instance closure types
- Add ProcessRegistry class references, improved safe navigation

LAL compiler fixes:
- Fix null-to-string conversion: use null-safe toStr() instead of String.valueOf()
- Add camelToSnake field name fallback for protobuf field access
- Add typed execute(FilterSpec, Binding) method signature
- Reorganize LAL test scripts into oap-cases/ and feature-cases/
- Add data-driven LALExpressionExecutionTest with 27 test cases

MAL checker enhancements:
- Add runtime execution comparison (mock SampleFamily data, execute both
  v1 and v2, compare output samples with labels and values)
- Handle increase()/rate() by priming CounterWindow with initial run
- Extract tagEqual patterns from expressions for matching mock data

All 1,187 MAL + 29 LAL + 22 hierarchy expressions now pass with zero gaps.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…L typed signature

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Give v2 (ANTLR4+Javassist) classes distinct FQCNs from v1 (Groovy)
so both can coexist on the classpath without source duplication in
v1-with-groovy test modules.

Package mapping:
- MAL: meter.analyzer.* → meter.analyzer.v2.*
- LAL: log.analyzer.* → log.analyzer.v2.*
- Hierarchy: config.compiler.* → config.v2.compiler.*

Also: remove v2-only files (MalExpression, MalFilter, LalExpression)
from v1-with-groovy modules, add mal-v1-with-groovy dependency to
lal-v1-with-groovy, fix cross-version enum comparison by name.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ime() scalar in MAL compiler

Add ANTLR4 lexer mode for regex literals (=~ /pattern/), def keyword with
type inference from initializer (String[][] for regex, String[] for split),
GString interpolation expansion, .size() to .length translation, decorate()
bean-mode closures, and time() as a scalar function in binary expressions.
Verified with 1,228 v1-v2 checker tests (1,197 MAL + 31 filter).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…v1-v2 checker data

Rewrite MAL run() code generation to use a single reassigned 'sf' variable
instead of multiple intermediate variables, producing cleaner decompiled output.
Add LocalVariableTable attribute so decompilers show 'samples' and 'sf' instead
of 'var1' and 'var2'. Integrate v2 compilers with runtime wiring, add checker
test data files, and clean up unused code across MAL/LAL/Hierarchy modules.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add LVT attribute to LAL execute() and consumer accept() methods, and to
Hierarchy apply() method, so decompilers show meaningful variable names
(filterSpec, binding, _t, u, l) instead of var0, var1, etc.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move 8 helper methods (getAt, toLong, toInt, toStr, toBool, isTruthy,
tagValue, safeCall) from being duplicated in every generated class via
addHelperMethods() to a shared LalRuntimeHelper in the rt package.
Generated code now calls LalRuntimeHelper.toStr() etc. via FQCN.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…th typed methods

Fix rateLimit() calls inside if-blocks within sampler generating empty bytecode
by handling the samplerContent grammar alternative in LALScriptParser.visitIfBody().

Replace generic LalRuntimeHelper.safeCall() and isTruthy() with specific typed
methods: isTrue() for Boolean conditions, isNotEmpty() for String non-emptiness,
toString() and trim() for null-safe navigation — making generated code explicit
about intended type semantics.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…licitly, add LVT

- Merge consumer sub-classes into single generated class with private methods
- Remove BINDING ThreadLocal from AbstractSpec; all spec methods take ExecutionContext explicitly
- Delete BindingAware.java and Binding.java, replace with ExecutionContext
- Add abort guard before _extractor/_sink calls matching v1 Groovy behavior
- Add LocalVariableTable to all generated methods (execute, _extractor, _sink)
- Rename binding→ctx throughout for consistency
- Add extraLogType to envoy-als.yaml for compile-time proto resolution
- Remove all Consumer callback methods from spec files
- Add finalizeSink abort check in FilterSpec

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…es, filters)

Previously only run() had LVT. Now all generated methods have named locals
in debuggers/decompilers instead of var0/var1/var2:
- metadata(): this, _samples, _scopeLabels, _aggLabels, _pct
- tag/instance apply(Map): this, param name
- tag/instance apply(Object) bridge: this, o
- forEach accept(): this, element, tags
- decorate accept(): this, _arg, param name
- filter test(): this, param name

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…version 10.4.0-SNAPSHOT

Replace CI-friendly ${revision} with hardcoded 10.4.0-SNAPSHOT in all 104 POMs.
This eliminates persistent "Could not find artifact ...pom:${revision}" errors
when building individual modules without -am. Also removes flatten-maven-plugin
(no longer needed), updates release scripts to use versions:set, and wires
LALSourceTypeProvider SPI for envoy-als extraLog type resolution in tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…chains

Cache the extraLog cast in a _p local variable and break safe-nav chains
into sequential _tN locals instead of deeply nested ternaries. Repeated
access to the same chain prefix reuses existing variables (dedup).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove bind()/evaluate() two-phase pattern from DSL. The mutable
ExecutionContext field made DSL unsafe for concurrent use. Now
evaluate(ExecutionContext) takes ctx as a parameter, matching the
stateless pattern already used by MAL and Hierarchy v2 runtimes.

Update LogFilterListener to store per-request contexts in a list
and pass each to the corresponding DSL.evaluate(ctx) call.
Update LogTestQuery to use the new single-call API.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@wu-sheng wu-sheng added the core feature Core and important feature. Sometimes, break backwards compatibility. label Mar 3, 2026
@wu-sheng wu-sheng changed the title Replace Groovy DSL runtime with ANTLR4 + Javassist for MAL, LAL, and Hierarchy Introduce MAL/LAL/Hierarchy V2 engine — replace Groovy DSL runtime with ANTLR4 + Javassist Mar 4, 2026
wu-sheng and others added 18 commits March 4, 2026 23:51
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
MAL execute ~4.9x, LAL compile ~39x / execute ~2.8x, Hierarchy execute ~2.6x
faster than Groovy v1.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ile skill

- Extract MAL codegen utility methods into MALCodegenHelper
- Add flatten:flatten to checkstyle command in compile skill
- Add contributing guide doc for Claude Code skills
- Add generate-classes skill

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ve failures

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…-plugin is restored

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…or new test modules

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…eactor modules

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…to ~6.8x

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 1: Fix MalInputDataGenerator to track per-rule tagEqual/tagMatch
variants, generating multiple sample variants per metric for complete
coverage. Handles tagNotEqual/tagNotMatch labels in input samples.

Phase 2: Add MalExpectedDataGenerator that runs v1 (Groovy) MAL
expressions and captures output (entities, samples, values) as rich
expected sections in .data.yaml files. Uses Mockito mockStatic for
K8s metadata mocking.

Phase 3: Enhance MalComparisonTest with hard assertions on expected
entities (scope/service/instance/endpoint/layer) and samples (labels/
values). EMPTY is a hard failure when rich expected exists. Duplicate
rule names disambiguated with _2/_3 suffix. v1 runtime errors fail
instead of silently skipping.

Phase 4: Add expected validation to LalComparisonTest for save, abort,
service, instance, endpoint, layer, tags, timestamp, and sampledTrace
fields. Fix enum comparison for reason/detectPoint fields.

All 1301 tests pass (1233 MAL + 35 LAL + 1 generator + 32 hierarchy).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PowerMock is a dead project. The only usage was powermock-reflect's Whitebox
class for setInternalState/getInternalState/invokeMethod — a thin wrapper
around java.lang.reflect. Replace with a project-owned ReflectUtil in the
server-testing module and add server-testing as test dependency to all 17
modules that previously relied on powermock-reflect.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…put data mock principles

- Copy vm.yaml (telegraf) and agent.yaml (zabbix) to test script directories
- Handle both 'metricsRules' and 'metrics' YAML keys (zabbix uses 'metrics')
- Handle numeric YAML keys (zabbix labels like '1', '2') via String.valueOf()
- Generate .data.yaml with proper label variants (e.g., cpu-total + cpu0 for tagEqual/tagNotEqual)
- Add CLAUDE.md documentation for input data mock principles in MAL, LAL, hierarchy modules
- Create CLAUDE.md for the checker test module

Total: 1268 MAL + 35 LAL + 31 filter = 1336 tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… LAL rule

Cover all branches in the k8s-service.yaml network-profiling-slow-trace rule:
- Virtual local process (process_id empty, local=true)
- Virtual remote process (process_id empty, local=false)
- HTTP without SSL (componentId 49)
- TCP with SSL (componentId 130)
- Default component (componentId 110)
- LOG_KIND false path (no sampledTrace extraction)

Also align v1/v2 ProcessRegistry mock return values so v1-v2 comparison
works correctly for virtual process branches.

LAL tests: 35 → 39 (+4 new entries). Total: 1340 tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ssRegistry dependencies

- Change inputData type from Map<String, Map> to Map<String, Object> to prevent
  ClassCastException when YAML values are Lists (multi-entry input data)
- Add instanceof List handling for multi-entry input data per rule
- Add @BeforeAll/@afterall mockStatic for K8sInfoRegistry and MetricsStreamProcessor
  (required by production ProcessRegistry for virtual process ID generation)
- Remove sampledTrace.processId/destProcessId from virtual process entries in
  envoy-als.input.data and k8s-service.input.data — values depend on ProcessRegistry
  implementation (mock vs production), validated via v1-v2 comparison in checker test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…data.yaml files

Add extractEntityFunctionLabels() to parse service/instance/endpoint/process function
arguments like instance(['host_name'], ['service_instance_id'], Layer.MYSQL) and ensure
these labels appear in all input samples. Without entity labels, scope/service/instance
extraction produces incorrect results.

Regenerated .data.yaml files for rules with entity function labels.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… PowerMock changelog

getInternalState() returns generic <T>, and when passed directly to setInternalState(),
Java resolves the overload to setInternalState(Class<?>, ...) instead of
setInternalState(Object, ...), causing ClassCastException. Fix by storing the result
in a local Object variable first.

Update changelog to reflect full PowerMock removal from all modules.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts:
#	docs/en/changes/changes.md
…th, regenerate expected data

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@wu-sheng
Copy link
Member Author

wu-sheng commented Mar 5, 2026

Gemini Code Review — V2 Engine Migration (Groovy Replacement)

I have completed a comprehensive review of the changes in the groovy-replace branch. The migration from Groovy-based V1 to the ANTLR4/Javassist-based V2 engine is architecturally sound and significantly improves the robustness of the OAP analyzer.

Key Technical Highlights:

  • Concurrency & Thread Safety: The removal of ThreadLocal in favor of explicit context passing (ExecutionContext, RunningContext) is a major improvement. The generated classes (MalExpr, LalExpr) are stateless and thread-safe, ensuring predictable behavior under high concurrency.
  • Feature Parity & Validation: Strong parity is verified via MalComparisonTest and LalComparisonTest. The latest fix to fail explicitly on V1 runtime errors during data generation ensures the integrity of the test expectations.
  • Performance Optimization: The transition to compiled bytecode (via LALClassGenerator and MALClassGenerator) eliminates the overhead of dynamic script evaluation, providing a more performant execution path for LAL, MAL, and Hierarchy rules.

The migration successfully modernizes the DSL engine while maintaining strict compatibility with existing rule configurations.

…oc for key code paths

- Remove initExp from MAL docs (mal.md, backend-meter.md, backend-zabbix.md);
  it was an internal Groovy startup validation mechanism, not an end-user feature
- Replace "Use Groovy script" in hierarchy-definition.yml with accurate grammar description
- Add Javadoc to HierarchyRuleClassGenerator (supported grammar, SourceFile/LineNumberTable
  debugging, toGetter field mapping and fallback, formatSourceFileName logic)
- Add Javadoc to HierarchyDefinitionService (loadProvider SPI discovery, match() behavior)
- Add changelog entries for initExp removal and hierarchy rule grammar change
- Add .claude/settings.local.json to .gitignore

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@wu-sheng
Copy link
Member Author

wu-sheng commented Mar 6, 2026

Gemini Verification Report: LAL/MAL/Hierarchy V2 Transition

1. Architecture & Engine Parity

The migration from the Groovy-based V1 engine to the ANTLR4 + Javassist-based V2 engine was reviewed for both architectural integrity and functional parity.

  • Engine Strictness: Verified that the LalComparisonTest and MalComparisonTest (cross-version checkers) are highly assertive. Any discrepancy in metric values, entity names, or scope types between V1 and V2 triggers an immediate test failure.
  • No Silent Passes: Confirmed that the framework handles engine crashes and mismatch states explicitly via fail() calls, ensuring no behavioral regressions.

2. Verification Scope

A systematic, file-by-file review was performed on all 87 script files located in test/script-cases/scripts/, including:

  • LAL Scripts: Envoy ALS, K8s network profiling, Nginx, and all Slow SQL variants.
  • MAL Meter Config: Java, Go, Python, Ruby agents, Satellite, Spring, etc.
  • MAL OTEL Rules: 20+ subdirectories covering 3rd-party integrations (ActiveMQ, AWS, MongoDB, Kafka, etc.).
  • Hierarchy: Core service-to-service and service-to-instance mapping rules.

3. Branch & Feature Coverage (100% Verified)

Every script was verified against its corresponding mock data (.input.data or .data.yaml) to ensure all logical paths are exercised:

  • Conditional Branches: Verified that every if/else, tagEqual, tagMatch, and tagNotMatch branch in the DSLs has at least one corresponding mock input sample.
  • V2 Advanced Features: Confirmed correct execution of V2-specific enhancements:
    • decorate() closures: Validated that MeterEntity property modifications (me.attrX = ...) are correctly applied and asserted.
    • GStrings & Concatenation: Verified complex string building in service and endpoint naming logic.
    • Slashy Regex: Confirmed support for Groovy-style regex literals within split and match operations.

4. Resolution of Previously Identified Gaps

All gaps identified during earlier stages of the review have been successfully addressed:

  • Null Endpoint Issue: Mock data for AWS EKS, DynamoDB, and Nginx was enriched with missing labels (e.g., ClusterName, TableName), ensuring valid name construction instead of null defaults.
  • Attribute Verification Gap: The testing framework was updated to include attr0-attr5 in the comparison logic, ensuring that decorate() logic is strictly validated.
  • LAL Coverage: Improved mesh-dp.yaml coverage from a single success path to 4 exhaustive cases covering virtual process resolution and all component/SSL combinations.

Conclusion

The V2 transition is implementation-complete and thoroughly verified. The test suite provides exhaustive evidence of behavioral equivalence between the old and new engines while maintaining 100% parity with the production script set. The PR is verified as stable and high-quality.

wu-sheng added 2 commits March 6, 2026 09:47
The env var controls class file dumping for all 4 DSL compilers (OAL, MAL,
LAL, Hierarchy), not just OAL. Rename to reflect its actual scope. Update
all documentation references accordingly.
Update the BanyanDB image tag in docker/.env and docker/docker-compose.yml
to match the version pinned in test/e2e-v2/script/env.
@wu-sheng wu-sheng merged commit b106892 into master Mar 6, 2026
327 of 329 checks passed
@wu-sheng wu-sheng deleted the groovy-replace branch March 6, 2026 03:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

complexity:high Relate to multiple(>4) components of SkyWalking core feature Core and important feature. Sometimes, break backwards compatibility. feature New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Remove deprecated PowerMock (Whitebox) and migrate to standard Mockito/Reflection

3 participants