/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.skywalking.oap.query.promql.rt; import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import lombok.extern.slf4j.Slf4j; import org.apache.skywalking.oap.query.graphql.resolver.MetricsQuery; import org.apache.skywalking.oap.query.graphql.resolver.RecordsQuery; import org.apache.skywalking.oap.query.promql.entity.ErrorType; import org.apache.skywalking.oap.query.promql.entity.LabelName; import org.apache.skywalking.oap.query.promql.entity.LabelValuePair; import org.apache.skywalking.oap.query.promql.entity.MetricRangeData; import org.apache.skywalking.oap.query.promql.entity.MetricInfo; import org.apache.skywalking.oap.query.promql.handler.PromQLApiHandler; import org.apache.skywalking.oap.query.promql.rt.exception.IllegalExpressionException; import org.apache.skywalking.oap.query.promql.rt.result.MetricsRangeResult; import org.apache.skywalking.oap.query.promql.rt.result.ParseResult; import org.apache.skywalking.oap.query.promql.rt.result.ParseResultType; import org.apache.skywalking.oap.query.promql.rt.result.ScalarResult; import org.apache.skywalking.oap.server.core.Const; import org.apache.skywalking.oap.server.core.analysis.Layer; import org.apache.skywalking.oap.server.core.query.enumeration.Order; import org.apache.skywalking.oap.server.core.query.enumeration.Scope; import org.apache.skywalking.oap.server.core.query.input.Duration; import org.apache.skywalking.oap.server.core.query.input.Entity; import org.apache.skywalking.oap.server.core.query.input.MetricsCondition; import org.apache.skywalking.oap.server.core.query.input.RecordCondition; import org.apache.skywalking.oap.server.core.query.input.TopNCondition; import org.apache.skywalking.oap.server.core.query.type.MetricsValues; import org.apache.skywalking.oap.server.core.query.type.Record; import org.apache.skywalking.oap.server.core.query.type.SelectedRecord; import org.apache.skywalking.oap.server.core.storage.annotation.Column; import org.apache.skywalking.oap.server.core.storage.annotation.ValueColumnMetadata; import org.apache.skywalking.oap.server.library.util.StringUtil; import org.apache.skywalking.promql.rt.grammar.PromQLParser; import org.apache.skywalking.promql.rt.grammar.PromQLParserBaseVisitor; import static org.apache.skywalking.oap.query.promql.rt.PromOpUtils.buildMatrixValues; import static org.apache.skywalking.oap.query.promql.rt.PromOpUtils.formatDuration; import static org.apache.skywalking.oap.query.promql.rt.PromOpUtils.matrixBinaryOp; import static org.apache.skywalking.oap.query.promql.rt.PromOpUtils.matrixCompareOp; import static org.apache.skywalking.oap.query.promql.rt.PromOpUtils.matrixScalarBinaryOp; import static org.apache.skywalking.oap.query.promql.rt.PromOpUtils.matrixScalarCompareOp; import static org.apache.skywalking.oap.query.promql.rt.PromOpUtils.scalarBinaryOp; import static org.apache.skywalking.oap.query.promql.rt.PromOpUtils.scalarCompareOp; import static org.apache.skywalking.oap.query.promql.rt.PromOpUtils.timestamp2Duration; @Slf4j public class PromQLExprQueryVisitor extends PromQLParserBaseVisitor { private final MetricsQuery metricsQuery; private final RecordsQuery recordsQuery; private final PromQLApiHandler.QueryType queryType; private Duration duration; public PromQLExprQueryVisitor(final MetricsQuery metricsQuery, final RecordsQuery recordsQuery, final Duration duration, final PromQLApiHandler.QueryType queryType) { this.metricsQuery = metricsQuery; this.recordsQuery = recordsQuery; this.duration = duration; this.queryType = queryType; } @Override public ParseResult visitAddSubOp(PromQLParser.AddSubOpContext ctx) { ParseResult left = visit(ctx.expression(0)); if (StringUtil.isNotBlank(left.getErrorInfo())) { return left; } ParseResult right = visit(ctx.expression(1)); if (StringUtil.isNotBlank(right.getErrorInfo())) { return right; } int opType = ctx.addSub().getStart().getType(); return binaryOp(left, right, opType); } @Override public ParseResult visitMulDivModOp(PromQLParser.MulDivModOpContext ctx) { ParseResult left = visit(ctx.expression(0)); if (StringUtil.isNotBlank(left.getErrorInfo())) { return left; } ParseResult right = visit(ctx.expression(1)); if (StringUtil.isNotBlank(right.getErrorInfo())) { return right; } int opType = ctx.mulDivMod().getStart().getType(); return binaryOp(left, right, opType); } @Override public ParseResult visitCompareOp(PromQLParser.CompareOpContext ctx) { ParseResult left = visit(ctx.expression(0)); if (StringUtil.isNotBlank(left.getErrorInfo())) { return left; } ParseResult right = visit(ctx.expression(1)); if (StringUtil.isNotBlank(right.getErrorInfo())) { return right; } boolean boolModifier = ctx.compare().BOOL() != null; int opType = ctx.compare().getStart().getType(); return compareOp(left, right, opType, boolModifier); } private ParseResult compareOp(ParseResult left, ParseResult right, int opType, boolean boolModifier) { try { if (left.getResultType() == ParseResultType.SCALAR && right.getResultType() == ParseResultType.SCALAR) { if (!boolModifier) { throw new IllegalExpressionException("Comparisons between scalars must use BOOL modifier."); } else { ScalarResult scalarLeft = (ScalarResult) left; ScalarResult scalarRight = (ScalarResult) right; int value = scalarCompareOp(scalarLeft.getValue(), scalarRight.getValue(), opType); ScalarResult scalarResult = new ScalarResult(); scalarResult.setValue(value); scalarResult.setResultType(ParseResultType.SCALAR); return scalarResult; } } else if (left.getResultType() == ParseResultType.METRICS_RANGE && right.getResultType() == ParseResultType.SCALAR) { return matrixScalarCompareOp((MetricsRangeResult) left, (ScalarResult) right, opType); } else if (left.getResultType() == ParseResultType.SCALAR && right.getResultType() == ParseResultType.METRICS_RANGE) { return matrixScalarCompareOp((MetricsRangeResult) right, (ScalarResult) left, opType); } else if (left.getResultType() == ParseResultType.METRICS_RANGE && right.getResultType() == ParseResultType.METRICS_RANGE) { try { return matrixCompareOp((MetricsRangeResult) left, (MetricsRangeResult) right, opType); } catch (IllegalExpressionException e) { MetricsRangeResult result = new MetricsRangeResult(); result.setErrorType(ErrorType.BAD_DATA); result.setErrorInfo(e.getMessage()); return result; } } } catch (IllegalExpressionException e) { MetricsRangeResult result = new MetricsRangeResult(); result.setErrorType(ErrorType.BAD_DATA); result.setErrorInfo(e.getMessage()); return result; } return new ParseResult(); } @Override public ParseResult visitNumberLiteral(PromQLParser.NumberLiteralContext ctx) { ScalarResult result = new ScalarResult(); double value = Double.parseDouble(ctx.NUMBER().getText()); result.setValue(value); result.setResultType(ParseResultType.SCALAR); return result; } @Override public ParseResult visitMetricInstant(PromQLParser.MetricInstantContext ctx) { ParseResult result = new ParseResult(); try { String metricName = ctx.metricName().getText(); Optional valueColumn = getValueColumn(metricName); if (!valueColumn.isPresent()) { result.setErrorType(ErrorType.BAD_DATA); result.setErrorInfo("Metric: [" + metricName + "] dose not exist."); return result; } if (ctx.labelList() == null) { result.setErrorType(ErrorType.BAD_DATA); result.setErrorInfo("No labels found in the expression."); return result; } Map labelMap = new HashMap<>(); for (PromQLParser.LabelContext labelCtx : ctx.labelList().label()) { String labelName = labelCtx.labelName().getText(); String labelValue = labelCtx.labelValue().getText(); String labelValueTrim = labelValue.substring(1, labelValue.length() - 1); try { labelMap.put(LabelName.labelOf(labelName), labelValueTrim); } catch (IllegalArgumentException e) { throw new IllegalExpressionException("Label:[" + labelName + "] is illegal."); } } final Layer layer; checkLabels(labelMap, LabelName.LAYER); try { layer = Layer.valueOf(labelMap.get(LabelName.LAYER)); } catch (IllegalArgumentException e) { throw new IllegalExpressionException( "Layer:[" + labelMap.get(LabelName.LAYER) + "] is missing or illegal."); } ValueColumnMetadata.ValueColumn metaData = valueColumn.get(); Scope scope = Scope.Finder.valueOf(metaData.getScopeId()); Column.ValueDataType dataType = metaData.getDataType(); MetricsRangeResult matrixResult = new MetricsRangeResult(); matrixResult.setResultType(ParseResultType.METRICS_RANGE); if (StringUtil.isNotBlank(labelMap.get(LabelName.TOP_N))) { if (Column.ValueDataType.SAMPLED_RECORD == dataType) { queryRecords(metricName, layer, scope, labelMap, matrixResult); } else { queryTopN(metricName, layer, scope, labelMap, matrixResult); } } else { if (Column.ValueDataType.COMMON_VALUE == dataType) { metricsValuesQuery(metricName, layer, scope, labelMap, matrixResult); } else if (Column.ValueDataType.LABELED_VALUE == dataType) { labeledMetricsValuesQuery(metricName, layer, scope, labelMap, matrixResult); } } return matrixResult; } catch (IllegalExpressionException e) { result.setErrorType(ErrorType.BAD_DATA); result.setErrorInfo(e.getMessage()); return result; } catch (IOException e) { result.setErrorType(ErrorType.INTERNAL); result.setErrorInfo("Internal IO exception."); log.error("Query metrics error.", e); return result; } } @Override public ParseResult visitMetricRange(PromQLParser.MetricRangeContext ctx) { if (PromQLApiHandler.QueryType.RANGE == queryType) { ParseResult result = new ParseResult(); result.setErrorType(ErrorType.BAD_DATA); result.setErrorInfo("Range expression should use instant query."); return result; } String timeRange = ctx.DURATION().getText().toUpperCase(); long endTS = System.currentTimeMillis(); long startTS = endTS - formatDuration(timeRange).getMillis(); duration = timestamp2Duration(startTS, endTS); ParseResult result = visit(ctx.metricInstant()); result.setRangeExpression(true); return result; } private void checkLabels(Map labelMap, LabelName... labelNames) throws IllegalExpressionException { StringBuilder missLabels = new StringBuilder(); int j = 0; for (int i = 0; i < labelNames.length; i++) { String labelName = labelNames[i].toString(); if (labelMap.get(labelNames[i]) == null) { missLabels.append(j++ > 0 ? "," : "").append(labelName); } } String result = missLabels.toString(); if (StringUtil.isNotBlank(result)) { throw new IllegalExpressionException("Metrics expression missing label: " + result); } } private void queryTopN(String metricName, Layer layer, Scope scope, Map labelMap, MetricsRangeResult matrixResult) throws IOException, IllegalExpressionException { TopNCondition topNCondition = buildTopNCondition(metricName, layer, scope, labelMap); List selectedRecords = metricsQuery.sortMetrics(topNCondition, duration); for (SelectedRecord selectedRecord : selectedRecords) { MetricRangeData metricData = new MetricRangeData(); MetricInfo metricInfo = buildMetricInfo(metricName, layer, scope, labelMap, Optional.empty(), Optional.ofNullable(selectedRecord.getName()), Optional.empty() ); metricData.setMetric(metricInfo); metricData.setValues(buildMatrixValues(duration, selectedRecord.getValue())); matrixResult.getMetricDataList().add(metricData); } } private void queryRecords(String metricName, Layer layer, Scope scope, Map labelMap, MetricsRangeResult matrixResult) throws IOException, IllegalExpressionException { RecordCondition recordCondition = buildRecordCondition(metricName, layer, scope, labelMap); List records = recordsQuery.readRecords(recordCondition, duration); for (Record record : records) { MetricRangeData metricData = new MetricRangeData(); MetricInfo metricInfo = buildMetricInfo(metricName, layer, scope, labelMap, Optional.empty(), Optional.empty(), Optional.ofNullable(record.getName()) ); metricData.setMetric(metricInfo); metricData.setValues(buildMatrixValues(duration, record.getValue())); matrixResult.getMetricDataList().add(metricData); } } private void metricsValuesQuery(String metricName, Layer layer, Scope scope, Map labelMap, MetricsRangeResult matrixResult) throws IOException, IllegalExpressionException { MetricsCondition metricsCondition = buildMetricsCondition(metricName, layer, scope, labelMap); MetricsValues metricsValues = metricsQuery.readMetricsValues( metricsCondition, duration); MetricRangeData metricData = new MetricRangeData(); MetricInfo metricInfo = buildMetricInfo( metricName, layer, scope, labelMap, Optional.empty(), Optional.empty(), Optional.empty()); metricData.setMetric(metricInfo); metricData.setValues(buildMatrixValues(duration, metricsValues)); matrixResult.getMetricDataList().add(metricData); } private void labeledMetricsValuesQuery(String metricName, Layer layer, Scope scope, Map labelMap, MetricsRangeResult matrixResult) throws IOException, IllegalExpressionException { MetricsCondition metricsCondition = buildMetricsCondition(metricName, layer, scope, labelMap); Map relabelMap = new HashMap<>(); String queryLabels = labelMap.get(LabelName.LABELS); List queryLabelList = Collections.EMPTY_LIST; if (StringUtil.isNotBlank(queryLabels)) { queryLabelList = Arrays.asList(queryLabels.split(Const.COMMA)); String relabels = labelMap.get(LabelName.RELABELS); List relabelList = Collections.EMPTY_LIST; if (StringUtil.isNotBlank(relabels)) { relabelList = Arrays.asList(relabels.split(Const.COMMA)); } for (int i = 0; i < queryLabelList.size(); i++) { if (relabelList.size() > i) { relabelMap.put(queryLabelList.get(i), relabelList.get(i)); continue; } relabelMap.put(queryLabelList.get(i), queryLabelList.get(i)); } } List metricsValuesList = metricsQuery.readLabeledMetricsValues( metricsCondition, queryLabelList, duration); for (MetricsValues metricsValues : metricsValuesList) { MetricRangeData metricData = new MetricRangeData(); MetricInfo metricInfo = buildMetricInfo( metricName, layer, scope, labelMap, Optional.ofNullable(relabelMap.getOrDefault( metricsValues.getLabel(), metricsValues.getLabel() )), Optional.empty(), Optional.empty() ); metricData.setMetric(metricInfo); metricData.setValues(buildMatrixValues(duration, metricsValues)); matrixResult.getMetricDataList().add(metricData); } } private ParseResult binaryOp(ParseResult left, ParseResult right, int opType) { if (left.getResultType() == ParseResultType.SCALAR && right.getResultType() == ParseResultType.SCALAR) { ScalarResult scalarLeft = (ScalarResult) left; ScalarResult scalarRight = (ScalarResult) right; double value = scalarBinaryOp(scalarLeft.getValue(), scalarRight.getValue(), opType); ScalarResult scalarResult = new ScalarResult(); scalarResult.setValue(value); scalarResult.setResultType(ParseResultType.SCALAR); return scalarResult; } else if (left.getResultType() == ParseResultType.METRICS_RANGE && right.getResultType() == ParseResultType.SCALAR) { return matrixScalarBinaryOp((MetricsRangeResult) left, (ScalarResult) right, opType); } else if (left.getResultType() == ParseResultType.SCALAR && right.getResultType() == ParseResultType.METRICS_RANGE) { return matrixScalarBinaryOp((MetricsRangeResult) right, (ScalarResult) left, opType); } else if (left.getResultType() == ParseResultType.METRICS_RANGE && right.getResultType() == ParseResultType.METRICS_RANGE) { try { return matrixBinaryOp((MetricsRangeResult) left, (MetricsRangeResult) right, opType); } catch (IllegalExpressionException e) { MetricsRangeResult result = new MetricsRangeResult(); result.setErrorType(ErrorType.BAD_DATA); result.setErrorInfo(e.getMessage()); return result; } } return new ParseResult(); } private Entity buildEntity(Layer layer, Scope scope, String serviceName, Map labelMap) throws IllegalExpressionException { Entity entity = new Entity(); entity.setScope(scope); entity.setNormal(layer.isNormal()); entity.setServiceName(serviceName); switch (scope) { case ServiceInstance: checkLabels(labelMap, LabelName.SERVICE_INSTANCE); entity.setServiceInstanceName(labelMap.get(LabelName.SERVICE_INSTANCE)); break; case Endpoint: checkLabels(labelMap, LabelName.ENDPOINT); entity.setEndpointName(labelMap.get(LabelName.ENDPOINT)); break; } return entity; } private TopNCondition buildTopNCondition(String metricName, Layer layer, Scope scope, Map labelMap) throws IllegalExpressionException { //sortMetrics query ParentService could be null. checkLabels(labelMap, LabelName.TOP_N, LabelName.ORDER); TopNCondition topNCondition = new TopNCondition(); topNCondition.setName(metricName); topNCondition.setParentService(labelMap.get(LabelName.PARENT_SERVICE)); topNCondition.setTopN(Integer.parseInt(labelMap.get(LabelName.TOP_N))); topNCondition.setOrder(Order.valueOf(labelMap.get(LabelName.ORDER))); topNCondition.setNormal(layer.isNormal()); topNCondition.setScope(scope); return topNCondition; } private RecordCondition buildRecordCondition(String metricName, Layer layer, Scope scope, Map labelMap) throws IllegalExpressionException { checkLabels(labelMap, LabelName.TOP_N, LabelName.PARENT_SERVICE, LabelName.ORDER); String parentServiceName = labelMap.get(LabelName.PARENT_SERVICE); RecordCondition recordCondition = new RecordCondition(); recordCondition.setName(metricName); recordCondition.setParentEntity(buildEntity(layer, scope, parentServiceName, labelMap)); recordCondition.setTopN(Integer.parseInt(labelMap.get(LabelName.TOP_N))); recordCondition.setOrder(Order.valueOf(labelMap.get(LabelName.ORDER))); return recordCondition; } private MetricsCondition buildMetricsCondition(String metricName, Layer layer, Scope scope, Map labelMap) throws IllegalExpressionException { checkLabels(labelMap, LabelName.SERVICE); String serviceName = labelMap.get(LabelName.SERVICE); MetricsCondition metricsCondition = new MetricsCondition(); metricsCondition.setEntity(buildEntity(layer, scope, serviceName, labelMap)); metricsCondition.setName(metricName); return metricsCondition; } private MetricInfo buildMetricInfo(String metricName, Layer layer, Scope scope, Map labelMap, Optional valueLabel, Optional topNEntityName, Optional recordName) throws IllegalExpressionException { MetricInfo metricInfo = new MetricInfo(metricName); valueLabel.ifPresent(s -> metricInfo.getLabels().add(new LabelValuePair(LabelName.LABEL, s))); metricInfo.getLabels().add(new LabelValuePair(LabelName.LAYER, layer.name())); switch (scope) { case Service: metricInfo.getLabels().add(new LabelValuePair(LabelName.SCOPE, Scope.Service.name())); if (topNEntityName.isPresent()) { metricInfo.getLabels().add(new LabelValuePair(LabelName.SERVICE, topNEntityName.get())); } else if (recordName.isPresent()) { metricInfo.getLabels().add(new LabelValuePair(LabelName.RECORD, recordName.get())); } else { checkLabels(labelMap, LabelName.SERVICE); metricInfo.getLabels() .add(new LabelValuePair(LabelName.SERVICE, labelMap.get(LabelName.SERVICE))); } break; case ServiceInstance: metricInfo.getLabels().add(new LabelValuePair(LabelName.SCOPE, Scope.ServiceInstance.name())); if (topNEntityName.isPresent()) { metricInfo.getLabels().add(new LabelValuePair(LabelName.SERVICE_INSTANCE, topNEntityName.get())); } else if (recordName.isPresent()) { metricInfo.getLabels().add(new LabelValuePair(LabelName.RECORD, recordName.get())); } else { checkLabels(labelMap, LabelName.SERVICE_INSTANCE); metricInfo.getLabels() .add(new LabelValuePair( LabelName.SERVICE_INSTANCE, labelMap.get(LabelName.SERVICE_INSTANCE) )); } break; case Endpoint: metricInfo.getLabels().add(new LabelValuePair(LabelName.SCOPE, Scope.Endpoint.name())); if (topNEntityName.isPresent()) { metricInfo.getLabels().add(new LabelValuePair(LabelName.ENDPOINT, topNEntityName.get())); } else if (recordName.isPresent()) { metricInfo.getLabels().add(new LabelValuePair(LabelName.RECORD, recordName.get())); } else { checkLabels(labelMap, LabelName.ENDPOINT); metricInfo.getLabels() .add(new LabelValuePair(LabelName.ENDPOINT, labelMap.get(LabelName.ENDPOINT))); } break; } return metricInfo; } private Optional getValueColumn(String metricName) { return ValueColumnMetadata.INSTANCE.readValueColumnDefinition(metricName); } }