This is an automated email from the ASF dual-hosted git repository.
wusheng pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking.git
The following commit(s) were added to refs/heads/master by this push:
new f54f639 Enhance Envoy metrics service analyzer by MAL (#6091)
f54f639 is described below
commit f54f639c4d9207c20490519121eeabe6acf37374
Author: Zhenxu Ke <[email protected]>
AuthorDate: Sat Jan 2 14:34:54 2021 +0800
Enhance Envoy metrics service analyzer by MAL (#6091)
---
.github/workflows/e2e.istio.yaml | 94 +++++++++++++++
CHANGES.md | 1 +
apm-dist-es7/src/main/assembly/binary-es7.xml | 1 +
apm-dist/src/main/assembly/binary.xml | 1 +
docs/en/concepts-and-designs/mal.md | 8 +-
docs/en/setup/envoy/als_setting.md | 2 +-
docs/en/setup/envoy/config.yaml | 3 +
.../envoy/examples/metrics/docker-compose.yaml | 2 +-
docs/en/setup/envoy/examples/metrics/envoy.yaml | 5 +-
docs/en/setup/envoy/metrics_service_setting.md | 11 ++
.../oap/meter/analyzer/dsl/SampleFamily.java | 43 ++++++-
.../prometheus/PrometheusMetricConverter.java | 23 ++--
.../oap/meter/analyzer/dsl/AggregationTest.java | 89 +++++++++++++-
oap-server/server-bootstrap/pom.xml | 3 +-
.../main/resources/envoy-metrics-rules/envoy.yaml | 59 ++++++++++
.../src/main/resources/oal/envoy.oal | 22 ----
.../ui-initialized-templates/istio-dp.yml | 82 +++++++++++++
.../resources/ui-initialized-templates/istio.yml | 2 +-
.../envoy-metrics-receiver-plugin/pom.xml | 5 +
.../receiver/envoy/EnvoyMetricReceiverConfig.java | 7 ++
.../envoy/EnvoyMetricReceiverProvider.java | 17 +--
.../oap/server/receiver/envoy/EnvoyOALDefine.java | 35 ------
.../receiver/envoy/MetricServiceGRPCHandler.java | 123 +++++++------------
.../adapters/ProtoMetricFamily2MetricsAdapter.java | 84 +++++++++++++
.../org/apache/skywalking/e2e/utils/Yamls.java | 4 +
.../skywalking/e2e/metrics/MetricsQuery.java | 12 ++
.../skywalking/e2e/mesh/MetricsServiceE2E.java | 130 +++++++++++++++++++++
.../metricsservice/instances-istio-dp-reviews.yml | 22 ++++
.../expected/metricsservice/instances.yml | 18 +++
.../resources/expected/metricsservice/services.yml | 28 +++++
30 files changed, 768 insertions(+), 168 deletions(-)
diff --git a/.github/workflows/e2e.istio.yaml b/.github/workflows/e2e.istio.yaml
index 4d24cee..effb0fb 100644
--- a/.github/workflows/e2e.istio.yaml
+++ b/.github/workflows/e2e.istio.yaml
@@ -129,3 +129,97 @@ jobs:
- name: Clean up
if: ${{ always() }}
run: minikube delete
+
+ metrics-service:
+ runs-on: ubuntu-16.04
+ timeout-minutes: 60
+ name: MetricsService
+ steps:
+ - uses: actions/checkout@v2
+ with:
+ submodules: true
+
+ - uses: actions/cache@v2
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: ${{ runner.os }}-maven-
+
+ - name: Build Docker Image
+ run: make docker
+
+ - name: Prepare envrionment
+ run: bash ${SCRIPTS_DIR}/pre.sh
+
+ - name: Install Minikube
+ run: bash ${SCRIPTS_DIR}/minikube.sh start
+
+ - name: Install Istio
+ run: |
+ bash ${SCRIPTS_DIR}/istio.sh \
+ --set profile=demo \
+ --set
meshConfig.defaultConfig.envoyMetricsService.address=skywalking-oap.istio-system:11800
\
+ --set values.telemetry.v2.enabled=false # disable the
metadata-exchange extension intentionally to make sure metrics service doesn't
rely on it
+
+ - name: Install SkyWalking
+ run: |
+ git clone https://github.com/apache/skywalking-kubernetes.git
+ cd skywalking-kubernetes
+ git reset --hard dd749f25913830c47a97430618cefc4167612e75
+ cd chart
+ helm dep up skywalking
+ helm -n istio-system install skywalking skywalking \
+ --set fullnameOverride=skywalking \
+ --set elasticsearch.replicas=1 \
+ --set elasticsearch.minimumMasterNodes=1 \
+ --set elasticsearch.imageTag=7.5.1 \
+ --set oap.replicas=1 \
+ --set ui.image.repository=skywalking/ui \
+ --set ui.image.tag=$TAG \
+ --set oap.image.tag=$TAG \
+ --set oap.image.repository=skywalking/oap \
+ --set oap.storageType=elasticsearch7
+ kubectl -n istio-system get pods
+
+ sleep 3
+ kubectl -n istio-system wait --for=condition=available
deployments/skywalking-oap --timeout=1200s
+ kubectl get pods -A -o wide --show-labels
+ kubectl get services -A -o wide
+
+ - name: Deploy demo services
+ run: bash ${SCRIPTS_DIR}/demo.sh
+
+ - name: Cluster Info
+ if: ${{ failure() }}
+ run: |
+ df -h
+ minikube logs
+ minikube status
+
+ - name: Set up Minikube tunnel
+ run: |
+ mkdir /tmp/minikube-tunnel
+ minikube tunnel > /tmp/minikube-tunnel/a.log &
+ export POD_NAME=$(kubectl get pods -n istio-system -l
"app=skywalking,release=skywalking,component=ui" -o
jsonpath="{.items[0].metadata.name}")
+ echo $POD_NAME
+ kubectl -n istio-system port-forward $POD_NAME 8080:8080 >
/tmp/minikube-tunnel/b.log &
+
+ - name: Run E2E test
+ run: |
+ export GATEWAY_HOST=$(minikube ip)
+ export GATEWAY_PORT=$(kubectl -n istio-system get service
istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="http2")].nodePort}')
+ export WEBAPP_HOST=127.0.0.1
+ export WEBAPP_PORT=8080
+
+ ./mvnw --batch-mode -f test/e2e/pom.xml -am -DfailIfNoTests=false
verify -Dit.test=org.apache.skywalking.e2e.mesh.MetricsServiceE2E
+
+ - name: Logs
+ if: ${{ failure() }}
+ run: |
+ kubectl -n istio-system logs --tail=10000 -l
"app=skywalking,release=skywalking,component=ui"
+ kubectl -n istio-system logs --tail=10000 -l
"app=skywalking,release=skywalking,component=oap"
+ cat /tmp/minikube-tunnel/*
+
+ - name: Clean up
+ if: ${{ always() }}
+ run: minikube delete
diff --git a/CHANGES.md b/CHANGES.md
index 47939e5..e55a97b 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -42,6 +42,7 @@ Release Notes.
* Improve query performance in storage-influxdb-plugin.
* Fix the uuid field in GRPCConfigWatcherRegister is not updated.
* Support Envoy {AccessLog,Metrics}Service API V3.
+* Adopt the [MAL](docs/en/concepts-and-designs/mal.md) in Envoy metrics
service analyzer.
#### UI
* Fix un-removed tags in trace query.
diff --git a/apm-dist-es7/src/main/assembly/binary-es7.xml
b/apm-dist-es7/src/main/assembly/binary-es7.xml
index 97d5790..d819c48 100644
--- a/apm-dist-es7/src/main/assembly/binary-es7.xml
+++ b/apm-dist-es7/src/main/assembly/binary-es7.xml
@@ -53,6 +53,7 @@
<include>endpoint-name-grouping.yml</include>
<include>oal/*.oal</include>
<include>fetcher-prom-rules/*.yaml</include>
+ <include>envoy-metrics-rules/*.yaml</include>
<include>meter-analyzer-config/*.yaml</include>
<include>otel-oc-rules/*</include>
<include>ui-initialized-templates/*</include>
diff --git a/apm-dist/src/main/assembly/binary.xml
b/apm-dist/src/main/assembly/binary.xml
index b6d460b..4ddc268 100644
--- a/apm-dist/src/main/assembly/binary.xml
+++ b/apm-dist/src/main/assembly/binary.xml
@@ -53,6 +53,7 @@
<include>endpoint-name-grouping.yml</include>
<include>oal/*.oal</include>
<include>fetcher-prom-rules/*.yaml</include>
+ <include>envoy-metrics-rules/*.yaml</include>
<include>meter-analyzer-config/*.yaml</include>
<include>otel-oc-rules/*</include>
<include>ui-initialized-templates/*</include>
diff --git a/docs/en/concepts-and-designs/mal.md
b/docs/en/concepts-and-designs/mal.md
index 30834af..b1edc70 100644
--- a/docs/en/concepts-and-designs/mal.md
+++ b/docs/en/concepts-and-designs/mal.md
@@ -59,7 +59,7 @@ Between two scalars: they evaluate to another scalar that is
the result of the o
1 + 2
```
-Between a sample family and a scalar, the operator is applied to the value of
every sample in the smaple family. For example:
+Between a sample family and a scalar, the operator is applied to the value of
every sample in the sample family. For example:
```
instance_trace_count + 2
@@ -110,9 +110,9 @@ Sample family supports the following aggregation operations
that can be used to
resulting in a new sample family of fewer samples(even single one) with
aggregated values:
- sum (calculate sum over dimensions)
- - min (select minimum over dimensions) (TODO)
- - max (select maximum over dimensions) (TODO)
- - avg (calculate the average over dimensions) (TODO)
+ - min (select minimum over dimensions)
+ - max (select maximum over dimensions)
+ - avg (calculate the average over dimensions)
These operations can be used to aggregate over all label dimensions or
preserve distinct dimensions by inputting `by` parameter.
diff --git a/docs/en/setup/envoy/als_setting.md
b/docs/en/setup/envoy/als_setting.md
index 7a5bb3e..811fe0d 100644
--- a/docs/en/setup/envoy/als_setting.md
+++ b/docs/en/setup/envoy/als_setting.md
@@ -23,7 +23,7 @@ You need three steps to open ALS.
`k8s-mesh` uses the metadata from Kubernetes cluster, hence in this analyzer
OAP needs access roles to `Pod`, `Service`, and `Endpoints`;
`mx-mesh` uses the Envoy metadata exchange mechanism to get the service name,
etc.,
this analyzer requires Istio to enable the metadata exchange filter(you can
enable it by
-`--set telemetry.v2.enabled=true`, or if you're using Istio 1.7+ and
installing it with profile `demo`/`preview`,
+`--set values.telemetry.v2.enabled=true`, or if you're using Istio 1.7+ and
installing it with profile `demo`/`preview`,
it should be enabled then).
Setting system env variable **SW_ENVOY_METRIC_ALS_HTTP_ANALYSIS** to activate
the analyzer,
such as `SW_ENVOY_METRIC_ALS_HTTP_ANALYSIS=k8s-mesh`.
diff --git a/docs/en/setup/envoy/config.yaml b/docs/en/setup/envoy/config.yaml
index 3a00c2e..7037f2a 100644
--- a/docs/en/setup/envoy/config.yaml
+++ b/docs/en/setup/envoy/config.yaml
@@ -39,6 +39,9 @@ node:
metadata:
skywalking: iscool
envoy: isawesome
+ LABELS:
+ app: test-app
+ NAME: service-instance-name
static_resources:
listeners:
diff --git a/docs/en/setup/envoy/examples/metrics/docker-compose.yaml
b/docs/en/setup/envoy/examples/metrics/docker-compose.yaml
index 2250d52..6f069da 100644
--- a/docs/en/setup/envoy/examples/metrics/docker-compose.yaml
+++ b/docs/en/setup/envoy/examples/metrics/docker-compose.yaml
@@ -17,7 +17,7 @@
version: "3"
services:
envoy:
- image: envoyproxy/envoy-alpine:latest
+ image: envoyproxy/envoy-alpine:v1.16.2
command: /usr/local/bin/envoy -c /etc/envoy.yaml --service-cluster
envoy-proxy
ports:
- 10000:10000
diff --git a/docs/en/setup/envoy/examples/metrics/envoy.yaml
b/docs/en/setup/envoy/examples/metrics/envoy.yaml
index af688f8..8539bcd 100644
--- a/docs/en/setup/envoy/examples/metrics/envoy.yaml
+++ b/docs/en/setup/envoy/examples/metrics/envoy.yaml
@@ -39,6 +39,9 @@ node:
metadata:
skywalking: iscool
envoy: isawesome
+ LABELS:
+ app: test-app
+ NAME: service-instance-name
static_resources:
listeners:
@@ -82,7 +85,7 @@ static_resources:
- endpoint:
address:
socket_address:
- address: skywalking
+ address: host.docker.internal
port_value: 11800
- name: service_google
diff --git a/docs/en/setup/envoy/metrics_service_setting.md
b/docs/en/setup/envoy/metrics_service_setting.md
index c58d81f..430ad81 100644
--- a/docs/en/setup/envoy/metrics_service_setting.md
+++ b/docs/en/setup/envoy/metrics_service_setting.md
@@ -39,6 +39,17 @@ A more complete static configuration, can be observed
[here](config.yaml).
Note that Envoy can also be configured dynamically through [xDS
Protocol](https://github.com/envoyproxy/data-plane-api/blob/master/XDS_PROTOCOL.md).
+**Attention**: Only use this when Envoy is under Istio's control, because
SkyWalking needs to parse the service name and service instance name from the
metadata that is injected by Istio. However, if you want to use this without
Istio, you need to inject the metadata yourself like this:
+
+```yaml
+node:
+ # ... other configs
+ metadata:
+ LABELS:
+ app: test-app
+ NAME: service-instance-name
+```
+
# Metrics data
Some of the Envoy statistics are listed in this
[list](https://www.envoyproxy.io/docs/envoy/latest/configuration/statistics). A
sample data that contains identifier can be found [here](identify.json), while
the metrics only can be observed [here](metrics.json).
diff --git
a/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/SampleFamily.java
b/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/SampleFamily.java
index 24e9fb4..9580d85 100644
---
a/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/SampleFamily.java
+++
b/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/SampleFamily.java
@@ -28,6 +28,7 @@ import groovy.lang.Closure;
import io.vavr.Function2;
import io.vavr.Tuple;
import io.vavr.Tuple2;
+import java.util.function.DoubleBinaryOperator;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.EqualsAndHashCode;
@@ -51,6 +52,7 @@ import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.mapping;
import static java.util.stream.Collectors.toList;
@@ -161,22 +163,55 @@ public class SampleFamily {
/* Aggregation operators */
public SampleFamily sum(List<String> by) {
+ return aggregate(by, Double::sum);
+ }
+
+ public SampleFamily max(List<String> by) {
+ return aggregate(by, Double::max);
+ }
+
+ public SampleFamily min(List<String> by) {
+ return aggregate(by, Double::min);
+ }
+
+ public SampleFamily avg(List<String> by) {
+ ExpressionParsingContext.get().ifPresent(ctx ->
ctx.aggregationLabels.addAll(by));
+ if (this == EMPTY) {
+ return EMPTY;
+ }
+ if (by == null) {
+ double result = Arrays.stream(samples).mapToDouble(s ->
s.value).average().orElse(0.0D);
+ return SampleFamily.build(this.context,
newSample(ImmutableMap.of(), samples[0].timestamp, result));
+ }
+ return SampleFamily.build(
+ this.context,
+ Arrays.stream(samples)
+ .map(sample -> Tuple.of(by.stream()
+ .collect(toImmutableMap(labelKey
-> labelKey, labelKey -> sample.labels.getOrDefault(labelKey, ""))), sample))
+ .collect(groupingBy(Tuple2::_1, mapping(Tuple2::_2,
toList())))
+ .entrySet().stream()
+ .map(entry -> newSample(entry.getKey(),
entry.getValue().get(0).timestamp, entry.getValue().stream()
+
.mapToDouble(s -> s.value).average().orElse(0.0D)))
+ .toArray(Sample[]::new)
+ );
+ }
+
+ protected SampleFamily aggregate(List<String> by, DoubleBinaryOperator
aggregator) {
ExpressionParsingContext.get().ifPresent(ctx ->
ctx.aggregationLabels.addAll(by));
if (this == EMPTY) {
return EMPTY;
}
if (by == null) {
- double result = Arrays.stream(samples).mapToDouble(s ->
s.value).reduce(Double::sum).orElse(0.0D);
+ double result = Arrays.stream(samples).mapToDouble(s ->
s.value).reduce(aggregator).orElse(0.0D);
return SampleFamily.build(this.context,
newSample(ImmutableMap.of(), samples[0].timestamp, result));
}
return SampleFamily.build(this.context, Arrays.stream(samples)
.map(sample -> Tuple.of(by.stream()
- .collect(ImmutableMap
- .toImmutableMap(labelKey -> labelKey, labelKey ->
sample.labels.getOrDefault(labelKey, ""))), sample))
+ .collect(toImmutableMap(labelKey -> labelKey, labelKey ->
sample.labels.getOrDefault(labelKey, ""))), sample))
.collect(groupingBy(Tuple2::_1, mapping(Tuple2::_2, toList())))
.entrySet().stream()
.map(entry -> newSample(entry.getKey(),
entry.getValue().get(0).timestamp, entry.getValue().stream()
- .mapToDouble(s -> s.value).reduce(Double::sum).orElse(0.0D)))
+ .mapToDouble(s -> s.value).reduce(aggregator).orElse(0.0D)))
.toArray(Sample[]::new));
}
diff --git
a/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/prometheus/PrometheusMetricConverter.java
b/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/prometheus/PrometheusMetricConverter.java
index 47c5e89..9af3960 100644
---
a/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/prometheus/PrometheusMetricConverter.java
+++
b/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/prometheus/PrometheusMetricConverter.java
@@ -86,15 +86,15 @@ public class PrometheusMetricConverter {
private Stream<Tuple2<String, SampleFamily>> convertMetric(Metric metric) {
return Match(metric).of(
Case($(instanceOf(Histogram.class)), t -> Stream.of(
- Tuple.of(metric.getName() + "_count",
SampleFamilyBuilder.newBuilder(Sample.builder().name(metric.getName() +
"_count")
+ Tuple.of(escapedName(metric.getName() + "_count"),
SampleFamilyBuilder.newBuilder(Sample.builder().name(escapedName(metric.getName()
+ "_count"))
.timestamp(metric.getTimestamp()).labels(ImmutableMap.copyOf(metric.getLabels())).value(((Histogram)
metric).getSampleCount()).build()).build()),
- Tuple.of(metric.getName() + "_sum",
SampleFamilyBuilder.newBuilder(Sample.builder().name(metric.getName() + "_sum")
+ Tuple.of(escapedName(metric.getName() + "_sum"),
SampleFamilyBuilder.newBuilder(Sample.builder().name(escapedName(metric.getName()
+ "_sum"))
.timestamp(metric.getTimestamp()).labels(ImmutableMap.copyOf(metric.getLabels())).value(((Histogram)
metric).getSampleSum()).build()).build()),
convertToSample(metric).orElse(NIL))),
Case($(instanceOf(Summary.class)), t -> Stream.of(
- Tuple.of(metric.getName() + "_count",
SampleFamilyBuilder.newBuilder(Sample.builder().name(metric.getName() +
"_count")
+ Tuple.of(escapedName(metric.getName() + "_count"),
SampleFamilyBuilder.newBuilder(Sample.builder().name(escapedName(metric.getName()
+ "_count"))
.timestamp(metric.getTimestamp()).labels(ImmutableMap.copyOf(metric.getLabels())).value(((Summary)
metric).getSampleCount()).build()).build()),
- Tuple.of(metric.getName() + "_sum",
SampleFamilyBuilder.newBuilder(Sample.builder().name(metric.getName() + "_sum")
+ Tuple.of(escapedName(metric.getName() + "_sum"),
SampleFamilyBuilder.newBuilder(Sample.builder().name(escapedName(metric.getName()
+ "_sum"))
.timestamp(metric.getTimestamp()).labels(ImmutableMap.copyOf(metric.getLabels())).value(((Summary)
metric).getSampleSum()).build()).build()),
convertToSample(metric).orElse(NIL))),
Case($(), t -> Stream.of(convertToSample(metric).orElse(NIL)))
@@ -104,13 +104,13 @@ public class PrometheusMetricConverter {
private Optional<Tuple2<String, SampleFamily>> convertToSample(Metric
metric) {
Sample[] ss = Match(metric).of(
Case($(instanceOf(Counter.class)), t ->
Collections.singletonList(Sample.builder()
- .name(t.getName())
+ .name(escapedName(t.getName()))
.labels(ImmutableMap.copyOf(t.getLabels()))
.timestamp(t.getTimestamp())
.value(t.getValue())
.build())),
Case($(instanceOf(Gauge.class)), t ->
Collections.singletonList(Sample.builder()
- .name(t.getName())
+ .name(escapedName(t.getName()))
.labels(ImmutableMap.copyOf(t.getLabels()))
.timestamp(t.getTimestamp())
.value(t.getValue())
@@ -118,7 +118,7 @@ public class PrometheusMetricConverter {
Case($(instanceOf(Histogram.class)), t -> t.getBuckets()
.entrySet().stream()
.map(b -> Sample.builder()
- .name(t.getName())
+ .name(escapedName(t.getName()))
.labels(ImmutableMap.<String, String>builder()
.putAll(t.getLabels())
.put("le", b.getKey().toString())
@@ -129,7 +129,7 @@ public class PrometheusMetricConverter {
Case($(instanceOf(Summary.class)),
t -> t.getQuantiles().entrySet().stream()
.map(b -> Sample.builder()
- .name(t.getName())
+ .name(escapedName(t.getName()))
.labels(ImmutableMap.<String, String>builder()
.putAll(t.getLabels())
.put("quantile", b.getKey().toString())
@@ -141,6 +141,11 @@ public class PrometheusMetricConverter {
if (ss.length < 1) {
return Optional.empty();
}
- return Optional.of(Tuple.of(metric.getName(),
SampleFamilyBuilder.newBuilder(ss).build()));
+ return Optional.of(Tuple.of(escapedName(metric.getName()),
SampleFamilyBuilder.newBuilder(ss).build()));
+ }
+
+ // Returns the escaped name of the given one, with "." replaced by "_"
+ protected String escapedName(final String name) {
+ return name.replaceAll("\\.", "_");
}
}
diff --git
a/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/dsl/AggregationTest.java
b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/dsl/AggregationTest.java
index 72ddef5..78934c1 100644
---
a/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/dsl/AggregationTest.java
+++
b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/dsl/AggregationTest.java
@@ -81,6 +81,93 @@ public class AggregationTest {
).build()),
false,
},
+
+ {
+ "min",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc",
"t3")).value(100).build(),
+ Sample.builder().labels(of("idc", "t1")).value(50).build(),
+ Sample.builder().labels(of("idc", "t2")).value(3).build()
+ ).build()),
+ "http_success_request.min()",
+
Result.success(SampleFamilyBuilder.newBuilder(Sample.builder().labels(ImmutableMap.of()).value(3).build()).build()),
+ false,
+ },
+ {
+ "min-by",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).build(),
+ Sample.builder().labels(of("idc", "t3", "region", "cn",
"svc", "catalog")).value(50).build(),
+ Sample.builder().labels(of("idc", "t1", "region", "us",
"svc", "product")).value(50).build(),
+ Sample.builder().labels(of("idc", "t1", "region", "us",
"instance", "10.0.0.1")).value(100).build(),
+ Sample.builder().labels(of("idc", "t3", "region", "cn",
"instance", "10.0.0.1")).value(3).build()
+ ).build()),
+ "http_success_request.min(by = ['region', 'idc'])",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1", "region",
"")).value(50).build(),
+ Sample.builder().labels(of("idc", "t1", "region",
"us")).value(50).build(),
+ Sample.builder().labels(of("idc", "t3", "region",
"cn")).value(3).build()
+ ).build()),
+ false,
+ },
+
+ {
+ "max",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc",
"t3")).value(100).build(),
+ Sample.builder().labels(of("idc", "t1")).value(50).build(),
+ Sample.builder().labels(of("idc", "t2")).value(3).build()
+ ).build()),
+ "http_success_request.max()",
+
Result.success(SampleFamilyBuilder.newBuilder(Sample.builder().labels(ImmutableMap.of()).value(100).build()).build()),
+ false,
+ },
+ {
+ "max-by",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).build(),
+ Sample.builder().labels(of("idc", "t3", "region", "cn",
"svc", "catalog")).value(50).build(),
+ Sample.builder().labels(of("idc", "t1", "region", "us",
"svc", "product")).value(50).build(),
+ Sample.builder().labels(of("idc", "t1", "region", "us",
"instance", "10.0.0.1")).value(100).build(),
+ Sample.builder().labels(of("idc", "t3", "region", "cn",
"instance", "10.0.0.1")).value(3).build()
+ ).build()),
+ "http_success_request.max(by = ['region', 'idc'])",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1", "region",
"")).value(50).build(),
+ Sample.builder().labels(of("idc", "t1", "region",
"us")).value(100).build(),
+ Sample.builder().labels(of("idc", "t3", "region",
"cn")).value(50).build()
+ ).build()),
+ false,
+ },
+
+ {
+ "avg",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc",
"t3")).value(100).build(),
+ Sample.builder().labels(of("idc", "t1")).value(50).build(),
+ Sample.builder().labels(of("idc", "t2")).value(3).build()
+ ).build()),
+ "http_success_request.avg()",
+
Result.success(SampleFamilyBuilder.newBuilder(Sample.builder().labels(ImmutableMap.of()).value(51).build()).build()),
+ false,
+ },
+ {
+ "avg-by",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).build(),
+ Sample.builder().labels(of("idc", "t3", "region", "cn",
"svc", "catalog")).value(51).build(),
+ Sample.builder().labels(of("idc", "t1", "region", "us",
"svc", "product")).value(50).build(),
+ Sample.builder().labels(of("idc", "t1", "region", "us",
"instance", "10.0.0.1")).value(100).build(),
+ Sample.builder().labels(of("idc", "t3", "region", "cn",
"instance", "10.0.0.1")).value(3).build()
+ ).build()),
+ "http_success_request.avg(by = ['region', 'idc'])",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1", "region",
"")).value(50).build(),
+ Sample.builder().labels(of("idc", "t1", "region",
"us")).value(75).build(),
+ Sample.builder().labels(of("idc", "t3", "region",
"cn")).value(27).build()
+ ).build()),
+ false,
+ },
});
}
@@ -102,4 +189,4 @@ public class AggregationTest {
}
assertThat(r, is(want));
}
-}
\ No newline at end of file
+}
diff --git a/oap-server/server-bootstrap/pom.xml
b/oap-server/server-bootstrap/pom.xml
index 78db4ed..8c63641 100644
--- a/oap-server/server-bootstrap/pom.xml
+++ b/oap-server/server-bootstrap/pom.xml
@@ -259,6 +259,7 @@
<exclude>endpoint-name-grouping.yml</exclude>
<exclude>oal/</exclude>
<exclude>fetcher-prom-rules/</exclude>
+ <exclude>envoy-metrics-rules/</exclude>
<exclude>meter-analyzer-config/</exclude>
<exclude>otel-oc-rules/</exclude>
<exclude>ui-initialized-templates/</exclude>
@@ -267,4 +268,4 @@
</plugin>
</plugins>
</build>
-</project>
\ No newline at end of file
+</project>
diff --git
a/oap-server/server-bootstrap/src/main/resources/envoy-metrics-rules/envoy.yaml
b/oap-server/server-bootstrap/src/main/resources/envoy-metrics-rules/envoy.yaml
new file mode 100644
index 0000000..a8c5a8b
--- /dev/null
+++
b/oap-server/server-bootstrap/src/main/resources/envoy-metrics-rules/envoy.yaml
@@ -0,0 +1,59 @@
+# 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.
+
+# This will parse a textual representation of a duration. The formats
+# accepted are based on the ISO-8601 duration format {@code PnDTnHnMn.nS}
+# with days considered to be exactly 24 hours.
+# <p>
+# Examples:
+# <pre>
+# "PT20.345S" -- parses as "20.345 seconds"
+# "PT15M" -- parses as "15 minutes" (where a minute is 60 seconds)
+# "PT10H" -- parses as "10 hours" (where an hour is 3600 seconds)
+# "P2D" -- parses as "2 days" (where a day is 24 hours or 86400
seconds)
+# "P2DT3H4M" -- parses as "2 days, 3 hours and 4 minutes"
+# "P-6H3M" -- parses as "-6 hours and +3 minutes"
+# "-P6H3M" -- parses as "-6 hours and -3 minutes"
+# "-P-6H+3M" -- parses as "+6 hours and -3 minutes"
+# </pre>
+
+expSuffix: tag({tags -> tags.cluster = 'istio-dp::' +
tags.cluster}).instance(['cluster'], ['instance'])
+metricPrefix: envoy
+metricsRules:
+ - name: heap_memory_used
+ exp: server_memory_heap_size
+ - name: heap_memory_max_used
+ exp: server_memory_heap_size.max(['cluster', 'instance'])
+ - name: memory_allocated
+ exp: server_memory_allocated
+ - name: memory_allocated_max
+ exp: server_memory_allocated.max(['cluster', 'instance'])
+ - name: memory_physical_size
+ exp: server_memory_physical_size
+ - name: memory_physical_size_max
+ exp: server_memory_physical_size.max(['cluster', 'instance'])
+
+ - name: total_connections_used
+ exp: server_total_connections.max(['cluster', 'instance'])
+ - name: parent_connections_used
+ exp: server_parent_connections.max(['cluster', 'instance'])
+
+ - name: worker_threads
+ exp: server_concurrency
+ - name: worker_threads_max
+ exp: server_concurrency.max(['cluster', 'instance'])
+
+ - name: bug_failures
+ exp: server_envoy_bug_failures
diff --git a/oap-server/server-bootstrap/src/main/resources/oal/envoy.oal
b/oap-server/server-bootstrap/src/main/resources/oal/envoy.oal
deleted file mode 100644
index 90d7b4d..0000000
--- a/oap-server/server-bootstrap/src/main/resources/oal/envoy.oal
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * 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.
- *
- */
-
-// Envoy instance metrics
-envoy_heap_memory_max_used = from(EnvoyInstanceMetric.value).filter(metricName
== "server.memory_heap_size").maxDouble();
-envoy_total_connections_used =
from(EnvoyInstanceMetric.value).filter(metricName ==
"server.total_connections").maxDouble();
-envoy_parent_connections_used =
from(EnvoyInstanceMetric.value).filter(metricName ==
"server.parent_connections").maxDouble();
\ No newline at end of file
diff --git
a/oap-server/server-bootstrap/src/main/resources/ui-initialized-templates/istio-dp.yml
b/oap-server/server-bootstrap/src/main/resources/ui-initialized-templates/istio-dp.yml
new file mode 100644
index 0000000..9441b95
--- /dev/null
+++
b/oap-server/server-bootstrap/src/main/resources/ui-initialized-templates/istio-dp.yml
@@ -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.
+
+templates:
+ - name: "Istio Data Plane"
+ type: "DASHBOARD"
+ configuration: |-
+ [
+ {
+ "name": "Istio Data Plane",
+ "type": "service",
+ "serviceGroup": "istio-dp",
+ "children": [
+ {
+ "name": "Data Plane",
+ "children": [
+ {
+ "width": "3",
+ "title": "Heap Memory Used",
+ "height": 350,
+ "entityType": "ServiceInstance",
+ "independentSelector": false,
+ "metricType": "REGULAR_VALUE",
+ "metricName":
"envoy_heap_memory_max_used,envoy_heap_memory_used,envoy_memory_allocated_max,envoy_memory_allocated,envoy_memory_physical_size,envoy_memory_physical_size_max",
+ "queryMetricType": "readMetricsValues",
+ "chartType": "ChartLine",
+ "unit": "MB",
+ "aggregation": "/",
+ "aggregationNum": "1048576"
+ },
+ {
+ "width": "3",
+ "title": "Connections Used",
+ "height": 350,
+ "entityType": "ServiceInstance",
+ "independentSelector": false,
+ "metricType": "REGULAR_VALUE",
+ "metricName":
"envoy_total_connections_used,envoy_parent_connections_used",
+ "queryMetricType": "readMetricsValues",
+ "chartType": "ChartLine"
+ },
+ {
+ "width": "3",
+ "title": "Concurrency",
+ "height": 350,
+ "entityType": "ServiceInstance",
+ "independentSelector": false,
+ "metricType": "REGULAR_VALUE",
+ "metricName":
"envoy_worker_threads,envoy_worker_threads_max",
+ "queryMetricType": "readMetricsValues",
+ "chartType": "ChartLine"
+ },
+ {
+ "width": "3",
+ "title": "Envoy Bug Failure",
+ "height": 350,
+ "entityType": "ServiceInstance",
+ "independentSelector": false,
+ "metricType": "REGULAR_VALUE",
+ "metricName": "envoy_bug_failures",
+ "queryMetricType": "readMetricsValues",
+ "chartType": "ChartLine"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ activated: true
+ disabled: false
diff --git
a/oap-server/server-bootstrap/src/main/resources/ui-initialized-templates/istio.yml
b/oap-server/server-bootstrap/src/main/resources/ui-initialized-templates/istio.yml
index bea98db..1e0f6df 100644
---
a/oap-server/server-bootstrap/src/main/resources/ui-initialized-templates/istio.yml
+++
b/oap-server/server-bootstrap/src/main/resources/ui-initialized-templates/istio.yml
@@ -172,4 +172,4 @@ templates:
# False means providing a basic template, user needs to add it manually.
activated: true
# True means wouldn't show up on the dashboard. Only keeps the definition
in the storage.
- disabled: false
\ No newline at end of file
+ disabled: false
diff --git
a/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/pom.xml
b/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/pom.xml
index 93eed36..c1caf8b 100644
--- a/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/pom.xml
+++ b/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/pom.xml
@@ -36,6 +36,11 @@
</dependency>
<dependency>
<groupId>org.apache.skywalking</groupId>
+ <artifactId>meter-analyzer</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.skywalking</groupId>
<artifactId>skywalking-mesh-receiver-plugin</artifactId>
<version>${project.version}</version>
</dependency>
diff --git
a/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/EnvoyMetricReceiverConfig.java
b/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/EnvoyMetricReceiverConfig.java
index 5819d80..fb029fb 100644
---
a/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/EnvoyMetricReceiverConfig.java
+++
b/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/EnvoyMetricReceiverConfig.java
@@ -24,7 +24,10 @@ import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import lombok.Getter;
+import org.apache.skywalking.oap.meter.analyzer.prometheus.rule.Rule;
+import org.apache.skywalking.oap.meter.analyzer.prometheus.rule.Rules;
import org.apache.skywalking.oap.server.library.module.ModuleConfig;
+import org.apache.skywalking.oap.server.library.module.ModuleStartException;
public class EnvoyMetricReceiverConfig extends ModuleConfig {
@Getter
@@ -39,4 +42,8 @@ public class EnvoyMetricReceiverConfig extends ModuleConfig {
}
return
Arrays.stream(alsHTTPAnalysis.trim().split(",")).map(String::trim).collect(Collectors.toList());
}
+
+ public List<Rule> rules() throws ModuleStartException {
+ return Rules.loadRules("envoy-metrics-rules",
Collections.singletonList("envoy"));
+ }
}
diff --git
a/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/EnvoyMetricReceiverProvider.java
b/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/EnvoyMetricReceiverProvider.java
index bbbce0b..1e3d8c0 100644
---
a/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/EnvoyMetricReceiverProvider.java
+++
b/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/EnvoyMetricReceiverProvider.java
@@ -20,19 +20,21 @@ package org.apache.skywalking.oap.server.receiver.envoy;
import org.apache.skywalking.aop.server.receiver.mesh.MeshReceiverModule;
import org.apache.skywalking.oap.server.core.CoreModule;
-import org.apache.skywalking.oap.server.core.oal.rt.OALEngineLoaderService;
import org.apache.skywalking.oap.server.core.server.GRPCHandlerRegister;
import org.apache.skywalking.oap.server.library.module.ModuleConfig;
import org.apache.skywalking.oap.server.library.module.ModuleDefine;
import org.apache.skywalking.oap.server.library.module.ModuleProvider;
import org.apache.skywalking.oap.server.library.module.ModuleStartException;
import
org.apache.skywalking.oap.server.library.module.ServiceNotProvidedException;
+import org.apache.skywalking.oap.server.receiver.envoy.als.mx.FieldsHelper;
import
org.apache.skywalking.oap.server.receiver.sharing.server.SharingServerModule;
import org.apache.skywalking.oap.server.telemetry.TelemetryModule;
public class EnvoyMetricReceiverProvider extends ModuleProvider {
private final EnvoyMetricReceiverConfig config;
+ protected String fieldMappingFile = "metadata-service-mapping.yaml";
+
public EnvoyMetricReceiverProvider() {
config = new EnvoyMetricReceiverConfig();
}
@@ -54,7 +56,11 @@ public class EnvoyMetricReceiverProvider extends
ModuleProvider {
@Override
public void prepare() throws ServiceNotProvidedException,
ModuleStartException {
-
+ try {
+ FieldsHelper.SINGLETON.init(fieldMappingFile);
+ } catch (final Exception e) {
+ throw new ModuleStartException("Failed to load
metadata-service-mapping.yaml", e);
+ }
}
@Override
@@ -63,12 +69,7 @@ public class EnvoyMetricReceiverProvider extends
ModuleProvider {
.provider()
.getService(GRPCHandlerRegister.class);
if (config.isAcceptMetricsService()) {
- getManager().find(CoreModule.NAME)
- .provider()
- .getService(OALEngineLoaderService.class)
- .load(EnvoyOALDefine.INSTANCE);
-
- final MetricServiceGRPCHandler handler = new
MetricServiceGRPCHandler(getManager());
+ final MetricServiceGRPCHandler handler = new
MetricServiceGRPCHandler(getManager(), config);
service.addHandler(handler);
service.addHandler(new MetricServiceGRPCHandlerV3(handler));
}
diff --git
a/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/EnvoyOALDefine.java
b/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/EnvoyOALDefine.java
deleted file mode 100644
index da0ba4c..0000000
---
a/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/EnvoyOALDefine.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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.server.receiver.envoy;
-
-import org.apache.skywalking.oap.server.core.oal.rt.OALDefine;
-
-/**
- * Envoy OAl script includes the metrics related to Envoy only.
- */
-public class EnvoyOALDefine extends OALDefine {
- public static final EnvoyOALDefine INSTANCE = new EnvoyOALDefine();
-
- private EnvoyOALDefine() {
- super(
- "oal/envoy.oal",
- "org.apache.skywalking.oap.server.core.source"
- );
- }
-}
\ No newline at end of file
diff --git
a/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/MetricServiceGRPCHandler.java
b/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/MetricServiceGRPCHandler.java
index bb274cf..2538a34 100644
---
a/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/MetricServiceGRPCHandler.java
+++
b/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/MetricServiceGRPCHandler.java
@@ -18,23 +18,31 @@
package org.apache.skywalking.oap.server.receiver.envoy;
-import io.envoyproxy.envoy.config.core.v3.Node;
-import io.envoyproxy.envoy.service.metrics.v3.MetricsServiceGrpc;
+import io.envoyproxy.envoy.service.metrics.v2.MetricsServiceGrpc;
import io.envoyproxy.envoy.service.metrics.v3.StreamMetricsMessage;
import io.envoyproxy.envoy.service.metrics.v3.StreamMetricsResponse;
import io.grpc.stub.StreamObserver;
import io.prometheus.client.Metrics;
import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.skywalking.apm.util.StringUtil;
+import
org.apache.skywalking.oap.meter.analyzer.prometheus.PrometheusMetricConverter;
import org.apache.skywalking.oap.server.core.CoreModule;
import org.apache.skywalking.oap.server.core.analysis.IDManager;
import org.apache.skywalking.oap.server.core.analysis.TimeBucket;
-import org.apache.skywalking.oap.server.core.source.EnvoyInstanceMetric;
+import org.apache.skywalking.oap.server.core.analysis.meter.MeterSystem;
import org.apache.skywalking.oap.server.core.analysis.NodeType;
import org.apache.skywalking.oap.server.core.source.ServiceInstanceUpdate;
import org.apache.skywalking.oap.server.core.source.SourceReceiver;
import org.apache.skywalking.oap.server.library.module.ModuleManager;
+import org.apache.skywalking.oap.server.library.module.ModuleStartException;
+import org.apache.skywalking.oap.server.library.util.prometheus.metrics.Metric;
+import org.apache.skywalking.oap.server.receiver.envoy.als.ServiceMetaInfo;
+import
org.apache.skywalking.oap.server.receiver.envoy.als.mx.ServiceMetaInfoAdapter;
+import
org.apache.skywalking.oap.server.receiver.envoy.metrics.adapters.ProtoMetricFamily2MetricsAdapter;
import org.apache.skywalking.oap.server.telemetry.TelemetryModule;
import org.apache.skywalking.oap.server.telemetry.api.CounterMetrics;
import org.apache.skywalking.oap.server.telemetry.api.HistogramMetrics;
@@ -44,10 +52,11 @@ import
org.apache.skywalking.oap.server.telemetry.api.MetricsTag;
@Slf4j
public class MetricServiceGRPCHandler extends
MetricsServiceGrpc.MetricsServiceImplBase {
private final SourceReceiver sourceReceiver;
- private CounterMetrics counter;
- private HistogramMetrics histogram;
+ private final CounterMetrics counter;
+ private final HistogramMetrics histogram;
+ private final List<PrometheusMetricConverter> converters;
- public MetricServiceGRPCHandler(ModuleManager moduleManager) {
+ public MetricServiceGRPCHandler(final ModuleManager moduleManager, final
EnvoyMetricReceiverConfig config) throws ModuleStartException {
sourceReceiver =
moduleManager.find(CoreModule.NAME).provider().getService(SourceReceiver.class);
MetricsCreator metricsCreator =
moduleManager.find(TelemetryModule.NAME)
.provider()
@@ -60,16 +69,23 @@ public class MetricServiceGRPCHandler extends
MetricsServiceGrpc.MetricsServiceI
"envoy_metric_in_latency", "The process latency of service metrics
receiver", MetricsTag.EMPTY_KEY,
MetricsTag.EMPTY_VALUE
);
+
+ final MeterSystem meterSystem =
moduleManager.find(CoreModule.NAME).provider().getService(MeterSystem.class);
+
+ converters = config.rules()
+ .stream()
+ .map(rule -> new PrometheusMetricConverter(rule,
meterSystem))
+ .collect(Collectors.toList());
}
@Override
public StreamObserver<StreamMetricsMessage>
streamMetrics(StreamObserver<StreamMetricsResponse> responseObserver) {
return new StreamObserver<StreamMetricsMessage>() {
private volatile boolean isFirst = true;
- private String serviceName = null;
- private String serviceInstanceName = null;
+ private ServiceMetaInfo service;
@Override
+ @SneakyThrows
public void onNext(StreamMetricsMessage message) {
if (log.isDebugEnabled()) {
log.debug("Received msg {}", message);
@@ -77,94 +93,41 @@ public class MetricServiceGRPCHandler extends
MetricsServiceGrpc.MetricsServiceI
if (isFirst) {
isFirst = false;
- StreamMetricsMessage.Identifier identifier =
message.getIdentifier();
- Node node = identifier.getNode();
- if (node != null) {
- String nodeId = node.getId();
- if (!StringUtil.isEmpty(nodeId)) {
- serviceInstanceName = nodeId;
- }
- String cluster = node.getCluster();
- if (!StringUtil.isEmpty(cluster)) {
- serviceName = cluster;
- if (serviceInstanceName == null) {
- serviceInstanceName = serviceName;
- }
- }
- }
-
- if (serviceName == null) {
- serviceName = serviceInstanceName;
- }
+ service = new
ServiceMetaInfoAdapter(message.getIdentifier().getNode().getMetadata());
}
if (log.isDebugEnabled()) {
- log.debug(
- "Envoy metrics reported from service[{}], service
instance[{}]", serviceName,
- serviceInstanceName
- );
+ log.debug("Envoy metrics reported from service[{}]",
service);
}
- if (StringUtil.isNotEmpty(serviceName) &&
StringUtil.isNotEmpty(serviceInstanceName)) {
+ if (service != null &&
StringUtil.isNotEmpty(service.getServiceName()) &&
StringUtil.isNotEmpty(service.getServiceInstanceName())) {
List<Metrics.MetricFamily> list =
message.getEnvoyMetricsList();
boolean needHeartbeatUpdate = true;
- for (int i = 0; i < list.size(); i++) {
+
+ for (final Metrics.MetricFamily metricFamily : list) {
counter.inc();
- final String serviceId =
IDManager.ServiceID.buildId(serviceName, NodeType.Normal);
- final String serviceInstanceId =
IDManager.ServiceInstanceID.buildId(
- serviceId, serviceInstanceName);
-
- HistogramMetrics.Timer timer = histogram.createTimer();
- try {
- Metrics.MetricFamily metricFamily = list.get(i);
- double value = 0;
- long timestamp = 0;
- switch (metricFamily.getType()) {
- case GAUGE:
- for (Metrics.Metric metrics :
metricFamily.getMetricList()) {
- timestamp = metrics.getTimestampMs();
- value = metrics.getGauge().getValue();
-
- if (timestamp > 1000000000000000000L) {
- /**
- * Several versions of envoy in
istio.deps send timestamp in nanoseconds,
- * instead of
milliseconds(protocol says).
- *
- * Sadly, but have to fix it
forcedly.
- *
- * An example of timestamp is
'1552303033488741055', clearly it is not in milliseconds.
- *
- * This should be removed in the
future.
- */
- timestamp /= 1_000_000;
- }
-
- EnvoyInstanceMetric metricSource = new
EnvoyInstanceMetric();
- metricSource.setServiceId(serviceId);
-
metricSource.setServiceName(serviceName);
- metricSource.setId(serviceInstanceId);
-
metricSource.setName(serviceInstanceName);
-
metricSource.setMetricName(metricFamily.getName());
- metricSource.setValue(value);
-
metricSource.setTimeBucket(TimeBucket.getMinuteTimeBucket(timestamp));
- sourceReceiver.receive(metricSource);
- }
- break;
- default:
- continue;
- }
- if (needHeartbeatUpdate) {
+ final String serviceId =
IDManager.ServiceID.buildId(service.getServiceName(), NodeType.Normal);
+
+ try (final HistogramMetrics.Timer ignored =
histogram.createTimer()) {
+ final ProtoMetricFamily2MetricsAdapter adapter =
new ProtoMetricFamily2MetricsAdapter(metricFamily);
+ final Stream<Metric> metrics =
adapter.adapt().peek(it -> {
+ it.getLabels().putIfAbsent("cluster",
service.getServiceName());
+ it.getLabels().putIfAbsent("instance",
service.getServiceInstanceName());
+ });
+ converters.forEach(converter ->
converter.toMeter(metrics));
+
+ if (needHeartbeatUpdate &&
list.get(0).getMetricCount() > 0) {
+ final long timestamp =
adapter.adaptTimestamp(list.get(0).getMetric(0));
+
// Send heartbeat
ServiceInstanceUpdate serviceInstanceUpdate =
new ServiceInstanceUpdate();
-
serviceInstanceUpdate.setName(serviceInstanceName);
+
serviceInstanceUpdate.setName(service.getServiceInstanceName());
serviceInstanceUpdate.setServiceId(serviceId);
serviceInstanceUpdate.setTimeBucket(TimeBucket.getMinuteTimeBucket(timestamp));
sourceReceiver.receive(serviceInstanceUpdate);
needHeartbeatUpdate = false;
}
- } finally {
- timer.finish();
}
}
}
diff --git
a/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/metrics/adapters/ProtoMetricFamily2MetricsAdapter.java
b/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/metrics/adapters/ProtoMetricFamily2MetricsAdapter.java
new file mode 100644
index 0000000..ffcef39
--- /dev/null
+++
b/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/metrics/adapters/ProtoMetricFamily2MetricsAdapter.java
@@ -0,0 +1,84 @@
+/*
+ * 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.server.receiver.envoy.metrics.adapters;
+
+import io.prometheus.client.Metrics;
+import java.util.Map;
+import java.util.stream.Stream;
+import lombok.RequiredArgsConstructor;
+import org.apache.skywalking.oap.server.library.util.prometheus.metrics.Gauge;
+import org.apache.skywalking.oap.server.library.util.prometheus.metrics.Metric;
+
+import static java.util.stream.Collectors.toMap;
+
+@RequiredArgsConstructor
+public class ProtoMetricFamily2MetricsAdapter {
+ protected final Metrics.MetricFamily metricFamily;
+
+ public Stream<Metric> adapt() {
+ switch (metricFamily.getType()) {
+ case GAUGE:
+ return metricFamily.getMetricList()
+ .stream()
+ .map(it -> Gauge.builder()
+ .name(adaptMetricsName(it))
+ .value(adaptValue(it))
+
.timestamp(adaptTimestamp(it))
+ .labels(adaptLabels(it))
+ .build());
+ default:
+ return Stream.of();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public String adaptMetricsName(final Metrics.Metric metric) {
+ return metricFamily.getName();
+ }
+
+ public double adaptValue(final Metrics.Metric it) {
+ return it.getGauge().getValue();
+ }
+
+ public Map<String, String> adaptLabels(final Metrics.Metric metric) {
+ return metric.getLabelList()
+ .stream()
+ .collect(toMap(Metrics.LabelPair::getName,
Metrics.LabelPair::getValue));
+ }
+
+ public long adaptTimestamp(final Metrics.Metric metric) {
+ long timestamp = metric.getTimestampMs();
+
+ if (timestamp > 1000000000000000000L) {
+ /*
+ * Several versions of envoy in istio.deps send timestamp in
nanoseconds,
+ * instead of milliseconds(protocol says).
+ *
+ * Sadly, but have to fix it forcefully.
+ *
+ * An example of timestamp is '1552303033488741055', clearly it is
not in milliseconds.
+ *
+ * This should be removed in the future.
+ */
+ timestamp /= 1_000_000;
+ }
+
+ return timestamp;
+ }
+}
diff --git
a/test/e2e/e2e-common/src/main/java/org/apache/skywalking/e2e/utils/Yamls.java
b/test/e2e/e2e-common/src/main/java/org/apache/skywalking/e2e/utils/Yamls.java
index 054f753..c9b695e 100644
---
a/test/e2e/e2e-common/src/main/java/org/apache/skywalking/e2e/utils/Yamls.java
+++
b/test/e2e/e2e-common/src/main/java/org/apache/skywalking/e2e/utils/Yamls.java
@@ -38,6 +38,10 @@ public final class Yamls {
<T> T as(final Class<T> klass);
}
+ public static boolean exists(final String file) {
+ return new ClassPathResource(Envs.resolve(file)).exists();
+ }
+
public static AsTypeBuilder load(final String file) throws IOException {
final InputStream inputStream = new
ClassPathResource(Envs.resolve(file)).getInputStream();
diff --git
a/test/e2e/e2e-data/src/main/java/org/apache/skywalking/e2e/metrics/MetricsQuery.java
b/test/e2e/e2e-data/src/main/java/org/apache/skywalking/e2e/metrics/MetricsQuery.java
index 020e3ee..d86e3eb 100644
---
a/test/e2e/e2e-data/src/main/java/org/apache/skywalking/e2e/metrics/MetricsQuery.java
+++
b/test/e2e/e2e-data/src/main/java/org/apache/skywalking/e2e/metrics/MetricsQuery.java
@@ -120,6 +120,18 @@ public class MetricsQuery extends
AbstractQuery<MetricsQuery> {
METER_INSTANCE_PERSISTENCE_EXECUTE_COUNT
};
+ public static String[] ALL_ENVOY_LINER_METRICS = {
+ "envoy_heap_memory_used",
+ "envoy_heap_memory_max_used",
+ "envoy_memory_allocated",
+ "envoy_memory_allocated_max",
+ "envoy_memory_physical_size",
+ "envoy_memory_physical_size_max",
+ "envoy_total_connections_used",
+ "envoy_worker_threads",
+ "envoy_worker_threads_max"
+ };
+
public static String METER_INSTANCE_PERSISTENCE_EXECUTE_PERCENTILE =
"meter_oap_instance_persistence_execute_percentile";
public static String[] ALL_SO11Y_LABELED_METRICS = {
diff --git
a/test/e2e/e2e-test/src/test/java/org/apache/skywalking/e2e/mesh/MetricsServiceE2E.java
b/test/e2e/e2e-test/src/test/java/org/apache/skywalking/e2e/mesh/MetricsServiceE2E.java
new file mode 100644
index 0000000..0d87e9b
--- /dev/null
+++
b/test/e2e/e2e-test/src/test/java/org/apache/skywalking/e2e/mesh/MetricsServiceE2E.java
@@ -0,0 +1,130 @@
+/*
+ * 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.e2e.mesh;
+
+import com.google.common.base.Strings;
+import java.net.URL;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.e2e.base.SkyWalkingTestAdapter;
+import org.apache.skywalking.e2e.base.TrafficController;
+import org.apache.skywalking.e2e.common.HostAndPort;
+import org.apache.skywalking.e2e.metrics.AtLeastOneOfMetricsMatcher;
+import org.apache.skywalking.e2e.metrics.MetricsValueMatcher;
+import org.apache.skywalking.e2e.metrics.ReadMetrics;
+import org.apache.skywalking.e2e.metrics.ReadMetricsQuery;
+import org.apache.skywalking.e2e.retryable.RetryableTest;
+import org.apache.skywalking.e2e.service.Service;
+import org.apache.skywalking.e2e.service.ServicesMatcher;
+import org.apache.skywalking.e2e.service.ServicesQuery;
+import org.apache.skywalking.e2e.service.instance.Instance;
+import org.apache.skywalking.e2e.service.instance.Instances;
+import org.apache.skywalking.e2e.service.instance.InstancesMatcher;
+import org.apache.skywalking.e2e.service.instance.InstancesQuery;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.TestInstance;
+
+import static
org.apache.skywalking.e2e.metrics.MetricsQuery.ALL_ENVOY_LINER_METRICS;
+import static org.apache.skywalking.e2e.utils.Times.now;
+import static org.apache.skywalking.e2e.utils.Yamls.exists;
+import static org.apache.skywalking.e2e.utils.Yamls.load;
+
+@Slf4j
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+public class MetricsServiceE2E extends SkyWalkingTestAdapter {
+ private final String swWebappHost =
Optional.ofNullable(Strings.emptyToNull(System.getenv("WEBAPP_HOST"))).orElse("127.0.0.1");
+
+ private final String swWebappPort =
Optional.ofNullable(Strings.emptyToNull(System.getenv("WEBAPP_PORT"))).orElse("12800");
+
+ protected HostAndPort swWebappHostPort = HostAndPort.builder()
+ .host(swWebappHost)
+
.port(Integer.parseInt(swWebappPort))
+ .build();
+
+ @BeforeAll
+ public void setUp() throws Exception {
+ LOGGER.info("set up");
+
+ queryClient(swWebappHostPort);
+
+ String gatewayHost =
Strings.isNullOrEmpty(System.getenv("GATEWAY_HOST")) ? "127.0.0.1" :
System.getenv("GATEWAY_HOST");
+ String gatewayPort =
Strings.isNullOrEmpty(System.getenv("GATEWAY_PORT")) ? "80" :
System.getenv("GATEWAY_PORT");
+
+ HostAndPort serviceHostPort = HostAndPort.builder()
+ .host(gatewayHost)
+
.port(Integer.parseInt(gatewayPort))
+ .build();
+
+ final URL url = new URL("http", serviceHostPort.host(),
serviceHostPort.port(), "/productpage");
+
+ trafficController =
+ TrafficController.builder()
+ .logResult(false)
+ .sender(() ->
restTemplate.getForEntity(url.toURI(), String.class))
+ .build()
+ .start();
+
+ LOGGER.info("set up done");
+ }
+
+ @RetryableTest
+ void test() throws Exception {
+ List<Service> services = graphql.services(new
ServicesQuery().start(startTime).end(now()));
+
+ services = services.stream().filter(s ->
s.getLabel().startsWith("istio-dp::")).collect(Collectors.toList());
+ LOGGER.info("services: {}", services);
+
load("expected/metricsservice/services.yml").as(ServicesMatcher.class).verify(services);
+ for (final Service service : services) {
+ if (service.getLabel().contains("egressgateway")) {
+ continue;
+ }
+
+ final Instances instances = graphql.instances(
+ new
InstancesQuery().serviceId(service.getKey()).start(startTime).end(now())
+ );
+
+ LOGGER.info("instances: {}", instances);
+
+ String instancesFile = "expected/metricsservice/instances-" +
service.getLabel() + ".yml";
+ instancesFile = instancesFile.replaceAll("::", "-");
+ if (!exists(instancesFile)) {
+ instancesFile = "expected/metricsservice/instances.yml";
+ }
+ load(instancesFile).as(InstancesMatcher.class).verify(instances);
+ for (Instance instance : instances.getInstances()) {
+ for (String metricsName : ALL_ENVOY_LINER_METRICS) {
+ LOGGER.info("verifying service instance: {}", instance);
+ final ReadMetrics instanceMetrics = graphql.readMetrics(
+ new
ReadMetricsQuery().stepByMinute().metricsName(metricsName)
+
.serviceName(service.getLabel()).instanceName(instance.getLabel())
+ );
+
+ LOGGER.info("{}: {}", metricsName, instanceMetrics);
+ final AtLeastOneOfMetricsMatcher instanceRespTimeMatcher =
new AtLeastOneOfMetricsMatcher();
+ final MetricsValueMatcher greaterThanZero = new
MetricsValueMatcher();
+ greaterThanZero.setValue("gt 0");
+ instanceRespTimeMatcher.setValue(greaterThanZero);
+
instanceRespTimeMatcher.verify(instanceMetrics.getValues());
+ }
+ }
+ }
+ }
+}
diff --git
a/test/e2e/e2e-test/src/test/resources/expected/metricsservice/instances-istio-dp-reviews.yml
b/test/e2e/e2e-test/src/test/resources/expected/metricsservice/instances-istio-dp-reviews.yml
new file mode 100644
index 0000000..1abf02c
--- /dev/null
+++
b/test/e2e/e2e-test/src/test/resources/expected/metricsservice/instances-istio-dp-reviews.yml
@@ -0,0 +1,22 @@
+# 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.
+
+instances:
+ - key: not null
+ label: not null
+ - key: not null
+ label: not null
+ - key: not null
+ label: not null
diff --git
a/test/e2e/e2e-test/src/test/resources/expected/metricsservice/instances.yml
b/test/e2e/e2e-test/src/test/resources/expected/metricsservice/instances.yml
new file mode 100644
index 0000000..7de572a
--- /dev/null
+++ b/test/e2e/e2e-test/src/test/resources/expected/metricsservice/instances.yml
@@ -0,0 +1,18 @@
+# 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.
+
+instances:
+ - key: not null
+ label: not null
diff --git
a/test/e2e/e2e-test/src/test/resources/expected/metricsservice/services.yml
b/test/e2e/e2e-test/src/test/resources/expected/metricsservice/services.yml
new file mode 100644
index 0000000..1453985
--- /dev/null
+++ b/test/e2e/e2e-test/src/test/resources/expected/metricsservice/services.yml
@@ -0,0 +1,28 @@
+# 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.
+
+services:
+ - key: not null
+ label: istio-dp::ratings
+ - key: not null
+ label: istio-dp::reviews
+ - key: not null
+ label: istio-dp::productpage
+ - key: not null
+ label: istio-dp::details
+ - key: not null
+ label: istio-dp::istio-ingressgateway
+ - key: not null
+ label: istio-dp::istio-egressgateway