skywalking中动态生成Metric文件

chengzhiyuan 10月前 ⋅ 77 阅读
ad

在不同的XxxProvider启动的时候,会读取不同的.oal文件,都会进入OALEngineLoaderServiceload方法,然后都会进入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;
}

 

至此,如何生成文件我们已经解析完毕了!

关于纵目

江苏纵目信息科技有限公司是一家专注于运维监控软件产品研发与销售的高科技企业。覆盖全链路应用性能监控、IT基础设施监控、物联网数据采集数据观测等场景,基于Skywalking、Zabbix、ThingsBoard等开源体系构建了ArgusAPM、ArgusOMS、ZeusIoT等产品,致力于帮助各行业客户构建集聚可观测性的统一运维平台、物联网大数据平台。

  点赞 0   收藏 0
  • chengzhiyuan
    共发布1篇文章 获得0个收藏
全部评论: 0