From 0f847438c780f99ce1b50fe7c585a0884280f33a Mon Sep 17 00:00:00 2001 From: Wan Kai Date: Mon, 5 Jun 2023 17:22:05 +0800 Subject: [PATCH] Support Metrics Query Expression(MQE) and allows users to do simple query-stage calculation through the expression. (#10881) --- docs/en/api/metrics-query-expression.md | 124 +++++ docs/en/api/query-protocol-deprecated.md | 84 ++++ docs/en/api/query-protocol.md | 97 ++-- docs/en/changes/changes.md | 4 + docs/menu.yml | 8 +- .../core/query/type/SelectedRecord.java | 2 +- .../server-query-plugin/mqe-grammar/pom.xml | 57 +++ .../skywalking/mqe/rt/grammar/MQELexer.g4 | 82 ++++ .../skywalking/mqe/rt/grammar/MQEParser.g4 | 67 +++ oap-server/server-query-plugin/pom.xml | 1 + .../promql/handler/PromQLApiHandler.java | 16 +- .../rt/exception/ParseErrorListener.java | 2 - .../query-graphql-plugin/pom.xml | 5 + .../query/graphql/GraphQLQueryProvider.java | 14 +- .../oap/query/graphql/mqe/rt/MQEVisitor.java | 426 ++++++++++++++++++ .../exception/IllegalExpressionException.java | 25 + .../mqe/rt/exception/ParseErrorListener.java | 38 ++ .../mqe/rt/operation/AggregationOp.java | 111 +++++ .../graphql/mqe/rt/operation/BinaryOp.java | 131 ++++++ .../graphql/mqe/rt/operation/FunctionOp.java | 72 +++ .../resolver/MetricsExpressionQuery.java | 98 ++++ .../graphql/type/mql/ExpressionResult.java | 31 ++ .../type/mql/ExpressionResultType.java | 46 ++ .../oap/query/graphql/type/mql/MQEValue.java | 32 ++ .../oap/query/graphql/type/mql/MQEValues.java | 34 ++ .../oap/query/graphql/type/mql/Metadata.java | 35 ++ .../src/main/resources/query-protocol | 2 +- skywalking-ui | 2 +- 28 files changed, 1563 insertions(+), 83 deletions(-) create mode 100644 docs/en/api/metrics-query-expression.md create mode 100644 docs/en/api/query-protocol-deprecated.md create mode 100644 oap-server/server-query-plugin/mqe-grammar/pom.xml create mode 100644 oap-server/server-query-plugin/mqe-grammar/src/main/antlr4/org/apache/skywalking/mqe/rt/grammar/MQELexer.g4 create mode 100644 oap-server/server-query-plugin/mqe-grammar/src/main/antlr4/org/apache/skywalking/mqe/rt/grammar/MQEParser.g4 create mode 100644 oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/MQEVisitor.java create mode 100644 oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/exception/IllegalExpressionException.java create mode 100644 oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/exception/ParseErrorListener.java create mode 100644 oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/operation/AggregationOp.java create mode 100644 oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/operation/BinaryOp.java create mode 100644 oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/operation/FunctionOp.java create mode 100644 oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/MetricsExpressionQuery.java create mode 100644 oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/ExpressionResult.java create mode 100644 oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/ExpressionResultType.java create mode 100644 oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/MQEValue.java create mode 100644 oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/MQEValues.java create mode 100644 oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/Metadata.java diff --git a/docs/en/api/metrics-query-expression.md b/docs/en/api/metrics-query-expression.md new file mode 100644 index 0000000000..87b0561389 --- /dev/null +++ b/docs/en/api/metrics-query-expression.md @@ -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 = Expression1 Expression2 Expression3 ... +``` + +The following document lists the operations supported by MQE. + +## Metrics Expression +### Common Value Metrics +Expression: +```text + +``` + +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 +{labels=',...', relabels=',...'} +``` +`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 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 +(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. + +(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(, , ) +``` + +`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) +``` diff --git a/docs/en/api/query-protocol-deprecated.md b/docs/en/api/query-protocol-deprecated.md new file mode 100644 index 0000000000..e6a2226a11 --- /dev/null +++ b/docs/en/api/query-protocol-deprecated.md @@ -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!]! +} +``` diff --git a/docs/en/api/query-protocol.md b/docs/en/api/query-protocol.md index fb7eb6a50c..95cccd8bf5 100644 --- a/docs/en/api/query-protocol.md +++ b/docs/en/api/query-protocol.md @@ -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 } ``` diff --git a/docs/en/changes/changes.md b/docs/en/changes/changes.md index ade246a373..03d4b663d5 100644 --- a/docs/en/changes/changes.md +++ b/docs/en/changes/changes.md @@ -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 diff --git a/docs/menu.yml b/docs/menu.yml index 25b93f08a1..8d1532820f 100644 --- a/docs/menu.yml +++ b/docs/menu.yml @@ -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" diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/SelectedRecord.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/SelectedRecord.java index e85ffe52ba..84128c8782 100644 --- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/SelectedRecord.java +++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/SelectedRecord.java @@ -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 diff --git a/oap-server/server-query-plugin/mqe-grammar/pom.xml b/oap-server/server-query-plugin/mqe-grammar/pom.xml new file mode 100644 index 0000000000..176876c0e9 --- /dev/null +++ b/oap-server/server-query-plugin/mqe-grammar/pom.xml @@ -0,0 +1,57 @@ + + + + + + server-query-plugin + org.apache.skywalking + 9.5.0-SNAPSHOT + + 4.0.0 + + mqe-grammar + jar + + + + org.antlr + antlr4-runtime + + + + + + + org.antlr + antlr4-maven-plugin + + + antlr + + antlr4 + + + + + true + + + + + diff --git a/oap-server/server-query-plugin/mqe-grammar/src/main/antlr4/org/apache/skywalking/mqe/rt/grammar/MQELexer.g4 b/oap-server/server-query-plugin/mqe-grammar/src/main/antlr4/org/apache/skywalking/mqe/rt/grammar/MQELexer.g4 new file mode 100644 index 0000000000..137cb01210 --- /dev/null +++ b/oap-server/server-query-plugin/mqe-grammar/src/main/antlr4/org/apache/skywalking/mqe/rt/grammar/MQELexer.g4 @@ -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; diff --git a/oap-server/server-query-plugin/mqe-grammar/src/main/antlr4/org/apache/skywalking/mqe/rt/grammar/MQEParser.g4 b/oap-server/server-query-plugin/mqe-grammar/src/main/antlr4/org/apache/skywalking/mqe/rt/grammar/MQEParser.g4 new file mode 100644 index 0000000000..8e463639c6 --- /dev/null +++ b/oap-server/server-query-plugin/mqe-grammar/src/main/antlr4/org/apache/skywalking/mqe/rt/grammar/MQEParser.g4 @@ -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; diff --git a/oap-server/server-query-plugin/pom.xml b/oap-server/server-query-plugin/pom.xml index ecc90f79a1..f637d6adc1 100644 --- a/oap-server/server-query-plugin/pom.xml +++ b/oap-server/server-query-plugin/pom.xml @@ -30,6 +30,7 @@ query-graphql-plugin zipkin-query-plugin + mqe-grammar promql-plugin diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/handler/PromQLApiHandler.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/handler/PromQLApiHandler.java index 3d24153216..247deede7c 100644 --- a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/handler/PromQLApiHandler.java +++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/handler/PromQLApiHandler.java @@ -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(); diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/exception/ParseErrorListener.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/exception/ParseErrorListener.java index 9bab42e86f..e54481c458 100644 --- a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/exception/ParseErrorListener.java +++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/exception/ParseErrorListener.java @@ -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, diff --git a/oap-server/server-query-plugin/query-graphql-plugin/pom.xml b/oap-server/server-query-plugin/query-graphql-plugin/pom.xml index d7f31c6c2f..b190b318a0 100644 --- a/oap-server/server-query-plugin/query-graphql-plugin/pom.xml +++ b/oap-server/server-query-plugin/query-graphql-plugin/pom.xml @@ -43,6 +43,11 @@ log-analyzer ${project.version} + + org.apache.skywalking + mqe-grammar + ${project.version} + com.graphql-java graphql-java diff --git a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/GraphQLQueryProvider.java b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/GraphQLQueryProvider.java index 47f040ac26..e0c9ead3ab 100644 --- a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/GraphQLQueryProvider.java +++ b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/GraphQLQueryProvider.java @@ -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())) diff --git a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/MQEVisitor.java b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/MQEVisitor.java new file mode 100644 index 0000000000..62453cab41 --- /dev/null +++ b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/MQEVisitor.java @@ -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 { + 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 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 queryLabelList = Collections.emptyList(); + List 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 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 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 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 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 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 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 queryLabelList, + List relabelList, + ExpressionResult result) throws IOException { + List metricsValuesList; + List 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 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 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); + } +} diff --git a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/exception/IllegalExpressionException.java b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/exception/IllegalExpressionException.java new file mode 100644 index 0000000000..ace094e759 --- /dev/null +++ b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/exception/IllegalExpressionException.java @@ -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); + } +} diff --git a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/exception/ParseErrorListener.java b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/exception/ParseErrorListener.java new file mode 100644 index 0000000000..1213458727 --- /dev/null +++ b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/exception/ParseErrorListener.java @@ -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); + } +} diff --git a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/operation/AggregationOp.java b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/operation/AggregationOp.java new file mode 100644 index 0000000000..8d9088ae70 --- /dev/null +++ b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/operation/AggregationOp.java @@ -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 aggregator) { + for (MQEValues resultValues : result.getResults()) { + OptionalDouble resultValue = aggregator.apply(resultValues); + if (resultValue.isPresent()) { + List 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> aggregator) { + for (MQEValues resultValues : result.getResults()) { + Optional resultValue = aggregator.apply(resultValues); + if (resultValue.isPresent()) { + List mqeValueList = new ArrayList<>(1); + mqeValueList.add(resultValue.get()); + resultValues.setValues(mqeValueList); + } else { + resultValues.setValues(Collections.emptyList()); + } + } + result.setType(ExpressionResultType.SINGLE_VALUE); + return result; + } +} diff --git a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/operation/BinaryOp.java b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/operation/BinaryOp.java new file mode 100644 index 0000000000..a839a3f255 --- /dev/null +++ b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/operation/BinaryOp.java @@ -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; + } +} diff --git a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/operation/FunctionOp.java b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/operation/FunctionOp.java new file mode 100644 index 0000000000..0b39fa73a8 --- /dev/null +++ b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/operation/FunctionOp.java @@ -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 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))); + } +} diff --git a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/MetricsExpressionQuery.java b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/MetricsExpressionQuery.java new file mode 100644 index 0000000000..4f8735575d --- /dev/null +++ b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/MetricsExpressionQuery.java @@ -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; + } +} diff --git a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/ExpressionResult.java b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/ExpressionResult.java new file mode 100644 index 0000000000..ea467bc017 --- /dev/null +++ b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/ExpressionResult.java @@ -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 results = new ArrayList<>(); + private String error; +} diff --git a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/ExpressionResultType.java b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/ExpressionResultType.java new file mode 100644 index 0000000000..5820e04c62 --- /dev/null +++ b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/ExpressionResultType.java @@ -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 +} diff --git a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/MQEValue.java b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/MQEValue.java new file mode 100644 index 0000000000..d6a7b548fd --- /dev/null +++ b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/MQEValue.java @@ -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; +} diff --git a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/MQEValues.java b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/MQEValues.java new file mode 100644 index 0000000000..7f45f41f5f --- /dev/null +++ b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/MQEValues.java @@ -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 values = new ArrayList<>(); +} diff --git a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/Metadata.java b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/Metadata.java new file mode 100644 index 0000000000..f63923166f --- /dev/null +++ b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/Metadata.java @@ -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 labels = new ArrayList<>(); +} diff --git a/oap-server/server-query-plugin/query-graphql-plugin/src/main/resources/query-protocol b/oap-server/server-query-plugin/query-graphql-plugin/src/main/resources/query-protocol index a711fd9d0f..957b0120af 160000 --- a/oap-server/server-query-plugin/query-graphql-plugin/src/main/resources/query-protocol +++ b/oap-server/server-query-plugin/query-graphql-plugin/src/main/resources/query-protocol @@ -1 +1 @@ -Subproject commit a711fd9d0f94a67a933eacf30a12976c6219c416 +Subproject commit 957b0120af74dde1b2fca2449914739bd97c435f diff --git a/skywalking-ui b/skywalking-ui index 986fe8b90e..dc22f8da6e 160000 --- a/skywalking-ui +++ b/skywalking-ui @@ -1 +1 @@ -Subproject commit 986fe8b90e238e889ab93ae92dc5b7482bf122f5 +Subproject commit dc22f8da6e5fe60fa0c4e90dc4e3f37361384b03