You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2026/08/04 13:29:52 UTC
(skywalking) branch master updated: Add the MAL functions tests back. (#13967)
This is an automated email from the ASF dual-hosted git repository.
wu-sheng 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 c693967467 Add the MAL functions tests back. (#13967)
c693967467 is described below
commit c69396746755f53581f74a33fc1ba58c5285cd6f
Author: Wan Kai <wa...@foxmail.com>
AuthorDate: Tue Aug 4 21:29:46 2026 +0800
Add the MAL functions tests back. (#13967)
- ScopeTest: compare the produced/expected MeterEntity key sets and iterate over
the expected map so a missing or extra entity fails the test.
- K8sTagTest: capture and restore the KubernetesServices/KubernetesPods enum
singletons so the mocked instances do not leak into other tests.
- DecorateTest: move MeterEntity naming-control setup into @BeforeAll/@AfterAll
and reset it so the global state is not leaked.
---
.../oap/meter/analyzer/v2/dsl/AggregationTest.java | 238 ++++++++
.../oap/meter/analyzer/v2/dsl/ArithmeticTest.java | 473 +++++++++++++++
.../oap/meter/analyzer/v2/dsl/BasicTest.java | 78 +++
.../oap/meter/analyzer/v2/dsl/DecorateTest.java | 64 ++
.../oap/meter/analyzer/v2/dsl/FilterTest.java | 99 ++++
.../oap/meter/analyzer/v2/dsl/FunctionTest.java | 145 +++++
.../oap/meter/analyzer/v2/dsl/IncreaseTest.java | 240 ++++++++
.../oap/meter/analyzer/v2/dsl/K8sTagTest.java | 340 +++++++++++
.../oap/meter/analyzer/v2/dsl/ScopeTest.java | 642 +++++++++++++++++++++
.../oap/meter/analyzer/v2/dsl/TagFilterTest.java | 140 +++++
.../oap/meter/analyzer/v2/dsl/ValueFilterTest.java | 148 +++++
.../analyzer/v2/dsl/counter/CounterWindowTest.java | 97 ++++
.../src/main/resources/application.yml | 4 +
13 files changed, 2708 insertions(+)
diff --git a/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/AggregationTest.java b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/AggregationTest.java
new file mode 100644
index 0000000000..0e438ac883
--- /dev/null
+++ b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/AggregationTest.java
@@ -0,0 +1,238 @@
+/*
+ * 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.meter.analyzer.v2.dsl;
+
+import com.google.common.collect.ImmutableMap;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static com.google.common.collect.ImmutableMap.of;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+/**
+ * Grouped aggregation expressions were adapted from the v1 named-arg Groovy idiom to the v2
+ * positional-list form: {@code sum(by = ['region', 'idc'])} became {@code sum(['region', 'idc'])}.
+ * v2's ANTLR grammar has no named arguments. Expected outputs are unchanged from v1.
+ *
+ * <p>Total (un-grouped) aggregation has no v2 MAL syntax and is pinned as a negative case per
+ * aggregation function: v1 {@code sum()} aggregated every sample into one, but v2's grammar
+ * ({@code stringList : L_BRACKET STRING (COMMA STRING)* R_BRACKET}) requires at least one grouping
+ * label and there is no no-arg overload, so both {@code sum()} and {@code sum([])} are rejected at
+ * parse time. Grouping labels are mandatory in v2; every production MAL rule supplies them.
+ */
+@Slf4j
+public class AggregationTest {
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] {
+ {
+ "sum",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(3).name("http_success_request").build()
+ ).build()),
+ // total aggregation: no v2 MAL syntax (see class javadoc), pinned as a negative case
+ "http_success_request.sum()",
+ Result.success(SampleFamilyBuilder.newBuilder(Sample.builder().labels(ImmutableMap.of()).value(53).name("http_success_request").build()).build()),
+ true,
+ },
+ {
+ "sum-by",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t3", "region", "cn", "svc", "catalog")).value(50).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t1", "region", "us", "svc", "product")).value(50).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t1", "region", "us", "instance", "10.0.0.1")).name("http_success_request").value(50).build(),
+ Sample.builder().labels(of("idc", "t3", "region", "cn", "instance", "10.0.0.1")).name("http_success_request").value(3).build()
+ ).build()),
+ "http_success_request.sum(['region', 'idc'])",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1", "region", "")).value(50).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t1", "region", "us")).value(100).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t3", "region", "cn")).value(53).name("http_success_request").build()
+ ).build()),
+ false,
+ },
+
+ {
+ "min",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t3")).value(100).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(3).name("http_success_request").build()
+ ).build()),
+ // total aggregation: no v2 MAL syntax (see class javadoc), pinned as a negative case
+ "http_success_request.min()",
+ Result.success(SampleFamilyBuilder.newBuilder(Sample.builder().labels(ImmutableMap.of()).value(3).name("http_success_request").build()).build()),
+ true,
+ },
+ {
+ "min-by",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t3", "region", "cn", "svc", "catalog")).value(50).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t1", "region", "us", "svc", "product")).value(50).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t1", "region", "us", "instance", "10.0.0.1")).value(100).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t3", "region", "cn", "instance", "10.0.0.1")).value(3).name("http_success_request").build()
+ ).build()),
+ "http_success_request.min(['region', 'idc'])",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1", "region", "")).value(50).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t1", "region", "us")).value(50).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t3", "region", "cn")).value(3).name("http_success_request").build()
+ ).build()),
+ false,
+ },
+ {
+ "max",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t3")).value(100).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(3).name("http_success_request").build()
+ ).build()),
+ // total aggregation: no v2 MAL syntax (see class javadoc), pinned as a negative case
+ "http_success_request.max()",
+ Result.success(SampleFamilyBuilder.newBuilder(Sample.builder().labels(ImmutableMap.of()).value(100).name("http_success_request").build()).build()),
+ true,
+ },
+ {
+ "max-by",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t3", "region", "cn", "svc", "catalog")).value(50).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t1", "region", "us", "svc", "product")).value(50).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t1", "region", "us", "instance", "10.0.0.1")).value(100).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t3", "region", "cn", "instance", "10.0.0.1")).value(3).name("http_success_request").build()
+ ).build()),
+ "http_success_request.max(['region', 'idc'])",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1", "region", "")).value(50).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t1", "region", "us")).value(100).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t3", "region", "cn")).value(50).name("http_success_request").build()
+ ).build()),
+ false,
+ },
+
+ {
+ "avg",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t3")).value(100).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(3).name("http_success_request").build()
+ ).build()),
+ // total aggregation: no v2 MAL syntax (see class javadoc), pinned as a negative case
+ "http_success_request.avg()",
+ Result.success(SampleFamilyBuilder.newBuilder(Sample.builder().labels(ImmutableMap.of()).value(51).name("http_success_request").build()).build()),
+ true,
+ },
+ {
+ "avg-by",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t3", "region", "cn", "svc", "catalog")).value(51).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t1", "region", "us", "svc", "product")).value(50).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t1", "region", "us", "instance", "10.0.0.1")).value(100).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t3", "region", "cn", "instance", "10.0.0.1")).value(3).name("http_success_request").build()
+ ).build()),
+ "http_success_request.avg(['region', 'idc'])",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1", "region", "")).value(50).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t1", "region", "us")).value(75).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t3", "region", "cn")).value(27).name("http_success_request").build()
+ ).build()),
+ false,
+ },
+
+ {
+ "count",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t3")).value(100).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(3).name("http_success_request").build()
+ ).build()),
+ // total aggregation: no v2 MAL syntax (see class javadoc), pinned as a negative case
+ "http_success_request.count()",
+ Result.success(SampleFamilyBuilder.newBuilder(Sample.builder().labels(ImmutableMap.of()).value(3).name("http_success_request").build()).build()),
+ true,
+ },
+ {
+ "count-by-one",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t1", "region", "cn", "instance", "10.0.0.1")).value(50).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t2", "region", "us", "instance", "10.0.0.2")).value(50).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t1", "region", "us", "instance", "10.0.0.3")).value(100).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t2", "region", "cn", "instance", "10.0.0.3")).value(3).name("http_success_request").build()
+ ).build()),
+ "http_success_request.count(['instance'])",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(ImmutableMap.of()).value(3).name("http_success_request").build()
+ ).build()),
+ false,
+ },
+ {
+ "count-by-multi",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t1", "region", "cn", "instance", "10.0.0.1")).value(50).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t1", "region", "cn", "instance", "10.0.0.1")).value(50).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t2", "region", "us", "instance", "10.0.0.2")).value(50).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t1", "region", "us", "instance", "10.0.0.3")).value(100).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t1", "region", "us", "instance", "10.0.0.4")).value(100).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t2", "region", "cn", "instance", "10.0.0.5")).value(3).name("http_success_request").build()
+ ).build()),
+ "http_success_request.count(['region','instance'])",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("region", "us")).value(3).name("http_success_request").build(),
+ Sample.builder().labels(of("region", "cn")).value(2).name("http_success_request").build()
+ ).build()),
+ false,
+ },
+ });
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("data")
+ public void test(String name,
+ ImmutableMap<String, SampleFamily> input,
+ String expression,
+ Result want,
+ boolean isThrow) {
+ Result r = null;
+ try {
+ // v2 compiles eagerly, so parse-time failures surface here too (v1 surfaced them at run()).
+ Expression e = DSL.parse(name, expression);
+ r = e.run(input);
+ } catch (Throwable t) {
+ if (isThrow) {
+ return;
+ }
+ log.error("Test failed", t);
+ fail("Should not throw anything");
+ }
+ if (isThrow) {
+ fail("Should throw something");
+ }
+ assertEquals(want, r);
+ }
+}
diff --git a/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/ArithmeticTest.java b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/ArithmeticTest.java
new file mode 100644
index 0000000000..1ee98fce30
--- /dev/null
+++ b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/ArithmeticTest.java
@@ -0,0 +1,473 @@
+/*
+ * 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.meter.analyzer.v2.dsl;
+
+import com.google.common.collect.ImmutableMap;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static com.google.common.collect.ImmutableMap.of;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+@Slf4j
+public class ArithmeticTest {
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] {
+ {
+ "plus-scalar-1",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder()
+ .labels(of("idc", "t1"))
+ .value(1600592418480.0)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t2"))
+ .value(1600592418481.0)
+ .name("http_success_request")
+ .build()
+ ).build()),
+ "1000 + http_success_request.tagEqual('idc','t1')",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder()
+ .labels(of("idc", "t1"))
+ .value(1600592419480.0)
+ .name("http_success_request")
+ .build()
+ ).build()),
+ false,
+ },
+ {
+ "plus-scalar",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder()
+ .labels(of("idc", "t1"))
+ .value(1600592418480.0)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t2"))
+ .value(1600592418481.0)
+ .name("http_success_request")
+ .build()
+ ).build()),
+ "http_success_request.tagEqual('idc','t1') + 1000",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder()
+ .labels(of("idc", "t1"))
+ .value(1600592419480.0)
+ .name("http_success_request")
+ .build()
+ ).build()),
+ false,
+ },
+ {
+ "minus-scalar",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder()
+ .labels(of("idc", "t1"))
+ .value(1600592418480.0)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t2"))
+ .value(1600592418481.0)
+ .name("http_success_request")
+ .build()
+ ).build()),
+ "http_success_request.tagEqual('idc','t1') - 1000",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder()
+ .labels(of("idc", "t1"))
+ .value(1600592417480.0)
+ .name("http_success_request")
+ .build()
+ ).build()),
+ false,
+ },
+ {
+ "multiply-scalar",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder()
+ .labels(of("idc", "t1"))
+ .value(1600592418480.0)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t2"))
+ .value(1600592418481.0)
+ .name("http_success_request")
+ .build()
+ ).build()),
+ "http_success_request.tagEqual('idc','t1') * 1000",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder()
+ .labels(of("idc", "t1"))
+ .value(1600592418480000.0)
+ .name("http_success_request")
+ .build()
+ ).build()),
+ false,
+ },
+ {
+ "divide-scalar",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder()
+ .labels(of("idc", "t1"))
+ .value(1600592418480.0)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t2"))
+ .value(1600592418481.0)
+ .name("http_success_request")
+ .build()
+ ).build()),
+ "http_success_request.tagEqual('idc','t1') / 10",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder()
+ .labels(of("idc", "t1"))
+ .value(160059241848.0)
+ .name("http_success_request")
+ .build()
+ ).build()),
+ false,
+ },
+ {
+ "divide-zero",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder()
+ .labels(of("idc", "t1"))
+ .value(1600592418480.0)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t2"))
+ .value(1600592418481.0)
+ .name("http_success_request")
+ .build()
+ ).build()),
+ "http_success_request.tagEqual('idc','t1') / 0",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder()
+ .labels(of("idc", "t1"))
+ .value(Double.POSITIVE_INFINITY)
+ .name("http_success_request")
+ .build()
+ ).build()),
+ false,
+ },
+ {
+ "empty-plus-empty",
+ of("http_success_request", SampleFamily.EMPTY,
+ "http_error_request", SampleFamily.EMPTY
+ ),
+ "http_success_request + http_error_request",
+ Result.fail("Parsed result is an EMPTY sample family"),
+ false,
+ },
+ {
+ "empty-plus-sampleFamily",
+ of("http_success_request", SampleFamily.EMPTY,
+ "http_error_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_error_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(3).name("http_error_request").build()
+ ).build()
+ ),
+ "http_success_request + http_error_request",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_error_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(3).name("http_error_request").build()
+ ).build()),
+ false,
+ },
+ {
+ "sampleFamily-plus-empty",
+ of("http_success_request", SampleFamily.EMPTY,
+ "http_error_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_error_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(3).name("http_error_request").build()
+ ).build()
+ ),
+ "http_error_request + http_success_request ",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_error_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(3).name("http_error_request").build()
+ ).build()),
+ false,
+ },
+ {
+ "sampleFamily-plus-sampleFamily",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(100).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(30).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t3")).value(40).name("http_success_request").build(),
+ Sample.builder().labels(of("region", "us")).value(80).name("http_success_request").build()
+ ).build(), "http_error_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_error_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(3).name("http_error_request").build(),
+ Sample.builder().labels(of("idc", "t5")).value(3).name("http_error_request").build(),
+ Sample.builder().labels(of("tz", "en-US")).value(3).name("http_error_request").build()
+ ).build()),
+ "http_success_request + http_error_request",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(150).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(33).name("http_success_request").build()
+ ).build()),
+ false,
+ },
+ {
+ "empty-minus-empty",
+ of("http_success_request", SampleFamily.EMPTY,
+ "http_error_request", SampleFamily.EMPTY
+ ),
+ "http_success_request - http_error_request",
+ Result.fail("Parsed result is an EMPTY sample family"),
+ false,
+ },
+ {
+ "empty-minus-sampleFamily",
+ of("http_success_request", SampleFamily.EMPTY,
+ "http_error_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_error_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(3).name("http_error_request").build()
+ ).build()
+ ),
+ "http_success_request - http_error_request",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(-50).name("http_error_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(-3).name("http_error_request").build()
+ ).build()),
+ false,
+ },
+ {
+ "sampleFamily-minus-empty",
+ of("http_success_request", SampleFamily.EMPTY,
+ "http_error_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_error_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(3).name("http_error_request").build()
+ ).build()
+ ),
+ "http_error_request - http_success_request ",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_error_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(3).name("http_error_request").build()
+ ).build()),
+ false,
+ },
+ {
+ "sampleFamily-minus-sampleFamily",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(100).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(30).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t3")).value(40).name("http_success_request").build(),
+ Sample.builder().labels(of("region", "us")).value(80).name("http_success_request").build()
+ ).build(), "http_error_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_error_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(3).name("http_error_request").build(),
+ Sample.builder().labels(of("idc", "t5")).value(3).name("http_error_request").build(),
+ Sample.builder().labels(of("tz", "en-US")).value(3).name("http_error_request").build()
+ ).build()),
+ "http_success_request - http_error_request",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(27).name("http_success_request").build()
+ ).build()),
+ false,
+ },
+ {
+ "sameSampleFamily-minus-sameSampleFamily",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder()
+ .labels(of("idc", "t1", "service", "service1"))
+ .value(100)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t2", "service", "service1"))
+ .value(30)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t3", "service", "service1"))
+ .value(40)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("region", "us", "service", "service1"))
+ .value(80)
+ .name("http_success_request")
+ .build()
+ ).build()),
+ "http_success_request.sum(['service']) - http_success_request.sum(['service'])",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("service", "service1")).value(0).name("http_success_request").build()
+ ).build()),
+ false,
+ },
+ {
+ "empty-multiple-empty",
+ of("http_success_request", SampleFamily.EMPTY,
+ "http_error_request", SampleFamily.EMPTY
+ ),
+ "http_success_request * http_error_request",
+ Result.fail("Parsed result is an EMPTY sample family"),
+ false,
+ },
+ {
+ "empty-multiple-sampleFamily",
+ of("http_success_request", SampleFamily.EMPTY,
+ "http_error_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_error_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(3).name("http_error_request").build()
+ ).build()
+ ),
+ "http_success_request * http_error_request",
+ Result.fail("Parsed result is an EMPTY sample family"),
+ false,
+ },
+ {
+ "sampleFamily-multiple-empty",
+ of("http_success_request", SampleFamily.EMPTY,
+ "http_error_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_error_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(3).name("http_error_request").build()
+ ).build()
+ ),
+ "http_error_request * http_success_request ",
+ Result.fail("Parsed result is an EMPTY sample family"),
+ false,
+ },
+ {
+ "sampleFamily-multiple-sampleFamily",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(100).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(30).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t3")).value(40).name("http_success_request").build(),
+ Sample.builder().labels(of("region", "us")).value(80).name("http_success_request").build()
+ ).build(), "http_error_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_error_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(3).name("http_error_request").build(),
+ Sample.builder().labels(of("idc", "t5")).value(3).name("http_error_request").build(),
+ Sample.builder().labels(of("tz", "en-US")).value(3).name("http_error_request").build()
+ ).build()),
+ "http_success_request * http_error_request",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(5000).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(90).name("http_success_request").build()
+ ).build()),
+ false,
+ },
+ {
+ "empty-divide-empty",
+ of("http_success_request", SampleFamily.EMPTY,
+ "http_error_request", SampleFamily.EMPTY
+ ),
+ "http_success_request / http_error_request",
+ Result.fail("Parsed result is an EMPTY sample family"),
+ false,
+ },
+ {
+ "empty-divide-sampleFamily",
+ of("http_success_request", SampleFamily.EMPTY,
+ "http_error_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_error_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(3).name("http_error_request").build()
+ ).build()
+ ),
+ "http_success_request / http_error_request",
+ Result.fail("Parsed result is an EMPTY sample family"),
+ false,
+ },
+ {
+ "sampleFamily-divide-empty",
+ of("http_success_request", SampleFamily.EMPTY,
+ "http_error_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_error_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(3).name("http_error_request").build()
+ ).build()
+ ),
+ "http_error_request / http_success_request ",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder()
+ .labels(of("idc", "t1"))
+ .value(Double.POSITIVE_INFINITY)
+ .name("http_error_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t2"))
+ .value(Double.POSITIVE_INFINITY)
+ .name("http_error_request")
+ .build()
+ ).build()),
+ false,
+ },
+ {
+ "sampleFamily-divide-sampleFamily",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(100).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(30).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t3")).value(40).name("http_success_request").build(),
+ Sample.builder().labels(of("region", "us")).value(80).name("http_success_request").build()
+ ).build(), "http_error_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_error_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(3).name("http_error_request").build(),
+ Sample.builder().labels(of("idc", "t5")).value(3).name("http_error_request").build(),
+ Sample.builder().labels(of("tz", "en-US")).value(3).name("http_error_request").build()
+ ).build()),
+ "http_success_request / http_error_request",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(2).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(10).name("http_success_request").build()
+ ).build()),
+ false,
+ },
+ });
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("data")
+ public void test(String name,
+ ImmutableMap<String, SampleFamily> input,
+ String expression,
+ Result want,
+ boolean isThrow) {
+ Result r = null;
+ try {
+ // v2 compiles eagerly, so parse-time failures surface here too (v1 surfaced them at run()).
+ Expression e = DSL.parse(name, expression);
+ r = e.run(input);
+ } catch (Throwable t) {
+ if (isThrow) {
+ return;
+ }
+ log.error("Test failed", t);
+ fail("Should not throw anything");
+ }
+ if (isThrow) {
+ fail("Should throw something");
+ }
+ assertEquals(want, r);
+ }
+}
diff --git a/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/BasicTest.java b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/BasicTest.java
new file mode 100644
index 0000000000..1f5e2ab7c4
--- /dev/null
+++ b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/BasicTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.meter.analyzer.v2.dsl;
+
+import com.google.common.collect.ImmutableMap;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static com.google.common.collect.ImmutableMap.of;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+@Slf4j
+public class BasicTest {
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] {
+ {
+ "default",
+ of("instance_cpu_percentage", SampleFamily.EMPTY),
+ "instance_cpu_percentage",
+ Result.fail("Parsed result is an EMPTY sample family"),
+ false,
+ },
+ {
+ "single-value",
+ of("instance_cpu_percentage", SampleFamilyBuilder.newBuilder(Sample.builder().value(1600592418480.0).name("instance_cpu_percentage").build()).build()),
+ "instance_cpu_percentage",
+ Result.success(SampleFamilyBuilder.newBuilder(Sample.builder().value(1600592418480.0).name("instance_cpu_percentage").build()).build()),
+ false,
+ },
+ });
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("data")
+ public void test(String name,
+ ImmutableMap<String, SampleFamily> input,
+ String expression,
+ Result want,
+ boolean isThrow) {
+ Result r = null;
+ try {
+ // v2 compiles eagerly, so parse-time failures surface here too (v1 surfaced them at run()).
+ Expression e = DSL.parse(name, expression);
+ r = e.run(input);
+ } catch (Throwable t) {
+ if (isThrow) {
+ return;
+ }
+ log.error("Test failed", t);
+ fail("Should not throw anything");
+ }
+ if (isThrow) {
+ fail("Should throw something");
+ }
+ assertEquals(want, r);
+ }
+}
diff --git a/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/DecorateTest.java b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/DecorateTest.java
new file mode 100644
index 0000000000..d6eec9b843
--- /dev/null
+++ b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/DecorateTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.meter.analyzer.v2.dsl;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.skywalking.oap.server.core.analysis.meter.MeterEntity;
+import org.apache.skywalking.oap.server.core.config.NamingControl;
+import org.apache.skywalking.oap.server.core.config.group.EndpointNameGrouping;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static com.google.common.collect.ImmutableMap.of;
+
+public class DecorateTest {
+ @BeforeAll
+ public static void setup() {
+ MeterEntity.setNamingControl(
+ new NamingControl(512, 512, 512, new EndpointNameGrouping()));
+ }
+
+ @AfterAll
+ public static void tearDown() {
+ MeterEntity.setNamingControl(null);
+ }
+
+ @Test
+ public void testDecorate() {
+ ImmutableMap<String, SampleFamily> input = ImmutableMap.of(
+ "http_success_request",
+ SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_success_request").build(),
+ Sample.builder()
+ .labels(of("idc", "t1", "region", "us", "instance", "10.0.0.1"))
+ .value(100)
+ .name("http_success_request")
+ .build()
+ ).build()
+ );
+ String expression = "http_success_request.sum(['idc']).service(['idc'], Layer.GENERAL).decorate({ me -> me.attr0 = me.layer.name()})";
+ Expression e = DSL.parse("decorate", expression);
+ Result r = e.run(input);
+ Assertions.assertTrue(r.isSuccess());
+ Assertions.assertEquals(
+ "GENERAL", r.getData().context.getMeterSamples().keySet().stream().findFirst().get().getAttr0());
+ }
+}
diff --git a/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/FilterTest.java b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/FilterTest.java
new file mode 100644
index 0000000000..476f82b9b5
--- /dev/null
+++ b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/FilterTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.meter.analyzer.v2.dsl;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static com.google.common.collect.ImmutableMap.of;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * v1 filtered inline as a chain method: {@code metric.filter({tags -> ...})}. v2 has no inline
+ * {@code .filter()} chain method; a MAL filter closure is compiled on its own via {@link
+ * FilterExpression} (the rule's {@code filter:} field) into a {@link MalFilter} and applied to the
+ * whole sample-family map. This test drives the v2 {@link FilterExpression} with the same closures
+ * and inputs. {@code FilterExpression.filter} filters samples within each family and drops any
+ * family that becomes EMPTY, so expected values are maps rather than a single {@code Result}.
+ */
+@Slf4j
+public class FilterTest {
+ public static Collection<Object[]> data() {
+ final SampleFamily sf =
+ SampleFamilyBuilder.newBuilder(
+ Sample.builder()
+ .value(1600592418480.0)
+ .labels(ImmutableMap.of("str", "val1"))
+ .name("instance_cpu_percentage")
+ .build(),
+ Sample.builder()
+ .value(1600592418480.0)
+ .labels(ImmutableMap.of("str", "val2"))
+ .name("instance_cpu_percentage")
+ .build())
+ .build();
+ return Arrays.asList(new Object[][]{
+ {
+ "filter-string",
+ of("instance_cpu_percentage", sf),
+ "{ tags -> tags.str == 'val1' }",
+ of("instance_cpu_percentage", SampleFamily.build(sf.context, sf.samples[0])),
+ },
+ {
+ "filter-equal-val2",
+ of("instance_cpu_percentage", sf),
+ "{ tags -> tags.str == 'val2' }",
+ of("instance_cpu_percentage", SampleFamily.build(sf.context, sf.samples[1])),
+ },
+ {
+ "filter-not-equal",
+ of("instance_cpu_percentage", sf),
+ "{ tags -> tags.str != 'val1' }",
+ of("instance_cpu_percentage", SampleFamily.build(sf.context, sf.samples[1])),
+ },
+ {
+ "filter-in-single",
+ of("instance_cpu_percentage", sf),
+ "{ tags -> tags.str in [ 'val2' ] }",
+ of("instance_cpu_percentage", SampleFamily.build(sf.context, sf.samples[1])),
+ },
+ {
+ "filter-in-multiple",
+ of("instance_cpu_percentage", sf),
+ "{ tags -> tags.str in [ 'val1', 'val2' ] }",
+ of("instance_cpu_percentage", sf),
+ },
+ });
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("data")
+ public void test(String name,
+ Map<String, SampleFamily> input,
+ String filterLiteral,
+ Map<String, SampleFamily> want) {
+ Map<String, SampleFamily> r = new FilterExpression(filterLiteral).filter(input);
+ assertEquals(want, r);
+ }
+}
diff --git a/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/FunctionTest.java b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/FunctionTest.java
new file mode 100644
index 0000000000..65eb830d14
--- /dev/null
+++ b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/FunctionTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.meter.analyzer.v2.dsl;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.Arrays;
+import java.util.Collection;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static com.google.common.collect.ImmutableMap.of;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+@Slf4j
+public class FunctionTest {
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] {
+ {
+ // v1 (interpreted Groovy) accepted a bare closure returning a map literal as a full
+ // tag override. v2's MAL grammar only accepts the arrow form ({tags -> ...}), so this
+ // exact v1 syntax is rejected at parse time. Kept as a negative case to pin that change;
+ // no production MAL rule uses the bare-map form.
+ "tag-override",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(Sample.builder().labels(of("region", "us")).name("http_success_request").build()).build()),
+ "http_success_request.tag({ ['svc':'product', 'instance':'10.0.0.1'] })",
+ Result.success(SampleFamilyBuilder.newBuilder(Sample.builder().labels(of("svc", "product", "instance", "10.0.0.1")).name("http_success_request").build()).build()),
+ true,
+ },
+ {
+ "tag-add",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(Sample.builder().labels(of("region", "us")).name("http_success_request").build()).build()),
+ "http_success_request.tag({tags -> tags.az = 'az1' })",
+ Result.success(SampleFamilyBuilder.newBuilder(Sample.builder().labels(of("region", "us", "az", "az1")).name("http_success_request").build()).build()),
+ false,
+ },
+ {
+ "tag-remove",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(Sample.builder().labels(of("region", "us")).name("http_success_request").build()).build()),
+ "http_success_request.tag({tags -> tags.remove('region') })",
+ Result.success(SampleFamilyBuilder.newBuilder(Sample.builder().labels(ImmutableMap.of()).name("http_success_request").build()).build()),
+ false,
+ },
+ {
+ "tag-update",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(Sample.builder().labels(of("region", "us")).name("http_success_request").build()).build()),
+ "http_success_request.tag({tags -> if (tags['region'] == 'us') {tags.region = 'zh'} })",
+ Result.success(SampleFamilyBuilder.newBuilder(Sample.builder().labels(of("region", "zh")).name("http_success_request").build()).build()),
+ false,
+ },
+ {
+ "tag-append",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(Sample.builder().labels(of("region", "us")).name("http_success_request").build()).build()),
+ "http_success_request.tag({tags -> tags.region = 'prefix::' + tags.region})",
+ Result.success(SampleFamilyBuilder.newBuilder(Sample.builder().labels(of("region", "prefix::us")).name("http_success_request").build()).build()),
+ false,
+ },
+ {
+ "histogram",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("le", "0.025")).value(100).name("http_success_request").build(),
+ Sample.builder().labels(of("le", "0.75")).value(12).name("http_success_request").build(),
+ Sample.builder().labels(of("le", "1.25")).value(36).name("http_success_request").build()).build()
+ ),
+ "http_success_request.histogram()",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("le", "25")).value(100).name("http_success_request").build(),
+ Sample.builder().labels(of("le", "750")).value(12).name("http_success_request").build(),
+ Sample.builder().labels(of("le", "1250")).value(36).name("http_success_request").build()).build()
+ ),
+ false,
+ },
+ {
+ "histogram_percentile",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("le", "0.025")).value(100).name("http_success_request").build(),
+ Sample.builder().labels(of("le", "0.75")).value(22).name("http_success_request").build(),
+ Sample.builder().labels(of("le", "1.25")).value(30).name("http_success_request").build()).build()
+ ),
+ "http_success_request.histogram().histogram_percentile([75,99])",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("le", "25")).value(100).name("http_success_request").build(),
+ Sample.builder().labels(of("le", "750")).value(22).name("http_success_request").build(),
+ Sample.builder().labels(of("le", "1250")).value(30).name("http_success_request").build()).build()
+ ),
+ false,
+ },
+ {
+ "for-each",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("region", "us")).name("http_success_request").build(),
+ Sample.builder().labels(of("region", "cn")).name("http_success_request").build()
+ ).build()),
+ "http_success_request.forEach(['v1', 'v2'], {element, tags -> tags[element] = 'test'})",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("region", "us", "v1", "test", "v2", "test")).name("http_success_request").build(),
+ Sample.builder().labels(of("region", "cn", "v1", "test", "v2", "test")).name("http_success_request").build()
+ ).build()),
+ false,
+ },
+ });
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("data")
+ public void test(String name,
+ ImmutableMap<String, SampleFamily> input,
+ String expression,
+ Result want,
+ boolean isThrow) {
+ Result r = null;
+ try {
+ // v2 compiles eagerly, so parse-time failures surface here too (v1 surfaced them at run()).
+ Expression e = DSL.parse(name, expression);
+ r = e.run(input);
+ } catch (Throwable t) {
+ if (isThrow) {
+ return;
+ }
+ log.error("Test failed", t);
+ fail("Should not throw anything");
+ }
+ if (isThrow) {
+ fail("Should throw something");
+ }
+ assertEquals(want, r);
+ }
+}
\ No newline at end of file
diff --git a/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/IncreaseTest.java b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/IncreaseTest.java
new file mode 100644
index 0000000000..86e77b6b9c
--- /dev/null
+++ b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/IncreaseTest.java
@@ -0,0 +1,240 @@
+/*
+ * 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.meter.analyzer.v2.dsl;
+
+import com.google.common.collect.ImmutableMap;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.oap.meter.analyzer.v2.dsl.counter.CounterWindow;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.Collection;
+import java.util.List;
+
+import static com.google.common.collect.ImmutableMap.of;
+import static java.time.Instant.parse;
+import static java.util.Arrays.asList;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+@Slf4j
+public class IncreaseTest {
+ public static Collection<Object[]> data() {
+ return asList(new Object[][] {
+ {
+ "increase",
+ asList(
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().name("http_success_request").labels(of("svc", "product"))
+ .timestamp(parse("2020-09-11T11:11:01.00Z").toEpochMilli()).value(50).name("http_success_request").build(),
+ Sample.builder().name("http_success_request").labels(of("svc", "catalog"))
+ .timestamp(parse("2020-09-11T11:11:01.00Z").toEpochMilli()).value(150).name("http_success_request").build()
+ ).build()),
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().name("http_success_request").labels(of("svc", "product"))
+ .timestamp(parse("2020-09-11T11:13:41.00Z").toEpochMilli()).value(330).name("http_success_request").build(),
+ Sample.builder().name("http_success_request").labels(of("svc", "catalog"))
+ .timestamp(parse("2020-09-11T11:13:41.00Z").toEpochMilli()).value(500).name("http_success_request").build()
+ ).build()),
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().name("http_success_request").labels(of("svc", "product"))
+ .timestamp(parse("2020-09-11T11:16:31.00Z").toEpochMilli()).value(380).name("http_success_request").build(),
+ Sample.builder().name("http_success_request").labels(of("svc", "catalog"))
+ .timestamp(parse("2020-09-11T11:16:31.00Z").toEpochMilli()).value(810).name("http_success_request").build()
+ ).build()),
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().name("http_success_request").labels(of("svc", "product"))
+ .timestamp(parse("2020-09-11T11:19:31.02Z").toEpochMilli()).value(1380).name("http_success_request").build(),
+ Sample.builder().name("http_success_request").labels(of("svc", "catalog"))
+ .timestamp(parse("2020-09-11T11:19:31.02Z").toEpochMilli()).value(1900).name("http_success_request").build()
+ ).build())
+ ),
+ "http_success_request.increase('PT5M')",
+ asList(
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().name("http_success_request").labels(of("svc", "product"))
+ .timestamp(parse("2020-09-11T11:11:01.00Z").toEpochMilli()).value(0).name("http_success_request").build(),
+ Sample.builder().name("http_success_request").labels(of("svc", "catalog"))
+ .timestamp(parse("2020-09-11T11:11:01.00Z").toEpochMilli()).value(0).name("http_success_request").build()
+ ).build()),
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().name("http_success_request").labels(of("svc", "product"))
+ .timestamp(parse("2020-09-11T11:13:41.00Z").toEpochMilli()).value(280).name("http_success_request").build(),
+ Sample.builder().name("http_success_request").labels(of("svc", "catalog"))
+ .timestamp(parse("2020-09-11T11:13:41.00Z").toEpochMilli()).value(350).name("http_success_request").build()
+ ).build()),
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().name("http_success_request").labels(of("svc", "product"))
+ .timestamp(parse("2020-09-11T11:16:31.00Z").toEpochMilli()).value(330).name("http_success_request").build(),
+ Sample.builder().name("http_success_request").labels(of("svc", "catalog"))
+ .timestamp(parse("2020-09-11T11:16:31.00Z").toEpochMilli()).value(660).name("http_success_request").build()
+ ).build()),
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().name("http_success_request").labels(of("svc", "product"))
+ .timestamp(parse("2020-09-11T11:19:31.02Z").toEpochMilli()).value(1050).name("http_success_request").build(),
+ Sample.builder().name("http_success_request").labels(of("svc", "catalog"))
+ .timestamp(parse("2020-09-11T11:19:31.02Z").toEpochMilli()).value(1400).name("http_success_request").build()
+ ).build())
+ ),
+ false,
+ },
+ {
+ "rate",
+ asList(
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().name("http_success_request").labels(of("svc", "product"))
+ .timestamp(parse("2020-09-11T11:11:01.00Z").toEpochMilli()).value(50).name("http_success_request").build(),
+ Sample.builder().name("http_success_request").labels(of("svc", "catalog"))
+ .timestamp(parse("2020-09-11T11:11:01.00Z").toEpochMilli()).value(150).name("http_success_request").build()
+ ).build()),
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().name("http_success_request").labels(of("svc", "product"))
+ .timestamp(parse("2020-09-11T11:13:41.00Z").toEpochMilli()).value(330).name("http_success_request").build(),
+ Sample.builder().name("http_success_request").labels(of("svc", "catalog"))
+ .timestamp(parse("2020-09-11T11:13:41.00Z").toEpochMilli()).value(500).name("http_success_request").build()
+ ).build()),
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().name("http_success_request").labels(of("svc", "product"))
+ .timestamp(parse("2020-09-11T11:16:31.00Z").toEpochMilli()).value(380).name("http_success_request").build(),
+ Sample.builder().name("http_success_request").labels(of("svc", "catalog"))
+ .timestamp(parse("2020-09-11T11:16:31.00Z").toEpochMilli()).value(810).name("http_success_request").build()
+ ).build()),
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().name("http_success_request").labels(of("svc", "product"))
+ .timestamp(parse("2020-09-11T11:19:31.02Z").toEpochMilli()).value(1380).name("http_success_request").build(),
+ Sample.builder().name("http_success_request").labels(of("svc", "catalog"))
+ .timestamp(parse("2020-09-11T11:19:31.02Z").toEpochMilli()).value(1900).name("http_success_request").build()
+ ).build())
+ ),
+ "http_success_request.rate('PT5M')",
+ asList(
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().name("http_success_request").labels(of("svc", "product"))
+ .timestamp(parse("2020-09-11T11:11:01.00Z").toEpochMilli()).value(0).name("http_success_request").build(),
+ Sample.builder().name("http_success_request").labels(of("svc", "catalog"))
+ .timestamp(parse("2020-09-11T11:11:01.00Z").toEpochMilli()).value(0).name("http_success_request").build()
+ ).build()),
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().name("http_success_request").labels(of("svc", "product"))
+ .timestamp(parse("2020-09-11T11:13:41.00Z").toEpochMilli()).value(1.75D).name("http_success_request").build(),
+ Sample.builder().name("http_success_request").labels(of("svc", "catalog"))
+ .timestamp(parse("2020-09-11T11:13:41.00Z").toEpochMilli()).value(2.1875D).name("http_success_request").build()
+ ).build()),
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().name("http_success_request").labels(of("svc", "product"))
+ .timestamp(parse("2020-09-11T11:16:31.00Z").toEpochMilli()).value(1D).name("http_success_request").build(),
+ Sample.builder().name("http_success_request").labels(of("svc", "catalog"))
+ .timestamp(parse("2020-09-11T11:16:31.00Z").toEpochMilli()).value(2D).name("http_success_request").build()
+ ).build()),
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().name("http_success_request").labels(of("svc", "product"))
+ .timestamp(parse("2020-09-11T11:19:31.02Z").toEpochMilli()).value(3D).name("http_success_request").build(),
+ Sample.builder().name("http_success_request").labels(of("svc", "catalog"))
+ .timestamp(parse("2020-09-11T11:19:31.02Z").toEpochMilli()).value(4D).name("http_success_request").build()
+ ).build())
+ ),
+ false,
+ },
+ {
+ "irate",
+ asList(
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().name("http_success_request").labels(of("svc", "product"))
+ .timestamp(parse("2020-09-11T11:11:01.00Z").toEpochMilli()).value(50).name("http_success_request").build(),
+ Sample.builder().name("http_success_request").labels(of("svc", "catalog"))
+ .timestamp(parse("2020-09-11T11:11:01.00Z").toEpochMilli()).value(150).name("http_success_request").build()
+ ).build()),
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().name("http_success_request").labels(of("svc", "product"))
+ .timestamp(parse("2020-09-11T11:13:41.00Z").toEpochMilli()).value(330).name("http_success_request").build(),
+ Sample.builder().name("http_success_request").labels(of("svc", "catalog"))
+ .timestamp(parse("2020-09-11T11:13:41.00Z").toEpochMilli()).value(500).name("http_success_request").build()
+ ).build()),
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().name("http_success_request").labels(of("svc", "product"))
+ .timestamp(parse("2020-09-11T11:16:31.00Z").toEpochMilli()).value(500).name("http_success_request").build(),
+ Sample.builder().name("http_success_request").labels(of("svc", "catalog"))
+ .timestamp(parse("2020-09-11T11:16:31.00Z").toEpochMilli()).value(840).name("http_success_request").build()
+ ).build()),
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().name("http_success_request").labels(of("svc", "product"))
+ .timestamp(parse("2020-09-11T11:19:31.02Z").toEpochMilli()).value(1040).name("http_success_request").build(),
+ Sample.builder().name("http_success_request").labels(of("svc", "catalog"))
+ .timestamp(parse("2020-09-11T11:19:31.02Z").toEpochMilli()).value(1560).name("http_success_request").build()
+ ).build())
+ ),
+ "http_success_request.irate()",
+ asList(
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().name("http_success_request").labels(of("svc", "product"))
+ .timestamp(parse("2020-09-11T11:11:01.00Z").toEpochMilli()).value(0).name("http_success_request").build(),
+ Sample.builder().name("http_success_request").labels(of("svc", "catalog"))
+ .timestamp(parse("2020-09-11T11:11:01.00Z").toEpochMilli()).value(0).name("http_success_request").build()
+ ).build()),
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().name("http_success_request").labels(of("svc", "product"))
+ .timestamp(parse("2020-09-11T11:13:41.00Z").toEpochMilli()).value(1.75D).name("http_success_request").build(),
+ Sample.builder().name("http_success_request").labels(of("svc", "catalog"))
+ .timestamp(parse("2020-09-11T11:13:41.00Z").toEpochMilli()).value(2.1875D).name("http_success_request").build()
+ ).build()),
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().name("http_success_request").labels(of("svc", "product"))
+ .timestamp(parse("2020-09-11T11:16:31.00Z").toEpochMilli()).value(1D).name("http_success_request").build(),
+ Sample.builder().name("http_success_request").labels(of("svc", "catalog"))
+ .timestamp(parse("2020-09-11T11:16:31.00Z").toEpochMilli()).value(2D).name("http_success_request").build()
+ ).build()),
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().name("http_success_request").labels(of("svc", "product"))
+ .timestamp(parse("2020-09-11T11:19:31.02Z").toEpochMilli()).value(3D).name("http_success_request").build(),
+ Sample.builder().name("http_success_request").labels(of("svc", "catalog"))
+ .timestamp(parse("2020-09-11T11:19:31.02Z").toEpochMilli()).value(4D).name("http_success_request").build()
+ ).build())
+ ),
+ false,
+ },
+ });
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("data")
+ public void test(String name,
+ List<ImmutableMap<String, SampleFamily>> input,
+ String expression,
+ List<Result> want,
+ boolean isThrow) {
+ Expression e = DSL.parse(name, expression);
+ CounterWindow.INSTANCE.reset();
+ for (int i = 0; i < input.size(); i++) {
+ Result r = null;
+ try {
+ r = e.run(input.get(i));
+ } catch (Throwable t) {
+ if (isThrow) {
+ return;
+ }
+ log.error("Test failed", t);
+ fail("Should not throw anything");
+ }
+ if (isThrow) {
+ fail("Should throw something");
+ }
+ assertEquals(want.get(i), r);
+ }
+ }
+}
diff --git a/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/K8sTagTest.java b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/K8sTagTest.java
new file mode 100644
index 0000000000..e2726e624f
--- /dev/null
+++ b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/K8sTagTest.java
@@ -0,0 +1,340 @@
+/*
+ * 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.meter.analyzer.v2.dsl;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import io.fabric8.kubernetes.api.model.LoadBalancerIngress;
+import io.fabric8.kubernetes.api.model.LoadBalancerStatus;
+import io.fabric8.kubernetes.api.model.ObjectMeta;
+import io.fabric8.kubernetes.api.model.Pod;
+import io.fabric8.kubernetes.api.model.PodStatus;
+import io.fabric8.kubernetes.api.model.Service;
+import io.fabric8.kubernetes.api.model.ServiceSpec;
+import io.fabric8.kubernetes.api.model.ServiceStatus;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.library.kubernetes.KubernetesPods;
+import org.apache.skywalking.library.kubernetes.KubernetesServices;
+import org.apache.skywalking.library.kubernetes.ObjectID;
+import org.apache.skywalking.oap.meter.analyzer.v2.dsl.tagOpt.Retag;
+import org.apache.skywalking.oap.server.core.analysis.IDManager;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
+import org.apache.skywalking.oap.server.testing.util.ReflectUtil;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Optional;
+
+import static com.google.common.collect.ImmutableMap.of;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+import static org.mockito.Mockito.when;
+
+@Slf4j
+@ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.WARN)
+public class K8sTagTest {
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] {
+ {
+ "Pod2Service",
+ of("container_cpu_usage_seconds_total", SampleFamilyBuilder.newBuilder(
+ Sample.builder()
+ .labels(
+ of(
+ "namespace", "default", "container", "my-nginx", "cpu", "total", "pod",
+ "my-nginx-5dc4865748-mbczh"
+ ))
+ .value(2)
+ .name("container_cpu_usage_seconds_total")
+ .build(),
+ Sample.builder()
+ .labels(
+ of(
+ "namespace", "kube-system", "container", "kube-state-metrics", "cpu", "total", "pod",
+ "kube-state-metrics-6f979fd498-z7xwx"
+ ))
+ .value(1)
+ .name("container_cpu_usage_seconds_total")
+ .build()
+ ).build()),
+ "container_cpu_usage_seconds_total.retagByK8sMeta('service' , K8sRetagType.Pod2Service , 'pod' , 'namespace')",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder()
+ .labels(
+ of(
+ "namespace", "default", "container", "my-nginx", "cpu", "total", "pod",
+ "my-nginx-5dc4865748-mbczh",
+ "service", "nginx-service.default"
+ ))
+ .value(2)
+ .name("container_cpu_usage_seconds_total")
+ .build(),
+ Sample.builder()
+ .labels(
+ of(
+ "namespace", "kube-system", "container", "kube-state-metrics", "cpu", "total", "pod",
+ "kube-state-metrics-6f979fd498-z7xwx",
+ "service", "kube-state-metrics.kube-system"
+ ))
+ .value(1)
+ .name("container_cpu_usage_seconds_total")
+ .build()
+ ).build()),
+ false,
+ },
+ {
+ "Pod2Service_no_pod",
+ of("container_cpu_usage_seconds_total", SampleFamilyBuilder.newBuilder(
+ Sample.builder()
+ .labels(
+ of(
+ "namespace", "default", "container", "my-nginx", "cpu", "total", "pod",
+ "my-nginx-5dc4865748-no-pod"
+ ))
+ .value(2)
+ .name("container_cpu_usage_seconds_total")
+ .build(),
+ Sample.builder()
+ .labels(
+ of(
+ "namespace", "kube-system", "container", "kube-state-metrics", "cpu", "total", "pod",
+ "kube-state-metrics-6f979fd498-z7xwx"
+ ))
+ .value(1)
+ .name("container_cpu_usage_seconds_total")
+ .build()
+ ).build()),
+ "container_cpu_usage_seconds_total.retagByK8sMeta('service' , K8sRetagType.Pod2Service , 'pod' , 'namespace')",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder()
+ .labels(
+ of(
+ "namespace", "default", "container", "my-nginx", "cpu", "total", "pod",
+ "my-nginx-5dc4865748-no-pod" , "service", Retag.BLANK
+ ))
+ .value(2)
+ .name("container_cpu_usage_seconds_total")
+ .build(),
+ Sample.builder()
+ .labels(
+ of(
+ "namespace", "kube-system", "container", "kube-state-metrics", "cpu", "total", "pod",
+ "kube-state-metrics-6f979fd498-z7xwx",
+ "service", "kube-state-metrics.kube-system"
+ ))
+ .value(1)
+ .name("container_cpu_usage_seconds_total")
+ .build()
+ ).build()),
+ false,
+ },
+ {
+ "Pod2Service_no_service",
+ of("container_cpu_usage_seconds_total", SampleFamilyBuilder.newBuilder(
+ Sample.builder()
+ .labels(
+ of(
+ "namespace", "default", "container", "my-nginx", "cpu", "total", "pod",
+ "my-nginx-5dc4865748-no-service"
+ ))
+ .value(2)
+ .name("container_cpu_usage_seconds_total")
+ .build(),
+ Sample.builder()
+ .labels(
+ of(
+ "namespace", "kube-system", "container", "kube-state-metrics", "cpu", "total", "pod",
+ "kube-state-metrics-6f979fd498-z7xwx"
+ ))
+ .value(1)
+ .name("container_cpu_usage_seconds_total")
+ .build()
+ ).build()),
+ "container_cpu_usage_seconds_total.retagByK8sMeta('service' , K8sRetagType.Pod2Service , 'pod' , 'namespace')",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder()
+ .labels(
+ of(
+ "namespace", "default", "container", "my-nginx", "cpu", "total", "pod",
+ "my-nginx-5dc4865748-no-service" , "service", Retag.BLANK
+ ))
+ .value(2)
+ .name("container_cpu_usage_seconds_total")
+ .build(),
+ Sample.builder()
+ .labels(
+ of(
+ "namespace", "kube-system", "container", "kube-state-metrics", "cpu", "total", "pod",
+ "kube-state-metrics-6f979fd498-z7xwx",
+ "service", "kube-state-metrics.kube-system"
+ ))
+ .value(1)
+ .name("container_cpu_usage_seconds_total")
+ .build()
+ ).build()),
+ false,
+ },
+ {
+ "IPAddress_to_name",
+ of("rover_network_profiling_process_write_bytes", SampleFamilyBuilder.newBuilder(
+ Sample.builder()
+ .labels(
+ of("service", "test", "instance", "test-instance", "side", "client",
+ "client_address", "1.1.1.1", "server_address", "2.2.2.2")
+ )
+ .value(2)
+ .name("rover_network_profiling_process_write_bytes")
+ .build()
+ ).build()),
+ "rover_network_profiling_process_write_bytes.forEach(['client', 'server'] , " +
+ "{prefix, tags -> tags[prefix + '_process_id'] = ProcessRegistry.generateVirtualRemoteProcess(tags.service, tags.instance, tags[prefix + '_address'])})",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder()
+ .labels(
+ of("service", "test", "instance", "test-instance", "side", "client",
+ "client_address", "1.1.1.1", "client_process_id", IDManager.ProcessID.buildId(
+ IDManager.ServiceInstanceID.buildId(IDManager.ServiceID.buildId("test", true), "test-instance"),
+ "my-nginx-5dc4865748-mbczh.default"),
+ "server_address", "2.2.2.2", "server_process_id", IDManager.ProcessID.buildId(
+ IDManager.ServiceInstanceID.buildId(IDManager.ServiceID.buildId("test", true), "test-instance"),
+ "kube-state-metrics.kube-system"))
+ )
+ .value(2)
+ .name("rover_network_profiling_process_write_bytes")
+ .build()
+ ).build()),
+ false,
+ }
+ });
+ }
+
+ private KubernetesServices originalServices;
+ private KubernetesPods originalPods;
+
+ @SneakyThrows
+ @BeforeEach
+ public void setup() {
+ // KubernetesServices/KubernetesPods are enum singletons; capture the real INSTANCE so the
+ // mock swap below can be undone in tearDown and does not leak into other tests in this JVM.
+ originalServices = KubernetesServices.INSTANCE;
+ originalPods = KubernetesPods.INSTANCE;
+ ReflectUtil.setInternalState(KubernetesServices.class, "INSTANCE",
+ Mockito.mock(KubernetesServices.class)
+ );
+ ReflectUtil.setInternalState(KubernetesPods.class, "INSTANCE",
+ Mockito.mock(KubernetesPods.class)
+ );
+
+ when(KubernetesServices.INSTANCE.list()).thenReturn(ImmutableList.of(
+ mockService("nginx-service", "default", of("run", "nginx"), "2.2.2.1"),
+ mockService("kube-state-metrics", "kube-system", of("run", "kube-state-metrics"), "2.2.2.2")));
+ ImmutableList.of(
+ mockService("nginx-service", "default", of("run", "nginx"), "2.2.2.1"),
+ mockService("kube-state-metrics", "kube-system", of("run", "kube-state-metrics"), "2.2.2.2"))
+ .forEach(svc ->
+ when(KubernetesServices.INSTANCE.findByID(ObjectID.builder().namespace(svc.getMetadata().getNamespace()).name(svc.getMetadata().getName()).build()))
+ .thenReturn(Optional.of(svc))
+ );
+ ImmutableList.of(
+ mockPod("my-nginx-5dc4865748-mbczh", "default", of("run", "nginx"), "1.1.1.1"),
+ mockPod("kube-state-metrics-6f979fd498-z7xwx", "kube-system", of("run", "kube-state-metrics"), "1.1.1.2"))
+ .forEach(pod -> {
+ when(KubernetesPods.INSTANCE.findByIP(pod.getStatus().getPodIP())).thenReturn(Optional.of(pod));
+ when(KubernetesPods.INSTANCE.findByObjectID(ObjectID.builder().name(pod.getMetadata().getName()).namespace(pod.getMetadata().getNamespace()).build())).thenReturn(Optional.of(pod));
+ });
+ }
+
+ @SneakyThrows
+ @AfterEach
+ public void tearDown() {
+ ReflectUtil.setInternalState(KubernetesServices.class, "INSTANCE", originalServices);
+ ReflectUtil.setInternalState(KubernetesPods.class, "INSTANCE", originalPods);
+ }
+
+ @ParameterizedTest(name = "{index}: {0}")
+ @MethodSource("data")
+ public void test(String name,
+ ImmutableMap<String, SampleFamily> input,
+ String expression,
+ Result want,
+ boolean isThrow) {
+ Expression e = DSL.parse(name, expression);
+ Result r = null;
+ try {
+ r = e.run(input);
+ } catch (Throwable t) {
+ if (isThrow) {
+ return;
+ }
+ log.error("Test failed", t);
+ fail("Should not throw anything");
+ }
+ if (isThrow) {
+ fail("Should throw something");
+ }
+ assertEquals(want, r);
+ }
+
+ private Service mockService(String name, String namespace, Map<String, String> selector, String ipAddress) {
+ Service service = new Service();
+ ObjectMeta serviceMeta = new ObjectMeta();
+ ServiceSpec v1ServiceSpec = new ServiceSpec();
+
+ serviceMeta.setName(name);
+ serviceMeta.setNamespace(namespace);
+ service.setMetadata(serviceMeta);
+ v1ServiceSpec.setSelector(selector);
+ service.setSpec(v1ServiceSpec);
+
+ final ServiceStatus v1ServiceStatus = new ServiceStatus();
+ final LoadBalancerStatus balancerStatus = new LoadBalancerStatus();
+ final LoadBalancerIngress loadBalancerIngress = new LoadBalancerIngress();
+ loadBalancerIngress.setIp(ipAddress);
+ balancerStatus.setIngress(Arrays.asList(loadBalancerIngress));
+ v1ServiceStatus.setLoadBalancer(balancerStatus);
+ service.setStatus(v1ServiceStatus);
+
+ return service;
+ }
+
+ private Pod mockPod(String name, String namespace, Map<String, String> labels, String ipAddress) {
+ Pod v1Pod = new Pod();
+ ObjectMeta podMeta = new ObjectMeta();
+ podMeta.setName(name);
+ podMeta.setNamespace(namespace);
+ podMeta.setLabels(labels);
+ final PodStatus status = new PodStatus();
+ status.setPodIP(ipAddress);
+ v1Pod.setStatus(status);
+ v1Pod.setMetadata(podMeta);
+
+ return v1Pod;
+ }
+}
diff --git a/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/ScopeTest.java b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/ScopeTest.java
new file mode 100644
index 0000000000..3529e5b521
--- /dev/null
+++ b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/ScopeTest.java
@@ -0,0 +1,642 @@
+/*
+ * 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.meter.analyzer.v2.dsl;
+
+import com.google.common.collect.ImmutableMap;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.oap.server.core.analysis.Layer;
+import org.apache.skywalking.oap.server.core.analysis.meter.MeterEntity;
+import org.apache.skywalking.oap.server.core.config.NamingControl;
+import org.apache.skywalking.oap.server.core.config.group.EndpointNameGrouping;
+import org.apache.skywalking.oap.server.core.source.DetectPoint;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.google.common.collect.ImmutableMap.of;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+@Slf4j
+public class ScopeTest {
+ public static Collection<Object[]> data() {
+ // This method is called before `@BeforeAll`.
+ MeterEntity.setNamingControl(
+ new NamingControl(512, 512, 512, new EndpointNameGrouping()));
+
+ return Arrays.asList(new Object[][] {
+ {
+ "sum_service",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_success_request").build(),
+ Sample.builder()
+ .labels(of("idc", "t3", "region", "cn", "svc", "catalog"))
+ .value(51)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t1", "region", "us", "svc", "product"))
+ .value(50)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t1", "region", "us", "instance", "10.0.0.1"))
+ .value(100)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t3", "region", "cn", "instance", "10.0.0.1"))
+ .value(3)
+ .name("http_success_request")
+ .build()
+ ).build()),
+ "http_success_request.sum(['idc']).service(['idc'], Layer.GENERAL)",
+ false,
+ new HashMap<MeterEntity, Sample[]>() {
+ {
+ put(
+ MeterEntity.newService("t1", Layer.GENERAL),
+ new Sample[] {Sample.builder().labels(of()).value(200).name("http_success_request").build()}
+ );
+ put(
+ MeterEntity.newService("t3", Layer.GENERAL),
+ new Sample[] {Sample.builder().labels(of()).value(54).name("http_success_request").build()}
+ );
+ }
+ }
+ },
+ {
+ "sum_service_labels",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_success_request").build(),
+ Sample.builder()
+ .labels(of("idc", "t3", "region", "cn", "svc", "catalog"))
+ .value(51)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t1", "region", "us", "svc", "product"))
+ .value(50)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t1", "region", "us", "instance", "10.0.0.1"))
+ .value(100)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t3", "region", "cn", "instance", "10.0.0.1"))
+ .value(3)
+ .name("http_success_request")
+ .build()
+ ).build()),
+ "http_success_request.sum(['region', 'idc']).service(['idc'], Layer.GENERAL)",
+ false,
+ new HashMap<MeterEntity, Sample[]>() {
+ {
+ put(
+ MeterEntity.newService("t1", Layer.GENERAL),
+ new Sample[] {
+ Sample.builder()
+ .labels(of("region", ""))
+ .value(50)
+ .name("http_success_request").build(),
+ Sample.builder()
+ .labels(of("region", "us"))
+ .value(150)
+ .name("http_success_request").build()
+ }
+ );
+ put(
+ MeterEntity.newService("t3", Layer.GENERAL),
+ new Sample[] {
+ Sample.builder()
+ .labels(of("region", "cn"))
+ .value(54)
+ .name("http_success_request").build()
+ }
+ );
+ }
+ }
+ },
+ {
+ "sum_service_m",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_success_request").build(),
+ Sample.builder()
+ .labels(of("idc", "t3", "region", "cn", "svc", "catalog"))
+ .value(51)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t1", "region", "us", "svc", "product"))
+ .value(50)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t1", "region", "us", "instance", "10.0.0.1"))
+ .value(100)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t3", "region", "cn", "instance", "10.0.0.1"))
+ .value(3)
+ .name("http_success_request")
+ .build()
+ ).build()),
+ "http_success_request.sum(['idc', 'region']).service(['idc' , 'region'], Layer.GENERAL)",
+ false,
+ new HashMap<MeterEntity, Sample[]>() {
+ {
+ put(
+ MeterEntity.newService("t1.us", Layer.GENERAL),
+ new Sample[] {Sample.builder().labels(of()).value(150).name("http_success_request").build()}
+ );
+ put(
+ MeterEntity.newService("t3.cn", Layer.GENERAL),
+ new Sample[] {Sample.builder().labels(of()).value(54).name("http_success_request").build()}
+ );
+ put(
+ MeterEntity.newService("t1", Layer.GENERAL),
+ new Sample[] {Sample.builder().labels(of()).value(50).name("http_success_request").build()}
+ );
+ }
+ }
+ },
+ {
+ "sum_service_endpoint",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_success_request").build(),
+ Sample.builder()
+ .labels(of("idc", "t3", "region", "cn", "svc", "catalog"))
+ .value(51)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t1", "region", "us", "svc", "product"))
+ .value(50)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t1", "region", "us", "instance", "10.0.0.1"))
+ .value(100)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t3", "region", "cn", "instance", "10.0.0.1"))
+ .value(3)
+ .name("http_success_request")
+ .build()
+ ).build()),
+ "http_success_request.sum(['region', 'idc']).endpoint(['idc'] , ['region'], Layer.GENERAL)",
+ false,
+ new HashMap<MeterEntity, Sample[]>() {
+ {
+ put(
+ MeterEntity.newEndpoint("t1", "us", Layer.GENERAL),
+ new Sample[] {Sample.builder().labels(of()).value(150).name("http_success_request").build()}
+ );
+ put(
+ MeterEntity.newEndpoint("t3", "cn", Layer.GENERAL),
+ new Sample[] {Sample.builder().labels(of()).value(54).name("http_success_request").build()}
+ );
+ put(
+ MeterEntity.newEndpoint("t1", "", Layer.GENERAL),
+ new Sample[] {Sample.builder().labels(of()).value(50).name("http_success_request").build()}
+ );
+ }
+ }
+ },
+
+ {
+ "sum_service_endpoint_labels",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_success_request").build(),
+ Sample.builder()
+ .labels(of("idc", "t3", "region", "cn", "svc", "catalog"))
+ .value(51)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t1", "region", "us", "svc", "product"))
+ .value(50)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t1", "region", "us", "instance", "10.0.0.1"))
+ .value(100)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t3", "region", "cn", "instance", "10.0.0.1"))
+ .value(3)
+ .name("http_success_request")
+ .build()
+ ).build()),
+ "http_success_request.sum(['region', 'idc' , 'instance']).endpoint(['idc'] , ['region'], Layer.GENERAL)",
+ false,
+ new HashMap<MeterEntity, Sample[]>() {
+ {
+ put(
+ MeterEntity.newEndpoint("t1", "us", Layer.GENERAL),
+ new Sample[] {
+ Sample.builder()
+ .labels(of("instance", ""))
+ .value(50)
+ .name("http_success_request").build(),
+ Sample.builder()
+ .labels(of("instance", "10.0.0.1"))
+ .value(100)
+ .name("http_success_request").build()
+ }
+ );
+ put(
+ MeterEntity.newEndpoint("t3", "cn", Layer.GENERAL),
+ new Sample[] {
+ Sample.builder()
+ .labels(of("instance", ""))
+ .value(51)
+ .name("http_success_request").build(),
+ Sample.builder()
+ .labels(of("instance", "10.0.0.1"))
+ .value(3)
+ .name("http_success_request").build()
+ }
+ );
+ put(
+ MeterEntity.newEndpoint("t1", "", Layer.GENERAL),
+ new Sample[] {
+ Sample.builder()
+ .labels(of("instance", ""))
+ .value(50)
+ .name("http_success_request").build()
+ }
+ );
+ }
+ }
+ },
+ {
+ "sum_service_endpoint_labels_m",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_success_request").build(),
+ Sample.builder()
+ .labels(of("idc", "t3", "region", "cn", "svc", "product"))
+ .value(51)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t1", "region", "us", "svc", "catalog"))
+ .value(50)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t1", "region", "us", "svc", "catalog", "instance", "10.0.0.1"))
+ .value(100)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t3", "region", "cn", "svc", "product", "instance", "10.0.0.1"))
+ .value(3)
+ .name("http_success_request")
+ .build()
+ ).build()),
+ "http_success_request.sum(['region', 'idc' , 'svc' , 'instance']).endpoint(['idc'] , ['region','svc'], Layer.GENERAL)",
+ false,
+ new HashMap<MeterEntity, Sample[]>() {
+ {
+ put(
+ MeterEntity.newEndpoint("t1", "us.catalog", Layer.GENERAL),
+ new Sample[] {
+ Sample.builder()
+ .labels(of("instance", ""))
+ .value(50)
+ .name("http_success_request").build(),
+ Sample.builder()
+ .labels(of("instance", "10.0.0.1"))
+ .value(100)
+ .name("http_success_request").build()
+ }
+ );
+ put(
+ MeterEntity.newEndpoint("t3", "cn.product", Layer.GENERAL),
+ new Sample[] {
+ Sample.builder()
+ .labels(of("instance", ""))
+ .value(51)
+ .name("http_success_request").build(),
+ Sample.builder()
+ .labels(of("instance", "10.0.0.1"))
+ .value(3)
+ .name("http_success_request").build()
+ }
+ );
+ put(
+ MeterEntity.newEndpoint("t1", "", Layer.GENERAL),
+ new Sample[] {
+ Sample.builder()
+ .labels(of("instance", ""))
+ .value(50)
+ .name("http_success_request").build()
+ }
+ );
+ }
+ }
+ },
+ {
+ "sum_service_instance",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_success_request").build(),
+ Sample.builder()
+ .labels(of("idc", "t3", "region", "cn", "svc", "catalog"))
+ .value(51)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t1", "region", "us", "svc", "product"))
+ .value(50)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t1", "region", "us", "instance", "10.0.0.1"))
+ .value(100)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t3", "region", "cn", "instance", "10.0.0.1"))
+ .value(3)
+ .name("http_success_request")
+ .build()
+ ).build()),
+ "http_success_request.sum(['region', 'idc']).instance(['idc'] , ['region'], Layer.GENERAL)",
+ false,
+ new HashMap<MeterEntity, Sample[]>() {
+ {
+ put(
+ MeterEntity.newServiceInstance("t1", "us", Layer.GENERAL, null),
+ new Sample[] {Sample.builder().labels(of()).value(150).name("http_success_request").build()}
+ );
+ put(
+ MeterEntity.newServiceInstance("t3", "cn", Layer.GENERAL, null),
+ new Sample[] {Sample.builder().labels(of()).value(54).name("http_success_request").build()}
+ );
+ put(
+ MeterEntity.newServiceInstance("t1", "", Layer.GENERAL, null),
+ new Sample[] {Sample.builder().labels(of()).value(50).name("http_success_request").build()}
+ );
+ }
+ }
+ },
+ {
+ "sum_service_instance_labels",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(50).name("http_success_request").build(),
+ Sample.builder()
+ .labels(of("idc", "t3", "region", "cn", "svc", "catalog"))
+ .value(51)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t1", "region", "us", "svc", "product"))
+ .value(50)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t1", "region", "us", "instance", "10.0.0.1"))
+ .value(100)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t3", "region", "cn", "instance", "10.0.0.1"))
+ .value(3)
+ .name("http_success_request")
+ .build()
+ ).build()),
+ "http_success_request.sum(['region', 'idc' , 'instance']).instance(['idc'] , ['region'], Layer.GENERAL)",
+ false,
+ new HashMap<MeterEntity, Sample[]>() {
+ {
+ put(
+ MeterEntity.newServiceInstance("t1", "us", Layer.GENERAL, null),
+ new Sample[] {
+ Sample.builder()
+ .labels(of("instance", ""))
+ .value(50)
+ .name("http_success_request").build(),
+ Sample.builder()
+ .labels(of("instance", "10.0.0.1"))
+ .value(100)
+ .name("http_success_request").build()
+ }
+ );
+ put(
+ MeterEntity.newServiceInstance("t3", "cn", Layer.GENERAL, null),
+ new Sample[] {
+ Sample.builder()
+ .labels(of("instance", ""))
+ .value(51)
+ .name("http_success_request").build(),
+ Sample.builder()
+ .labels(of("instance", "10.0.0.1"))
+ .value(3)
+ .name("http_success_request").build()
+ }
+ );
+ put(
+ MeterEntity.newServiceInstance("t1", "", Layer.GENERAL, null),
+ new Sample[] {
+ Sample.builder()
+ .labels(of("instance", ""))
+ .value(50)
+ .name("http_success_request").build()
+ }
+ );
+ }
+ }
+ },
+ {
+ "sum_service_relation",
+ of("envoy_cluster_metrics_up_cx_active", SampleFamilyBuilder.newBuilder(
+ Sample.builder()
+ .labels(of("app", "productpage", "cluster_name", "details"))
+ .value(11)
+ .name("envoy_cluster_metrics_up_cx_active")
+ .build(),
+ Sample.builder()
+ .labels(of("app", "productpage", "cluster_name", "reviews"))
+ .value(16)
+ .name("envoy_cluster_metrics_up_cx_active")
+ .build()
+ ).build()),
+ "envoy_cluster_metrics_up_cx_active.sum(['app' ,'cluster_name']).serviceRelation(DetectPoint.CLIENT, ['app'], ['cluster_name'], Layer.GENERAL)",
+ false,
+ new HashMap<MeterEntity, Sample[]>() {
+ {
+ put(
+ MeterEntity.newServiceRelation("productpage", "details", DetectPoint.CLIENT, Layer.GENERAL, 0),
+ new Sample[] {
+ Sample.builder()
+ .labels(of())
+ .value(11)
+ .name("envoy_cluster_metrics_up_cx_active").build()
+ }
+ );
+ put(
+ MeterEntity.newServiceRelation("productpage", "reviews", DetectPoint.CLIENT, Layer.GENERAL, 0),
+ new Sample[] {
+ Sample.builder()
+ .labels(of())
+ .value(16)
+ .name("envoy_cluster_metrics_up_cx_active").build()
+ }
+ );
+ }
+ }
+ },
+ {
+ "sum_service_relation_with_component",
+ of("envoy_cluster_metrics_up_cx_active", SampleFamilyBuilder.newBuilder(
+ Sample.builder()
+ .labels(of("app", "productpage", "cluster_name", "details", "env", "test", "component", "10"))
+ .value(11)
+ .name("envoy_cluster_metrics_up_cx_active")
+ .build(),
+ Sample.builder()
+ .labels(of("app", "productpage", "cluster_name", "reviews", "env", "test", "component", "10"))
+ .value(16)
+ .name("envoy_cluster_metrics_up_cx_active")
+ .build()
+ ).build()),
+ "envoy_cluster_metrics_up_cx_active.sum(['app' ,'cluster_name', 'env', 'component']).serviceRelation(DetectPoint.CLIENT, ['app', 'env'], ['cluster_name'], '-', Layer.GENERAL, 'component')",
+ false,
+ new HashMap<MeterEntity, Sample[]>() {
+ {
+ put(
+ MeterEntity.newServiceRelation("productpage-test", "details", DetectPoint.CLIENT, Layer.GENERAL, 10),
+ new Sample[] {
+ Sample.builder()
+ .labels(of())
+ .value(11)
+ .name("envoy_cluster_metrics_up_cx_active").build()
+ }
+ );
+ put(
+ MeterEntity.newServiceRelation("productpage-test", "reviews", DetectPoint.CLIENT, Layer.GENERAL, 10),
+ new Sample[] {
+ Sample.builder()
+ .labels(of())
+ .value(16)
+ .name("envoy_cluster_metrics_up_cx_active").build()
+ }
+ );
+ }
+ }
+ },
+ {
+ "sum_process_relation",
+ of("rover_network_profiling_process_write_bytes", SampleFamilyBuilder.newBuilder(
+ Sample.builder()
+ .labels(of("service", "test", "instance", "test-instance", "side", "server", "client_process_id", "abc", "server_process_id", "def", "component", "1"))
+ .value(11)
+ .name("rover_network_profiling_process_write_bytes")
+ .build(),
+ Sample.builder()
+ .labels(of("service", "test", "instance", "test-instance", "side", "client", "client_process_id", "abc", "server_process_id", "def", "component", "2"))
+ .value(12)
+ .name("rover_network_profiling_process_write_bytes")
+ .build()
+ ).build()),
+ "rover_network_profiling_process_write_bytes.sum(['service' ,'instance', 'side', 'client_process_id', 'server_process_id', 'component'])" +
+ ".processRelation('side', ['service'], ['instance'], 'client_process_id', 'server_process_id', 'component')",
+ false,
+ new HashMap<MeterEntity, Sample[]>() {
+ {
+ put(
+ MeterEntity.newProcessRelation("test", "test-instance", "abc", "def", 1, DetectPoint.SERVER),
+ new Sample[] {
+ Sample.builder()
+ .labels(of())
+ .value(11)
+ .name("rover_network_profiling_process_write_bytes").build()
+ }
+ );
+ put(
+ MeterEntity.newProcessRelation("test", "test-instance", "abc", "def", 2, DetectPoint.CLIENT),
+ new Sample[] {
+ Sample.builder()
+ .labels(of())
+ .value(12)
+ .name("rover_network_profiling_process_write_bytes").build()
+ }
+ );
+ }
+ }
+ }
+ });
+ }
+
+ @BeforeAll
+ public static void setup() {
+ MeterEntity.setNamingControl(
+ new NamingControl(512, 512, 512, new EndpointNameGrouping()));
+ }
+
+ @AfterAll
+ public static void tearDown() {
+ MeterEntity.setNamingControl(null);
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("data")
+ public void test(final String name,
+ final ImmutableMap<String, SampleFamily> input,
+ final String expression,
+ final boolean isThrow,
+ final Map<MeterEntity, Sample[]> want) {
+ Expression e = DSL.parse(name, expression);
+ Result r = null;
+ try {
+ r = e.run(input);
+ } catch (Throwable t) {
+ if (isThrow) {
+ return;
+ }
+ log.error("Test failed", t);
+ fail("Should not throw anything");
+ }
+ if (isThrow) {
+ fail("Should throw something");
+ }
+ assertTrue(r.isSuccess());
+ Map<MeterEntity, Sample[]> meterSamplesR = r.getData().context.getMeterSamples();
+ // Compare the entity sets first so a missing (or extra) expected entity fails the test,
+ // then iterate over `want` so every expected entity's samples are asserted.
+ assertEquals(want.keySet(), meterSamplesR.keySet());
+ want.forEach((meterEntity, samples) -> {
+ assertArrayEquals(samples, meterSamplesR.get(meterEntity));
+ });
+ }
+}
diff --git a/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/TagFilterTest.java b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/TagFilterTest.java
new file mode 100644
index 0000000000..3808dc8f0a
--- /dev/null
+++ b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/TagFilterTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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.meter.analyzer.v2.dsl;
+
+import com.google.common.collect.ImmutableMap;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static com.google.common.collect.ImmutableMap.of;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+@Slf4j
+public class TagFilterTest {
+ public static Collection<Object[]> data() {
+ final SampleFamily sf =
+ SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t2")).value(50).name("http_success_request").build(),
+ Sample.builder()
+ .labels(of("idc", "t3", "region", "cn", "svc", "catalog"))
+ .value(50)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t1", "region", "us", "svc", "product"))
+ .value(50)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t1", "region", "us", "instance", "10.0.0.1"))
+ .name("http_success_request")
+ .value(50)
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t3", "region", "cn", "instance", "10.0.0.1"))
+ .name("http_success_request")
+ .value(3)
+ .build()
+ ).build();
+ return Arrays.asList(new Object[][] {
+ {
+ "tagEqual",
+ of("http_success_request", sf),
+ "http_success_request.tagEqual('idc', 't3')",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder()
+ .labels(of("idc", "t3", "region", "cn", "svc", "catalog"))
+ .value(50)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t3", "region", "cn", "instance", "10.0.0.1"))
+ .name("http_success_request")
+ .value(3)
+ .build()
+ ).build()),
+ false,
+ },
+ {
+ "tagNotEqual",
+ of("http_success_request", sf),
+ "http_success_request.tagNotEqual('idc', 't1')",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t2")).value(50).name("http_success_request").build(),
+ Sample.builder()
+ .labels(of("idc", "t3", "region", "cn", "svc", "catalog"))
+ .value(50)
+ .name("http_success_request")
+ .build(),
+ Sample.builder()
+ .labels(of("idc", "t3", "region", "cn", "instance", "10.0.0.1"))
+ .name("http_success_request")
+ .value(3)
+ .build()
+ ).build()),
+ false,
+ },
+ {
+ "tagMatch",
+ of("http_success_request", sf),
+ "http_success_request.tagMatch('idc', 't1|t2|t3')",
+ Result.success(sf),
+ false,
+ },
+ {
+ "tagNotMatch",
+ of("http_success_request", sf),
+ "http_success_request.tagNotMatch('idc', 't1|t3')",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t2")).value(50).name("http_success_request").build()).build()),
+ false,
+ },
+ });
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("data")
+ public void test(String name,
+ ImmutableMap<String, SampleFamily> input,
+ String expression,
+ Result want,
+ boolean isThrow) {
+ Result r = null;
+ try {
+ // v2 compiles eagerly, so parse-time failures surface here too (v1 surfaced them at run()).
+ Expression e = DSL.parse(name, expression);
+ r = e.run(input);
+ } catch (Throwable t) {
+ if (isThrow) {
+ return;
+ }
+ log.error("Test failed", t);
+ fail("Should not throw anything");
+ }
+ if (isThrow) {
+ fail("Should throw something");
+ }
+ assertEquals(want, r);
+ }
+}
diff --git a/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/ValueFilterTest.java b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/ValueFilterTest.java
new file mode 100644
index 0000000000..3aa9db1e34
--- /dev/null
+++ b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/ValueFilterTest.java
@@ -0,0 +1,148 @@
+/*
+ * 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.meter.analyzer.v2.dsl;
+
+import com.google.common.collect.ImmutableMap;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static com.google.common.collect.ImmutableMap.of;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+@Slf4j
+public class ValueFilterTest {
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] {
+ {
+ "valueEqual",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(2).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(2).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t3")).value(1).name("http_success_request").build()
+ ).build()),
+ "http_success_request.valueEqual(1)",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t3")).value(1).name("http_success_request").build()
+ ).build()),
+ false,
+ },
+ {
+ "valueNotEqual",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(2).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(2).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t3")).value(1).name("http_success_request").build()
+ ).build()),
+ "http_success_request.valueNotEqual(1)",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(2).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(2).name("http_success_request").build()
+ ).build()),
+ false,
+ },
+ {
+ "valueGreater",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(2).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(2).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t3")).value(1).name("http_success_request").build()
+ ).build()),
+ "http_success_request.valueGreater(1)",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(2).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(2).name("http_success_request").build()
+ ).build()),
+ false,
+ },
+ {
+ "valueGreaterEqual",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(2).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(2).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t3")).value(1).name("http_success_request").build()
+ ).build()),
+ "http_success_request.valueGreaterEqual(1)",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(2).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(2).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t3")).value(1).name("http_success_request").build()
+ ).build()),
+ false,
+ },
+ {
+ "valueLess",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(2).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(2).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t3")).value(1).name("http_success_request").build()
+ ).build()),
+ "http_success_request.valueLess(2)",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t3")).value(1).name("http_success_request").build()
+ ).build()),
+ false,
+ },
+ {
+ "valueLessEqual",
+ of("http_success_request", SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(2).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(2).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t3")).value(1).name("http_success_request").build()
+ ).build()),
+ "http_success_request.valueLessEqual(2)",
+ Result.success(SampleFamilyBuilder.newBuilder(
+ Sample.builder().labels(of("idc", "t1")).value(2).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t2")).value(2).name("http_success_request").build(),
+ Sample.builder().labels(of("idc", "t3")).value(1).name("http_success_request").build()
+ ).build()),
+ false,
+ },
+ });
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("data")
+ public void test(final String name,
+ final ImmutableMap<String, SampleFamily> input,
+ final String expression,
+ final Result want,
+ final boolean isThrow) {
+ Result r = null;
+ try {
+ // v2 compiles eagerly, so parse-time failures surface here too (v1 surfaced them at run()).
+ Expression e = DSL.parse(name, expression);
+ r = e.run(input);
+ } catch (Throwable t) {
+ if (isThrow) {
+ return;
+ }
+ log.error("Test failed", t);
+ fail("Should not throw anything");
+ }
+ if (isThrow) {
+ fail("Should throw something");
+ }
+ assertEquals(want, r);
+ }
+}
diff --git a/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/counter/CounterWindowTest.java b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/counter/CounterWindowTest.java
new file mode 100644
index 0000000000..3e1a24f354
--- /dev/null
+++ b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/dsl/counter/CounterWindowTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.meter.analyzer.v2.dsl.counter;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import io.vavr.Tuple2;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.time.Duration;
+import java.util.List;
+
+import static java.time.Instant.parse;
+
+public class CounterWindowTest {
+
+ public static List<Tuple2<Long, Double>> parameters() {
+ return Lists.newArrayList(
+ new Tuple2<>(parse("2020-09-11T11:11:01.03Z").toEpochMilli(), 10d),
+ new Tuple2<>(parse("2020-09-11T11:11:15.99Z").toEpochMilli(), 11d),
+ new Tuple2<>(parse("2020-09-11T11:11:31.00Z").toEpochMilli(), 12d),
+ new Tuple2<>(parse("2020-09-11T11:11:46.09Z").toEpochMilli(), 13d),
+ new Tuple2<>(parse("2020-09-11T11:12:00.97Z").toEpochMilli(), 14d),
+ new Tuple2<>(parse("2020-09-11T11:11:00.97Z").toEpochMilli(), 15d),
+ new Tuple2<>(parse("2020-09-11T11:12:16.60Z").toEpochMilli(), 16d),
+ new Tuple2<>(parse("2020-09-11T11:12:31.66Z").toEpochMilli(), 17d)
+ );
+ }
+
+ @Test
+ public void testPT15S() {
+ double[] actuals = parameters().stream().mapToDouble(e -> {
+ Tuple2<Long, Double> increase = CounterWindow.INSTANCE.increase(
+ "test", ImmutableMap.<String, String>builder().build(), e._2,
+ Duration.parse("PT15S").getSeconds() * 1000, e._1
+ );
+ return e._2 - increase._2;
+ }).toArray();
+
+ Assertions.assertArrayEquals(new double[] {0, 1d, 1d, 1d, 1d, 0d, 2d, 1d}, actuals, 0.d);
+ }
+
+ @Test
+ public void testPT35S() {
+ double[] actuals = parameters().stream().mapToDouble(e -> {
+ Tuple2<Long, Double> increase = CounterWindow.INSTANCE.increase(
+ "test", ImmutableMap.<String, String>builder().build(), e._2,
+ Duration.parse("PT35S").getSeconds() * 1000, e._1
+ );
+ return e._2 - increase._2;
+ }).toArray();
+
+ Assertions.assertArrayEquals(new double[] {0, 1d, 2d, 2d, 2d, 0d, 3d, 3d}, actuals, 0.d);
+ }
+
+ @Test
+ public void testPT1M() {
+ double[] actuals = parameters().stream().mapToDouble(e -> {
+ Tuple2<Long, Double> increase = CounterWindow.INSTANCE.increase(
+ "test", ImmutableMap.<String, String>builder().build(), e._2,
+ Duration.parse("PT1M").getSeconds() * 1000, e._1
+ );
+ return e._2 - increase._2;
+ }).toArray();
+
+ Assertions.assertArrayEquals(new double[] {0, 1d, 2d, 3d, 4d, 0d, 5d, 5d}, actuals, 0.d);
+ }
+
+ @Test
+ public void testPT2M() {
+ double[] actuals = parameters().stream().mapToDouble(e -> {
+ Tuple2<Long, Double> increase = CounterWindow.INSTANCE.increase(
+ "test", ImmutableMap.<String, String>builder().build(), e._2,
+ Duration.parse("PT2M").getSeconds() * 1000, e._1
+ );
+ return e._2 - increase._2;
+ }).toArray();
+
+ Assertions.assertArrayEquals(new double[] {0, 1d, 2d, 3d, 4d, 0d, 1d, 2d}, actuals, 0.d);
+ }
+}
diff --git a/oap-server/server-starter/src/main/resources/application.yml b/oap-server/server-starter/src/main/resources/application.yml
index 9fa3eeb315..ef43d37de0 100644
--- a/oap-server/server-starter/src/main/resources/application.yml
+++ b/oap-server/server-starter/src/main/resources/application.yml
@@ -201,6 +201,8 @@ storage:
enableCustomRouting: ${SW_STORAGE_ES_ENABLE_CUSTOM_ROUTING:false}
mysql:
properties:
+ # Please do not include the username and password in the jdbcUrl; set them through dataSource.user and dataSource.password below.
+ # If you want to hide the URL so that it cannot be seen in the configdump information, add `jdbcUrl` to keywords4MaskingSecretsOfConfig.
jdbcUrl: ${SW_JDBC_URL:"jdbc:mysql://localhost:3306/swtest?rewriteBatchedStatements=true&allowMultiQueries=true"}
dataSource.user: ${SW_DATA_SOURCE_USER:root}
dataSource.password: ${SW_DATA_SOURCE_PASSWORD:root@1234}
@@ -213,6 +215,8 @@ storage:
asyncBatchPersistentPoolSize: ${SW_STORAGE_ASYNC_BATCH_PERSISTENT_POOL_SIZE:4}
postgresql:
properties:
+ # Please do not include the username and password in the jdbcUrl; set them through dataSource.user and dataSource.password below.
+ # If you want to hide the URL so that it cannot be seen in the configdump information, add `jdbcUrl` to keywords4MaskingSecretsOfConfig.
jdbcUrl: ${SW_JDBC_URL:"jdbc:postgresql://localhost:5432/skywalking"}
dataSource.user: ${SW_DATA_SOURCE_USER:postgres}
dataSource.password: ${SW_DATA_SOURCE_PASSWORD:123456}