OALEngineLoaderService
的load
方法,然后都会进入OALRuntime
类的start
去解析.oal。那么.oal文件是如何被解析成AnalysisResult
类的?
@Override
public void start(ClassLoader currentClassLoader) throws ModuleStartException, OALCompileException {
if (!IS_RT_TEMP_FOLDER_INIT_COMPLETED) {
prepareRTTempFolder();
IS_RT_TEMP_FOLDER_INIT_COMPLETED = true;
}
this.currentClassLoader = currentClassLoader;
Reader read;
try {
read = ResourceUtils.read(oalDefine.getConfigFile());
} catch (FileNotFoundException e) {
throw new ModuleStartException("Can't locate " + oalDefine.getConfigFile(), e);
}
OALScripts oalScripts;
try {
ScriptParser scriptParser = ScriptParser.createFromFile(read, oalDefine.getSourcePackage());
//解析得到的oalScripts中存放着本次.oal文件解析得到的所有的AnalysisResult类
oalScripts = scriptParser.parse();
} catch (IOException e) {
throw new ModuleStartException("OAL script parse analysis failure.", e);
}
this.generateClassAtRuntime(oalScripts);
}
我们在07、skywalking中使用antlr4
一文中已经测试过了,所有的.oal文件中定义的规则,都可以被OALParser.g4
进行正确解析!那么我们也可以根据OALParser.g4
中的解析器对.oal
中的规则进行反推!
我们从 root 规则开始一步步分析,java-agent.oal
文件中,instance_jvm_old_gc_time 这条语句匹配了哪些语法规则,如下图所示:
整个 instance_jvm_old_gc_time 语句匹配了 aggregationStatement 规则,其中 variable 规则定义了 OAL 语言中变量名称的结构,等号左边的 instance_jvm_old_gc_time 变量名称会匹配到该规则,而整个等号右边的内容会匹配到 metricStatement 规则(除了结尾的分号)。
metricStatement 规则如下图所示,开头的 from 关键字、source、sourceAttribute 三部分比较简单,你可以直接在 OALParser.g4 文件中找到相应的定义。后面的 filterStatement 表达式开头是 filter 关键字,紧接着的括号中是一个 ==、>、<、>= 或 <= 表达式。最后的聚合函数则是我们在其他编程语言中常见的函数调用格式(可以包含参数),例如这里的 longAvg() 以及前文看到的 p99(10)。
看完对 Antlr4 示例以及 metricStatement 规则的分析,相信你已经可以独立分析 OAL 语言剩余的语法规则,filterStatement 以及 aggregateFunction 两个语法规则就不再展开分析了。
匹配规则,上面,我们已经了解了,那么就看如何去构建AnalysisResult
类
在OALListener
类中,我们已经定义好了,每经过一个节点,需要干点什么事情,那么我们把instance_jvm_old_gc_time = from(ServiceInstanceJVMGC.time).filter(phase == GCPhase.OLD).longAvg();
这条规则套进去的话,其实它的节点树,应该是如下的样子
那么在每一步中都干了什么?
(1).enterAggregationStatement() 方法:创建该语句对应的 AnalysisResult 对象。
(2).exitVariable() 方法:填充 AnalysisResult 的 varName、metricsName、tableName 三个字段,会对大小写以及下划线进行处理。
(3).enterSource() 方法:填充 AnalysisResult 的 sourceName、sourceScopeId 两个字段。
(4).enterSourceAttribute() 方法:填充 AnalysisResult 的 sourceAttribute 字段。
(5).enterFilterStatement() 方法:创建 ConditionExpression 对象。
(6)~(8) 三个方法分别填充 ConditionExpression 对象中的三个字段。
(9).exitFilterStatement() 方法:将 ConditionExpression 添加到 AnalysisResult 中的 filterExpressionsParserResult 集合。
(10).enterFunctionName() 方法:填充 AnalysisResult 的 aggregationFunctionName 字段。
到此为止,该 AnalysisResult 填充的字段如下图所示:
(11).exitAggregationStatement() 方法:这里使用 DeepAnalysis 分析前 10 步从 OAL 语句获取到的信息,从而完整填充整个 AnalysisResult 对象。下面就主要分析DeepAnalysis到底做了哪些事情!
-
在 DeepAnalysis 中首先会根据 aggregationFunctionName 确定当前指标的类型并填充AnalysisResult 中的 metricsClassName 字段。示例中的 longAvg 会查找到 LongAvgMetrics 类,如下图所示:
-
接下来会查找 LongAvgMetrics 类中 @Entrance 注解标注的入口方法,即 combine() 方法,创建相应的 EntryMethod 对象填充到 entryMethod 字段中。这里生成的 EntryMethod 对象不仅包含入口方法的名称,还会根据入口方法参数上的注解生成相应的参数表达式。
依然以 LongAvgMetrics 为例,combine() 方法的定义如下:
@Entrance public final void combine(@SourceFrom long summation, @ConstOne long count) { this.summation += summation; this.count += count; }
在
combine
方法中的参数为:@SourceFrom long summation, @ConstOne long count
,其中@SourceFrom
标识了该参数来自 OAL 语句前面指定的 source.sourceAttribute,即 ServiceInstanceJVMGC.time, @ ConstOne 标识该参数固定为 1。看下源码,是不是这样public class DeepAnalysis { public AnalysisResult analysis(AnalysisResult result) { //......此处省略了很多代码 //创建EntryMethod EntryMethod entryMethod = new EntryMethod(); result.setEntryMethod(entryMethod); entryMethod.setMethodName(entranceMethod.getName()); // 这儿获取到加了@Entrance的方法的所有参数,并遍历参数 for (Parameter parameter : entranceMethod.getParameters()) { //获取到参数类型 Class<?> parameterType = parameter.getType(); //获取到参数注解 Annotation[] parameterAnnotations = parameter.getAnnotations(); if (parameterAnnotations == null || parameterAnnotations.length == 0) { throw new IllegalArgumentException( "Entrance method:" + entranceMethod + " doesn't include the annotation."); } Annotation annotation = parameterAnnotations[0]; //如果注解为 SourceFrom if (annotation instanceof SourceFrom) { entryMethod.addArg( parameterType, TypeCastUtil.withCast( //解析@SourceFrom注解 //result 为当前AnalysisResult对象,result.getFrom().getSourceAttribute()可以得到AnalysisResult中的SourceAttribute属性值time //就是把@SourceFrom long summation 转换成 long source.getTime()的值 result.getFrom().getSourceCastType(), "source." + ClassMethodUtil.toGetMethod(result.getFrom().getSourceAttribute()) ) ); } else if (annotation instanceof ConstOne) { //如果注解为ConstOne ,固定为1 entryMethod.addArg(parameterType, "1"); } else if (annotation instanceof org.apache.skywalking.oap.server.core.analysis.metrics.annotation.Expression) { if (isNull(result.getAggregationFuncStmt().getFuncConditionExpressions()) || result.getAggregationFuncStmt().getFuncConditionExpressions().isEmpty()) { throw new IllegalArgumentException( "Entrance method:" + entranceMethod + " argument can't find funcParamExpression."); } else { ConditionExpression expression = result.getAggregationFuncStmt().getNextFuncCondition; final FilterMatchers.MatcherInfo matcherInfo = FilterMatchers.INSTANCE.find( expression.getExpressionType()); final String getter = matcherInfo.isBooleanType() ? ClassMethodUtil.toIsMethod(expression.getAttributes()) : ClassMethodUtil.toGetMethod(expression.getAttributes()); final Expression argExpression = new ; argExpression.setRight(expression.getValue()); argExpression.setExpressionObject(matcherInfo.getMatcher().getName()); argExpression.setLeft(TypeCastUtil.withCast(expression.getCastType(), "source." + getter)); entryMethod.addArg(argExpression); } } else if (annotation instanceof Arg) { entryMethod.addArg(parameterType, result.getAggregationFuncStmt().getNextFuncArg()); } else { throw new IllegalArgumentException( "Entrance method:" + entranceMethod + " doesn't the expected annotation."); } } //......此处省略很多代码 } }
最终创建的 EntryMethod 对象如下图所示:
-
在上面,我们已经得到了
LongAvgMetrics
,并把类名设置到了AnalysisResult
的MetricsClassName属性中了。接下来,扫描 LongAvgMetrics 中的全部字段,将所有 @Column 注解标注的字段封装成 DataColumn 对象记录到AnalysisResult的 persistentFields 集合中
到此为止,instance_jvm_old_gc_time 这条 OAL 语句对应的 AnalysisResult 对象填充完毕。
在OALListener
类的exitAggregationStatement
的方法的代码如下:
@Override
public void exitAggregationStatement(@NotNull OALParser.AggregationStatementContext ctx) {
DeepAnalysis deepAnalysis = new DeepAnalysis();
//deepAnalysis.analysis(current)我们上面已经分析过了!处理好的AnalysisResult对象,通过`results.add`放到了metricsStmts属性中
results.add(deepAnalysis.analysis(current));
current = null;
}
当完成对一个.oal文件的全部规则解析完成后,回到OALRuntime
类的start
方法
@Override
public void start(ClassLoader currentClassLoader) throws ModuleStartException, OALCompileException {
if (!IS_RT_TEMP_FOLDER_INIT_COMPLETED) {
prepareRTTempFolder();
IS_RT_TEMP_FOLDER_INIT_COMPLETED = true;
}
this.currentClassLoader = currentClassLoader;
Reader read;
try {
read = ResourceUtils.read(oalDefine.getConfigFile());
} catch (FileNotFoundException e) {
throw new ModuleStartException("Can't locate " + oalDefine.getConfigFile(), e);
}
OALScripts oalScripts;
try {
ScriptParser scriptParser = ScriptParser.createFromFile(read, oalDefine.getSourcePackage());
oalScripts = scriptParser.parse();//拿到了对一个.oal文件中所有规则,全部解析好了的OALScripts对象!这个对象中放着所有解析好的AnalysisResult
} catch (IOException e) {
throw new ModuleStartException("OAL script parse analysis failure.", e);
}
//将所有的AnalysisResult,去生成java代码
this.generateClassAtRuntime(oalScripts);
}
private void generateClassAtRuntime(OALScripts oalScripts) throws OALCompileException {
//获取所有的AnalysisResult
List<AnalysisResult> metricsStmts = oalScripts.getMetricsStmts();
//遍历所有的AnalysisResult,挨个创建DispatcherContext,并把创建好的DispatcherContext放到allDispatcherContext这个map中,以保证不会重复创建。并把AnalysisResult信息,放到创建好的DispatcherContext的metrics属性中,注意,这个metrics属性是一个list。
metricsStmts.forEach(this::buildDispatcherContext);
for (AnalysisResult metricsStmt : metricsStmts) {
//generateMetricsClass(metricsStmt),详情见下面的, 分析1
metricsClasses.add(generateMetricsClass(metricsStmt));
//generateMetricsBuilderClass(metricsStmt)
generateMetricsBuilderClass(metricsStmt);
}
//遍历allDispatcherContext中所有的DispatcherContext
for (Map.Entry<String, DispatcherContext> entry : allDispatcherContext.getAllContext().entrySet()) {
//generateDispatcherClass(entry.getKey(), entry.getValue()) 与上面的generateMetricsClass(metricsStmt) 类似 这儿就不分析了
dispatcherClasses.add(generateDispatcherClass(entry.getKey(), entry.getValue()));
}
oalScripts.getDisableCollection().getAllDisableSources().forEach(disable -> {
DisableRegister.INSTANCE.add(disable);
});
}
分析1:
generateMetricsClass(metricsStmt)
的具体过程
private Class generateMetricsClass(AnalysisResult metricsStmt) throws OALCompileException {
//实际调用的是 metricsStmt.getMetricsName() + "Metrics" 确定要要生成的类名
String className = metricsClassName(metricsStmt, false);
//判断要生成的MetricsClass的父类存不存在
CtClass parentMetricsClass = null;
try {
parentMetricsClass = classPool.get(METRICS_FUNCTION_PACKAGE + metricsStmt.getMetricsClassName());
} catch (NotFoundException e) {
log.error("Can't find parent class for " + className + ".", e);
throw new OALCompileException(e.getMessage(), e);
}
//metricsClassName(metricsStmt, true)会返回要构建类的全类名,使用javassist去构建一个类对象,parentMetricsClass是指定构建的类对象的父类
CtClass metricsClass = classPool.makeClass(metricsClassName(metricsStmt, true), parentMetricsClass);
try {
//指定要构建类对象要实现的接口
metricsClass.addInterface(classPool.get(WITH_METADATA_INTERFACE));
} catch (NotFoundException e) {
log.error("Can't find WithMetadata interface for " + className + ".", e);
throw new OALCompileException(e.getMessage(), e);
}
//获取要生成类的字节码
ClassFile metricsClassClassFile = metricsClass.getClassFile();
//获取到当前要生成类的常量池
ConstPool constPool = metricsClassClassFile.getConstPool();
//创建空构造
try {
CtConstructor defaultConstructor = CtNewConstructor.make("public " + className + "() {}", metricsClass);
metricsClass.addConstructor(defaultConstructor);
} catch (CannotCompileException e) {
log.error("Can't add empty constructor in " + className + ".", e);
throw new OALCompileException(e.getMessage(), e);
}
//遍历AnalysisResult中的所有的字段信息,然后挨个往要生成的类中写,并设置好get/set方法,给每个字段加上@Column注解
for (SourceColumn field : metricsStmt.getFieldsFromSource()) {
try {
CtField newField = CtField.make(
"private " + field.getType()
.getName() + " " + field.getFieldName() + ";", metricsClass);
metricsClass.addField(newField);
metricsClass.addMethod(CtNewMethod.getter(field.getFieldGetter(), newField));
metricsClass.addMethod(CtNewMethod.setter(field.getFieldSetter(), newField));
AnnotationsAttribute annotationsAttribute = new AnnotationsAttribute(
constPool, AnnotationsAttribute.visibleTag);
/**
* Add @Column(name = "${sourceField.columnName}")
*/
Annotation columnAnnotation = new Annotation(Column.class.getName(), constPool);
columnAnnotation.addMemberValue("name", new StringMemberValue(field.getColumnName(), constPool));
if (field.getType().equals(String.class)) {
columnAnnotation.addMemberValue("length", new IntegerMemberValue(constPool, field.getLength()));
}
annotationsAttribute.addAnnotation(columnAnnotation);
if (field.isID()) {
// Add SeriesID = 0 annotation to ID field.
Annotation banyanShardingKeyAnnotation = new Annotation(BanyanDB.SeriesID.class.getName(), constPool);
banyanShardingKeyAnnotation.addMemberValue("index", new IntegerMemberValue(constPool, 0));
annotationsAttribute.addAnnotation(banyanShardingKeyAnnotation);
}
newField.getFieldInfo().addAttribute(annotationsAttribute);
} catch (CannotCompileException e) {
log.error(
"Can't add field(including set/get) " + field.getFieldName() + " in " + className + ".", e);
throw new OALCompileException(e.getMessage(), e);
}
}
//遍历METRICS_CLASS_METHODS中所有定好的方法名,["id",
// "hashCode",
// "remoteHashCode",
// "equals",
// "serialize",
// "deserialize",
// "getMeta",
// "toHour",
// "toDay"]
for (String method : METRICS_CLASS_METHODS) {
StringWriter methodEntity = new StringWriter();
try {
//挨个读取 .ftl 模板去生成方法,.ftl返回的方法在methodEntity中
configuration.getTemplate("metrics/" + method + ".ftl").process(metricsStmt, methodEntity);
//写入方法到类中
metricsClass.addMethod(CtNewMethod.make(methodEntity.toString(), metricsClass));
} catch (Exception e) {
log.error("Can't generate method " + method + " for " + className + ".", e);
throw new OALCompileException(e.getMessage(), e);
}
}
//给要生成的类加上 @Stream(name = "${tableName}", scopeId = ${sourceScopeId}, builder = ${metricsName}Metrics.Builder.class, processor = MetricsStreamProcessor.class)
AnnotationsAttribute annotationsAttribute = new AnnotationsAttribute(
constPool, AnnotationsAttribute.visibleTag);
Annotation streamAnnotation = new Annotation(Stream.class.getName(), constPool);
streamAnnotation.addMemberValue("name", new StringMemberValue(metricsStmt.getTableName(), constPool));
streamAnnotation.addMemberValue(
"scopeId", new IntegerMemberValue(constPool, metricsStmt.getFrom().getSourceScopeId()));
streamAnnotation.addMemberValue(
"builder", new ClassMemberValue(metricsBuilderClassName(metricsStmt, true), constPool));
streamAnnotation.addMemberValue("processor", new ClassMemberValue(METRICS_STREAM_PROCESSOR, constPool));
annotationsAttribute.addAnnotation(streamAnnotation);
metricsClassClassFile.addAttribute(annotationsAttribute);
Class targetClass;
try {
//把生成的类,写入到JVM中
if (SystemUtils.isJavaVersionAtMost(JavaVersion.JAVA_1_8)) {
targetClass = metricsClass.toClass(currentClassLoader, null);
} else {
targetClass = metricsClass.toClass(MetricClassPackageHolder.class);
}
} catch (CannotCompileException e) {
log.error("Can't compile/load " + className + ".", e);
throw new OALCompileException(e.getMessage(), e);
}
log.debug("Generate metrics class, " + metricsClass.getName());
//生成实实在在的类,只有打开了openEngineDebug,才会生效,生成的文件位于:
writeGeneratedFile(metricsClass, "metrics");
return targetClass;
}
.ftl文件大致分析下
protected org.apache.skywalking.oap.server.core.storage.StorageID id0() {
org.apache.skywalking.oap.server.core.storage.StorageID id = new org.apache.skywalking.oap.server.core.storage.StorageID().append(TIME_BUCKET, getTimeBucket());
<!-- 遍历 AnalysisResult中的 fieldsFromSource集合 -->
<#list fieldsFromSource as sourceField>
<!-- 根据ID配置决定是否参与构造Document Id–>
<#if sourceField.isID()>
id.append("${sourceField.columnName}", ${sourceField.fieldName});
</#if>
</#list>
return id;
}
至此,如何生成文件我们已经解析完毕了!