This is an automated email from the ASF dual-hosted git repository. wusheng pushed a commit to branch groovy-replace in repository https://gitbox.apache.org/repos/asf/skywalking.git
commit f4127038b610a02cd31a5d3b6deef3071b51ac21 Author: Wu Sheng <[email protected]> AuthorDate: Sun Mar 1 10:24:42 2026 +0800 Replace groovy-replacement-plan.md with dsl-compiler-design.md covering 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 <[email protected]> --- docs/en/academy/dsl-compiler-design.md | 157 +++ docs/en/academy/groovy-replacement-plan.md | 1025 -------------------- docs/en/concepts-and-designs/lal.md | 34 +- .../service-hierarchy-configuration.md | 2 +- docs/en/concepts-and-designs/service-hierarchy.md | 46 +- docs/menu.yml | 2 + .../provider/meter/process/MeterProcessor.java | 2 +- 7 files changed, 200 insertions(+), 1068 deletions(-) diff --git a/docs/en/academy/dsl-compiler-design.md b/docs/en/academy/dsl-compiler-design.md new file mode 100644 index 0000000000..ce14082e20 --- /dev/null +++ b/docs/en/academy/dsl-compiler-design.md @@ -0,0 +1,157 @@ +# DSL Compiler Design: ANTLR4 + Javassist + +## Overview + +SkyWalking OAP server uses four domain-specific languages (DSLs) for telemetry analysis. +All four share the same compilation tech stack: **ANTLR4** for grammar parsing and **Javassist** for +runtime bytecode generation. + +| DSL | Purpose | Input | Generated Output | +|-----|---------|-------|-----------------| +| **OAL** (Observability Analysis Language) | Trace/mesh metrics aggregation | `.oal` script files | Metrics classes, builders, dispatchers | +| **MAL** (Meter Analysis Language) | Meter/metrics expression evaluation | YAML config `exp` fields | `MalExpression` implementations | +| **LAL** (Log Analysis Language) | Log processing pipelines | YAML config `filter` blocks | `LalExpression` implementations | +| **Hierarchy Matching Rules** | Service hierarchy relationship matching | YAML config expressions | `BiFunction<Service, Service, Boolean>` implementations | + +## Compilation Pipeline + +All four DSLs follow the same three-phase compilation pipeline at OAP startup: + +``` +DSL string (from .oal script or YAML config) + | + v +Phase 1: ANTLR4 Parsing + Lexer + Parser (generated from .g4 grammars at build time) + → Immutable AST model + | + v +Phase 2: Java Source Generation + Walk AST model, emit Java source code as strings + | + v +Phase 3: Javassist Bytecode Generation + ClassPool.makeClass() → CtClass → addMethod(source) → toClass() + → Ready-to-use class instance loaded into JVM +``` + +### What Each DSL Generates + +| DSL | Interface / Base Class | Key Method | +|-----|----------------------|------------| +| OAL | Extends metrics function class (e.g., `LongAvgMetrics`) | `id()`, `serialize()`, `deserialize()`, plus dispatcher `dispatch(source)` | +| MAL metric | `MalExpression` | `SampleFamily run(Map<String, SampleFamily> samples)` | +| MAL filter | `Predicate<Map<String, String>>` | `boolean test(Map<String, String> tags)` | +| LAL | `LalExpression` | `void execute(Object filterSpec, Object binding)` | +| Hierarchy | `BiFunction<Service, Service, Boolean>` | `Boolean apply(Service upper, Service lower)` | + +OAL is the most complex -- it generates **three classes per metric** (metrics class with storage annotations, +metrics builder for serialization, and source dispatcher for routing), whereas MAL/LAL/Hierarchy each generate +a single functional class per expression. + +## ANTLR4 Grammars + +Each DSL has its own ANTLR4 lexer and parser grammar. The Maven ANTLR4 plugin generates Java lexer/parser +classes at build time; these are then used at runtime to parse DSL strings. + +| DSL | Grammar Location | +|-----|-----------------| +| OAL | `oap-server/oal-grammar/src/main/antlr4/.../OALLexer.g4`, `OALParser.g4` | +| MAL | `oap-server/analyzer/meter-analyzer/src/main/antlr4/.../MALLexer.g4`, `MALParser.g4` | +| LAL | `oap-server/analyzer/log-analyzer/src/main/antlr4/.../LALLexer.g4`, `LALParser.g4` | +| Hierarchy | `oap-server/analyzer/hierarchy/src/main/antlr4/.../HierarchyRuleLexer.g4`, `HierarchyRuleParser.g4` | + +## Javassist Constraints + +Javassist compiles Java source strings into bytecode but has limitations that shape the code generation: + +- **No anonymous inner classes or lambdas** -- Callback-based APIs (e.g., LAL's `filterSpec.extractor(Consumer)`) + require pre-compiling each callback as a separate `CtClass` implementing the needed interface. +- **No generics in method bodies** -- Generated source uses raw types with explicit casts. +- **Class loading anchor** -- Each DSL uses a `PackageHolder` marker class so that + `ctClass.toClass(PackageHolder.class)` loads the generated class into the correct module/package + (required for JDK 9+ module system). + +OAL additionally uses **FreeMarker templates** to generate method bodies for metrics classes, builders, and +dispatchers, since these classes are more complex and benefit from template-driven generation. + +## Module Structure + +``` +oap-server/ + oal-grammar/ # OAL: ANTLR4 grammar + oal-rt/ # OAL: compiler + runtime (Javassist + FreeMarker) + analyzer/ + meter-analyzer/ # MAL: grammar + compiler + runtime + log-analyzer/ # LAL: grammar + compiler + runtime + hierarchy/ # Hierarchy: grammar + compiler + runtime + agent-analyzer/ # Calls MAL compiler for meter data +``` + +OAL keeps grammar and runtime in separate modules (`oal-grammar` and `oal-rt`) because `server-core` +depends on the grammar while the runtime implementation depends on `server-core` (avoiding circular +dependency). MAL, LAL, and Hierarchy are each self-contained in a single module. + +## Groovy Replacement (MAL, LAL, Hierarchy) + +Reference: [Discussion #13716](https://github.com/apache/skywalking/discussions/13716) + +MAL, LAL, and Hierarchy previously used **Groovy** as the runtime scripting engine. OAL has always used +ANTLR4 + Javassist. The Groovy-based DSLs were replaced for the following reasons: + +1. **Startup cost** -- 1,250+ `GroovyShell.parse()` calls at OAP boot, each spinning up the full Groovy + compiler pipeline. + +2. **Runtime execution overhead** -- MAL expressions execute on every metrics ingestion cycle. Per-expression + overhead from dynamic Groovy compounds at scale: property resolution through 4+ layers of indirection, + `ExpandoMetaClass` closure allocation for simple arithmetic, and megamorphic call sites that defeat JIT + optimization. + +3. **Late error detection** -- MAL uses dynamic Groovy; typos in metric names or invalid method chains are + only discovered when that specific expression runs with real data. + +4. **Debugging complexity** -- Stack traces include Groovy MOP internals (`CallSite`, `MetaClassImpl`, + `ExpandoMetaClass`), obscuring the actual expression logic. + +5. **GraalVM incompatibility** -- `invokedynamic` bootstrapping and `ExpandoMetaClass` are fundamentally + incompatible with ahead-of-time (AOT) compilation, blocking the + [GraalVM native-image distribution](https://github.com/apache/skywalking-graalvm-distro). + +The DSL grammar for users remains **100% unchanged** -- the same expressions written in YAML config files +work exactly as before. Only the internal compilation engine was replaced. + +### Verification: Groovy v1 Checker + +To ensure the new Java compilers produce identical results to the original Groovy implementation, +a **dual-path comparison test suite** is maintained under `test/script-compiler/`: + +``` +test/script-compiler/ + mal-groovy/ # MAL v1: original Groovy-based implementation + lal-groovy/ # LAL v1: original Groovy-based implementation + hierarchy-groovy/ # Hierarchy v1: original Groovy-based implementation + mal-lal-v1-v2-checker/ # Runs every MAL/LAL expression through BOTH v1 and v2, compares results + hierarchy-v1-v2-checker/ # Runs every hierarchy rule through BOTH v1 and v2, compares results +``` + +The checker mechanism: + +1. Loads all production YAML config files (the same files used by OAP at runtime) +2. For each DSL expression, compiles with **both** v1 (Groovy) and v2 (ANTLR4 + Javassist) +3. Compares the compiled artifacts: + - **MAL**: Compare generated Java source code, extracted metadata (sample names, aggregation labels, + downsampling type, percentile config), and execution results with sample data + - **LAL**: Compare compiled expression execution against FilterSpec/Binding mocks + - **Hierarchy**: Compare `BiFunction` evaluation with test Service pairs + +This ensures 100% behavioral parity. The Groovy v1 modules are **test-only dependencies** -- they are not +included in the OAP distribution. + +#### Current Checker Results + +| Checker | Expressions Tested | Status | +|---------|-------------------|--------| +| MAL metric expressions | 1,187 | All pass | +| MAL filter expressions | 29 | All pass | +| LAL scripts | 10 | All pass | +| Hierarchy rules | 22 | All pass | diff --git a/docs/en/academy/groovy-replacement-plan.md b/docs/en/academy/groovy-replacement-plan.md deleted file mode 100644 index f656a7b94d..0000000000 --- a/docs/en/academy/groovy-replacement-plan.md +++ /dev/null @@ -1,1025 +0,0 @@ -# Groovy Replacement Plan: Build-Time Transpiler for MAL, LAL, and Hierarchy Scripts - -Reference: [Discussion #13716](https://github.com/apache/skywalking/discussions/13716) -Reference Implementation: [skywalking-graalvm-distro](https://github.com/apache/skywalking-graalvm-distro) - -## 1. Background and Motivation - -SkyWalking OAP server currently uses Groovy as the runtime scripting engine for three subsystems: - -| Subsystem | YAML Files | Expressions | Groovy Pattern | -|-----------|-----------|-------------|----------------| -| MAL (Meter Analysis Language) | 71 (11 meter-analyzer-config, 55 otel-rules, 2 log-mal-rules, 2 envoy-metrics-rules, 1 telegraf-rules) | 1,254 metric + 29 filter | Dynamic Groovy: `propertyMissing()`, `ExpandoMetaClass` on `Number`, closures | -| LAL (Log Analysis Language) | 8 | 10 rules | `@CompileStatic` Groovy: delegation-based closure DSL, safe navigation (`?.`), `as` casts | -| Hierarchy Matching | 1 (hierarchy-definition.yml) | 4 rules | `GroovyShell.evaluate()` for `Closure<Boolean>` | - -### Problems with Groovy Runtime - -1. **Startup Cost**: 1,250+ `GroovyShell.parse()` calls at OAP boot, each spinning up the full Groovy compiler pipeline. -2. **Runtime Errors Instead of Compile-Time Errors**: MAL uses dynamic Groovy -- typos in metric names or invalid method chains are only discovered when that specific expression runs with real data. -3. **Debugging Complexity**: Stack traces include Groovy MOP internals (`CallSite`, `MetaClassImpl`, `ExpandoMetaClass`), obscuring the actual expression logic. -4. **Runtime Execution Performance (Most Critical)**: MAL expressions execute on every metrics ingestion cycle. Per-expression overhead from dynamic Groovy compounds at scale: - - Property resolution: `CallSite` -> `MetaClassImpl.invokePropertyOrMissing()` -> `ExpressionDelegate.propertyMissing()` -> `ThreadLocal<Map>` lookup (4+ layers of indirection per metric name lookup) - - Method calls: Groovy `CallSite` dispatch with MetaClass lookup and MOP interception checks - - Arithmetic (`metric * 1000`): `ExpandoMetaClass` closure allocation + metaclass lookup + dynamic dispatch for what Java does as a single `imul` - - Per ingestion cycle: ~1,250 `propertyMissing()` calls, ~3,750 MOP method dispatches, ~29 metaclass arithmetic ops, ~200 closure allocations - - JIT cannot optimize Groovy's megamorphic call sites, defeating inlining and branch prediction -5. **GraalVM Incompatibility**: `invokedynamic` bootstrapping and `ExpandoMetaClass` are fundamentally incompatible with AOT compilation. - -### Goal - -Eliminate Groovy from the OAP runtime entirely. Groovy becomes a **build-time-only** dependency used solely for AST parsing by the transpiler. Zero `GroovyShell`, zero `ExpandoMetaClass`, zero MOP at runtime. - ---- - -## 2. Solution Architecture - -### Build-Time Transpiler: Groovy DSL -> Pure Java Source Code - -``` -BUILD TIME (Maven compile phase): - MAL YAML files (71 files, 1,250+ expressions) - LAL YAML files (8 files, 10 scripts) - | - v - MalToJavaTranspiler / LalToJavaTranspiler - (Groovy CompilationUnit at CONVERSION phase -- AST parsing only, no execution) - | - v - ~1,254 MalExpr_*.java + ~6 LalExpr_*.java + MalFilter_*.java - | - v - javax.tools.JavaCompiler -> .class files on classpath - META-INF/mal-expressions.txt (manifest) - META-INF/mal-filter-expressions.properties (manifest) - META-INF/lal-expressions.txt (manifest) - -RUNTIME (OAP Server): - Class.forName(className) -> MalExpression / LalExpression instance - Zero Groovy. Zero GroovyShell. Zero ExpandoMetaClass. -``` - -The transpiler approach is already fully implemented and validated in the [skywalking-graalvm-distro](https://github.com/apache/skywalking-graalvm-distro) repository. - ---- - -## 3. Detailed Design - -### 3.1 New Functional Interfaces - -Three core interfaces replace Groovy's `DelegatingScript` and `Closure`: - -```java -// MAL: replaces DelegatingScript + ExpandoMetaClass + ExpressionDelegate.propertyMissing() -@FunctionalInterface -public interface MalExpression { - SampleFamily run(Map<String, SampleFamily> samples); -} - -// MAL: replaces Closure<Boolean> from GroovyShell.evaluate() for filter expressions -@FunctionalInterface -public interface MalFilter { - boolean test(Map<String, String> tags); -} - -// LAL: replaces LALDelegatingScript + @CompileStatic closure DSL -@FunctionalInterface -public interface LalExpression { - void execute(FilterSpec filterSpec, Binding binding); -} -``` - -### 3.2 SampleFamily: Closure -> Functional Interface - -Five `SampleFamily` methods currently accept `groovy.lang.Closure`. Each gets a new overload with a Java functional interface. During transition both overloads coexist; eventually the Closure overloads are removed. - -| Method | Current (Groovy) | New (Java) | Functional Interface | -|--------|-----------------|------------|---------------------| -| `tag()` | `Closure<?>` with `tags.key = val` | `TagFunction` | `Function<Map<String,String>, Map<String,String>>` | -| `filter()` | `Closure<Boolean>` with `tags.x == 'y'` | `SampleFilter` | `Predicate<Map<String,String>>` | -| `forEach()` | `Closure<Void>` with `(prefix, tags) -> ...` | `ForEachFunction` | `BiConsumer<String, Map<String,String>>` | -| `decorate()` | `Closure<Void>` with `entity -> ...` | `DecorateFunction` | `Consumer<MeterEntity>` | -| `instance(..., closure)` | `Closure<?>` with `tags -> Map.of(...)` | `PropertiesExtractor` | `Function<Map<String,String>, Map<String,String>>` | - -Source location in upstream: `oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/SampleFamily.java` - -### 3.3 MAL Transpiler: AST Mapping Rules - -The `MalToJavaTranspiler` (~1,230 lines) parses each MAL expression string into a Groovy AST at `Phases.CONVERSION` (no code execution), then walks the AST to emit equivalent Java code. - -#### Expression Mappings - -| Groovy Construct | Java Output | Notes | -|-----------------|-------------|-------| -| `metric_name` (bare property) | `samples.getOrDefault("metric_name", SampleFamily.EMPTY)` | Replaces `propertyMissing()` dispatch | -| `.sum(['a','b'])` | `.sum(List.of("a", "b"))` | Direct method call | -| `.tagEqual('resource', 'cpu')` | `.tagEqual("resource", "cpu")` | Direct method call | -| `100 * metric` | `metric.multiply(100)` | Commutative: operands swapped | -| `100 - metric` | `metric.minus(100).negative()` | Non-commutative: negate | -| `100 / metric` | `metric.newValue(v -> 100 / v)` | Non-commutative: newValue | -| `metricA / metricB` | `metricA.div(metricB)` | SampleFamily-SampleFamily op | -| `.tag({tags -> tags.cluster = ...})` | `.tag(tags -> { tags.put("cluster", ...); return tags; })` | Closure -> lambda | -| `.filter({tags -> tags.job_name in [...]})` | `.filter(tags -> "...".equals(tags.get("job_name")))` | Closure -> predicate | -| `.forEach(['a','b'], {p, tags -> ...})` | `.forEach(List.of("a","b"), (p, tags) -> { ... })` | Closure -> BiConsumer | -| `.decorate({entity -> ...})` | `.decorate(entity -> { ... })` | Closure -> Consumer | -| `.instance(..., {tags -> Map.of(...)})` | `.instance(..., tags -> Map.of(...))` | Closure -> Function | -| `Layer.K8S` | `Layer.K8S` | Enum constant, passed through | -| `time()` | `Instant.now().getEpochSecond()` | Direct Java API | -| `AVG`, `SUM`, etc. | `DownsamplingType.AVG`, etc. | Enum constant reference | - -#### Closure Body Translation - -Inside closures, Groovy property-style access is mapped to explicit `Map` operations: - -| Groovy Closure Pattern | Java Lambda Output | -|----------------------|-------------------| -| `tags.key = "value"` | `tags.put("key", "value")` | -| `tags.key` (read) | `tags.get("key")` | -| `tags.remove("key")` | `tags.remove("key")` | -| `tags.key == "value"` | `"value".equals(tags.get("key"))` | -| `tags.key != "value"` | `!"value".equals(tags.get("key"))` | -| `tags.key in ["a","b"]` | `List.of("a","b").contains(tags.get("key"))` | -| `if/else` in closure | `if/else` in lambda | -| `entity.serviceId = val` | `entity.setServiceId(val)` | - -#### Filter Expression Mappings - -Filter expressions (`filter: "{ tags -> ... }"`) generate `MalFilter` implementations: - -| Groovy Filter | Java Output | -|--------------|-------------| -| `tags.job_name == 'mysql'` | `"mysql".equals(tags.get("job_name"))` | -| `tags.job_name != 'test'` | `!"test".equals(tags.get("job_name"))` | -| `tags.job_name in ['a','b']` | `List.of("a","b").contains(tags.get("job_name"))` | -| `cond1 && cond2` | `cond1 && cond2` | -| `cond1 \|\| cond2` | `cond1 \|\| cond2` | -| `!cond` | `!cond` | -| `tags.job_name` (truthiness) | `tags.get("job_name") != null` | - -### 3.4 LAL Transpiler: AST Mapping Rules - -The `LalToJavaTranspiler` (~950 lines) handles LAL's `@CompileStatic` delegation-based DSL. LAL scripts have a fundamentally different structure from MAL -- they are statement-based builder patterns rather than expression-based computations. - -#### Statement Mappings - -| Groovy Construct | Java Output | -|-----------------|-------------| -| `filter { ... }` | Body unwrapped, emitted directly on `filterSpec` | -| `json {}` | `filterSpec.json()` | -| `json { abortOnFailure false }` | `filterSpec.json(jp -> { jp.abortOnFailure(false); })` | -| `text { regexp /pattern/ }` | `filterSpec.text(tp -> { tp.regexp("pattern"); })` | -| `yaml {}` | `filterSpec.yaml()` | -| `extractor { ... }` | `filterSpec.extractor(ext -> { ... })` | -| `sink { ... }` | `filterSpec.sink(s -> { ... })` | -| `abort {}` | `filterSpec.abort()` | -| `service parsed.service as String` | `ext.service(String.valueOf(getAt(binding.parsed(), "service")))` | -| `layer parsed.layer as String` | `ext.layer(String.valueOf(getAt(binding.parsed(), "layer")))` | -| `tag(key: val)` | `ext.tag(Map.of("key", val))` | -| `timestamp parsed.time as String` | `ext.timestamp(String.valueOf(getAt(binding.parsed(), "time")))` | - -#### Property Access and Safe Navigation - -| Groovy Pattern | Java Output | -|---------------|-------------| -| `parsed.field` | `getAt(binding.parsed(), "field")` | -| `parsed.field.nested` | `getAt(getAt(binding.parsed(), "field"), "nested")` | -| `parsed?.field?.nested` | `((__v0 = binding.parsed()) == null ? null : ((__v1 = getAt(__v0, "field")) == null ? null : getAt(__v1, "nested")))` | -| `log.tags` | `binding.log().getTags()` | - -#### Cast and Type Handling - -| Groovy Pattern | Java Output | -|---------------|-------------| -| `expr as String` | `String.valueOf(expr)` | -| `expr as Long` | `toLong(expr)` | -| `expr as Integer` | `toInt(expr)` | -| `expr as Boolean` | `toBoolean(expr)` | -| `"${expr}"` (GString) | `"" + expr` | - -#### LAL Spec Consumer Overloads - -LAL spec classes (`FilterSpec`, `ExtractorSpec`, `SinkSpec`) get additional method overloads accepting `java.util.function.Consumer` alongside existing Groovy `Closure` parameters: - -```java -// FilterSpec - existing -public void extractor(Closure<?> cl) { ... } -// FilterSpec - new overload -public void extractor(Consumer<ExtractorSpec> consumer) { ... } - -// SinkSpec - existing -public void sampler(Closure<?> cl) { ... } -// SinkSpec - new overload -public void sampler(Consumer<SamplerSpec> consumer) { ... } -``` - -Methods requiring Consumer overloads: `text()`, `json()`, `yaml()`, `extractor()`, `sink()`, `slowSql()`, `sampledTrace()`, `metrics()`, `sampler()`, `enforcer()`, `dropper()`. - -#### SHA-256 Deduplication - -LAL manifest is keyed by SHA-256 hash of the DSL content. Identical scripts across different YAML files share one compiled class. In practice, 10 LAL rules map to 6 unique classes. - -### 3.5 Hierarchy Script: v1/v2 Module Split - -The hierarchy matching rules in `hierarchy-definition.yml` use `GroovyShell.evaluate()` to compile 4 Groovy closures at runtime. Unlike MAL/LAL, hierarchy does not need a transpiler (only 4 rules, finite set), but it follows the same v1/v2/checker module pattern for consistency and to remove Groovy from `server-core`. - -#### Current State (server-core, Groovy-coupled) - -`HierarchyDefinitionService.java` lives in `server-core` and is registered as a `Service` in `CoreModule`. Its inner class `MatchingRule` holds a Groovy `Closure<Boolean>`: - -```java -// server-core/...config/HierarchyDefinitionService.java (current) -public static class MatchingRule { - private final String name; - private final String expression; - private final Closure<Boolean> closure; // groovy.lang.Closure - - public MatchingRule(final String name, final String expression) { - GroovyShell sh = new GroovyShell(); - closure = (Closure<Boolean>) sh.evaluate(expression); // Groovy at runtime - } -} -``` - -This `MatchingRule` is referenced by three classes in `server-core`: -- `HierarchyDefinitionService` -- builds the rule map from YAML -- `HierarchyService` -- calls `matchingRule.getClosure().call(service, comparedService)` for auto-matching -- `HierarchyQueryService` -- reads the hierarchy definition map - -#### Step 1: Make server-core Groovy-Free - -Refactor `MatchingRule` in `server-core` to use a Java functional interface instead of `Closure<Boolean>`: - -```java -// server-core/...config/HierarchyDefinitionService.java (refactored) -public static class MatchingRule { - private final String name; - private final String expression; - private final BiFunction<Service, Service, Boolean> matcher; // pure Java - - public MatchingRule(final String name, final String expression, - final BiFunction<Service, Service, Boolean> matcher) { - this.name = name; - this.expression = expression; - this.matcher = matcher; - } - - public boolean match(Service upper, Service lower) { - return matcher.apply(upper, lower); - } -} -``` - -`HierarchyDefinitionService.init()` no longer compiles Groovy expressions itself. Instead, it receives a `Map<String, BiFunction<Service, Service, Boolean>>` (the rule registry) from outside -- injected by whichever implementation module (v1 or v2) is active. - -`HierarchyService` changes from `matchingRule.getClosure().call(u, l)` to `matchingRule.match(u, l)`. - -Remove all `groovy.lang.*` imports from `server-core`. - -#### Step 2: hierarchy-v1 (Groovy-based, for checker only) - -```java -// analyzer/hierarchy-v1/.../GroovyHierarchyRuleProvider.java -public class GroovyHierarchyRuleProvider { - public static Map<String, BiFunction<Service, Service, Boolean>> buildRules( - Map<String, String> ruleExpressions) { - Map<String, BiFunction<Service, Service, Boolean>> rules = new HashMap<>(); - GroovyShell sh = new GroovyShell(); - ruleExpressions.forEach((name, expression) -> { - Closure<Boolean> closure = (Closure<Boolean>) sh.evaluate(expression); - rules.put(name, (u, l) -> closure.call(u, l)); - }); - return rules; - } -} -``` - -This module depends on Groovy and wraps the original `GroovyShell.evaluate()` logic. It is NOT included in the runtime classpath -- only used by the checker. - -#### Step 3: hierarchy-v2 (Pure Java, for runtime) - -```java -// analyzer/hierarchy-v2/.../JavaHierarchyRuleProvider.java -public class JavaHierarchyRuleProvider { - private static final Map<String, BiFunction<Service, Service, Boolean>> RULE_REGISTRY; - static { - RULE_REGISTRY = new HashMap<>(); - RULE_REGISTRY.put("name", - (u, l) -> Objects.equals(u.getName(), l.getName())); - RULE_REGISTRY.put("short-name", - (u, l) -> Objects.equals(u.getShortName(), l.getShortName())); - RULE_REGISTRY.put("lower-short-name-remove-ns", (u, l) -> { - String sn = l.getShortName(); - int dot = sn.lastIndexOf('.'); - return dot > 0 && Objects.equals(u.getShortName(), sn.substring(0, dot)); - }); - RULE_REGISTRY.put("lower-short-name-with-fqdn", (u, l) -> { - String sn = u.getShortName(); - int colon = sn.lastIndexOf(':'); - return colon > 0 && Objects.equals( - sn.substring(0, colon), - l.getShortName() + ".svc.cluster.local"); - }); - } - - public static Map<String, BiFunction<Service, Service, Boolean>> buildRules( - Map<String, String> ruleExpressions) { - Map<String, BiFunction<Service, Service, Boolean>> rules = new HashMap<>(); - ruleExpressions.forEach((name, expression) -> { - BiFunction<Service, Service, Boolean> fn = RULE_REGISTRY.get(name); - if (fn == null) { - throw new IllegalArgumentException( - "Unknown hierarchy matching rule: " + name - + ". Known rules: " + RULE_REGISTRY.keySet()); - } - rules.put(name, fn); - }); - return rules; - } -} -``` - -Unknown rule names fail fast at startup with `IllegalArgumentException`. The YAML file (`hierarchy-definition.yml`) continues to reference rule names (`name`, `short-name`, etc.) -- the Groovy expression strings in `auto-matching-rules` become documentation-only at runtime. - -#### Step 4: hierarchy-v1-v2-checker - -```java -// analyzer/hierarchy-v1-v2-checker/.../HierarchyRuleComparisonTest.java -class HierarchyRuleComparisonTest { - // Load rule expressions from hierarchy-definition.yml - // For each rule: - // Path A: GroovyHierarchyRuleProvider.buildRules() (v1) - // Path B: JavaHierarchyRuleProvider.buildRules() (v2) - // Construct test Service pairs (matching and non-matching cases) - // Assert v1.match(u, l) == v2.match(u, l) for all test pairs -} -``` - -Test cases cover all 4 rules with realistic service name patterns: -- `name`: exact match and mismatch -- `short-name`: exact shortName match and mismatch -- `lower-short-name-remove-ns`: `"svc" == "svc.namespace"` and edge cases (no dot, empty) -- `lower-short-name-with-fqdn`: `"db:3306"` vs `"db.svc.cluster.local"` and edge cases (no colon, wrong suffix) - ---- - -## 4. Module Structure - -### 4.1 Upstream Module Layout - -``` -oap-server/ - server-core/ # MODIFIED: MatchingRule uses BiFunction (no Groovy imports) - - analyzer/ - meter-analyzer/ # Modified: add MalExpression, functional interfaces - log-analyzer/ # Modified: add LalExpression, Consumer overloads - - mal-lal-v1/ # NEW: Move existing Groovy-based code here - meter-analyzer-v1/ # Original MAL (GroovyShell + ExpandoMetaClass) - log-analyzer-v1/ # Original LAL (GroovyShell + @CompileStatic) - - mal-lal-v2/ # NEW: Pure Java transpiler-based implementations - meter-analyzer-v2/ # MalExpression loader + functional interface dispatch - log-analyzer-v2/ # LalExpression loader + Consumer dispatch - mal-transpiler/ # Build-time: Groovy AST -> Java source (MAL) - lal-transpiler/ # Build-time: Groovy AST -> Java source (LAL) - - mal-lal-v1-v2-checker/ # NEW: Dual-path comparison tests (MAL + LAL) - 73 MAL test classes (1,281 assertions) - 5 LAL test classes (19 assertions) - - hierarchy-v1/ # NEW: Groovy-based hierarchy rule provider (checker only) - hierarchy-v2/ # NEW: Pure Java hierarchy rule provider (runtime) - hierarchy-v1-v2-checker/ # NEW: Dual-path comparison tests (hierarchy) -``` - -### 4.2 Dependency Graph - -``` -mal-transpiler ──────────────> groovy (build-time only, for AST parsing) -lal-transpiler ──────────────> groovy (build-time only, for AST parsing) -hierarchy-v1 ────────────────> groovy (checker only, not runtime) - -meter-analyzer-v2 ──────────> meter-analyzer (interfaces + SampleFamily) -log-analyzer-v2 ────────────> log-analyzer (interfaces + spec classes) -hierarchy-v2 ───────────────> server-core (MatchingRule with BiFunction) - -mal-lal-v1-v2-checker ──────> mal-lal-v1 (Groovy path) -mal-lal-v1-v2-checker ──────> mal-lal-v2 (Java path) -hierarchy-v1-v2-checker ────> hierarchy-v1 (Groovy path) -hierarchy-v1-v2-checker ────> hierarchy-v2 (Java path) - -server-starter ─────────────> meter-analyzer-v2 (runtime, no Groovy) -server-starter ─────────────> log-analyzer-v2 (runtime, no Groovy) -server-starter ─────────────> hierarchy-v2 (runtime, no Groovy) -server-starter ────────────X─> mal-lal-v1 (NOT in runtime) -server-starter ────────────X─> hierarchy-v1 (NOT in runtime) -``` - -### 4.3 Key Design Principle: No Coexistence - -v1 (Groovy) and v2 (Java) never coexist in the OAP runtime classpath. The `mal-lal-v1` and `hierarchy-v1` modules are only dependencies of their respective checker modules for CI validation. The runtime (`server-starter`) depends only on v2 modules. - ---- - -## 5. Implementation Steps - -### Phase 1: Interfaces and SampleFamily Modifications - -**Files to modify:** - -1. **Create `MalExpression.java`** in `meter-analyzer` - - Path: `oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/MalExpression.java` - -2. **Create `MalFilter.java`** in `meter-analyzer` - - Path: `oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/MalFilter.java` - -3. **Create functional interfaces** in `meter-analyzer` - - `TagFunction extends Function<Map<String,String>, Map<String,String>>` - - `SampleFilter extends Predicate<Map<String,String>>` - - `ForEachFunction extends BiConsumer<String, Map<String,String>>` - - `DecorateFunction extends Consumer<MeterEntity>` - - `PropertiesExtractor extends Function<Map<String,String>, Map<String,String>>` - -4. **Add overloads to `SampleFamily.java`** - - Add `tag(TagFunction)` alongside existing `tag(Closure<?>)` - - Add `filter(SampleFilter)` alongside existing `filter(Closure<Boolean>)` - - Add `forEach(List, ForEachFunction)` alongside existing `forEach(List, Closure)` - - Add `decorate(DecorateFunction)` alongside existing `decorate(Closure)` - - Add `instance(..., PropertiesExtractor)` alongside existing `instance(..., Closure)` - -5. **Create `LalExpression.java`** in `log-analyzer` - - Path: `oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/dsl/LalExpression.java` - -6. **Add Consumer overloads to LAL spec classes** - - `FilterSpec`: `text(Consumer)`, `json(Consumer)`, `yaml(Consumer)`, `extractor(Consumer)`, `sink(Consumer)`, `filter(Consumer)` - - `ExtractorSpec`: `slowSql(Consumer)`, `sampledTrace(Consumer)`, `metrics(Consumer)` - - `SinkSpec`: `sampler(Consumer)`, `enforcer(Consumer)`, `dropper(Consumer)` - -### Phase 2: MAL Transpiler - -**New module: `oap-server/analyzer/mal-lal-v2/mal-transpiler/`** - -1. **`MalToJavaTranspiler.java`** (~1,230 lines) - - Uses `org.codehaus.groovy.control.CompilationUnit` at `Phases.CONVERSION` - - Walks AST via recursive visitor pattern - - Core methods: - - `transpileExpression(String className, String expression)` -> generates Java source for `MalExpression` - - `transpileFilter(String className, String filterLiteral)` -> generates Java source for `MalFilter` - - `collectSampleNames(Expression expr)` -> extracts all metric name references - - `visitExpression(Expression node)` -> recursive Java code emitter - - `visitClosureExpression(ClosureExpression node, String contextType)` -> closure-to-lambda - - `compileAll()` -> batch `javac` compile + manifest generation - -2. **Maven integration**: `exec-maven-plugin` during `generate-sources` phase - - Reads all MAL YAML files from resources - - Generates Java source to `target/generated-sources/mal/` - - Compiles to `target/classes/` - - Writes `META-INF/mal-expressions.txt` and `META-INF/mal-filter-expressions.properties` - -### Phase 3: LAL Transpiler - -**New module: `oap-server/analyzer/mal-lal-v2/lal-transpiler/`** - -1. **`LalToJavaTranspiler.java`** (~950 lines) - - Same AST approach as MAL but statement-based emission - - Core methods: - - `transpile(String className, String dslText)` -> generates Java source for `LalExpression` - - `emitStatement(Statement node, String receiver, BindingContext ctx)` -> statement emitter - - `visitConditionExpr(Expression node)` -> boolean expression emitter - - `emitPropertyAccess(PropertyExpression node)` -> `getAt()` with null safety - - SHA-256 deduplication: identical DSL content shares one class - - Helper methods in generated class: `getAt()`, `toLong()`, `toInt()`, `toBoolean()`, `isTruthy()`, `isNonEmptyString()` - -2. **Maven integration**: same `exec-maven-plugin` approach - - Writes `META-INF/lal-expressions.txt` (SHA-256 hash -> FQCN) - -### Phase 4: Runtime Loading (v2 Modules) - -**New module: `oap-server/analyzer/mal-lal-v2/meter-analyzer-v2/`** - -1. **Modified `DSL.java`** (MAL runtime): - ```java - public static Expression parse(String metricName, String expression) { - Map<String, String> manifest = loadManifest("META-INF/mal-expressions.txt"); - String className = manifest.get(metricName); - MalExpression malExpr = (MalExpression) Class.forName(className) - .getDeclaredConstructor().newInstance(); - return new Expression(metricName, expression, malExpr); - } - ``` - -2. **Modified `Expression.java`** (MAL runtime): - - Wraps `MalExpression` instead of `DelegatingScript` - - `run()` calls `malExpression.run(sampleFamilies)` directly - - No `ExpandoMetaClass`, no `ExpressionDelegate`, no `ThreadLocal<Map>` - -3. **Modified `FilterExpression.java`** (MAL runtime): - - Loads `MalFilter` from `META-INF/mal-filter-expressions.properties` - - `filter()` calls `malFilter::test` via `SampleFamily.filter(SampleFilter)` - -**New module: `oap-server/analyzer/mal-lal-v2/log-analyzer-v2/`** - -4. **Modified `DSL.java`** (LAL runtime): - - Computes SHA-256 of DSL text, loads class from `META-INF/lal-expressions.txt` - - `evaluate()` calls `lalExpression.execute(filterSpec, binding)` directly - - No `GroovyShell`, no `LALDelegatingScript` - -### Phase 5: Hierarchy v1/v2 Module Split - -**Step 5a: Refactor `server-core` to remove Groovy** - -**File to modify:** `oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/config/HierarchyDefinitionService.java` - -1. Change `MatchingRule.closure` from `Closure<Boolean>` to `BiFunction<Service, Service, Boolean> matcher` -2. Add constructor that accepts the `BiFunction` matcher directly -3. Replace `getClosure()` with `match(Service upper, Service lower)` method -4. Change `init()` to accept a rule registry (`Map<String, BiFunction<Service, Service, Boolean>>`) from outside instead of calling `GroovyShell.evaluate()` internally -5. Remove all `groovy.lang.*` imports - -**File to modify:** `oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/hierarchy/HierarchyService.java` - -6. Change `matchingRule.getClosure().call(service, comparedService)` (lines 201-203, 220-222) to `matchingRule.match(service, comparedService)` - -**Step 5b: Create hierarchy-v1 module** - -**New module:** `oap-server/analyzer/hierarchy-v1/` - -1. `GroovyHierarchyRuleProvider.java`: wraps original `GroovyShell.evaluate()` logic -2. Takes `Map<String, String>` (rule name -> Groovy expression) from YAML -3. Returns `Map<String, BiFunction<Service, Service, Boolean>>` by evaluating closures -4. Depends on Groovy -- NOT included in runtime, only used by checker - -**Step 5c: Create hierarchy-v2 module** - -**New module:** `oap-server/analyzer/hierarchy-v2/` - -1. `JavaHierarchyRuleProvider.java`: static `RULE_REGISTRY` with 4 Java lambdas -2. Takes `Map<String, String>` (rule name -> Groovy expression) from YAML (expression ignored, only name used for registry lookup) -3. Returns `Map<String, BiFunction<Service, Service, Boolean>>` from the registry -4. Fails fast with `IllegalArgumentException` for unknown rule names -5. Zero Groovy dependency - -### Phase 6: Comparison Test Suites - -**New module: `oap-server/analyzer/mal-lal-v1-v2-checker/`** - -1. **MAL comparison tests** (73 test classes): - - Base class `MALScriptComparisonBase` runs dual-path comparison: - - Path A: Fresh Groovy compilation with upstream `CompilerConfiguration` - - Path B: Load transpiled `MalExpression` from manifest - - Both receive identical `Map<String, SampleFamily>` input - - Compare: `ExpressionParsingContext` (scope, function, datatype, downsampling), `SampleFamily` result (values, labels, entity descriptions) - - JUnit 5 `@TestFactory` generates `DynamicTest` per metric rule - - Test data must be non-trivial to prevent vacuous agreement (both returning empty/null) - -2. **LAL comparison tests** (5 test classes): - - Base class `LALScriptComparisonBase` runs dual-path comparison: - - Path A: Groovy with `@CompileStatic` + `LALPrecompiledExtension` - - Path B: Load `LalExpression` from manifest via SHA-256 - - Compare: `shouldAbort()`, `shouldSave()`, LogData.Builder state, metrics container, `databaseSlowStatement`, `sampledTraceBuilder` - -3. **Test statistics**: 1,281 MAL assertions + 19 LAL assertions = 1,300 total - -**New module: `oap-server/analyzer/hierarchy-v1-v2-checker/`** - -4. **Hierarchy comparison tests**: - - Load rule expressions from `hierarchy-definition.yml` - - For each of the 4 rules: - - Path A: `GroovyHierarchyRuleProvider.buildRules()` (v1, Groovy closures) - - Path B: `JavaHierarchyRuleProvider.buildRules()` (v2, Java lambdas) - - Construct test `Service` pairs covering matching and non-matching cases: - - `name`: exact match `("svc", "svc")` -> true, `("svc", "other")` -> false - - `short-name`: shortName match/mismatch - - `lower-short-name-remove-ns`: `"svc"` vs `"svc.namespace"` -> true, no dot -> false, empty -> false - - `lower-short-name-with-fqdn`: `"db:3306"` vs `"db.svc.cluster.local"` -> true, no colon -> false, wrong suffix -> false - - Assert `v1.match(u, l) == v2.match(u, l)` for all test pairs - -### Phase 7: Cleanup and Dependency Removal - -1. **Move v1 code to `mal-lal-v1/` and `hierarchy-v1/`** (or mark as `<scope>test</scope>`) -2. **Remove Groovy from runtime classpath**: `groovy-5.0.3.jar` (~7 MB) becomes test-only -3. **Remove from `server-starter` dependencies**: replace v1 with v2 module references for MAL, LAL, and hierarchy -4. **Remove `NumberClosure.java`**: no longer needed without `ExpandoMetaClass` -5. **Remove `ExpressionDelegate.propertyMissing()`**: replaced by `samples.getOrDefault()` -6. **Remove Groovy closure overloads from `SampleFamily`** (after v1 is fully deprecated) -7. **Remove `LALDelegatingScript.java`**: replaced by `LalExpression` interface -8. **Verify `server-core` has zero Groovy imports**: `HierarchyDefinitionService` and `HierarchyService` now use `BiFunction` only - -### Phase 8: Replace v2 Manifest Loading with Real Compilers (ANTLR4 + Javassist) - -Phase 7 completed: v2 modules are standalone (zero Groovy), v1 depends on v2. Currently, v2 loads transpiled classes via manifest files (`META-INF/mal-expressions.txt`, `META-INF/lal-expressions.txt`) that were pre-compiled at build time. This prevents on-demand config changes since MAL/LAL/hierarchy configs are in the final package and users may want to modify them. - -The goal is to replace this manifest-based approach with **real compilers** following the OAL pattern: ANTLR4 grammar -> parser -> model -> Javassist class generation -> listener notification. This enables runtime compilation when configs change. - -#### Module Renaming: Drop `-v2` Suffix - -Since v1 modules move to `test/script-compiler/`, the v2 modules become the primary ones and lose the `-v2` suffix: -- `meter-analyzer-v2` -> `meter-analyzer` (package stays `o.a.s.oap.meter.analyzer`) -- `log-analyzer-v2` -> `log-analyzer` (package stays `o.a.s.oap.log.analyzer`) -- `hierarchy-v2` -> `hierarchy` (no v1 name conflict since v1 moves out) -- `.v2.` sub-packages (`dsl.v2.DSL`, `dsl.v2.Binding`, etc.) merge back into parent packages (`dsl.DSL`, `dsl.Binding`) - -#### Target Module Structure - -``` -oap-server/ - mal-grammar/ NEW — ANTLR4 grammar for MAL expressions - lal-grammar/ NEW — ANTLR4 grammar for LAL scripts - hierarchy-rule-grammar/ NEW — ANTLR4 grammar for hierarchy matching rules - -oap-server/analyzer/ - agent-analyzer/ (stays) - event-analyzer/ (stays) - meter-analyzer/ (renamed from meter-analyzer-v2, runtime MAL, calls mal-compiler) - log-analyzer/ (renamed from log-analyzer-v2, runtime LAL, calls lal-compiler) - hierarchy/ (renamed from hierarchy-v2, calls hierarchy-rule-compiler) - mal-compiler/ NEW — MAL expression compiler engine - lal-compiler/ NEW — LAL script compiler engine - hierarchy-rule-compiler/ NEW — hierarchy rule compiler engine - -test/script-compiler/ NEW — aggregator for v1/transpiler/checker (not in dist) - mal-groovy/ <- meter-analyzer v1 (Groovy) - lal-groovy/ <- log-analyzer v1 (Groovy) - hierarchy-groovy/ <- hierarchy-v1 (Groovy) - mal-transpiler/ <- mal-transpiler - lal-transpiler/ <- lal-transpiler - mal-lal-v1-v2-checker/ <- mal-lal-v1-v2-checker - hierarchy-v1-v2-checker/ <- hierarchy-v1-v2-checker -``` - -#### Generated Class Grouping by Config File Name - -MAL metrics come from YAML config files (e.g., `oap.yaml`, `spring-micrometer.yaml`). Each file contains multiple `metricsRules`. The compiler groups generated classes by source file name. - -- **MAL Compiler API**: `MALCompilerEngine.compile(configFileName, MetricRuleConfig)` -> grouped by file -- **Generated class naming**: `rt.<configFile>.MalExpr_<metricName>`, e.g., `rt.oap.MalExpr_instance_jvm_cpu` -- **LAL Compiler API**: `LALCompilerEngine.compile(configFileName, List<LALConfig>)` -> grouped by file -- **Generated class naming**: `rt.<configFile>.LalExpr_<ruleName>` - -#### Eliminate `ExpressionParsingContext` ThreadLocal (MAL only) - -The current MAL `run()` method serves dual purposes controlled by a ThreadLocal: -1. **Parse phase** (startup): `ExpressionParsingContext` ThreadLocal is set, `run()` is called with an empty map to discover which metric names the expression references -2. **Runtime phase** (every ingestion cycle): ThreadLocal is not set, `run()` computes the actual result - -This is eliminated by extracting metadata statically. The `MalExpression` interface gains a `metadata()` method: - -```java -public interface MalExpression { - /** Pure computation. No side effects. */ - SampleFamily run(Map<String, SampleFamily> samples); - - /** Compile-time metadata -- sample names, scope, downsampling, etc. */ - ExpressionMetadata metadata(); -} -``` - -The ANTLR4 compiler extracts all metadata from the parse tree at compile time: - -| Metadata | Extracted from | -|--|--| -| `sampleNames` | Bare identifiers (metric references) | -| `scopeType` | Terminal method: `.service()`, `.instance()`, `.endpoint()` | -| `downsampling` | Aggregation arg: `AVG`, `SUM`, `MAX`, etc. | -| `percentiles` | `.percentile()` call arguments | -| `isHistogram` | Presence of `.histogram()` in chain | - -Generated class emits metadata as static fields: - -```java -public class MalExpr_instance_jvm_cpu implements MalExpression { - private static final ExpressionMetadata METADATA = new ExpressionMetadata( - List.of("instance_jvm_cpu"), // sampleNames - ScopeType.SERVICE_INSTANCE, // from .service()/instance() call - DownsamplingType.AVG // from aggregation - ); - - @Override - public ExpressionMetadata metadata() { return METADATA; } - - @Override - public SampleFamily run(Map<String, SampleFamily> samples) { - return ((SampleFamily) samples.getOrDefault("instance_jvm_cpu", SampleFamily.EMPTY)) - .sum(List.of("service", "instance")); - } -} -``` - -Result: `run()` is pure computation, `metadata()` is static facts, `ExpressionParsingContext` and its ThreadLocal are deleted. No dry run with empty map at startup. - -LAL and hierarchy do **not** have this problem -- LAL passes `Binding` explicitly as a parameter, hierarchy rules are stateless lambdas. - -#### Implementation Sub-Phases - -- 8.1: Rename modules (drop -v2 suffix, flatten .v2. sub-packages) -- 8.2: Grammar modules (ANTLR4 .g4 files) -- 8.3: Compiler model + parser (no code gen) -- 8.4: Javassist code generation (including static `ExpressionMetadata` on generated MAL classes) -- 8.5: Engine integration (wire compilers into renamed modules, delete `ExpressionParsingContext`) -- 8.6: Move v1/transpiler/checker to test/script-compiler/ -- 8.7: Cleanup (remove manifests, verify zero Groovy) - ---- - -## 6. What Gets Removed from Runtime - -| Component | Current | After | -|-----------|---------|-------| -| `GroovyShell.parse()` in MAL `DSL.java` | 1,250+ calls at boot | `Class.forName()` from manifest | -| `GroovyShell.evaluate()` in MAL `FilterExpression.java` | 29 filter compilations | `Class.forName()` from manifest | -| `GroovyShell.parse()` in LAL `DSL.java` | 10 script compilations | `Class.forName()` from manifest | -| `GroovyShell.evaluate()` in `HierarchyDefinitionService` | 4 rule compilations | `hierarchy-v2` Java lambda registry | -| `Closure<Boolean>` in `MatchingRule` | Groovy closure in `server-core` | `BiFunction<Service, Service, Boolean>` (Groovy-free `server-core`) | -| `ExpandoMetaClass` registration in `Expression.empower()` | Runtime metaclass on `Number` | Direct `multiply()`/`div()` method calls | -| `ExpressionDelegate.propertyMissing()` | Dynamic property dispatch | `samples.getOrDefault()` | -| `groovy.lang.Closure` in `SampleFamily` | 5 method signatures | Java functional interfaces | -| `groovy-5.0.3.jar` runtime dependency | ~7 MB on classpath | Removed (build-time only) | - ---- - -## 7. Transpiler Technical Details - -### 7.1 AST Parsing Strategy - -Both transpilers use Groovy's `CompilationUnit` at `Phases.CONVERSION`: - -```java -CompilationUnit cu = new CompilationUnit(); -cu.addSource("expression", new StringReaderSource( - new StringReader(groovyCode), cu.getConfiguration())); -cu.compile(Phases.CONVERSION); // Parse + AST transform, no codegen -ModuleNode ast = cu.getAST(); -``` - -This extracts the complete syntax tree without: -- Generating Groovy bytecode -- Resolving classes on classpath -- Activating MOP or MetaClass - -The Groovy dependency is therefore **build-time only**. - -### 7.2 MAL Arithmetic Operand Swap - -The transpiler must replicate the exact behavior of upstream's `ExpandoMetaClass` on `Number`. When a `Number` appears on the left side of an operator with a `SampleFamily` on the right, the operands must be handled carefully: - -``` -N + SF -> SF.plus(N) // commutative, swap operands -N - SF -> SF.minus(N).negative() // non-commutative: (N - SF) = -(SF - N) -N * SF -> SF.multiply(N) // commutative, swap operands -N / SF -> SF.newValue(v -> N / v) // non-commutative: per-sample (N / sample_value) -``` - -The transpiler detects `Number` vs `SampleFamily` operand types by tracking whether a sub-expression references sample names (metric properties) or is a numeric literal/constant. - -### 7.3 Batch Compilation - -Generated Java sources are compiled in a single `javac` invocation via `javax.tools.JavaCompiler`: - -```java -JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); -List<JavaFileObject> sources = /* all generated .java files */; -compiler.getTask(null, fileManager, diagnostics, options, null, sources).call(); -``` - -This avoids 1,250+ individual `javac` invocations and provides full cross-file type checking. - -### 7.4 Manifest Format - -**`META-INF/mal-expressions.txt`**: -``` -metric_name_1=org.apache.skywalking.oap.meter.analyzer.dsl.generated.MalExpr_metric_name_1 -metric_name_2=org.apache.skywalking.oap.meter.analyzer.dsl.generated.MalExpr_metric_name_2 -... -``` - -**`META-INF/mal-filter-expressions.properties`**: -``` -{ tags -> tags.job_name == 'mysql-monitoring' }=org.apache.skywalking.oap.meter.analyzer.dsl.generated.MalFilter_0a1b2c3d -... -``` - -**`META-INF/lal-expressions.txt`**: -``` -sha256_hash_1=org.apache.skywalking.oap.log.analyzer.dsl.generated.LalExpr_sha256_hash_1 -sha256_hash_2=org.apache.skywalking.oap.log.analyzer.dsl.generated.LalExpr_sha256_hash_2 -... -``` - ---- - -## 8. Worked Examples - -### 8.1 MAL Expression: K8s Node CPU Capacity - -**Groovy (upstream):** -```groovy -kube_node_status_capacity.tagEqual('resource', 'cpu') - .sum(['node']) - .tag({tags -> tags.node_name = tags.node; tags.remove("node")}) - .service(['node_name'], Layer.K8S) -``` - -**Transpiled Java:** -```java -public class MalExpr_k8s_node_cpu implements MalExpression { - @Override - public SampleFamily run(Map<String, SampleFamily> samples) { - return samples.getOrDefault("kube_node_status_capacity", SampleFamily.EMPTY) - .tagEqual("resource", "cpu") - .sum(List.of("node")) - .tag(tags -> { - tags.put("node_name", tags.get("node")); - tags.remove("node"); - return tags; - }) - .service(List.of("node_name"), Layer.K8S); - } -} -``` - -### 8.2 MAL Expression: Arithmetic with Number on Left - -**Groovy (upstream):** -```groovy -100 - container_cpu_usage / container_resource_limit_cpu * 100 -``` - -**Transpiled Java:** -```java -public class MalExpr_cpu_percent implements MalExpression { - @Override - public SampleFamily run(Map<String, SampleFamily> samples) { - return samples.getOrDefault("container_cpu_usage", SampleFamily.EMPTY) - .div(samples.getOrDefault("container_resource_limit_cpu", SampleFamily.EMPTY)) - .multiply(100) - .minus(100) - .negative(); - } -} -``` - -### 8.3 MAL Filter Expression - -**Groovy (upstream):** -```groovy -{ tags -> tags.job_name == 'mysql-monitoring' } -``` - -**Transpiled Java:** -```java -public class MalFilter_mysql implements MalFilter { - @Override - public boolean test(Map<String, String> tags) { - return "mysql-monitoring".equals(tags.get("job_name")); - } -} -``` - -### 8.4 LAL Expression: MySQL Slow SQL - -**Groovy (upstream):** -```groovy -filter { - json {} - extractor { - layer parsed.layer as String - service parsed.service as String - timestamp parsed.time as String - if (tag("LOG_KIND") == "SLOW_SQL") { - slowSql { - id parsed.id as String - statement parsed.statement as String - latency parsed.query_time as Long - } - } - } - sink {} -} -``` - -**Transpiled Java:** -```java -public class LalExpr_mysql_slowsql implements LalExpression { - - @Override - public void execute(FilterSpec filterSpec, Binding binding) { - filterSpec.json(); - filterSpec.extractor(ext -> { - ext.layer(String.valueOf(getAt(binding.parsed(), "layer"))); - ext.service(String.valueOf(getAt(binding.parsed(), "service"))); - ext.timestamp(String.valueOf(getAt(binding.parsed(), "time"))); - if ("SLOW_SQL".equals(ext.tag("LOG_KIND"))) { - ext.slowSql(ss -> { - ss.id(String.valueOf(getAt(binding.parsed(), "id"))); - ss.statement(String.valueOf(getAt(binding.parsed(), "statement"))); - ss.latency(toLong(getAt(binding.parsed(), "query_time"))); - }); - } - }); - filterSpec.sink(s -> {}); - } - - private static Object getAt(Object obj, String key) { - if (obj instanceof Binding.Parsed) return ((Binding.Parsed) obj).getAt(key); - if (obj instanceof Map) return ((Map<?, ?>) obj).get(key); - return null; - } - - private static long toLong(Object val) { - if (val instanceof Number) return ((Number) val).longValue(); - if (val instanceof String) return Long.parseLong((String) val); - return 0L; - } -} -``` - -### 8.5 Hierarchy Rule: lower-short-name-remove-ns - -**Groovy (upstream, in hierarchy-definition.yml):** -```groovy -{ (u, l) -> { - if(l.shortName.lastIndexOf('.') > 0) - return u.shortName == l.shortName.substring(0, l.shortName.lastIndexOf('.')); - return false; -} } -``` - -**Java replacement (in HierarchyDefinitionService.java):** -```java -RULE_REGISTRY.put("lower-short-name-remove-ns", (u, l) -> { - String sn = l.getShortName(); - int dot = sn.lastIndexOf('.'); - return dot > 0 && Objects.equals(u.getShortName(), sn.substring(0, dot)); -}); -``` - ---- - -## 9. Verification Strategy - -### 9.1 Dual-Path Comparison Testing - -Every generated Java class is validated against the original Groovy behavior in CI: - -``` -For each MAL YAML file: - For each metric rule: - 1. Compile expression with Groovy (v1 path) - 2. Load transpiled MalExpression (v2 path) - 3. Construct realistic sample data (non-trivial to prevent vacuous agreement) - 4. Run both paths with identical input - 5. Assert identical output: - - ExpressionParsingContext (scope, function, datatype, samples, downsampling) - - SampleFamily result (values, labels, entity descriptions) -``` - -### 9.2 Staleness Detection - -Properties files record SHA-256 hashes of upstream classes that have same-FQCN replacements. If upstream changes a class, the staleness test fails, forcing review of the replacement. - -### 9.3 Automatic Coverage - -New MAL/LAL YAML rules added to `server-starter/src/main/resources/` are automatically covered by the transpiler and comparison tests -- if the transpiler produces different results from Groovy, the build fails. - ---- - -## 10. Statistics - -| Metric | Count | -|--------|-------| -| MAL YAML files processed | 71 | -| MAL metric expressions transpiled | 1,254 | -| MAL filter expressions transpiled | 29 | -| LAL YAML files processed | 8 | -| LAL rules transpiled | 10 (6 unique after SHA-256 dedup) | -| Hierarchy rules replaced | 4 | -| Total generated Java classes | ~1,289 | -| Comparison test assertions | 1,300+ (MAL: 1,281, LAL: 19, hierarchy: 4 rules x multiple service pairs) | -| Lines of transpiler code (MAL) | ~1,230 | -| Lines of transpiler code (LAL) | ~950 | -| Runtime JAR removed | groovy-5.0.3.jar (~7 MB) | - ---- - -## 11. Risk Assessment - -| Risk | Mitigation | -|------|-----------| -| Transpiler misses an AST pattern | 1,300 dual-path comparison tests catch any divergence | -| New MAL/LAL expression uses unsupported Groovy syntax | Transpiler throws clear error at build time; new pattern must be added | -| Upstream SampleFamily/Spec changes break replacement | Staleness tests detect SHA-256 changes | -| Performance regression | Eliminated dynamic dispatch should only improve performance; benchmark with `MetricConvert` pipeline | -| Custom user MAL/LAL scripts | Users who extend default rules with custom Groovy scripts must follow the same syntax subset supported by the transpiler | - ---- - -## 12. Migration Timeline - -1. **Phase 1**: Add interfaces and functional interface overloads to existing `meter-analyzer` and `log-analyzer` (non-breaking, additive changes) -2. **Phase 2-3**: Implement MAL and LAL transpilers in new `mal-lal-v2/` modules -3. **Phase 4**: Implement v2 runtime loaders (modified `DSL.java`, `Expression.java`, `FilterExpression.java`) -4. **Phase 5**: Hierarchy v1/v2 module split -- refactor `server-core` to remove Groovy, create `hierarchy-v1/` (Groovy, checker-only) and `hierarchy-v2/` (Java lambdas, runtime) -5. **Phase 6**: Build comparison test suites -- `mal-lal-v1-v2-checker/` AND `hierarchy-v1-v2-checker/` -6. **Phase 7**: Switch `server-starter` from v1 to v2 for all three subsystems (MAL, LAL, hierarchy), remove Groovy from runtime classpath -7. **Phase 8**: Replace v2 manifest-based class loading with real ANTLR4 + Javassist compilers following the OAL pattern, enabling runtime compilation when configs change. Rename modules (drop `-v2` suffix), move v1/transpiler/checker to `test/script-compiler/`. diff --git a/docs/en/concepts-and-designs/lal.md b/docs/en/concepts-and-designs/lal.md index f843871cf8..4dff550d3d 100644 --- a/docs/en/concepts-and-designs/lal.md +++ b/docs/en/concepts-and-designs/lal.md @@ -31,7 +31,7 @@ are cases where you may want the filter chain to stop earlier when specified con the remaining filter chain from where it's declared, and all the remaining components won't be executed at all. `abort` function serves as a fast-fail mechanism in LAL. -```groovy +``` filter { if (log.service == "TestingService") { // Don't waste resources on TestingServices abort {} // all remaining components won't be executed at all @@ -67,7 +67,7 @@ We can add tags like following: ] ``` And we can use this method to get the value of the tag key `TEST_KEY`. -```groovy +``` filter { if (tag("TEST_KEY") == "TEST_VALUE") { ... @@ -95,7 +95,7 @@ See examples below. #### `json` -```groovy +``` filter { json { abortOnFailure true // this is optional because it's default behaviour @@ -105,7 +105,7 @@ filter { #### `yaml` -```groovy +``` filter { yaml { abortOnFailure true // this is optional because it's default behaviour @@ -123,7 +123,7 @@ For unstructured logs, there are some `text` parsers for use. all the captured groups can be used later in the extractors or sinks. `regexp` returns a `boolean` indicating whether the log matches the pattern or not. -```groovy +``` filter { text { abortOnFailure true // this is optional because it's default behaviour @@ -181,7 +181,7 @@ dropped) and is used to associate with traces / metrics. not dropped) and is used to associate with traces / metrics. The parameter of `timestamp` can be a millisecond: -```groovy +``` filter { // ... parser @@ -191,7 +191,7 @@ filter { } ``` or a datetime string with a specified pattern: -```groovy +``` filter { // ... parser @@ -210,9 +210,7 @@ not dropped) and is used to associate with service. `tag` extracts the tags from the `parsed` result, and set them into the `LogData`. The form of this extractor should look something like this: `tag key1: value, key2: value2`. You may use the properties of `parsed` as both keys and values. -```groovy -import javax.swing.text.LayeredHighlighter - +``` filter { // ... parser @@ -242,7 +240,7 @@ log-analyzer: Examples are as follows: -```groovy +``` filter { // ... extractor { @@ -338,7 +336,7 @@ dropped) and is used to associate with TopNDatabaseStatement. An example of LAL to distinguish slow logs: -```groovy +``` filter { json{ } @@ -386,7 +384,7 @@ An example of JSON sent to OAP is as following: ``` Examples are as follows: -```groovy +``` filter { json { } @@ -447,7 +445,7 @@ final sampling result. See examples in [Enforcer](#enforcer). Examples 1, `rateLimit`: -```groovy +``` filter { // ... parser @@ -469,7 +467,7 @@ filter { Examples 2, `possibility`: -```groovy +``` filter { // ... parser @@ -492,7 +490,7 @@ filter { Dropper is a special sink, meaning that all logs are dropped without any exception. This is useful when you want to drop debugging logs. -```groovy +``` filter { // ... parser @@ -510,7 +508,7 @@ filter { Or if you have multiple filters, some of which are for extracting metrics, only one of them has to be persisted. -```groovy +``` filter { // filter A: this is for persistence // ... parser @@ -539,7 +537,7 @@ Enforcer is another special sink that forcibly samples the log. A typical use ca configured a sampler and want to save some logs forcibly, such as to save error logs even if the sampling mechanism has been configured. -```groovy +``` filter { // ... parser diff --git a/docs/en/concepts-and-designs/service-hierarchy-configuration.md b/docs/en/concepts-and-designs/service-hierarchy-configuration.md index 6aa0d40d56..7aac0e99fa 100644 --- a/docs/en/concepts-and-designs/service-hierarchy-configuration.md +++ b/docs/en/concepts-and-designs/service-hierarchy-configuration.md @@ -69,7 +69,7 @@ layer-levels: ### Auto Matching Rules - The auto matching rules are defined in the `auto-matching-rules` section. -- Use Groovy script to define the matching rules, the input parameters are the upper service(u) and the lower service(l) and the return value is a boolean, +- The matching rules are expressions where the input parameters are the upper service(u) and the lower service(l) and the return value is a boolean, which are used to match the relation between the upper service(u) and the lower service(l) on the different layers. - The default matching rules required the service name configured as SkyWalking default and follow the [Showcase](https://github.com/apache/skywalking-showcase). If you customized the service name in any layer, you should customize the related matching rules according your service name rules. diff --git a/docs/en/concepts-and-designs/service-hierarchy.md b/docs/en/concepts-and-designs/service-hierarchy.md index 8cd18bccda..5f3c5144fc 100644 --- a/docs/en/concepts-and-designs/service-hierarchy.md +++ b/docs/en/concepts-and-designs/service-hierarchy.md @@ -45,7 +45,7 @@ If you want to customize it according to your own needs, please refer to [Servic #### GENERAL On K8S_SERVICE - Rule name: `lower-short-name-remove-ns` -- Groovy script: `{ (u, l) -> u.shortName == l.shortName.substring(0, l.shortName.lastIndexOf('.')) }` +- Matching expression: `{ (u, l) -> u.shortName == l.shortName.substring(0, l.shortName.lastIndexOf('.')) }` - Description: GENERAL.service.shortName == K8S_SERVICE.service.shortName without namespace - Matched Example: - GENERAL.service.name: `agent::songs` @@ -53,7 +53,7 @@ If you want to customize it according to your own needs, please refer to [Servic #### GENERAL On APISIX - Rule name: `lower-short-name-remove-ns` -- Groovy script: `{ (u, l) -> u.shortName == l.shortName.substring(0, l.shortName.lastIndexOf('.')) }` +- Matching expression: `{ (u, l) -> u.shortName == l.shortName.substring(0, l.shortName.lastIndexOf('.')) }` - Description: GENERAL.service.shortName == APISIX.service.shortName without namespace - Matched Example: - GENERAL.service.name: `agent::frontend` @@ -62,7 +62,7 @@ If you want to customize it according to your own needs, please refer to [Servic #### VIRTUAL_DATABASE On MYSQL - Rule name: `lower-short-name-with-fqdn` -- Groovy script: `{ (u, l) -> u.shortName.substring(0, u.shortName.lastIndexOf(':')) == l.shortName.concat('.svc.cluster.local') }` +- Matching expression: `{ (u, l) -> u.shortName.substring(0, u.shortName.lastIndexOf(':')) == l.shortName.concat('.svc.cluster.local') }` - Description: VIRTUAL_DATABASE.service.shortName remove port == MYSQL.service.shortName with fqdn suffix - Matched Example: - VIRTUAL_DATABASE.service.name: `mysql.skywalking-showcase.svc.cluster.local:3306` @@ -70,7 +70,7 @@ If you want to customize it according to your own needs, please refer to [Servic #### VIRTUAL_DATABASE On POSTGRESQL - Rule name: `lower-short-name-with-fqdn` -- Groovy script: `{ (u, l) -> u.shortName.substring(0, u.shortName.lastIndexOf(':')) == l.shortName.concat('.svc.cluster.local') }` +- Matching expression: `{ (u, l) -> u.shortName.substring(0, u.shortName.lastIndexOf(':')) == l.shortName.concat('.svc.cluster.local') }` - Description: VIRTUAL_DATABASE.service.shortName remove port == POSTGRESQL.service.shortName with fqdn suffix - Matched Example: - VIRTUAL_DATABASE.service.name: `psql.skywalking-showcase.svc.cluster.local:5432` @@ -78,7 +78,7 @@ If you want to customize it according to your own needs, please refer to [Servic #### VIRTUAL_DATABASE On CLICKHOUSE - Rule name: `lower-short-name-with-fqdn` -- Groovy script: `{ (u, l) -> u.shortName.substring(0, u.shortName.lastIndexOf(':')) == l.shortName.concat('.svc.cluster.local') }` +- Matching expression: `{ (u, l) -> u.shortName.substring(0, u.shortName.lastIndexOf(':')) == l.shortName.concat('.svc.cluster.local') }` - Description: VIRTUAL_DATABASE.service.shortName remove port == CLICKHOUSE.service.shortName with fqdn suffix - Matched Example: - VIRTUAL_DATABASE.service.name: `clickhouse.skywalking-showcase.svc.cluster.local:8123` @@ -87,7 +87,7 @@ If you want to customize it according to your own needs, please refer to [Servic #### VIRTUAL_MQ On ROCKETMQ - Rule name: `lower-short-name-with-fqdn` -- Groovy script: `{ (u, l) -> u.shortName.substring(0, u.shortName.lastIndexOf(':')) == l.shortName.concat('.svc.cluster.local') }` +- Matching expression: `{ (u, l) -> u.shortName.substring(0, u.shortName.lastIndexOf(':')) == l.shortName.concat('.svc.cluster.local') }` - Description: VIRTUAL_MQ.service.shortName remove port == ROCKETMQ.service.shortName with fqdn suffix - Matched Example: - VIRTUAL_MQ.service.name: `rocketmq.skywalking-showcase.svc.cluster.local:9876` @@ -95,7 +95,7 @@ If you want to customize it according to your own needs, please refer to [Servic #### VIRTUAL_MQ On RABBITMQ - Rule name: `lower-short-name-with-fqdn` -- Groovy script: `{ (u, l) -> u.shortName.substring(0, u.shortName.lastIndexOf(':')) == l.shortName.concat('.svc.cluster.local') }` +- Matching expression: `{ (u, l) -> u.shortName.substring(0, u.shortName.lastIndexOf(':')) == l.shortName.concat('.svc.cluster.local') }` - Description: VIRTUAL_MQ.service.shortName remove port == RABBITMQ.service.shortName with fqdn suffix - Matched Example: - VIRTUAL_MQ.service.name: `rabbitmq.skywalking-showcase.svc.cluster.local:5672` @@ -103,7 +103,7 @@ If you want to customize it according to your own needs, please refer to [Servic - #### VIRTUAL_MQ On KAFKA - Rule name: `lower-short-name-with-fqdn` -- Groovy script: `{ (u, l) -> u.shortName.substring(0, u.shortName.lastIndexOf(':')) == l.shortName.concat('.svc.cluster.local') }` +- Matching expression: `{ (u, l) -> u.shortName.substring(0, u.shortName.lastIndexOf(':')) == l.shortName.concat('.svc.cluster.local') }` - Description: VIRTUAL_MQ.service.shortName remove port == KAFKA.service.shortName with fqdn suffix - Matched Example: - VIRTUAL_MQ.service.name: `kafka.skywalking-showcase.svc.cluster.local:9092` @@ -111,7 +111,7 @@ If you want to customize it according to your own needs, please refer to [Servic #### VIRTUAL_MQ On PULSAR - Rule name: `lower-short-name-with-fqdn` -- Groovy script: `{ (u, l) -> u.shortName.substring(0, u.shortName.lastIndexOf(':')) == l.shortName.concat('.svc.cluster.local') }` +- Matching expression: `{ (u, l) -> u.shortName.substring(0, u.shortName.lastIndexOf(':')) == l.shortName.concat('.svc.cluster.local') }` - Description: VIRTUAL_MQ.service.shortName remove port == PULSAR.service.shortName with fqdn suffix - Matched Example: - VIRTUAL_MQ.service.name: `pulsar.skywalking-showcase.svc.cluster.local:6650` @@ -119,7 +119,7 @@ If you want to customize it according to your own needs, please refer to [Servic #### MESH On MESH_DP - Rule name: `name` -- Groovy script: `{ (u, l) -> u.name == l.name }` +- Matching expression: `{ (u, l) -> u.name == l.name }` - Description: MESH.service.name == MESH_DP.service.name - Matched Example: - MESH.service.name: `mesh-svr::songs.sample-services` @@ -127,7 +127,7 @@ If you want to customize it according to your own needs, please refer to [Servic #### MESH On K8S_SERVICE - Rule name: `short-name` -- Groovy script: `{ (u, l) -> u.shortName == l.shortName }` +- Matching expression: `{ (u, l) -> u.shortName == l.shortName }` - Description: MESH.service.shortName == K8S_SERVICE.service.shortName - Matched Example: - MESH.service.name: `mesh-svr::songs.sample-services` @@ -135,7 +135,7 @@ If you want to customize it according to your own needs, please refer to [Servic #### MESH_DP On K8S_SERVICE - Rule name: `short-name` -- Groovy script: `{ (u, l) -> u.shortName == l.shortName }` +- Matching expression: `{ (u, l) -> u.shortName == l.shortName }` - Description: MESH_DP.service.shortName == K8S_SERVICE.service.shortName - Matched Example: - MESH_DP.service.name: `mesh-svr::songs.sample-services` @@ -143,7 +143,7 @@ If you want to customize it according to your own needs, please refer to [Servic #### MYSQL On K8S_SERVICE - Rule name: `short-name` -- Groovy script: `{ (u, l) -> u.shortName == l.shortName }` +- Matching expression: `{ (u, l) -> u.shortName == l.shortName }` - Description: MYSQL.service.shortName == K8S_SERVICE.service.shortName - Matched Example: - MYSQL.service.name: `mysql::mysql.skywalking-showcase` @@ -151,7 +151,7 @@ If you want to customize it according to your own needs, please refer to [Servic #### POSTGRESQL On K8S_SERVICE - Rule name: `short-name` -- Groovy script: `{ (u, l) -> u.shortName == l.shortName }` +- Matching expression: `{ (u, l) -> u.shortName == l.shortName }` - Description: POSTGRESQL.service.shortName == K8S_SERVICE.service.shortName - Matched Example: - POSTGRESQL.service.name: `postgresql::psql.skywalking-showcase` @@ -159,7 +159,7 @@ If you want to customize it according to your own needs, please refer to [Servic #### CLICKHOUSE On K8S_SERVICE - Rule name: `short-name` -- Groovy script: `{ (u, l) -> u.shortName == l.shortName }` +- Matching expression: `{ (u, l) -> u.shortName == l.shortName }` - Description: CLICKHOUSE.service.shortName == K8S_SERVICE.service.shortName - Matched Example: - CLICKHOUSE.service.name: `clickhouse::clickhouse.skywalking-showcase` @@ -167,7 +167,7 @@ If you want to customize it according to your own needs, please refer to [Servic #### NGINX On K8S_SERVICE - Rule name: `short-name` -- Groovy script: `{ (u, l) -> u.shortName == l.shortName }` +- Matching expression: `{ (u, l) -> u.shortName == l.shortName }` - Description: NGINX.service.shortName == K8S_SERVICE.service.shortName - Matched Example: - NGINX.service.name: `nginx::nginx.skywalking-showcase` @@ -175,7 +175,7 @@ If you want to customize it according to your own needs, please refer to [Servic #### APISIX On K8S_SERVICE - Rule name: `short-name` -- Groovy script: `{ (u, l) -> u.shortName == l.shortName }` +- Matching expression: `{ (u, l) -> u.shortName == l.shortName }` - Description: APISIX.service.shortName == K8S_SERVICE.service.shortName - Matched Example: - APISIX.service.name: `APISIX::frontend.sample-services` @@ -183,7 +183,7 @@ If you want to customize it according to your own needs, please refer to [Servic #### ROCKETMQ On K8S_SERVICE - Rule name: `short-name` -- Groovy script: `{ (u, l) -> u.shortName == l.shortName }` +- Matching expression: `{ (u, l) -> u.shortName == l.shortName }` - Description: ROCKETMQ.service.shortName == K8S_SERVICE.service.shortName - Matched Example: - ROCKETMQ.service.name: `rocketmq::rocketmq.skywalking-showcase` @@ -191,7 +191,7 @@ If you want to customize it according to your own needs, please refer to [Servic #### RABBITMQ On K8S_SERVICE - Rule name: `short-name` -- Groovy script: `{ (u, l) -> u.shortName == l.shortName }` +- Matching expression: `{ (u, l) -> u.shortName == l.shortName }` - Description: RABBITMQ.service.shortName == K8S_SERVICE.service.shortName - Matched Example: - RABBITMQ.service.name: `rabbitmq::rabbitmq.skywalking-showcase` @@ -199,7 +199,7 @@ If you want to customize it according to your own needs, please refer to [Servic #### KAFKA On K8S_SERVICE - Rule name: `short-name` -- Groovy script: `{ (u, l) -> u.shortName == l.shortName }` +- Matching expression: `{ (u, l) -> u.shortName == l.shortName }` - Description: KAFKA.service.shortName == K8S_SERVICE.service.shortName - Matched Example: - KAFKA.service.name: `kafka::kafka.skywalking-showcase` @@ -207,7 +207,7 @@ If you want to customize it according to your own needs, please refer to [Servic #### PULSAR On K8S_SERVICE - Rule name: `short-name` -- Groovy script: `{ (u, l) -> u.shortName == l.shortName }` +- Matching expression: `{ (u, l) -> u.shortName == l.shortName }` - Description: PULSAR.service.shortName == K8S_SERVICE.service.shortName - Matched Example: - PULSAR.service.name: `pulsar::pulsar.skywalking-showcase` @@ -215,7 +215,7 @@ If you want to customize it according to your own needs, please refer to [Servic #### SO11Y_OAP On K8S_SERVICE - Rule name: `short-name` -- Groovy script: `{ (u, l) -> u.shortName == l.shortName }` +- Matching expression: `{ (u, l) -> u.shortName == l.shortName }` - Description: SO11Y_OAP.service.shortName == K8S_SERVICE.service.shortName - Matched Example: - SO11Y_OAP.service.name: `demo-oap.skywalking-showcase` @@ -223,7 +223,7 @@ If you want to customize it according to your own needs, please refer to [Servic #### KONG On K8S_SERVICE - Rule name: `short-name` -- Groovy script: `{ (u, l) -> u.shortName == l.shortName }` +- Matching expression: `{ (u, l) -> u.shortName == l.shortName }` - Description: KONG.service.shortName == K8S_SERVICE.service.shortName - Matched Example: - KONG.service.name: `kong::kong.skywalking-showcase` diff --git a/docs/menu.yml b/docs/menu.yml index adb2ee8936..72d08d1e32 100644 --- a/docs/menu.yml +++ b/docs/menu.yml @@ -392,6 +392,8 @@ catalog: path: "/en/concepts-and-designs/ebpf-cpu-profiling" - name: "Diagnose Service Mesh Network Performance with eBPF" path: "/en/academy/diagnose-service-mesh-network-performance-with-ebpf" + - name: "DSL Compiler Design" + path: "/en/academy/dsl-compiler-design" - name: "FAQs" path: "/en/faq/readme" - name: "Contributing Guides" diff --git a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/meter/process/MeterProcessor.java b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/meter/process/MeterProcessor.java index d94afe0cdf..ea0900863a 100644 --- a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/meter/process/MeterProcessor.java +++ b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/meter/process/MeterProcessor.java @@ -53,7 +53,7 @@ public class MeterProcessor { private final MeterProcessService processService; /** - * All of meters has been read. Using it to process groovy script. + * All of meters has been read. Using it to process MAL expressions. */ private final Map<String, List<SampleBuilder>> meters = new HashMap<>();
