Support Metrics Query Expression(MQE) and allows users to do simple query-stage calculation through the expression. (#10881)

This commit is contained in:
Wan Kai 2023-06-05 17:22:05 +08:00 committed by GitHub
parent ecda943fba
commit 0f847438c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 1563 additions and 83 deletions

View File

@ -0,0 +1,124 @@
# Metrics Query Expression(MQE) Syntax
MQE is a string that consists of one or more expressions. Each expression could be a combination of one or more operations.
The expression allows users to do simple query-stage calculation through [MQE APIs](./query-protocol.md#mqe-apis).
```text
Expression = <Operation> Expression1 <Operation> Expression2 <Operation> Expression3 ...
```
The following document lists the operations supported by MQE.
## Metrics Expression
### Common Value Metrics
Expression:
```text
<metric_name>
```
For example:
If we want to query the `service_sla` metric, we can use the following expression:
```text
service_sla
```
### Labeled Value Metrics
Expression:
```text
<metric_name>{labels='<label_name1>,...', relabels='<new_label_name1>,...'}
```
`labels` is the selected labels name of the metric, and `relabels` is the labels name of the metric after the label is renamed.
If `labels` is not specified, all labels of the metric will be selected. And if `relabels` is not specified, the label name will not be changed.
For example:
If we want to query the `service_percentile` metric with the labels `0,1,2,3,4`, and rename the labels to `P50,P75,P90,P95,P99`, we can use the following expression:
```text
service_percentile{labels='0,1,2,3,4', relabels='P50,P75,P90,P95,P99'}
```
## Binary Operation
Binary Operation is an operation that takes two expressions and performs a calculation on their results.
The following table lists the binary operations supported by MQE.
Expression:
```text
Expression1 <Binary-Operator> Expression2
```
| Operator | Definition |
|----------|----------------------|
| + | addition |
| - | subtraction |
| * | multiplication |
| / | division |
| % | modulo |
For example:
If we want to transform the service_sla metric value to percent, we can use the following expression:
```text
service_sla / 100
```
## Aggregation Operation
Aggregation Operation takes an expression and performs aggregate calculation on its results.
Expression:
```text
<Aggregation-Operator>(Expression)
```
| Operator | Definition |
|----------|--------------------------------|
| avg | average the result |
| count | count number of the result |
| sum | sum the result |
| max | select maximum from the result |
| min | select minimum from the result |
For example:
If we want to query the average value of the `service_cpm` metric, we can use the following expression:
```text
avg(service_cpm)
```
## Function Operation
Function Operation takes an expression and performs function calculation on its results.
<Function-Operator>(Expression, parameters)
| Operator | Definition | parameters |
|----------|---------------------------------------------------------------------------|--------------------------------------------------------------------|
| abs | returns the absolute value of the result | |
| ceil | returns the smallest integer value that is greater or equal to the result | |
| floor | returns the largest integer value that is greater or equal to the result | |
| round | returns result round to specific decimal places | `places`: a positive integer specific decimal places of the result |
For example:
If we want to query the average value of the `service_cpm` metric in seconds,
and round the result to 2 decimal places, we can use the following expression:
```text
round(service_cpm / 60 , 2)
```
## TopN Operation
TopN Operation takes an expression and performs TopN calculation on its results.
```text
top_n(<metric_name>, <top_number>, <order>)
```
`top_number` is the number of the top results, should be a positive integer.
`order` is the order of the top results. The value of `order` can be `asc` or `des`.
For example:
If we want to query the top 10 services with the highest `service_cpm` metric value, we can use the following expression:
```text
top_n(service_instance_cpm, 10, des)
```

View File

@ -0,0 +1,84 @@
# Deprecated Query Protocol
The following query services are deprecated since 9.5.0. All these queries are still available for the short term to keep compatibility.
Query protocol official repository, https://github.com/apache/skywalking-query-protocol.
### Metrics
Metrics query targets all objects defined in [OAL script](../concepts-and-designs/oal.md) and [MAL](../concepts-and-designs/mal.md).
You may obtain the metrics data in linear or thermodynamic matrix formats based on the aggregation functions in script.
#### V2 APIs
Provide Metrics V2 query APIs since 8.0.0, including metadata, single/multiple values, heatmap, and sampled records metrics.
```graphql
extend type Query {
# Read metrics single value in the duration of required metrics
readMetricsValue(condition: MetricsCondition!, duration: Duration!): Long!
# Read metrics single value in the duration of required metrics
# NullableValue#isEmptyValue == true indicates no telemetry data rather than aggregated value is actually zero.
readNullableMetricsValue(condition: MetricsCondition!, duration: Duration!): NullableValue!
# Read time-series values in the duration of required metrics
readMetricsValues(condition: MetricsCondition!, duration: Duration!): MetricsValues!
# Read entity list of required metrics and parent entity type.
sortMetrics(condition: TopNCondition!, duration: Duration!): [SelectedRecord!]!
# Read value in the given time duration, usually as a linear.
# labels: the labels you need to query.
readLabeledMetricsValues(condition: MetricsCondition!, labels: [String!]!, duration: Duration!): [MetricsValues!]!
# Heatmap is bucket based value statistic result.
readHeatMap(condition: MetricsCondition!, duration: Duration!): HeatMap
# Deprecated since 9.3.0, replaced by readRecords defined in record.graphqls
# Read the sampled records
# TopNCondition#scope is not required.
readSampledRecords(condition: TopNCondition!, duration: Duration!): [SelectedRecord!]!
}
```
#### V1 APIs
3 types of metrics can be queried. V1 APIs were introduced since 6.x. Now they are a shell to V2 APIs.
1. Single value. Most default metrics are in single value. `getValues` and `getLinearIntValues` are suitable for this purpose.
2. Multiple value. A metric defined in OAL includes multiple value calculations. Use `getMultipleLinearIntValues` to obtain all values. `percentile` is a typical multiple value function in OAL.
3. Heatmap value. Read [Heatmap in WIKI](https://en.wikipedia.org/wiki/Heat_map) for details. `thermodynamic` is the only OAL function. Use `getThermodynamic` to get the values.
```graphql
extend type Query {
getValues(metric: BatchMetricConditions!, duration: Duration!): IntValues
getLinearIntValues(metric: MetricCondition!, duration: Duration!): IntValues
# Query the type of metrics including multiple values, and format them as multiple lines.
# The seq of these multiple lines base on the calculation func in OAL
# Such as, should us this to query the result of func percentile(50,75,90,95,99) in OAL,
# then five lines will be responded, p50 is the first element of return value.
getMultipleLinearIntValues(metric: MetricCondition!, numOfLinear: Int!, duration: Duration!): [IntValues!]!
getThermodynamic(metric: MetricCondition!, duration: Duration!): Thermodynamic
}
```
### Aggregation
Aggregation query means that the metrics data need a secondary aggregation at query stage, which causes the query
interfaces to have some different arguments. A typical example of aggregation query is the `TopN` list of services.
Metrics stream aggregation simply calculates the metrics values of each service, but the expected list requires ordering metrics data
by their values.
Aggregation query is for single value metrics only.
```graphql
# The aggregation query is different with the metric query.
# All aggregation queries require backend or/and storage do aggregation in query time.
extend type Query {
# TopN is an aggregation query.
getServiceTopN(name: String!, topN: Int!, duration: Duration!, order: Order!): [TopNEntity!]!
getAllServiceInstanceTopN(name: String!, topN: Int!, duration: Duration!, order: Order!): [TopNEntity!]!
getServiceInstanceTopN(serviceId: ID!, name: String!, topN: Int!, duration: Duration!, order: Order!): [TopNEntity!]!
getAllEndpointTopN(name: String!, topN: Int!, duration: Duration!, order: Order!): [TopNEntity!]!
getEndpointTopN(serviceId: ID!, name: String!, topN: Int!, duration: Duration!, order: Order!): [TopNEntity!]!
}
```
### Record
Record is a general and abstract type for collected raw data.
In the observability, traces and logs have specific and well-defined meanings, meanwhile, the general records represent other
collected records. Such as sampled slow SQL statement, HTTP request raw data(request/response header/body)
```graphql
extend type Query {
# Query collected records with given metric name and parent entity conditions, and return in the requested order.
readRecords(condition: RecordCondition!, duration: Duration!): [Record!]!
}
```

View File

@ -4,6 +4,8 @@ native visualization tool or 3rd party system, including Web UI, CLI or private
Query protocol official repository, https://github.com/apache/skywalking-query-protocol.
All deprecated APIs are moved [here](./query-protocol-deprecated.md).
### Metadata
Metadata contains concise information on all services and their instances, endpoints, etc. under monitoring.
You may query the metadata in different ways.
@ -68,11 +70,13 @@ extend type Query {
```
### Metrics
Metrics query targets all objects defined in [OAL script](../concepts-and-designs/oal.md) and [MAL](../concepts-and-designs/mal.md).
You may obtain the metrics data in linear or thermodynamic matrix formats based on the aggregation functions in script.
Metrics query targets all objects defined in [OAL script](../concepts-and-designs/oal.md) and [MAL](../concepts-and-designs/mal.md).
#### V3 APIs
Provide Metrics V3 query APIs since 9.5.0, including metadata and MQE.
SkyWalking Metrics Query Expression(MQE) is an extension query mechanism. MQE allows users to do simple query-stage calculation like well known PromQL
through GraphQL. The expression's syntax can refer to [here](./metrics-query-expression.md).
#### V2 APIs
Provide Metrics V2 query APIs since 8.0.0, including metadata, single/multiple values, heatmap, and sampled records metrics.
```graphql
extend type Query {
# Metrics definition metadata query. Response the metrics type which determines the suitable query methods.
@ -80,75 +84,38 @@ extend type Query {
# Get the list of all available metrics in the current OAP server.
# Param, regex, could be used to filter the metrics by name.
listMetrics(regex: String): [MetricDefinition!]!
# Read metrics single value in the duration of required metrics
readMetricsValue(condition: MetricsCondition!, duration: Duration!): Long!
# Read time-series values in the duration of required metrics
readMetricsValues(condition: MetricsCondition!, duration: Duration!): MetricsValues!
# Read entity list of required metrics and parent entity type.
sortMetrics(condition: TopNCondition!, duration: Duration!): [SelectedRecord!]!
# Read value in the given time duration, usually as a linear.
# labels: the labels you need to query.
readLabeledMetricsValues(condition: MetricsCondition!, labels: [String!]!, duration: Duration!): [MetricsValues!]!
# Heatmap is bucket based value statistic result.
readHeatMap(condition: MetricsCondition!, duration: Duration!): HeatMap
# Deprecated since 9.3.0, replaced by readRecords defined in record.graphqls
# Read the sampled records
# TopNCondition#scope is not required.
readSampledRecords(condition: TopNCondition!, duration: Duration!): [SelectedRecord!]!
# The return type of the given expression, the MQEValues will be empty.
returnTypeOfMQE(expression: String!): ExpressionResult!
execExpression(expression: String!, entity: Entity!, duration: Duration!): ExpressionResult!
}
```
#### V1 APIs
3 types of metrics can be queried. V1 APIs were introduced since 6.x. Now they are a shell to V2 APIs.
1. Single value. Most default metrics are in single value. `getValues` and `getLinearIntValues` are suitable for this purpose.
1. Multiple value. A metric defined in OAL includes multiple value calculations. Use `getMultipleLinearIntValues` to obtain all values. `percentile` is a typical multiple value function in OAL.
1. Heatmap value. Read [Heatmap in WIKI](https://en.wikipedia.org/wiki/Heat_map) for details. `thermodynamic` is the only OAL function. Use `getThermodynamic` to get the values.
```graphql
extend type Query {
getValues(metric: BatchMetricConditions!, duration: Duration!): IntValues
getLinearIntValues(metric: MetricCondition!, duration: Duration!): IntValues
# Query the type of metrics including multiple values, and format them as multiple lines.
# The seq of these multiple lines base on the calculation func in OAL
# Such as, should us this to query the result of func percentile(50,75,90,95,99) in OAL,
# then five lines will be responded, p50 is the first element of return value.
getMultipleLinearIntValues(metric: MetricCondition!, numOfLinear: Int!, duration: Duration!): [IntValues!]!
getThermodynamic(metric: MetricCondition!, duration: Duration!): Thermodynamic
type ExpressionResult {
type: ExpressionResultType!
# When the type == TIME_SERIES_VALUES, the results would be a collection of MQEValues.
# In other legal type cases, only one MQEValues is expected in the array.
results: [MQEValues!]!
# When type == ExpressionResultType.UNKNOWN,
# the error message includes the expression resolving errors.
error: String
}
```
Metrics are defined in the `config/oal/*.oal` files.
### Aggregation
Aggregation query means that the metrics data need a secondary aggregation at query stage, which causes the query
interfaces to have some different arguments. A typical example of aggregation query is the `TopN` list of services.
Metrics stream aggregation simply calculates the metrics values of each service, but the expected list requires ordering metrics data
by their values.
Aggregation query is for single value metrics only.
```graphql
# The aggregation query is different with the metric query.
# All aggregation queries require backend or/and storage do aggregation in query time.
extend type Query {
# TopN is an aggregation query.
getServiceTopN(name: String!, topN: Int!, duration: Duration!, order: Order!): [TopNEntity!]!
getAllServiceInstanceTopN(name: String!, topN: Int!, duration: Duration!, order: Order!): [TopNEntity!]!
getServiceInstanceTopN(serviceId: ID!, name: String!, topN: Int!, duration: Duration!, order: Order!): [TopNEntity!]!
getAllEndpointTopN(name: String!, topN: Int!, duration: Duration!, order: Order!): [TopNEntity!]!
getEndpointTopN(serviceId: ID!, name: String!, topN: Int!, duration: Duration!, order: Order!): [TopNEntity!]!
}
```
### Record
Record is a general and abstract type for collected raw data.
In the observability, traces and logs have specific and well-defined meanings, meanwhile, the general records represent other
collected records. Such as sampled slow SQL statement, HTTP request raw data(request/response header/body)
```graphql
extend type Query {
# Query collected records with given metric name and parent entity conditions, and return in the requested order.
readRecords(condition: RecordCondition!, duration: Duration!): [Record!]!
enum ExpressionResultType {
# Can't resolve the type of the given expression.
UNKNOWN
# A single value
SINGLE_VALUE
# A collection of time-series values.
# The value could have labels or not.
TIME_SERIES_VALUES
# A collection of aggregated values through metric sort function
SORTED_LIST
# A collection of sampled records.
# When the original metric type is sampled records
RECORD_LIST
}
```

View File

@ -48,6 +48,9 @@
* Support Redis slow logs collection.
* Fix data loss when query continuous profiling task record.
* Adapt the continuous profiling task query GraphQL.
* Support Metrics Query Expression(MQE) and allows users to do simple query-stage calculation through the expression.
* Deprecated metrics query v2 protocol.
* Deprecated record query protocol.
#### UI
* Revert: cpm5d function. This feature is cancelled from backend.
@ -77,6 +80,7 @@
* Add grizzly icon.
* Fix: the Instance List data display error.
* Fix: set topN type to Number.
* Support Metrics Query Expression(MQE) and allows users to do simple query-stage calculation through the expression.
#### Documentation

View File

@ -292,7 +292,13 @@ catalog:
- name: "Query APIs"
catalog:
- name: "GraphQL APIs"
path: "/en/api/query-protocol"
catalog:
- name: "GraphQL APIs"
path: "/en/api/query-protocol"
- name: "MQE Syntax"
path: "/en/api/metrics-query-expression"
- name: "Deprecated APIs"
path: "/en/api/query-protocol-deprecated"
- name: "PromQL APIs"
path: "/en/api/promql-service"
- name: "Security Notice"

View File

@ -22,7 +22,7 @@ import lombok.Getter;
import lombok.Setter;
/**
* SelectedRecord is an abtract data element, including id, name, value and a reference id.
* SelectedRecord is an abstract data element, including id, name, value and a reference id.
*/
@Setter
@Getter

View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
~
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>server-query-plugin</artifactId>
<groupId>org.apache.skywalking</groupId>
<version>9.5.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>mqe-grammar</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.antlr</groupId>
<artifactId>antlr4-runtime</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.antlr</groupId>
<artifactId>antlr4-maven-plugin</artifactId>
<executions>
<execution>
<id>antlr</id>
<goals>
<goal>antlr4</goal>
</goals>
</execution>
</executions>
<configuration>
<visitor>true</visitor>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,82 @@
/*
* 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.
*
*/
lexer grammar MQELexer;
// Keywords
BOOL options { caseInsensitive=true; }: 'bool';
LABLES: 'labels';
RELABELS: 'relabels';
// Constructors symbols
DOT: '.';
COMMA: ',';
L_PAREN: '(';
R_PAREN: ')';
L_BRACKET: '[';
R_BRACKET: ']';
L_BRACE: '{';
R_BRACE: '}';
EQ: '=';
// Scalar Binary operators
SUB: '-';
ADD: '+';
MUL: '*';
DIV: '/';
MOD: '%';
DEQ: '==';
NEQ: '!=';
LTE: '<=';
LT: '<';
GTE: '>=';
GT: '>';
// Aggregation operators
AVG: 'avg';
COUNT: 'count';
MAX: 'max';
MIN: 'min';
SUM: 'sum';
// Functions
ABS: 'abs';
CEIL: 'ceil';
FLOOR: 'floor';
ROUND: 'round';
// TopN
TOP_N: 'top_n';
// Order
ASC options { caseInsensitive=true; }: 'asc';
DES options { caseInsensitive=true; }: 'des';
// Literals
INTEGER: Digit+;
DECIMAL: Digit+ DOT Digit+;
NAME_STRING: NameLetter+;
VALUE_STRING: '\'' .*? '\'' | '"' .*? '"';
// Fragments
fragment Digit: [0-9];
fragment NameLetter: [a-zA-Z0-9_];
WS : [ \t\r\n]+ -> skip;

View File

@ -0,0 +1,67 @@
/*
* 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.
*
*/
parser grammar MQEParser;
options { tokenVocab = MQELexer; }
root: expression;
expression
: expressionNode # exprNode
| expression mulDivMod expression # mulDivModOp
| expression addSub expression # addSubOp
| expression compare expression # compareOp
| aggregation L_PAREN expression R_PAREN # aggregationOp
| function0 L_PAREN expression R_PAREN #function0OP
| function1 L_PAREN expression COMMA parameter R_PAREN #function1OP
| topN L_PAREN metric COMMA parameter COMMA order R_PAREN #topNOP
;
expressionNode: metric| scalar;
addSub: ADD | SUB ;
mulDivMod: MUL | DIV | MOD;
compare: (DEQ | NEQ | LTE | LT | GTE | GT) BOOL?;
metricName: NAME_STRING;
metric: metricName | metricName L_BRACE labelList? R_BRACE;
labelName: LABLES | RELABELS;
labelValue: VALUE_STRING;
label: labelName EQ labelValue;
labelList: label (COMMA label)*;
scalar: INTEGER | DECIMAL;
aggregation:
AVG | COUNT | SUM | MAX | MIN ;
// 0 parameter function
function0:
ABS | CEIL | FLOOR;
// 1 parameter function
function1:
ROUND;
topN: TOP_N;
parameter: INTEGER;
order: ASC | DES;

View File

@ -30,6 +30,7 @@
<modules>
<module>query-graphql-plugin</module>
<module>zipkin-query-plugin</module>
<module>mqe-grammar</module>
<module>promql-plugin</module>
</modules>
</project>

View File

@ -137,9 +137,9 @@ public class PromQLApiHandler {
if (match.isPresent()) {
PromQLLexer lexer = new PromQLLexer(
CharStreams.fromString(match.get()));
lexer.addErrorListener(ParseErrorListener.INSTANCE);
lexer.addErrorListener(new ParseErrorListener());
PromQLParser parser = new PromQLParser(new CommonTokenStream(lexer));
parser.addErrorListener(ParseErrorListener.INSTANCE);
parser.addErrorListener(new ParseErrorListener());
ParseTree tree;
try {
tree = parser.expression();
@ -217,9 +217,9 @@ public class PromQLApiHandler {
SeriesQueryRsp response = new SeriesQueryRsp();
PromQLLexer lexer = new PromQLLexer(
CharStreams.fromString(match));
lexer.addErrorListener(ParseErrorListener.INSTANCE);
lexer.addErrorListener(new ParseErrorListener());
PromQLParser parser = new PromQLParser(new CommonTokenStream(lexer));
parser.addErrorListener(ParseErrorListener.INSTANCE);
parser.addErrorListener(new ParseErrorListener());
ParseTree tree;
try {
tree = parser.expression();
@ -295,9 +295,9 @@ public class PromQLApiHandler {
PromQLLexer lexer = new PromQLLexer(
CharStreams.fromString(query));
lexer.addErrorListener(ParseErrorListener.INSTANCE);
lexer.addErrorListener(new ParseErrorListener());
PromQLParser parser = new PromQLParser(new CommonTokenStream(lexer));
parser.addErrorListener(ParseErrorListener.INSTANCE);
parser.addErrorListener(new ParseErrorListener());
ParseTree tree;
try {
tree = parser.expression();
@ -356,9 +356,9 @@ public class PromQLApiHandler {
ExprQueryRsp response = new ExprQueryRsp();
PromQLLexer lexer = new PromQLLexer(
CharStreams.fromString(query));
lexer.addErrorListener(ParseErrorListener.INSTANCE);
lexer.addErrorListener(new ParseErrorListener());
PromQLParser parser = new PromQLParser(new CommonTokenStream(lexer));
parser.addErrorListener(ParseErrorListener.INSTANCE);
parser.addErrorListener(new ParseErrorListener());
ParseTree tree;
try {
tree = parser.expression();

View File

@ -24,8 +24,6 @@ import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;
public class ParseErrorListener extends BaseErrorListener {
public static final ParseErrorListener INSTANCE = new ParseErrorListener();
@Override
public void syntaxError(Recognizer<?, ?> recognizer,
Object offendingSymbol,

View File

@ -43,6 +43,11 @@
<artifactId>log-analyzer</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>mqe-grammar</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java</artifactId>

View File

@ -37,6 +37,7 @@ import org.apache.skywalking.oap.query.graphql.resolver.LogTestQuery;
import org.apache.skywalking.oap.query.graphql.resolver.MetadataQuery;
import org.apache.skywalking.oap.query.graphql.resolver.MetadataQueryV2;
import org.apache.skywalking.oap.query.graphql.resolver.MetricQuery;
import org.apache.skywalking.oap.query.graphql.resolver.MetricsExpressionQuery;
import org.apache.skywalking.oap.query.graphql.resolver.MetricsQuery;
import org.apache.skywalking.oap.query.graphql.resolver.Mutation;
import org.apache.skywalking.oap.query.graphql.resolver.OndemandLogQuery;
@ -98,11 +99,13 @@ public class GraphQLQueryProvider extends ModuleProvider {
.file("query-protocol/topology.graphqls")
.resolvers(new TopologyQuery(getManager()))
/*
* Metrics v2 query protocol is an alternative metrics query(s) of original v1,
* defined in the metric.graphql, top-n-records.graphqls, and aggregation.graphqls.
* Since 9.5.0.
* Metrics v3 query protocol is an enhanced metrics query(s) from original v1 and v2
* powered by newly added Metrics Query Expression Language to fetch and
* manipulate metrics data in the query stage.
*/
.file("query-protocol/metrics-v2.graphqls")
.resolvers(new MetricsQuery(getManager()))
.file("query-protocol/metrics-v3.graphqls")
.resolvers(new MetricsExpressionQuery(getManager()))
////////
//Deprecated Queries
////////
@ -112,6 +115,9 @@ public class GraphQLQueryProvider extends ModuleProvider {
.resolvers(new AggregationQuery(getManager()))
.file("query-protocol/top-n-records.graphqls")
.resolvers(new TopNRecordsQuery(getManager()))
//Deprecated since 9.5.0
.file("query-protocol/metrics-v2.graphqls")
.resolvers(new MetricsQuery(getManager()))
////////
.file("query-protocol/trace.graphqls")
.resolvers(new TraceQuery(getManager()))

View File

@ -0,0 +1,426 @@
/*
* 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.graphql.mqe.rt;
import java.io.IOException;
import java.util.ArrayList;
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.mqe.rt.exception.IllegalExpressionException;
import org.apache.skywalking.oap.query.graphql.mqe.rt.operation.AggregationOp;
import org.apache.skywalking.oap.query.graphql.resolver.MetricsQuery;
import org.apache.skywalking.oap.query.graphql.resolver.RecordsQuery;
import org.apache.skywalking.oap.query.graphql.type.mql.ExpressionResult;
import org.apache.skywalking.oap.query.graphql.type.mql.ExpressionResultType;
import org.apache.skywalking.oap.query.graphql.type.mql.MQEValue;
import org.apache.skywalking.oap.query.graphql.type.mql.MQEValues;
import org.apache.skywalking.oap.query.graphql.type.mql.Metadata;
import org.apache.skywalking.oap.server.core.Const;
import org.apache.skywalking.oap.server.core.query.DurationUtils;
import org.apache.skywalking.oap.server.core.query.PointOfTime;
import org.apache.skywalking.oap.server.core.query.enumeration.Order;
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.KVInt;
import org.apache.skywalking.oap.server.core.query.type.KeyValue;
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.mqe.rt.grammar.MQEParser;
import org.apache.skywalking.mqe.rt.grammar.MQEParserBaseVisitor;
import static org.apache.skywalking.oap.query.graphql.mqe.rt.operation.BinaryOp.doBinaryOp;
import static org.apache.skywalking.oap.query.graphql.mqe.rt.operation.FunctionOp.doFunction0Op;
import static org.apache.skywalking.oap.query.graphql.mqe.rt.operation.FunctionOp.doFunction1Op;
@Slf4j
public class MQEVisitor extends MQEParserBaseVisitor<ExpressionResult> {
private final MetricsQuery metricsQuery;
private final RecordsQuery recordsQuery;
private final Entity entity;
private final Duration duration;
public MQEVisitor(final MetricsQuery metricsQuery,
final RecordsQuery recordsQuery,
final Entity entity,
final Duration duration) {
this.metricsQuery = metricsQuery;
this.recordsQuery = recordsQuery;
this.entity = entity;
this.duration = duration;
}
/**
* This constructor is only used for parsing expresstions, won't query any metrics from backend.
*/
public MQEVisitor() {
this.metricsQuery = null;
this.recordsQuery = null;
this.entity = null;
this.duration = null;
}
@Override
public ExpressionResult visitAddSubOp(MQEParser.AddSubOpContext ctx) {
ExpressionResult left = visit(ctx.expression(0));
if (StringUtil.isNotBlank(left.getError())) {
return left;
}
ExpressionResult right = visit(ctx.expression(1));
if (StringUtil.isNotBlank(right.getError())) {
return right;
}
int opType = ctx.addSub().getStart().getType();
try {
return doBinaryOp(left, right, opType);
} catch (IllegalExpressionException e) {
ExpressionResult result = new ExpressionResult();
result.setType(ExpressionResultType.UNKNOWN);
result.setError(e.getMessage());
return result;
}
}
@Override
public ExpressionResult visitMulDivModOp(MQEParser.MulDivModOpContext ctx) {
ExpressionResult left = visit(ctx.expression(0));
if (StringUtil.isNotBlank(left.getError())) {
return left;
}
ExpressionResult right = visit(ctx.expression(1));
if (StringUtil.isNotBlank(right.getError())) {
return right;
}
int opType = ctx.mulDivMod().getStart().getType();
try {
return doBinaryOp(left, right, opType);
} catch (IllegalExpressionException e) {
ExpressionResult result = new ExpressionResult();
result.setType(ExpressionResultType.UNKNOWN);
result.setError(e.getMessage());
return result;
}
}
@Override
public ExpressionResult visitScalar(MQEParser.ScalarContext ctx) {
ExpressionResult result = new ExpressionResult();
double value = Double.parseDouble(ctx.getText());
MQEValue mqeValue = new MQEValue();
mqeValue.setDoubleValue(value);
mqeValue.setEmptyValue(false);
MQEValues mqeValues = new MQEValues();
mqeValues.getValues().add(mqeValue);
result.getResults().add(mqeValues);
result.setType(ExpressionResultType.SINGLE_VALUE);
return result;
}
@Override
public ExpressionResult visitAggregationOp(MQEParser.AggregationOpContext ctx) {
int opType = ctx.aggregation().getStart().getType();
ExpressionResult expResult = visit(ctx.expression());
if (StringUtil.isNotEmpty(expResult.getError())) {
return expResult;
}
try {
return AggregationOp.doAggregationOp(expResult, opType);
} catch (IllegalExpressionException e) {
ExpressionResult result = new ExpressionResult();
result.setType(ExpressionResultType.UNKNOWN);
result.setError(e.getMessage());
return result;
}
}
@Override
public ExpressionResult visitFunction0OP(MQEParser.Function0OPContext ctx) {
int opType = ctx.function0().getStart().getType();
ExpressionResult expResult = visit(ctx.expression());
if (StringUtil.isNotEmpty(expResult.getError())) {
return expResult;
}
try {
return doFunction0Op(expResult, opType);
} catch (IllegalExpressionException e) {
ExpressionResult result = new ExpressionResult();
result.setType(ExpressionResultType.UNKNOWN);
result.setError(e.getMessage());
return result;
}
}
@Override
public ExpressionResult visitFunction1OP(MQEParser.Function1OPContext ctx) {
int opType = ctx.function1().getStart().getType();
ExpressionResult expResult = visit(ctx.expression());
if (StringUtil.isNotEmpty(expResult.getError())) {
return expResult;
}
try {
return doFunction1Op(expResult, opType, ctx.parameter());
} catch (IllegalExpressionException e) {
ExpressionResult result = new ExpressionResult();
result.setType(ExpressionResultType.UNKNOWN);
result.setError(e.getMessage());
return result;
}
}
@Override
public ExpressionResult visitTopNOP(MQEParser.TopNOPContext ctx) {
return visit(ctx.metric());
}
@Override
public ExpressionResult visitMetric(MQEParser.MetricContext ctx) {
ExpressionResult result = new ExpressionResult();
String metricName = ctx.metricName().getText();
Optional<ValueColumnMetadata.ValueColumn> valueColumn = ValueColumnMetadata.INSTANCE.readValueColumnDefinition(
metricName);
if (valueColumn.isEmpty()) {
result.setType(ExpressionResultType.UNKNOWN);
result.setError("Metric: [" + metricName + "] dose not exist.");
return result;
}
Column.ValueDataType dataType = valueColumn.get().getDataType();
try {
if (Column.ValueDataType.COMMON_VALUE == dataType) {
if (ctx.parent instanceof MQEParser.TopNOPContext) {
MQEParser.TopNOPContext parent = (MQEParser.TopNOPContext) ctx.parent;
querySortMetrics(metricName, Integer.parseInt(parent.parameter().getText()),
Order.valueOf(parent.order().getText().toUpperCase()), result
);
} else {
queryMetrics(metricName, result);
}
} else if (Column.ValueDataType.LABELED_VALUE == dataType) {
List<String> queryLabelList = Collections.emptyList();
List<String> relabelList = Collections.emptyList();
if (ctx.labelList() != null) {
for (MQEParser.LabelContext labelCtx : ctx.labelList().label()) {
int labelName = labelCtx.labelName().getStart().getType();
String labelValue = labelCtx.labelValue().getText();
String labelValueTrim = labelValue.substring(1, labelValue.length() - 1);
switch (labelName) {
case MQEParser.LABLES:
if (StringUtil.isNotBlank(labelValueTrim)) {
queryLabelList = Arrays.asList(labelValueTrim.split(Const.COMMA));
}
break;
case MQEParser.RELABELS:
if (StringUtil.isNotBlank(labelValueTrim)) {
relabelList = Arrays.asList(labelValueTrim.split(Const.COMMA));
}
break;
}
}
}
queryLabeledMetrics(metricName, queryLabelList, relabelList, result);
} else if (Column.ValueDataType.SAMPLED_RECORD == dataType) {
if (ctx.parent instanceof MQEParser.TopNOPContext) {
MQEParser.TopNOPContext parent = (MQEParser.TopNOPContext) ctx.parent;
queryRecords(metricName, Integer.parseInt(parent.parameter().getText()),
Order.valueOf(parent.order().getText()), result
);
} else {
throw new IllegalExpressionException(
"Metric: [" + metricName + "] is topN record, need top_n function for query.");
}
}
} catch (IllegalExpressionException e) {
ExpressionResult errorResult = new ExpressionResult();
errorResult.setType(ExpressionResultType.UNKNOWN);
errorResult.setError(e.getMessage());
return errorResult;
} catch (IOException e) {
ExpressionResult errorResult = new ExpressionResult();
errorResult.setType(ExpressionResultType.UNKNOWN);
errorResult.setError("Internal IO exception, query metrics error.");
log.error("Query metrics from backend error.", e);
return errorResult;
}
return result;
}
private void querySortMetrics(String metricName,
int topN,
Order order,
ExpressionResult result) throws IOException {
List<SelectedRecord> selectedRecords;
// API `returnTypeOfMQE` not require entity and duration.
if (entity != null && duration != null && metricsQuery != null) {
TopNCondition topNCondition = new TopNCondition();
topNCondition.setName(metricName);
topNCondition.setTopN(topN);
topNCondition.setParentService(entity.getServiceName());
topNCondition.setOrder(order);
topNCondition.setNormal(entity.getNormal());
selectedRecords = metricsQuery.sortMetrics(topNCondition, duration);
} else {
selectedRecords = Collections.emptyList();
}
List<MQEValue> mqeValueList = new ArrayList<>(selectedRecords.size());
selectedRecords.forEach(selectedRecord -> {
MQEValue mqeValue = new MQEValue();
mqeValue.setId(selectedRecord.getName());
mqeValue.setEmptyValue(false);
mqeValue.setDoubleValue(Double.parseDouble(selectedRecord.getValue()));
mqeValueList.add(mqeValue);
});
Metadata metadata = new Metadata();
metadata.setName(metricName);
MQEValues mqeValues = new MQEValues();
mqeValues.setValues(mqeValueList);
mqeValues.setMetric(metadata);
result.getResults().add(mqeValues);
result.setType(ExpressionResultType.SORTED_LIST);
}
private void queryRecords(String metricName, int topN, Order order, ExpressionResult result) throws IOException {
List<Record> records;
// API `returnTypeOfMQE` not require entity and duration.
if (entity != null && duration != null && recordsQuery != null) {
RecordCondition recordCondition = new RecordCondition();
recordCondition.setName(metricName);
recordCondition.setTopN(topN);
recordCondition.setParentEntity(entity);
recordCondition.setOrder(order);
records = recordsQuery.readRecords(recordCondition, duration);
} else {
records = Collections.emptyList();
}
List<MQEValue> mqeValueList = new ArrayList<>(records.size());
records.forEach(record -> {
MQEValue mqeValue = new MQEValue();
mqeValue.setId(record.getName());
mqeValue.setEmptyValue(false);
mqeValue.setDoubleValue(Double.parseDouble(record.getValue()));
mqeValue.setTraceID(record.getRefId());
mqeValueList.add(mqeValue);
});
Metadata metadata = new Metadata();
metadata.setName(metricName);
MQEValues mqeValues = new MQEValues();
mqeValues.setValues(mqeValueList);
mqeValues.setMetric(metadata);
result.getResults().add(mqeValues);
result.setType(ExpressionResultType.RECORD_LIST);
}
private void queryMetrics(String metricName, ExpressionResult result) throws IOException {
MetricsValues metricsValues;
List<PointOfTime> times;
if (entity != null && duration != null && metricsQuery != null) {
MetricsCondition metricsCondition = new MetricsCondition();
metricsCondition.setName(metricName);
metricsCondition.setEntity(entity);
metricsValues = metricsQuery.readMetricsValues(metricsCondition, duration);
times = duration.assembleDurationPoints();
} else {
metricsValues = new MetricsValues();
times = Collections.emptyList();
}
List<MQEValue> mqeValueList = new ArrayList<>(times.size());
for (int i = 0; i < times.size(); i++) {
long retTimestamp = DurationUtils.INSTANCE.parseToDateTime(duration.getStep(), times.get(i).getPoint())
.getMillis();
KVInt kvInt = metricsValues.getValues().getValues().get(i);
MQEValue mqeValue = new MQEValue();
mqeValue.setId(Long.toString(retTimestamp));
mqeValue.setEmptyValue(kvInt.isEmptyValue());
mqeValue.setDoubleValue(kvInt.getValue());
mqeValueList.add(mqeValue);
}
Metadata metadata = new Metadata();
metadata.setName(metricName);
MQEValues mqeValues = new MQEValues();
mqeValues.setValues(mqeValueList);
mqeValues.setMetric(metadata);
result.getResults().add(mqeValues);
result.setType(ExpressionResultType.TIME_SERIES_VALUES);
}
private void queryLabeledMetrics(String metricName,
List<String> queryLabelList,
List<String> relabelList,
ExpressionResult result) throws IOException {
List<MetricsValues> metricsValuesList;
List<PointOfTime> times;
if (entity != null && duration != null && metricsQuery != null) {
MetricsCondition metricsCondition = new MetricsCondition();
metricsCondition.setName(metricName);
metricsCondition.setEntity(entity);
metricsValuesList = metricsQuery.readLabeledMetricsValues(
metricsCondition, queryLabelList, duration);
times = duration.assembleDurationPoints();
} else {
metricsValuesList = Collections.emptyList();
times = Collections.emptyList();
}
Map<String, String> relabelMap = new HashMap<>();
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));
}
metricsValuesList.forEach(metricsValues -> {
List<MQEValue> mqeValueList = new ArrayList<>(times.size());
for (int i = 0; i < times.size(); i++) {
long retTimestamp = DurationUtils.INSTANCE.parseToDateTime(duration.getStep(), times.get(i).getPoint())
.getMillis();
KVInt kvInt = metricsValues.getValues().getValues().get(i);
MQEValue mqeValue = new MQEValue();
mqeValue.setEmptyValue(kvInt.isEmptyValue());
mqeValue.setId(Long.toString(retTimestamp));
mqeValueList.add(mqeValue);
if (!kvInt.isEmptyValue()) {
mqeValue.setDoubleValue(kvInt.getValue());
}
}
Metadata metadata = new Metadata();
metadata.setName(metricName);
KeyValue labelValue = new KeyValue("label", relabelMap.getOrDefault(
metricsValues.getLabel(),
metricsValues.getLabel()
));
metadata.getLabels().add(labelValue);
MQEValues mqeValues = new MQEValues();
mqeValues.setValues(mqeValueList);
mqeValues.setMetric(metadata);
result.getResults().add(mqeValues);
});
result.setType(ExpressionResultType.TIME_SERIES_VALUES);
}
}

View File

@ -0,0 +1,25 @@
/*
* 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.graphql.mqe.rt.exception;
public class IllegalExpressionException extends Exception {
public IllegalExpressionException(String message) {
super(message);
}
}

View File

@ -0,0 +1,38 @@
/*
* 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.graphql.mqe.rt.exception;
import graphql.org.antlr.v4.runtime.misc.ParseCancellationException;
import org.antlr.v4.runtime.BaseErrorListener;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;
public class ParseErrorListener extends BaseErrorListener {
@Override
public void syntaxError(Recognizer<?, ?> recognizer,
Object offendingSymbol,
int line,
int charPositionInLine,
String msg,
RecognitionException e)
throws ParseCancellationException {
throw new ParseCancellationException("line " + line + ":" + charPositionInLine + " " + msg);
}
}

View File

@ -0,0 +1,111 @@
/*
* 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.graphql.mqe.rt.operation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.function.Function;
import java.util.stream.DoubleStream;
import org.apache.skywalking.mqe.rt.grammar.MQEParser;
import org.apache.skywalking.oap.query.graphql.mqe.rt.exception.IllegalExpressionException;
import org.apache.skywalking.oap.query.graphql.type.mql.ExpressionResult;
import org.apache.skywalking.oap.query.graphql.type.mql.ExpressionResultType;
import org.apache.skywalking.oap.query.graphql.type.mql.MQEValue;
import org.apache.skywalking.oap.query.graphql.type.mql.MQEValues;
public class AggregationOp {
public static ExpressionResult doAggregationOp(ExpressionResult result,
int opType) throws IllegalExpressionException {
switch (opType) {
case MQEParser.AVG:
return aggregateResult(result, mqeValues -> mqeValues.getValues()
.stream()
.filter(mqeValue -> !mqeValue.isEmptyValue())
.flatMapToDouble(mqeValue -> DoubleStream.of(
mqeValue.getDoubleValue()))
.average());
case MQEParser.COUNT:
return aggregateResult(result, mqeValues -> OptionalDouble.of(
mqeValues.getValues().stream().filter(mqeValue -> !mqeValue.isEmptyValue()).count()));
case MQEParser.MAX:
return selectResult(result, mqeValues -> mqeValues.getValues()
.stream()
.filter(mqeValue -> !mqeValue.isEmptyValue())
.max(Comparator.comparingDouble(
MQEValue::getDoubleValue)));
case MQEParser.MIN:
return selectResult(result, mqeValues -> mqeValues.getValues()
.stream()
.filter(mqeValue -> !mqeValue.isEmptyValue())
.min(Comparator.comparingDouble(
MQEValue::getDoubleValue)));
case MQEParser.SUM:
return aggregateResult(result, mqeValues -> OptionalDouble.of(mqeValues.getValues()
.stream()
.filter(
mqeValue -> !mqeValue.isEmptyValue())
.flatMapToDouble(
mqeValue -> DoubleStream.of(
mqeValue.getDoubleValue()))
.sum()));
default:
throw new IllegalExpressionException("Unsupported aggregation operation.");
}
}
private static ExpressionResult aggregateResult(ExpressionResult result,
Function<MQEValues, OptionalDouble> aggregator) {
for (MQEValues resultValues : result.getResults()) {
OptionalDouble resultValue = aggregator.apply(resultValues);
if (resultValue.isPresent()) {
List<MQEValue> mqeValueList = new ArrayList<>(1);
//no id
MQEValue mqeValue = new MQEValue();
mqeValue.setEmptyValue(false);
mqeValue.setDoubleValue(resultValue.getAsDouble());
mqeValueList.add(mqeValue);
resultValues.setValues(mqeValueList);
} else {
resultValues.setValues(Collections.emptyList());
}
}
result.setType(ExpressionResultType.SINGLE_VALUE);
return result;
}
private static ExpressionResult selectResult(ExpressionResult result,
Function<MQEValues, Optional<MQEValue>> aggregator) {
for (MQEValues resultValues : result.getResults()) {
Optional<MQEValue> resultValue = aggregator.apply(resultValues);
if (resultValue.isPresent()) {
List<MQEValue> mqeValueList = new ArrayList<>(1);
mqeValueList.add(resultValue.get());
resultValues.setValues(mqeValueList);
} else {
resultValues.setValues(Collections.emptyList());
}
}
result.setType(ExpressionResultType.SINGLE_VALUE);
return result;
}
}

View File

@ -0,0 +1,131 @@
/*
* 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.graphql.mqe.rt.operation;
import org.apache.skywalking.mqe.rt.grammar.MQEParser;
import org.apache.skywalking.oap.query.graphql.mqe.rt.exception.IllegalExpressionException;
import org.apache.skywalking.oap.query.graphql.type.mql.ExpressionResult;
import org.apache.skywalking.oap.query.graphql.type.mql.ExpressionResultType;
import org.apache.skywalking.oap.query.graphql.type.mql.MQEValue;
import org.apache.skywalking.oap.query.graphql.type.mql.MQEValues;
public class BinaryOp {
public static ExpressionResult doBinaryOp(ExpressionResult left,
ExpressionResult right,
int opType) throws IllegalExpressionException {
if (left.getType() == ExpressionResultType.SINGLE_VALUE && right.getType() == ExpressionResultType.SINGLE_VALUE) {
double scalarLeft = left.getResults().get(0).getValues().get(0).getDoubleValue();
double scalarRight = right.getResults().get(0).getValues().get(0).getDoubleValue();
double value = scalarBinaryOp(scalarLeft, scalarRight, opType);
ExpressionResult result = new ExpressionResult();
MQEValue mqeValue = new MQEValue();
mqeValue.setDoubleValue(value);
mqeValue.setEmptyValue(false);
MQEValues mqeValues = new MQEValues();
mqeValues.getValues().add(mqeValue);
result.getResults().add(mqeValues);
result.setType(ExpressionResultType.SINGLE_VALUE);
return result;
} else if ((left.getType() == ExpressionResultType.TIME_SERIES_VALUES ||
left.getType() == ExpressionResultType.SORTED_LIST ||
left.getType() == ExpressionResultType.RECORD_LIST)
&& right.getType() == ExpressionResultType.SINGLE_VALUE) {
return many2OneBinaryOp(left, right, opType);
} else if (left.getType() == ExpressionResultType.SINGLE_VALUE &&
(right.getType() == ExpressionResultType.TIME_SERIES_VALUES ||
right.getType() == ExpressionResultType.SORTED_LIST ||
right.getType() == ExpressionResultType.RECORD_LIST)) {
return many2OneBinaryOp(right, left, opType);
} else if (left.getType() == ExpressionResultType.TIME_SERIES_VALUES && right.getType() == ExpressionResultType.TIME_SERIES_VALUES) {
return seriesBinaryOp(right, left, opType);
}
throw new IllegalExpressionException("Unsupported binary operation.");
}
//scalar with scalar
private static double scalarBinaryOp(double leftValue, double rightValue, int opType) {
double calculatedResult = 0;
switch (opType) {
case MQEParser.ADD:
calculatedResult = leftValue + rightValue;
break;
case MQEParser.SUB:
calculatedResult = leftValue - rightValue;
break;
case MQEParser.MUL:
calculatedResult = leftValue * rightValue;
break;
case MQEParser.DIV:
calculatedResult = leftValue / rightValue;
break;
case MQEParser.MOD:
calculatedResult = leftValue % rightValue;
break;
}
return calculatedResult;
}
//series with series
private static ExpressionResult seriesBinaryOp(ExpressionResult seriesLeft,
ExpressionResult seriesRight,
int opType) throws IllegalExpressionException {
if (seriesLeft.getResults().size() == 1 && seriesRight.getResults().size() == 1) {
MQEValues mqeValuesL = seriesLeft.getResults().get(0);
MQEValues mqeValuesR = seriesRight.getResults().get(0);
for (int i = 0; i < mqeValuesL.getValues().size(); i++) {
//clean metric info
mqeValuesL.setMetric(null);
MQEValue valueL = mqeValuesL.getValues().get(i);
MQEValue valueR = mqeValuesR.getValues().get(i);
if (valueL.isEmptyValue() || valueR.isEmptyValue()) {
valueL.setEmptyValue(true);
valueL.setDoubleValue(0);
continue;
}
//time should be mapped
double newValue = scalarBinaryOp(valueL.getDoubleValue(), valueR.getDoubleValue(), opType);
mqeValuesL.getValues().get(i).setDoubleValue(newValue);
}
} else {
throw new IllegalExpressionException("Binary operation don't support labeled metrics.");
}
return seriesLeft;
}
//series or list with scalar
private static ExpressionResult many2OneBinaryOp(ExpressionResult manyResult, ExpressionResult singleResult, int opType) {
manyResult.getResults().forEach(mqeValues -> {
mqeValues.getValues().forEach(mqeValue -> {
if (!mqeValue.isEmptyValue()) {
double newValue = scalarBinaryOp(
mqeValue.getDoubleValue(), singleResult.getResults()
.get(0)
.getValues()
.get(0)
.getDoubleValue(), opType);
mqeValue.setDoubleValue(newValue);
}
});
});
return manyResult;
}
}

View File

@ -0,0 +1,72 @@
/*
* 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.graphql.mqe.rt.operation;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.function.Function;
import org.apache.skywalking.mqe.rt.grammar.MQEParser;
import org.apache.skywalking.oap.query.graphql.mqe.rt.exception.IllegalExpressionException;
import org.apache.skywalking.oap.query.graphql.type.mql.ExpressionResult;
public class FunctionOp {
public static ExpressionResult doFunction0Op(ExpressionResult expResult,
int opType) throws IllegalExpressionException {
switch (opType) {
case MQEParser.ABS:
return FunctionOp.transResult(expResult, Math::abs);
case MQEParser.CEIL:
return FunctionOp.transResult(expResult, Math::ceil);
case MQEParser.FLOOR:
return FunctionOp.transResult(expResult, Math::floor);
}
throw new IllegalExpressionException("Unsupported function.");
}
public static ExpressionResult doFunction1Op(ExpressionResult expResult,
int opType,
MQEParser.ParameterContext params) throws IllegalExpressionException {
switch (opType) {
case MQEParser.ROUND:
return FunctionOp.transResult(expResult, aDouble -> {
BigDecimal bd = BigDecimal.valueOf(aDouble);
return bd.setScale(Integer.parseInt(params.INTEGER().getText()), RoundingMode.HALF_UP).doubleValue();
});
}
throw new IllegalExpressionException("Unsupported function.");
}
private static ExpressionResult transResult(ExpressionResult expResult, Function<Double, Double> calculator) {
expResult.getResults().forEach(resultValues -> {
resultValues.getValues().forEach(mqeValue -> {
if (!mqeValue.isEmptyValue()) {
double newValue = calculator.apply(mqeValue.getDoubleValue());
mqeValue.setDoubleValue(newValue);
}
});
});
return expResult;
}
public static void main(String[] args) {
System.out.printf(String.valueOf(Math.floor(1.1112)));
}
}

View File

@ -0,0 +1,98 @@
/*
* 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.graphql.resolver;
import graphql.kickstart.tools.GraphQLQueryResolver;
import graphql.org.antlr.v4.runtime.misc.ParseCancellationException;
import java.text.DecimalFormat;
import java.util.Collections;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import org.apache.skywalking.oap.query.graphql.mqe.rt.MQEVisitor;
import org.apache.skywalking.oap.query.graphql.mqe.rt.exception.ParseErrorListener;
import org.apache.skywalking.oap.query.graphql.type.mql.ExpressionResult;
import org.apache.skywalking.oap.query.graphql.type.mql.ExpressionResultType;
import org.apache.skywalking.oap.server.core.query.input.Entity;
import org.apache.skywalking.oap.server.core.query.input.Duration;
import org.apache.skywalking.oap.server.library.module.ModuleManager;
import org.apache.skywalking.mqe.rt.grammar.MQELexer;
import org.apache.skywalking.mqe.rt.grammar.MQEParser;
public class MetricsExpressionQuery implements GraphQLQueryResolver {
private final MetricsQuery metricsQuery;
private final RecordsQuery recordsQuery;
private final DecimalFormat valueFormat = new DecimalFormat();
public MetricsExpressionQuery(ModuleManager moduleManager) {
this.metricsQuery = new MetricsQuery(moduleManager);
this.recordsQuery = new RecordsQuery(moduleManager);
this.valueFormat.setGroupingUsed(false);
}
public ExpressionResult returnTypeOfMQE(String expression) {
MQELexer lexer = new MQELexer(
CharStreams.fromString(expression));
lexer.addErrorListener(new ParseErrorListener());
MQEParser parser = new MQEParser(new CommonTokenStream(lexer));
parser.addErrorListener(new ParseErrorListener());
ParseTree tree;
try {
tree = parser.expression();
} catch (ParseCancellationException e) {
ExpressionResult errorResult = new ExpressionResult();
errorResult.setType(ExpressionResultType.UNKNOWN);
errorResult.setError(e.getMessage());
return errorResult;
}
MQEVisitor visitor = new MQEVisitor();
ExpressionResult parseResult = visitor.visit(tree);
parseResult.setResults(Collections.emptyList());
return parseResult;
}
public ExpressionResult execExpression(String expression, Entity entity, Duration duration) {
MQELexer lexer = new MQELexer(
CharStreams.fromString(expression));
lexer.addErrorListener(new ParseErrorListener());
MQEParser parser = new MQEParser(new CommonTokenStream(lexer));
parser.addErrorListener(new ParseErrorListener());
ParseTree tree;
try {
tree = parser.expression();
} catch (ParseCancellationException e) {
ExpressionResult errorResult = new ExpressionResult();
errorResult.setType(ExpressionResultType.UNKNOWN);
errorResult.setError(e.getMessage());
return errorResult;
}
MQEVisitor visitor = new MQEVisitor(metricsQuery, recordsQuery, entity, duration);
ExpressionResult parseResult = visitor.visit(tree);
parseResult.getResults().forEach(mqeValues -> {
mqeValues.getValues().forEach(mqeValue -> {
if (!mqeValue.isEmptyValue()) {
mqeValue.setValue(valueFormat.format(mqeValue.getDoubleValue()));
}
});
});
return parseResult;
}
}

View File

@ -0,0 +1,31 @@
/*
* 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.graphql.type.mql;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
@Data
public class ExpressionResult {
private ExpressionResultType type = ExpressionResultType.UNKNOWN;
private List<MQEValues> results = new ArrayList<>();
private String error;
}

View File

@ -0,0 +1,46 @@
/*
* 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.graphql.type.mql;
public enum ExpressionResultType {
/**
* Can't resolve the type of the given expression.
*/
UNKNOWN,
/**
* A single value
*/
SINGLE_VALUE,
/**
* A collection of time-series values.
* The value could have labels or not.
*/
TIME_SERIES_VALUES,
/**
* A collection of aggregated values through metric sort function.
*/
SORTED_LIST,
/**
* A collection of sampled records.
* When the original metric type is sampled records.
*/
RECORD_LIST
}

View File

@ -0,0 +1,32 @@
/*
* 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.graphql.type.mql;
import lombok.Data;
@Data
public class MQEValue {
private String id;
private String value;
private String traceID;
private double doubleValue;
private boolean isEmptyValue;
}

View File

@ -0,0 +1,34 @@
/*
* 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.graphql.type.mql;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
import lombok.Setter;
public class MQEValues {
@Getter
@Setter
private Metadata metric;
@Getter
@Setter
private List<MQEValue> values = new ArrayList<>();
}

View File

@ -0,0 +1,35 @@
/*
* 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.graphql.type.mql;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
import lombok.Setter;
import org.apache.skywalking.oap.server.core.query.type.KeyValue;
public class Metadata {
@Getter
@Setter
private String name;
@Getter
@Setter
private List<KeyValue> labels = new ArrayList<>();
}

@ -1 +1 @@
Subproject commit a711fd9d0f94a67a933eacf30a12976c6219c416
Subproject commit 957b0120af74dde1b2fca2449914739bd97c435f

@ -1 +1 @@
Subproject commit 986fe8b90e238e889ab93ae92dc5b7482bf122f5
Subproject commit dc22f8da6e5fe60fa0c4e90dc4e3f37361384b03