【SkyWalking从入门到精通】第43篇:OAL实现原理:从脚本到字节码的魔法之旅

📅 2026/7/13 9:54:45
【SkyWalking从入门到精通】第43篇:OAL实现原理:从脚本到字节码的魔法之旅
上一篇【第42篇】OAL语法全解析声明式指标计算的优雅之舞下一篇【第44篇】自定义OAL指标实战打造你的专属可观测仪表盘一、开篇编译器把想法变成现实的炼金术如果你看过《哈利·波特》一定记得魔杖挥动、咒语念出、魔法发生的瞬间。OAL编译器的运作方式很像这个魔法过程魔杖 Antlr4词法/语法分析器咒语 .oal文件开发者写的脚本魔法 字节码生成把脚本变成JVM可执行的类.oal文件 .class文件 service_resp_time ServiceRespTimeMetrics.class from Service.latency; combine() (聚合逻辑) Builder (序列化) Column注解 (存储映射) Stream注解 (注册到引擎) 图1OAL编译 文本到字节码的转化二、技术栈全景图OAL编译器的技术栈由三层组成 | OAL 编译器技术栈 | | | | [应用层] | | OALEngine.java 编译器入口协调编译流程 | | OALDefine.java 编译器配置源码/输出路径 | | | | [语法层] | | oal-grammar.g4 Antlr4语法定义文件 | | OALLexer.java Antlr4生成的词法分析器 | | OALParser.java Antlr4生成的语法分析器 | | OALVisitor.java 遍历AST的Visitor | | | | [代码层] | | ClassGenerator.java 生成Java类源码 | | DynamicClassLoader.java 加载生成的类到JVM | | AnnotationMeta.java 处理注解元数据 | | | 图2OAL编译器的三层技术栈三、Antlr4词法语法定义OAL的语法是在.g4文件中定义的。以下是核心语法规则的简化版// OAL.g4 - Antlr4语法定义简化版 grammar OAL; // 解析器规则 // 入口规则 script : (metricStatement | disableStatement | importStatement)* EOF ; // 指标定义语句 metricStatement : metricName from source (. filterExpr)? . aggregateFunc ; ; metricName : IDENTIFIER ; source : IDENTIFIER ; aggregateFunc : avg ( sourceAttr ) # avgFunc | sum ( (sourceAttr)? ) # sumFunc | max ( sourceAttr ) # maxFunc | min ( sourceAttr ) # minFunc | count ( ) # countFunc | p99 ( sourceAttr ) # p99Func | p95 ( sourceAttr ) # p95Func | p90 ( sourceAttr ) # p90Func | p75 ( sourceAttr ) # p75Func | p50 ( sourceAttr ) # p50Func | percentile ( sourceAttr (, NUMBER) ) # percentileFunc | histogram ( sourceAttr (, NUMBER) ) # histogramFunc ; filterExpr : ( conditionExpr ) ; conditionExpr : sourceAttr op( | ! | | | | ) valueExpr # compareExpr | conditionExpr conditionExpr # andExpr | conditionExpr || conditionExpr # orExpr | sourceAttr like STRING_LITERAL # likeExpr | sourceAttr not like STRING_LITERAL # notLikeExpr | ( conditionExpr ) # parenExpr ; sourceAttr : IDENTIFIER (. IDENTIFIER)* ; valueExpr : NUMBER | BOOLEAN_LITERAL | STRING_LITERAL ; // 禁用语句 disableStatement : disable ( metricName ) ; ; // 词法规则 IDENTIFIER : [a-zA-Z_][a-zA-Z0-9_]* ; NUMBER : [0-9] (. [0-9])? ; BOOLEAN_LITERAL : true | false ; STRING_LITERAL : .*? | \ .*? \ ; WS : [ \t\r\n] - skip ; LINE_COMMENT : // ~[\r\n]* - skip ; BLOCK_COMMENT : /* .*? */ - skip ;四、编译流水线的五个阶段Stage 1词法分析Lexing输入: service_resp_time from Service.avg(latency); 输出: Token流 [IDENTIFIER:service_resp_time] [OP:] [KEYWORD:from] [IDENTIFIER:Service] [OP:.] [KEYWORD:avg] [OP:(] [IDENTIFIER:latency] [OP:)] [OP:;]// 词法分析调用CharStreaminputCharStreams.fromFileName(service.oal);OALLexerlexernewOALLexer(input);CommonTokenStreamtokensnewCommonTokenStream(lexer);Stage 2语法分析ParsingToken流被组织成一棵抽象语法树ASTMetricStatement / | \ metricName source aggregateFunc / / | \ service_resp_time Service avg sourceAttr | latency 图3AST——抽象语法树的样子// 语法分析调用OALParserparsernewOALParser(tokens);ParseTreetreeparser.script();Stage 3语义分析VisitingVisitor遍历AST构建AnalyzeDefinition中间表示publicclassOALScriptVisitorextendsOALBaseVisitorObject{privateListAnalyzeDefinitiondefinitionsnewArrayList();OverridepublicObjectvisitMetricStatement(OALParser.MetricStatementContextctx){// 1. 获取指标名称StringmetricNamectx.metricName().getText();// 2. 获取数据源类型StringsourceNamectx.source().getText();Class?extendsSourcesourceClassSourceMapper.getSourceClass(sourceName);// 3. 获取聚合函数AggregateFunctionaggregateFuncextractAggregateFunc(ctx.aggregateFunc());// 4. 获取过滤表达式如果有FilterExpressionfilterExprnull;if(ctx.filterExpr()!null){filterExprextractFilterExpr(ctx.filterExpr());}// 5. 构建AnalyzeDefinitionAnalyzeDefinitiondefnewAnalyzeDefinition();def.setMetricName(metricName);def.setSourceClass(sourceClass);def.setAggregateFunction(aggregateFunc);def.setFilterExpression(filterExpr);definitions.add(def);returnnull;}}Stage 4代码生成Code Generation这是最核心的阶段。根据AnalyzeDefinition生成Java源码publicclassMetricsClassGenerator{/** * 生成Metrics类的完整Java源码 */publicStringgenerate(AnalyzeDefinitiondef){StringBuildercodenewStringBuilder();// 包名和导入 code.append(package org.apache.skywalking.oap.server.core.analysis.metrics;\n\n);code.append(import org.apache.skywalking.oap.server.core.analysis.metrics.annotation.*;\n);code.append(import org.apache.skywalking.oap.server.core.storage.annotation.*;\n);code.append(import lombok.*;\n\n);// Stream注解 code.append(Stream(\n);code.append( name \).append(def.getMetricName()).append(\,\n);code.append( scopeId ).append(def.getScopeId()).append(,\n);code.append( builder ).append(def.getClassName()).append(.Builder.class,\n);code.append( processor ).append(def.getClassName()).append(.Processor.class\n);code.append()\n);// Metrics注解 code.append(MetricsExtension(\n);code.append( supportDownSampling true,\n);code.append( supportUpdate false,\n);code.append( timeRelativeID true\n);code.append()\n);// 类定义 code.append(Data\n);code.append(EqualsAndHashCode(callSuper true)\n);code.append(public class ).append(def.getClassName()).append( extends Metrics implements LongValueHolder {\n\n);// 指标字段 code.append( Column(\n);code.append( columnName \value\,\n);code.append( dataType Column.ValueDataType.COMMON_VALUE,\n);code.append( function Function.).append(def.getFunction()).append(\n);code.append( )\n);code.append( private long value;\n\n);if(def.needsTotal()){code.append( Column(columnName \total\)\n);code.append( private long total;\n\n);}if(def.needsSum()){code.append( Column(columnName \sum\)\n);code.append( private long sum;\n\n);}// ID和时间字段 code.append( Override\n);code.append( Column(columnName \entity_id\)\n);code.append( public String id() {\n);code.append( return entityId;\n);code.append( }\n\n);code.append( Column(columnName \time_bucket\)\n);code.append( private long timeBucket;\n\n);// Processor内部类聚合逻辑code.append( public static class Processor {\n\n);// combine方法 - 从Source计算code.append( public void combine(\n);code.append( SourceFrom ).append(def.getSourceClassName()).append( source,\n);code.append( Arg ).append(def.getClassName()).append( metrics,\n);code.append( Context MetricsContext context\n);code.append( ) {\n);// 过滤逻辑if(def.getFilterExpression()!null){code.append( if (!().append(def.getFilterExpression().toJavaCode()).append()) return;\n\n);}// 聚合逻辑switch(def.getFunction()){caseAvg:code.append( metrics.total;\n);code.append( metrics.sum source.).append(def.getFieldName()).append(;\n);code.append( metrics.value metrics.sum / metrics.total;\n);break;caseSum:code.append( metrics.value source.).append(def.getFieldName()).append(;\n);break;caseMax:code.append( metrics.value Math.max(metrics.value, source.).append(def.getFieldName()).append();\n);break;caseMin:code.append( if (metrics.total 0) metrics.value Long.MAX_VALUE;\n);code.append( metrics.value Math.min(metrics.value, source.).append(def.getFieldName()).append();\n);code.append( metrics.total;\n);break;caseCount:code.append( metrics.value;\n);break;}code.append( }\n\n);// L2聚合combinecode.append( public void combine(\n);code.append( SourceFrom ).append(def.getClassName()).append( source,\n);code.append( Arg ).append(def.getClassName()).append( metrics\n);code.append( ) {\n);// ... L2聚合逻辑code.append( }\n\n);code.append( }\n\n);// Builder内部类 code.append( public static class Builder implements StorageBuilder).append(def.getClassName()).append( {\n);code.append( Override\n);code.append( public ).append(def.getClassName()).append( storage2Entity(MapString, Object dbMap) {\n);// ... 反序列化逻辑code.append( }\n\n);code.append( Override\n);code.append( public MapString, Object entity2Storage().append(def.getClassName()).append( metrics) {\n);// ... 序列化逻辑code.append( }\n);code.append( }\n);code.append(}\n);returncode.toString();}}Stage 5类加载Class LoadingpublicclassDynamicClassLoaderextendsClassLoader{/** * 编译生成的Java源码并加载到JVM */publicClass?compileAndLoad(StringclassName,StringsourceCode)throwsException{// 方式1使用javax.tools.JavaCompiler编译JavaCompilercompilerToolProvider.getSystemJavaCompiler();DiagnosticCollectorJavaFileObjectdiagnosticsnewDiagnosticCollector();// 内存中的源文件JavaFileObjectsourceFilenewJavaSourceFromString(className,sourceCode);// 编译JavaCompiler.CompilationTasktaskcompiler.getTask(null,null,diagnostics,null,null,Collections.singletonList(sourceFile));booleansuccesstask.call();if(!success){// 收集编译错误StringBuildererrorsnewStringBuilder();for(Diagnostic?diagnostic:diagnostics.getDiagnostics()){errors.append(diagnostic.toString()).append(\n);}thrownewCompilationException(OAL编译失败: errors);}// 加载编译好的字节码byte[]classBytesgetCompiledBytes(className);returndefineClass(className,classBytes,0,classBytes.length);}}五、生成的Metrics类与存储的自动绑定编译生成的类通过注解与存储层自动绑定// 生成类上的注解决定了存储行为Stream(nameservice_resp_time)// 在ES中对应索引名MetricsExtension(supportDownSamplingtrue,// 支持分钟-小时-天降采样supportUpdatefalse,// 不支持更新只追加timeRelativeIDtrue// ID包含时间信息)// 字段上的注解决定了存储字段Column(columnNamevalue,// ES中的字段名dataTypeColumn.ValueDataType.COMMON_VALUE,// 普通值类型functionFunction.Avg// 聚合函数类型)privatelongvalue;// 这些注解在StorageModule初始化时被解析// IModelInstaller.install() 会// 1. 扫描所有Stream注解的类// 2. 为每个类创建ES索引模板// 3. 设置字段映射关系// 4. 配置TTL策略六、调试OAL编译结果的方法# 方法1开启Debug日志# 在log4j2.xml中添加# Logger nameorg.apache.skywalking.oal levelDEBUG/# 启动OAP后观察日志grepGenerated classlogs/skywalking-oap-server.log# 方法2添加JVM参数导出生成的源码# 在oapService.sh的JAVA_OPTS中添加-Dskywalking.oal.export-sourcetrue -Dskywalking.oal.export-dir/tmp/oal-generated/# 启动后查看生成的Java文件ls/tmp/oal-generated/# 输出# ServiceRespTimeMetrics.java# ServiceCpmMetrics.java# EndpointAvgMetrics.java# ...# 方法3单元测试验证Test voidtestOALCompilation(){OALEngine engineOALEngine.create();String oalScripttest_metric from Service.avg(latency);;ListAnalyzeDefinitiondefsengine.compile(oalScript);assertEquals(1, defs.size());assertEquals(test_metric, defs.get(0).getMetricName());assertEquals(Avg, defs.get(0).getFunction());}七、常见编译错误与修复错误原因修复Unknown source: Xxx数据源类不存在检查类路径确认继承了Source接口Unknown field: xxx in Source Yyy字段名拼写错误检查Source类的字段定义Function xxx not supported使用了不支持的聚合函数检查aggregateFunc中的函数列表Filter syntax error过滤表达式语法错误检查括号配对、操作符拼写Cycle detected: A - B - A指标间循环引用重构指标定义消除循环八、总结OAL编译器的实现展现了领域特定语言DSL的经典设计模式Antlr4做前端词法语法分析标准化的解析框架Visitor模式做中端语义分析中间表示遍历AST收集信息代码生成做后端Java源码→字节码最终产物是高效的可执行代码这种架构不仅保证了OAL的灵活性和性能也让扩展新语法变得简单——只需修改.g4文件增加新的aggregateFunc规则即可。上一篇【第42篇】OAL语法全解析声明式指标计算的优雅之舞下一篇【第44篇】自定义OAL指标实战打造你的专属可观测仪表盘