This is an automated email from the ASF dual-hosted git repository.
wusheng pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking.git
The following commit(s) were added to refs/heads/master by this push:
new f0e3c64352 Get Alarm Runtime Status: support query the running status
for the whole cluster. (#13570)
f0e3c64352 is described below
commit f0e3c643523ce6253b52343e2b5ede515ab4b111
Author: Wan Kai <[email protected]>
AuthorDate: Mon Nov 10 12:19:24 2025 +0800
Get Alarm Runtime Status: support query the running status for the whole
cluster. (#13570)
---
dist-material/alarm-settings.yml | 2 +-
dist-material/config-examples/alarm-settings.yml | 2 +-
docs/en/changes/changes.md | 1 +
docs/en/setup/backend/backend-alarm.md | 14 +-
docs/en/status/query_alarm_runtime_status.md | 309 ++++++++++++++-------
.../core/alarm/provider/AlarmModuleProvider.java | 2 +
.../core/alarm/provider/AlarmStatusWatcher.java | 169 +++++++++++
.../alarm/provider/status/AlarmRuleDetail.java | 50 ++++
.../core/alarm/provider/status/AlarmRuleList.java} | 21 +-
.../provider/status/AlarmRunningContext.java} | 39 ++-
.../alarm/provider/status/ClusterAlarmStatus.java} | 18 +-
.../provider/status/InstanceAlarmStatus.java} | 18 +-
.../oap/server/core/alarm/AlarmModule.java | 2 +-
...mModule.java => AlarmStatusWatcherService.java} | 35 ++-
.../server/core/remote/RemoteServiceHandler.java | 47 ++++
.../core/remote/client/GRPCRemoteClient.java | 3 +-
.../server/core/remote/client/RemoteClient.java | 8 +
.../core/remote/client/SelfRemoteClient.java | 6 +
.../server-core/src/main/proto/RemoteService.proto | 26 ++
.../oap/query/debug/AlarmStatusQueryHandler.java | 162 ++---------
.../oap/query/debug/AlarmStatusQueryService.java | 188 +++++++++++++
.../oap/query/debug/StatusQueryModule.java | 2 +-
.../oap/query/debug/StatusQueryProvider.java | 2 +-
.../src/main/resources/alarm-settings.yml | 2 +-
24 files changed, 803 insertions(+), 325 deletions(-)
diff --git a/dist-material/alarm-settings.yml b/dist-material/alarm-settings.yml
index 51c775d8ae..5dd6a9d2ab 100644
--- a/dist-material/alarm-settings.yml
+++ b/dist-material/alarm-settings.yml
@@ -16,7 +16,7 @@
# Sample alarm rules.
rules:
- # Rule unique name, must be ended with `_rule`.
+ # Rule unique id, must be ended with `_rule`.
service_resp_time_rule:
# A MQE expression, the result type must be `SINGLE_VALUE` and the root
operation of the expression must be a Compare Operation
# which provides `1`(true) or `0`(false) result. When the result is
`1`(true), the alarm will be triggered.
diff --git a/dist-material/config-examples/alarm-settings.yml
b/dist-material/config-examples/alarm-settings.yml
index aa7d7bfa82..afd68583ba 100644
--- a/dist-material/config-examples/alarm-settings.yml
+++ b/dist-material/config-examples/alarm-settings.yml
@@ -16,7 +16,7 @@
# Sample alarm rules.
rules:
- # Rule unique name, must be ended with `_rule`.
+ # Rule unique id, must be ended with `_rule`.
endpoint_percent_rule:
expression: sum((endpoint_sla / 100) < 75) >= 3
# The length of time to evaluate the metrics
diff --git a/docs/en/changes/changes.md b/docs/en/changes/changes.md
index e841fcc257..a23435da41 100644
--- a/docs/en/changes/changes.md
+++ b/docs/en/changes/changes.md
@@ -117,6 +117,7 @@
* Library-pprof-parser: feat: add PprofSegmentParser.
* Storage: feat: add languageType column to ProfileThreadSnapshotRecord.
* Feat: add go profile analyzer
+* Get Alarm Runtime Status: support query the running status for the whole
cluster.
#### UI
diff --git a/docs/en/setup/backend/backend-alarm.md
b/docs/en/setup/backend/backend-alarm.md
index ed3cfc6f16..f1a2045e34 100644
--- a/docs/en/setup/backend/backend-alarm.md
+++ b/docs/en/setup/backend/backend-alarm.md
@@ -18,7 +18,7 @@ Defines the relation between scope and entity name.
## Rules
An alerting rule is made up of the following elements:
-- **Rule name**. A unique name shown in the alarm message. It must end with
`_rule`.
+- **Rule id**. A unique name shown in the alarm message. It must end with
`_rule`.
- **Expression**. A [MQE](../../api/metrics-query-expression.md) expression
that defines the conditions of the rule.
The result type must be `SINGLE_VALUE` and the root operation of the
expression must be a
[Compare Operation](../../api/metrics-query-expression.md#compare-operation)
or [Bool Operation](../../api/metrics-query-expression.md#bool-operation) which
provides `1`(true) or `0`(false) result.
@@ -34,9 +34,9 @@ The metrics names in the expression could be found in the
[list of all potential
- **Exclude names regex**. A regex that excludes entity names. Both rules will
take effect if both include-label list and include-label regex are set.
- **Tags**. Tags are key/value pairs that are attached to alarms. Tags are
used to specify distinguishing attributes of alarms that are meaningful and
relevant to users. If you want to make these tags searchable on the SkyWalking
UI, you may set the tag keys in `core/default/searchableAlarmTags` or through
the system environment variable `SW_SEARCHABLE_ALARM_TAG_KEYS`. The key `level`
is supported by default.
- **Period**. The size of metrics cache in minutes for checking the alarm
conditions. This is a time window that corresponds to the backend deployment
env time.
-- **Hooks**. Binding the specific names of the hooks when the alarm is
triggered.
- The name format is `{hookType}.{hookName}` (slack.custom1 e.g.) and must be
defined in the `hooks` section of the `alarm-settings.yml` file.
- If the hook name is not specified, the global hook will be used.
+- **Hooks**. Binding the specific ids of the hooks when the alarm is triggered.
+ The id format is `{hookType}.{hookName}` (slack.custom1 e.g.) and must be
defined in the `hooks` section of the `alarm-settings.yml` file.
+ If the hook id is not specified, the global hook will be used.
- **Silence period**. After the alarm is triggered at Time-N (TN), there will
be silence during the **TN -> TN + period**.
By default, it works in the same manner as **period**. The same Alarm (having
the same ID in the same metrics name) may only be triggered once within a
period.
@@ -61,7 +61,7 @@ the calculation is `((1001 + 10001 + ... + 1001) / 7) > 1000`
and the result wou
```yaml
rules:
- # Rule unique name, must be ended with `_rule`.
+ # Rule unique id, must be ended with `_rule`.
endpoint_percent_rule:
# A MQE expression and the root operation of the expression must be a
Compare Operation.
expression: sum((endpoint_sla / 100) < 75) >= 3
@@ -145,8 +145,8 @@ is through [AI powered baseline
calculation](../ai-pipeline/metrics-baseline-int
## Hooks
Hooks are a way to send alarm messages to the outside world. SkyWalking
supports multiple hooks of the same type, each hook can support different
configurations.
-For example, you can configure two Slack hooks, one named `default` and set
`is-default: true` means this hook will apply on all `Alarm Rules` **without
config** `hooks`.
-Another named `custom1` will only apply on the `Alarm Rules` which **with
config** `hooks` and include the name `slack.custom1`.
+For example, you can configure two Slack hooks, one id is `default` and set
`is-default: true` means this hook will apply on all `Alarm Rules` **without
config** `hooks`.
+Another id is `custom1` will only apply on the `Alarm Rules` which **with
config** `hooks` and include the id `slack.custom1`.
```yaml
hooks:
diff --git a/docs/en/status/query_alarm_runtime_status.md
b/docs/en/status/query_alarm_runtime_status.md
index e3678e0df2..389f1d616e 100644
--- a/docs/en/status/query_alarm_runtime_status.md
+++ b/docs/en/status/query_alarm_runtime_status.md
@@ -1,6 +1,9 @@
# Get Alarm Runtime Status
OAP calculates the alarm conditions in the memory based on the alarm rules and
the metrics data.
+If the OAP cluster has multiple instances, each instance will calculate the
alarm conditions independently.
+You can query from any OAP instance to get the all instances' alarm running
status.
+
The following APIs are exposed to make the alerting running kernel visible.
## Get Alarm Running Rules
@@ -12,9 +15,33 @@ Return the list of alarm running rules.
```json
{
- "ruleNames": [
- "service_percentile_rule",
- "service_resp_time_rule"
+ "oapInstances": [
+ {
+ "address": "127.0.0.1_11800",
+ "status": {
+ "ruleList": [
+ {
+ "id": "service_percentile_rule"
+ },
+ {
+ "id": "service_resp_time_rule"
+ }
+ ]
+ }
+ },
+ {
+ "address": "127.0.0.1_11801",
+ "status": {
+ "ruleList": [
+ {
+ "id": "service_percentile_rule"
+ },
+ {
+ "id": "service_resp_time_rule"
+ }
+ ]
+ }
+ }
]
}
```
@@ -23,60 +50,84 @@ Return the list of alarm running rules.
Return the detailed information of the alarm running rule.
-- URL, `http://{core restHost}:{core restPort}/status/alarm/rules/{ruleName}`
+- URL, `http://{core restHost}:{core restPort}/status/alarm/rules/{ruleId}`
- HTTP GET method.
```json
{
- "ruleName": "service_resp_time_rule",
- "expression": "sum(service_resp_time > baseline(service_resp_time,upper)) >=
1",
- "period": 10,
- "silentPeriod": 10,
- "additonalPeriod": 0,
- "includeNames": [
- "mock_a_service",
- "mock_b_service",
- "mock_c_service"
- ],
- "excludeNames": [],
- "includeNamesRegex": "",
- "excludeNamesRegex": "",
- "affectedEntities": [
- {
- "scope": "SERVICE",
- "name": "mock_b_service"
- },
- {
- "scope": "SERVICE",
- "name": "mock_a_service"
- },
- {
- "scope": "SERVICE",
- "name": "mock_c_service"
- }
- ],
- "tags": [
+ "oapInstances": [
{
- "key": "level",
- "value": "WARNING"
- }
- ],
- "hooks": [
- "webhook.default",
- "wechat.default"
- ],
- "includeMetrics": [
- "service_resp_time"
- ],
- "formattedMessages": [
- {
- "mock_b_service": "Response time of service mock_b_service is more than
upper baseline in 1 minutes of last 10 minutes."
- },
- {
- "mock_a_service": "Response time of service mock_a_service is more than
upper baseline in 1 minutes of last 10 minutes."
+ "address": "127.0.0.1_11800",
+ "status": {
+ "ruleId": "service_resp_time_rule",
+ "expression": "sum(service_resp_time > 1000) >= 1",
+ "period": 10,
+ "silencePeriod": 10,
+ "additionalPeriod": 0,
+ "includeEntityNames": [],
+ "excludeEntityNames": [],
+ "includeEntityNamesRegex": "",
+ "excludeEntityNamesRegex": "",
+ "runningEntities": [
+ {
+ "scope": "SERVICE",
+ "name": "mock_b_service",
+ "formattedMessage": "Response time of mock_b_service is more than
upper baseline in 1 minutes of last 10 minutes."
+ }
+ ],
+ "tags": [
+ {
+ "key": "level",
+ "value": "WARNING"
+ }
+ ],
+ "hooks": [
+ "webhook.default",
+ "wechat.default"
+ ],
+ "includeMetrics": [
+ "service_resp_time"
+ ]
+ }
},
{
- "mock_c_service": "Response time of service mock_c_service is more than
upper baseline in 1 minutes of last 10 minutes."
+ "address": "127.0.0.1_11801",
+ "status": {
+ "ruleId": "service_resp_time_rule",
+ "expression": "sum(service_resp_time > 1000) >= 1",
+ "period": 10,
+ "silencePeriod": 10,
+ "additionalPeriod": 0,
+ "includeEntityNames": [],
+ "excludeEntityNames": [],
+ "includeEntityNamesRegex": "",
+ "excludeEntityNamesRegex": "",
+ "runningEntities": [
+ {
+ "scope": "SERVICE",
+ "name": "mock_a_service",
+ "formattedMessage": "Response time of mock_a_service is more than
upper baseline in 1 minutes of last 10 minutes."
+ },
+ {
+ "scope": "SERVICE",
+ "name": "mock_c_service",
+ "formattedMessage": "Response time of service mock_c_service is
more than upper baseline in 1 minutes of last 10 minutes."
+ }
+ ],
+ "tags": [
+ {
+ "key": "level",
+ "value": "WARNING"
+ }
+ ],
+ "hooks": [
+ "webhook.default",
+ "wechat.default"
+ ],
+ "includeMetrics": [
+ "service_resp_time"
+ ]
+ }
}
]
}
@@ -84,78 +135,126 @@ Return the detailed information of the alarm running rule.
- `additonalPeriod` is the additional period if the expression includes the
[increase/rate function](../api/metrics-query-expression.md#trend-operation).
This additional period is used to enlarge window size for calculating the
trend value.
-- `affectedEntities` is the entities that have metrics data and being
calculated by the alarm rule.
-- `formattedMessages` is the result message according to the message template
and the affected entities.
+- `runningEntities` is the entities that have metrics data and being
calculated by the alarm rule.
+- `formattedMessages` is the result message according to the message template
and the affected running entities.
## Get Alarm Running Context
Return the running context of the alarm rule.
-- URL, `http://{core restHost}:{core
restPort}/status/alarm/{ruleName}/{entityName}`
+- URL, `http://{core restHost}:{core
restPort}/status/alarm/{ruleId}/{entityName}`
- HTTP GET method.
```json
{
- "expression": "sum(service_resp_time > baseline(service_resp_time,upper)) >=
1",
- "endTime": "2025-02-12T13:39:00.000",
- "additionalPeriod": 0,
- "size": 10,
- "silenceCountdown": 10,
- "windowValues": [
- {
- "index": 0,
- "metrics": []
- },
+ "oapInstances": [
{
- "index": 1,
- "metrics": []
- },
- {
- "index": 2,
- "metrics": []
- },
- {
- "index": 3,
- "metrics": []
- },
- {
- "index": 4,
- "metrics": []
- },
- {
- "index": 5,
- "metrics": []
- },
- {
- "index": 6,
- "metrics": []
- },
- {
- "index": 7,
- "metrics": [
- {
- "timeBucket": 202502121437,
- "name": "service_resp_time",
- "value": "6000"
+ "address": "127.0.0.1_11800",
+ "status": {
+ "ruleId": "service_resp_time_rule",
+ "expression": "sum(service_resp_time > 1000) >= 1",
+ "endTime": "2025-11-10T09:39:00.000",
+ "additionalPeriod": 0,
+ "size": 10,
+ "silenceCountdown": 10,
+ "entityName": "v2|mock_b_service|default|test-cluster|-",
+ "windowValues": [
+ {
+ "index": 0,
+ "metrics": []
+ },
+ {
+ "index": 1,
+ "metrics": []
+ },
+ {
+ "index": 2,
+ "metrics": []
+ },
+ {
+ "index": 3,
+ "metrics": []
+ },
+ {
+ "index": 4,
+ "metrics": []
+ },
+ {
+ "index": 5,
+ "metrics": []
+ },
+ {
+ "index": 6,
+ "metrics": []
+ },
+ {
+ "index": 7,
+ "metrics": [
+ {
+ "name": "service_resp_time",
+ "timeBucket": 202502121437,
+ "value": "6000"
+ }
+ ]
+ },
+ {
+ "index": 8,
+ "metrics": []
+ },
+ {
+ "index": 9,
+ "metrics": []
+ }
+ ],
+ "mqeMetricsSnapshot": {
+ "service_resp_time":
"[{\"metric\":{\"labels\":[]},\"values\":[{\"id\":\"202502121430\",\"doubleValue\":0.0,\"isEmptyValue\":true},{\"id\":\"202502121431\",\"doubleValue\":0.0,\"isEmptyValue\":true},{\"id\":\"202502121432\",\"doubleValue\":0.0,\"isEmptyValue\":true},{\"id\":\"202502121433\",\"doubleValue\":0.0,\"isEmptyValue\":true},{\"id\":\"202502121434\",\"doubleValue\":0.0,\"isEmptyValue\":true},{\"id\":\"202502121435\",\"doubleValue\":0.0,\"isEmptyValue\":true},{\"id\":\"2
[...]
+ "baseline(service_resp_time,upper)":
"[{\"metric\":{\"labels\":[]},\"values\":[{\"id\":\"202502121430\",\"doubleValue\":10.0,\"isEmptyValue\":false},{\"id\":\"202502121431\",\"doubleValue\":10.0,\"isEmptyValue\":false},{\"id\":\"202502121432\",\"doubleValue\":10.0,\"isEmptyValue\":false},{\"id\":\"202502121433\",\"doubleValue\":10.0,\"isEmptyValue\":false},{\"id\":\"202502121434\",\"doubleValue\":10.0,\"isEmptyValue\":false},{\"id\":\"202502121435\",\"doubleValue\":10.0,\"isEmp
[...]
}
- ]
- },
- {
- "index": 8,
- "metrics": []
+ }
},
{
- "index": 9,
- "metrics": []
+ "address": "127.0.0.1_11801",
+ "status": {
+ "ruleId": "service_resp_time_rule",
+ "expression": "sum(service_resp_time > 1000) >= 1",
+ "additionalPeriod": 0,
+ "size": 0,
+ "silenceCountdown": 0,
+ "windowValues": []
+ }
}
- ],
- "mqeMetricsSnapshot": {
- "service_resp_time":
"[{\"metric\":{\"labels\":[]},\"values\":[{\"id\":\"202502121430\",\"doubleValue\":0.0,\"isEmptyValue\":true},{\"id\":\"202502121431\",\"doubleValue\":0.0,\"isEmptyValue\":true},{\"id\":\"202502121432\",\"doubleValue\":0.0,\"isEmptyValue\":true},{\"id\":\"202502121433\",\"doubleValue\":0.0,\"isEmptyValue\":true},{\"id\":\"202502121434\",\"doubleValue\":0.0,\"isEmptyValue\":true},{\"id\":\"202502121435\",\"doubleValue\":0.0,\"isEmptyValue\":true},{\"id\":\"2025021
[...]
- "baseline(service_resp_time,upper)":
"[{\"metric\":{\"labels\":[]},\"values\":[{\"id\":\"202502121430\",\"doubleValue\":10.0,\"isEmptyValue\":false},{\"id\":\"202502121431\",\"doubleValue\":10.0,\"isEmptyValue\":false},{\"id\":\"202502121432\",\"doubleValue\":10.0,\"isEmptyValue\":false},{\"id\":\"202502121433\",\"doubleValue\":10.0,\"isEmptyValue\":false},{\"id\":\"202502121434\",\"doubleValue\":10.0,\"isEmptyValue\":false},{\"id\":\"202502121435\",\"doubleValue\":10.0,\"isEmptyValu
[...]
- }
+ ]
}
```
`size` is the window size. Equal to the `period + additionalPeriod`.
`silenceCountdown` is the countdown of the silence period. -1 means silence
countdown is not running.
`windowValues` is the original metrics data. The `index` is the index of the
window, starting from 0.
`mqeMetricsSnapshot` is the metrics data in the MQE format. When checking
conditions, these data will be calculated according to the expression.
+
+## Get Errors When Querying Status from OAP Instances
+
+If some errors occur when querying the status from OAP instances, the error
messages will be returned.
+
+```json
+{
+ "oapInstances": [
+ {
+ "address": "127.0.0.1_11800",
+ "status": {
+ "ruleList": [
+ {
+ "id": "service_percentile_rule"
+ },
+ {
+ "id": "service_resp_time_rule"
+ }
+ ]
+ }
+ },
+ {
+ "address": "127.0.0.1_11801",
+ "errorMsg": "UNAVAILABLE: io exception"
+ }
+ ]
+}
+```
\ No newline at end of file
diff --git
a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmModuleProvider.java
b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmModuleProvider.java
index 8a3e5ef52c..6d4f61002e 100644
---
a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmModuleProvider.java
+++
b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmModuleProvider.java
@@ -27,6 +27,7 @@ import org.apache.skywalking.oap.server.core.CoreModule;
import org.apache.skywalking.oap.server.core.alarm.AlarmModule;
import org.apache.skywalking.oap.server.core.alarm.AlarmRulesWatcherService;
import org.apache.skywalking.oap.server.core.alarm.AlarmStandardPersistence;
+import org.apache.skywalking.oap.server.core.alarm.AlarmStatusWatcherService;
import org.apache.skywalking.oap.server.core.alarm.MetricsNotify;
import org.apache.skywalking.oap.server.library.module.ModuleDefine;
import org.apache.skywalking.oap.server.library.module.ModuleProvider;
@@ -61,6 +62,7 @@ public class AlarmModuleProvider extends ModuleProvider {
notifyHandler = new NotifyHandler(alarmRulesWatcher, getManager());
this.registerServiceImplementation(MetricsNotify.class, notifyHandler);
this.registerServiceImplementation(AlarmRulesWatcherService.class,
alarmRulesWatcher);
+ this.registerServiceImplementation(AlarmStatusWatcherService.class,
new AlarmStatusWatcher(getManager()));
}
@Override
diff --git
a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmStatusWatcher.java
b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmStatusWatcher.java
new file mode 100644
index 0000000000..1b476d038a
--- /dev/null
+++
b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmStatusWatcher.java
@@ -0,0 +1,169 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.oap.server.core.alarm.provider;
+
+import com.google.gson.Gson;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.apache.skywalking.oap.server.core.alarm.AlarmModule;
+import org.apache.skywalking.oap.server.core.alarm.AlarmRulesWatcherService;
+import org.apache.skywalking.oap.server.core.alarm.AlarmStatusWatcherService;
+import
org.apache.skywalking.oap.server.core.alarm.provider.status.AlarmRuleDetail;
+import
org.apache.skywalking.oap.server.core.alarm.provider.status.AlarmRuleList;
+import
org.apache.skywalking.oap.server.core.alarm.provider.status.AlarmRunningContext;
+import
org.apache.skywalking.oap.server.core.analysis.metrics.DoubleValueHolder;
+import org.apache.skywalking.oap.server.core.analysis.metrics.IntValueHolder;
+import
org.apache.skywalking.oap.server.core.analysis.metrics.LabeledValueHolder;
+import org.apache.skywalking.oap.server.core.analysis.metrics.LongValueHolder;
+import org.apache.skywalking.oap.server.core.analysis.metrics.Metrics;
+import org.apache.skywalking.oap.server.library.module.ModuleManager;
+
+public class AlarmStatusWatcher implements AlarmStatusWatcherService {
+ private final static Gson GSON = new Gson();
+ private AlarmRulesWatcherService rulesWatcherService;
+ private final ModuleManager moduleManager;
+ private AlarmRulesWatcher alarmRulesWatcher;
+
+ public AlarmStatusWatcher(ModuleManager moduleManager) {
+ this.moduleManager = moduleManager;
+ }
+
+ private AlarmRulesWatcher getAlarmRulesWatcher() {
+ if (alarmRulesWatcher == null) {
+ alarmRulesWatcher = (AlarmRulesWatcher)
moduleManager.find(AlarmModule.NAME)
+
.provider().getService(AlarmRulesWatcherService.class);
+ }
+ return alarmRulesWatcher;
+ }
+
+ @Override
+ public String getAlarmRules() {
+ Map<String, RunningRule> runningRules =
getAlarmRulesWatcher().getRunningContext()
+ .values()
+ .stream()
+
.map(List::stream)
+
.flatMap(r -> r)
+ .collect(
+
Collectors.toMap(
+
RunningRule::getRuleName, r -> r));
+ AlarmRuleList alarmRuleList = new AlarmRuleList();
+ runningRules.keySet().forEach(ruleName -> {
+ AlarmRuleList.RuleInfo alarmRule = new AlarmRuleList.RuleInfo();
+ alarmRule.setId(ruleName);
+ alarmRuleList.getRuleList().add(alarmRule);
+ });
+ return GSON.toJson(alarmRuleList);
+ }
+
+ @Override
+ public String getAlarmRuleById(final String ruleId) {
+ AlarmRuleDetail ruleDetail = new AlarmRuleDetail();
+ Map<String, RunningRule> runningRules =
getAlarmRulesWatcher().getRunningContext()
+ .values()
+ .stream()
+
.flatMap(List::stream)
+ .collect(
+
Collectors.toMap(
+
RunningRule::getRuleName,
+
r -> r
+ ));
+ RunningRule rule = runningRules.get(ruleId);
+ if (rule == null) {
+ return "";
+ }
+ ruleDetail.setRuleId(rule.getRuleName());
+ ruleDetail.setExpression(rule.getExpression());
+ ruleDetail.setPeriod(rule.getPeriod());
+ ruleDetail.setSilencePeriod(rule.getSilencePeriod());
+ ruleDetail.setAdditionalPeriod(rule.getAdditionalPeriod());
+ ruleDetail.setIncludeEntityNames(rule.getIncludeNames());
+ ruleDetail.setExcludeEntityNames(rule.getExcludeNames());
+ ruleDetail.setIncludeEntityNamesRegex(
+ rule.getIncludeNamesRegex() == null ? "" :
rule.getIncludeNamesRegex().toString());
+ ruleDetail.setExcludeEntityNamesRegex(
+ rule.getExcludeNamesRegex() == null ? "" :
rule.getExcludeNamesRegex().toString());
+ ruleDetail.setTags(rule.getTags());
+ ruleDetail.setHooks(rule.getHooks());
+ ruleDetail.setIncludeMetrics(rule.getIncludeMetrics());
+ Map<AlarmEntity, RunningRule.Window> windows = rule.getWindows();
+ windows.keySet().forEach(e -> {
+ AlarmRuleDetail.RunningEntity entity = new
AlarmRuleDetail.RunningEntity();
+ entity.setScope(e.getScope());
+ entity.setName(e.getName());
+ entity.setFormattedMessage(rule.getFormatter().format(e));
+ ruleDetail.getRunningEntities().add(entity);
+ });
+
+ return GSON.toJson(ruleDetail);
+ }
+
+ @Override
+ public String getAlarmRuleContext(final String ruleName, final String
entityName) {
+ Map<String, RunningRule> runningRules =
getAlarmRulesWatcher().getRunningContext().values().stream().flatMap(List::stream)
+
.collect(Collectors.toMap(RunningRule::getRuleName, r -> r));
+ RunningRule rule = runningRules.get(ruleName);
+ if (rule == null) {
+ return "";
+ }
+ AlarmRunningContext runningContext = new AlarmRunningContext();
+ runningContext.setRuleId(rule.getRuleName());
+ runningContext.setExpression(rule.getExpression());
+ Map<AlarmEntity, RunningRule.Window> windows = rule.getWindows();
+ RunningRule.Window window = windows.keySet().stream().filter(e ->
e.getName().equals(entityName)).map(windows::get)
+ .findFirst().orElse(null);
+ if (window == null) {
+ return GSON.toJson(runningContext);
+ }
+ runningContext.setEntityName(entityName);
+ runningContext.setEndTime(window.getEndTime().toString());
+ runningContext.setAdditionalPeriod(window.getAdditionalPeriod());
+ runningContext.setSize(window.getSize());
+ runningContext.setSilenceCountdown(window.getSilenceCountdown());
+ window.scanWindowValues(values -> {
+ for (int i = 0; i < values.size(); i++) {
+ AlarmRunningContext.WindowValue windowValue = new
AlarmRunningContext.WindowValue();
+ runningContext.getWindowValues().add(windowValue);
+ windowValue.setIndex(i);
+ Map<String, Metrics> m = values.get(i);
+ if (null != m) {
+ m.forEach((name, metric) -> {
+ AlarmRunningContext.Metric metricValue = new
AlarmRunningContext.Metric();
+ metricValue.setTimeBucket(metric.getTimeBucket());
+ metricValue.setName(name);
+ String value = "";
+ if (metric instanceof LongValueHolder) {
+ value = Long.toString(((LongValueHolder)
metric).getValue());
+ } else if (metric instanceof IntValueHolder) {
+ value = Integer.toString(((IntValueHolder)
metric).getValue());
+ } else if (metric instanceof DoubleValueHolder) {
+ value = Double.toString(((DoubleValueHolder)
metric).getValue());
+ } else if (metric instanceof LabeledValueHolder) {
+ value = ((LabeledValueHolder)
metric).getValue().toString();
+ }
+ metricValue.setValue(value);
+ windowValue.getMetrics().add(metricValue);
+ });
+ }
+ }
+ });
+ runningContext.setMqeMetricsSnapshot(window.getMqeMetricsSnapshot());
+ return GSON.toJson(runningContext);
+ }
+}
diff --git
a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/status/AlarmRuleDetail.java
b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/status/AlarmRuleDetail.java
new file mode 100644
index 0000000000..9978da65e6
--- /dev/null
+++
b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/status/AlarmRuleDetail.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.oap.server.core.alarm.provider.status;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import lombok.Data;
+import org.apache.skywalking.oap.server.core.analysis.manual.searchtag.Tag;
+
+@Data
+public class AlarmRuleDetail {
+ private String ruleId;
+ private String expression;
+ private int period;
+ private int silencePeriod;
+ private int additionalPeriod;
+ private List<String> includeEntityNames = new ArrayList<>();
+ private List<String> excludeEntityNames = new ArrayList<>();
+ private String includeEntityNamesRegex;
+ private String excludeEntityNamesRegex;
+ private List<RunningEntity> runningEntities = new ArrayList<>();
+ private List<Tag> tags = new ArrayList<>();
+ private Set<String> hooks = new HashSet<>();
+ private Set<String> includeMetrics = new HashSet<>();
+
+ @Data
+ public static class RunningEntity {
+ private String scope;
+ private String name;
+ private String formattedMessage;
+ }
+}
diff --git
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/remote/client/RemoteClient.java
b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/status/AlarmRuleList.java
similarity index 71%
copy from
oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/remote/client/RemoteClient.java
copy to
oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/status/AlarmRuleList.java
index b69791c4fc..76755248cd 100644
---
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/remote/client/RemoteClient.java
+++
b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/status/AlarmRuleList.java
@@ -16,17 +16,18 @@
*
*/
-package org.apache.skywalking.oap.server.core.remote.client;
+package org.apache.skywalking.oap.server.core.alarm.provider.status;
-import org.apache.skywalking.oap.server.core.remote.data.StreamData;
+import java.util.ArrayList;
+import java.util.List;
+import lombok.Data;
-public interface RemoteClient extends Comparable<RemoteClient> {
+@Data
+public class AlarmRuleList {
+ private List<RuleInfo> ruleList = new ArrayList<>();
- Address getAddress();
-
- void connect();
-
- void close();
-
- void push(String nextWorkerName, StreamData streamData);
+ @Data
+ public static class RuleInfo {
+ private String id;
+ }
}
diff --git
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/alarm/AlarmModule.java
b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/status/AlarmRunningContext.java
similarity index 50%
copy from
oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/alarm/AlarmModule.java
copy to
oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/status/AlarmRunningContext.java
index 843cd961af..d0ee7e52fe 100644
---
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/alarm/AlarmModule.java
+++
b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/status/AlarmRunningContext.java
@@ -16,24 +16,35 @@
*
*/
-package org.apache.skywalking.oap.server.core.alarm;
+package org.apache.skywalking.oap.server.core.alarm.provider.status;
-import org.apache.skywalking.oap.server.library.module.ModuleDefine;
+import com.google.gson.JsonObject;
+import java.util.ArrayList;
+import java.util.List;
+import lombok.Data;
-/**
- * Alarm module define the main bridge entrance of the alarm implementor.
- * <p>
- * SkyWalking supports alarm implementation pluggable.
- */
-public class AlarmModule extends ModuleDefine {
- public static final String NAME = "alarm";
+@Data
+public class AlarmRunningContext {
+ private String ruleId;
+ private String expression;
+ private String endTime;
+ private int additionalPeriod;
+ private int size;
+ private int silenceCountdown;
+ private String entityName;
+ private List<WindowValue> windowValues = new ArrayList<>();
+ private JsonObject mqeMetricsSnapshot;
- public AlarmModule() {
- super(NAME);
+ @Data
+ public static class Metric {
+ private String name;
+ private long timeBucket;
+ private String value;
}
- @Override
- public Class[] services() {
- return new Class[] {MetricsNotify.class,
AlarmRulesWatcherService.class};
+ @Data
+ public static class WindowValue {
+ private int index;
+ private List<Metric> metrics = new ArrayList<>();
}
}
diff --git
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/remote/client/RemoteClient.java
b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/status/ClusterAlarmStatus.java
similarity index 71%
copy from
oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/remote/client/RemoteClient.java
copy to
oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/status/ClusterAlarmStatus.java
index b69791c4fc..df82f58805 100644
---
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/remote/client/RemoteClient.java
+++
b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/status/ClusterAlarmStatus.java
@@ -16,17 +16,13 @@
*
*/
-package org.apache.skywalking.oap.server.core.remote.client;
+package org.apache.skywalking.oap.server.core.alarm.provider.status;
-import org.apache.skywalking.oap.server.core.remote.data.StreamData;
+import java.util.ArrayList;
+import java.util.List;
+import lombok.Data;
-public interface RemoteClient extends Comparable<RemoteClient> {
-
- Address getAddress();
-
- void connect();
-
- void close();
-
- void push(String nextWorkerName, StreamData streamData);
+@Data
+public class ClusterAlarmStatus<T> {
+ private List<T> oapInstances = new ArrayList<>();
}
diff --git
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/remote/client/RemoteClient.java
b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/status/InstanceAlarmStatus.java
similarity index 71%
copy from
oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/remote/client/RemoteClient.java
copy to
oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/status/InstanceAlarmStatus.java
index b69791c4fc..5ef8120818 100644
---
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/remote/client/RemoteClient.java
+++
b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/status/InstanceAlarmStatus.java
@@ -16,17 +16,13 @@
*
*/
-package org.apache.skywalking.oap.server.core.remote.client;
+package org.apache.skywalking.oap.server.core.alarm.provider.status;
-import org.apache.skywalking.oap.server.core.remote.data.StreamData;
+import lombok.Data;
-public interface RemoteClient extends Comparable<RemoteClient> {
-
- Address getAddress();
-
- void connect();
-
- void close();
-
- void push(String nextWorkerName, StreamData streamData);
+@Data
+public class InstanceAlarmStatus<T> {
+ private String address;
+ private T status;
+ private String errorMsg;
}
diff --git
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/alarm/AlarmModule.java
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/alarm/AlarmModule.java
index 843cd961af..8f963a2fd8 100644
---
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/alarm/AlarmModule.java
+++
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/alarm/AlarmModule.java
@@ -34,6 +34,6 @@ public class AlarmModule extends ModuleDefine {
@Override
public Class[] services() {
- return new Class[] {MetricsNotify.class,
AlarmRulesWatcherService.class};
+ return new Class[] {MetricsNotify.class,
AlarmRulesWatcherService.class, AlarmStatusWatcherService.class};
}
}
diff --git
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/alarm/AlarmModule.java
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/alarm/AlarmStatusWatcherService.java
similarity index 52%
copy from
oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/alarm/AlarmModule.java
copy to
oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/alarm/AlarmStatusWatcherService.java
index 843cd961af..ab2d183cb1 100644
---
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/alarm/AlarmModule.java
+++
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/alarm/AlarmStatusWatcherService.java
@@ -18,22 +18,27 @@
package org.apache.skywalking.oap.server.core.alarm;
-import org.apache.skywalking.oap.server.library.module.ModuleDefine;
+import org.apache.skywalking.oap.server.library.module.Service;
-/**
- * Alarm module define the main bridge entrance of the alarm implementor.
- * <p>
- * SkyWalking supports alarm implementation pluggable.
- */
-public class AlarmModule extends ModuleDefine {
- public static final String NAME = "alarm";
+public interface AlarmStatusWatcherService extends Service {
+ /**
+ * Get all alarm rules in JSON format
+ * @return JSON String of all alarm rules
+ */
+ String getAlarmRules();
- public AlarmModule() {
- super(NAME);
- }
+ /**
+ * Get a specific alarm rule details by its id in JSON format
+ * @param ruleId id of the alarm rule
+ * @return JSON String of the specified alarm rule
+ */
+ String getAlarmRuleById(String ruleId);
- @Override
- public Class[] services() {
- return new Class[] {MetricsNotify.class,
AlarmRulesWatcherService.class};
- }
+ /**
+ * Get the context information of a specific alarm rule for a given entity
+ * @param ruleId id of the alarm rule
+ * @param entityName Name of the entity
+ * @return Context information in JSON String format
+ */
+ String getAlarmRuleContext(String ruleId, String entityName);
}
diff --git
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/remote/RemoteServiceHandler.java
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/remote/RemoteServiceHandler.java
index b7f80c8384..42bfef0bee 100644
---
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/remote/RemoteServiceHandler.java
+++
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/remote/RemoteServiceHandler.java
@@ -22,11 +22,15 @@ import io.grpc.Status;
import io.grpc.stub.StreamObserver;
import java.util.Objects;
import org.apache.skywalking.oap.server.core.CoreModule;
+import org.apache.skywalking.oap.server.core.alarm.AlarmModule;
+import org.apache.skywalking.oap.server.core.alarm.AlarmStatusWatcherService;
import org.apache.skywalking.oap.server.core.remote.data.StreamData;
import org.apache.skywalking.oap.server.core.remote.grpc.proto.Empty;
import org.apache.skywalking.oap.server.core.remote.grpc.proto.RemoteData;
import org.apache.skywalking.oap.server.core.remote.grpc.proto.RemoteMessage;
import
org.apache.skywalking.oap.server.core.remote.grpc.proto.RemoteServiceGrpc;
+import org.apache.skywalking.oap.server.core.remote.grpc.proto.StatusRequest;
+import org.apache.skywalking.oap.server.core.remote.grpc.proto.StatusResponse;
import org.apache.skywalking.oap.server.core.worker.AbstractWorker;
import org.apache.skywalking.oap.server.core.worker.IWorkerInstanceGetter;
import org.apache.skywalking.oap.server.core.worker.RemoteHandleWorker;
@@ -55,6 +59,7 @@ public class RemoteServiceHandler extends
RemoteServiceGrpc.RemoteServiceImplBas
private CounterMetrics remoteInErrorCounter;
private CounterMetrics remoteInTargetNotFoundCounter;
private HistogramMetrics remoteInHistogram;
+ private AlarmStatusWatcherService alarmStatusWatcher;
public RemoteServiceHandler(ModuleDefineHolder moduleDefineHolder) {
this.moduleDefineHolder = moduleDefineHolder;
@@ -168,4 +173,46 @@ public class RemoteServiceHandler extends
RemoteServiceGrpc.RemoteServiceImplBas
}
};
}
+
+ @Override
+ public void syncStatus(StatusRequest statusRequest,
StreamObserver<StatusResponse> responseObserver) {
+ if
(statusRequest.getRequestCase().equals(StatusRequest.RequestCase.ALARMREQUEST))
{
+ switch (statusRequest.getAlarmRequest().getRequestType()) {
+ case GET_ALARM_RULES:
+ responseObserver.onNext(
+
StatusResponse.newBuilder().setAlarmStatus(getAlarmStatusWatcher().getAlarmRules()).build());
+ responseObserver.onCompleted();
+ break;
+ case GET_ALARM_RULE_BY_ID:
+ responseObserver.onNext(
+ StatusResponse.newBuilder()
+
.setAlarmStatus(getAlarmStatusWatcher().getAlarmRuleById(
+
statusRequest.getAlarmRequest().getRuleId()))
+ .build());
+ responseObserver.onCompleted();
+ break;
+ case GET_ALARM_RULE_CONTEXT:
+ responseObserver.onNext(
+ StatusResponse.newBuilder()
+
.setAlarmStatus(getAlarmStatusWatcher().getAlarmRuleContext(
+
statusRequest.getAlarmRequest().getRuleId(),
+
statusRequest.getAlarmRequest().getEntityName()
+ ))
+ .build());
+ responseObserver.onCompleted();
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown request type:
" + statusRequest.getRequestCase());
+
+ }
+ }
+ }
+
+ private AlarmStatusWatcherService getAlarmStatusWatcher() {
+ if (alarmStatusWatcher == null) {
+ alarmStatusWatcher = moduleDefineHolder.find(AlarmModule.NAME)
+
.provider().getService(AlarmStatusWatcherService.class);
+ }
+ return alarmStatusWatcher;
+ }
}
diff --git
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/remote/client/GRPCRemoteClient.java
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/remote/client/GRPCRemoteClient.java
index 0bfc460930..3cbc17f16d 100644
---
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/remote/client/GRPCRemoteClient.java
+++
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/remote/client/GRPCRemoteClient.java
@@ -110,7 +110,8 @@ public class GRPCRemoteClient implements RemoteClient {
*
* @return a channel when the state to be ready
*/
- ManagedChannel getChannel() {
+ @Override
+ public ManagedChannel getChannel() {
return getClient().getChannel();
}
diff --git
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/remote/client/RemoteClient.java
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/remote/client/RemoteClient.java
index b69791c4fc..e451ba5fa8 100644
---
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/remote/client/RemoteClient.java
+++
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/remote/client/RemoteClient.java
@@ -18,6 +18,8 @@
package org.apache.skywalking.oap.server.core.remote.client;
+import io.grpc.ManagedChannel;
+import javax.annotation.Nullable;
import org.apache.skywalking.oap.server.core.remote.data.StreamData;
public interface RemoteClient extends Comparable<RemoteClient> {
@@ -29,4 +31,10 @@ public interface RemoteClient extends
Comparable<RemoteClient> {
void close();
void push(String nextWorkerName, StreamData streamData);
+
+ /**
+ * Get the underlying gRPC channel. It may return null if the remote
client is self.
+ */
+ @Nullable
+ ManagedChannel getChannel();
}
diff --git
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/remote/client/SelfRemoteClient.java
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/remote/client/SelfRemoteClient.java
index 46cceafaab..40de112be3 100644
---
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/remote/client/SelfRemoteClient.java
+++
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/remote/client/SelfRemoteClient.java
@@ -18,6 +18,7 @@
package org.apache.skywalking.oap.server.core.remote.client;
+import io.grpc.ManagedChannel;
import lombok.extern.slf4j.Slf4j;
import org.apache.skywalking.oap.server.core.CoreModule;
import org.apache.skywalking.oap.server.core.UnexpectedException;
@@ -84,6 +85,11 @@ public class SelfRemoteClient implements RemoteClient {
}
}
+ @Override
+ public ManagedChannel getChannel() {
+ return null;
+ }
+
@Override
public int compareTo(RemoteClient o) {
return address.compareTo(o.getAddress());
diff --git a/oap-server/server-core/src/main/proto/RemoteService.proto
b/oap-server/server-core/src/main/proto/RemoteService.proto
index 60dceaeb2c..c2c6764f59 100644
--- a/oap-server/server-core/src/main/proto/RemoteService.proto
+++ b/oap-server/server-core/src/main/proto/RemoteService.proto
@@ -24,6 +24,8 @@ option java_package =
"org.apache.skywalking.oap.server.core.remote.grpc.proto";
service RemoteService {
rpc call (stream RemoteMessage) returns (Empty) {
}
+ rpc syncStatus (StatusRequest) returns (StatusResponse) {
+ }
}
message RemoteMessage {
@@ -41,3 +43,27 @@ message RemoteData {
message Empty {
}
+
+message StatusRequest {
+ oneof request {
+ AlarmRequest alarmRequest = 1;
+ }
+}
+
+message StatusResponse {
+ oneof response {
+ string alarmStatus = 1;
+ }
+}
+
+message AlarmRequest {
+ RequestType requestType = 1;
+ string ruleId = 2;
+ string entityName = 3;
+
+ enum RequestType {
+ GET_ALARM_RULES = 0;
+ GET_ALARM_RULE_BY_ID = 1;
+ GET_ALARM_RULE_CONTEXT = 2;
+ }
+}
\ No newline at end of file
diff --git
a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/AlarmStatusQueryHandler.java
b/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/AlarmStatusQueryHandler.java
index 3c653d7d6c..4a87fff324 100644
---
a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/AlarmStatusQueryHandler.java
+++
b/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/AlarmStatusQueryHandler.java
@@ -19,176 +19,48 @@
package org.apache.skywalking.oap.query.debug;
import com.google.gson.Gson;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonObject;
import com.linecorp.armeria.common.HttpResponse;
import com.linecorp.armeria.common.MediaType;
import com.linecorp.armeria.server.annotation.ExceptionHandler;
import com.linecorp.armeria.server.annotation.Get;
import com.linecorp.armeria.server.annotation.Param;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
-import org.apache.skywalking.oap.server.core.alarm.AlarmModule;
-import org.apache.skywalking.oap.server.core.alarm.AlarmRulesWatcherService;
-import org.apache.skywalking.oap.server.core.alarm.provider.AlarmEntity;
-import org.apache.skywalking.oap.server.core.alarm.provider.AlarmRulesWatcher;
-import org.apache.skywalking.oap.server.core.alarm.provider.RunningRule;
-import
org.apache.skywalking.oap.server.core.analysis.metrics.DoubleValueHolder;
-import org.apache.skywalking.oap.server.core.analysis.metrics.IntValueHolder;
-import
org.apache.skywalking.oap.server.core.analysis.metrics.LabeledValueHolder;
-import org.apache.skywalking.oap.server.core.analysis.metrics.LongValueHolder;
-import org.apache.skywalking.oap.server.core.analysis.metrics.Metrics;
import org.apache.skywalking.oap.server.library.module.ModuleManager;
@Slf4j
@ExceptionHandler(StatusQueryExceptionHandler.class)
public class AlarmStatusQueryHandler {
- private final Gson gson = new Gson();
+ private final static Gson GSON = new Gson();
private final ModuleManager moduleManager;
- private AlarmRulesWatcher alarmRulesWatcher;
+ private AlarmStatusQueryService queryService;
public AlarmStatusQueryHandler(final ModuleManager manager) {
this.moduleManager = manager;
}
- private AlarmRulesWatcher getAlarmRulesWatcher() {
- if (alarmRulesWatcher == null) {
- alarmRulesWatcher = (AlarmRulesWatcher)
moduleManager.find(AlarmModule.NAME)
-
.provider().getService(AlarmRulesWatcherService.class);
+ private AlarmStatusQueryService getQueryService() {
+ if (queryService == null) {
+ queryService = moduleManager.find(StatusQueryModule.NAME)
+
.provider().getService(AlarmStatusQueryService.class);
}
- return alarmRulesWatcher;
+ return queryService;
}
@Get("/status/alarm/rules")
public HttpResponse getAlarmRules() {
- Map<String, RunningRule> runningRules =
getAlarmRulesWatcher().getRunningContext().values().stream().map(List::stream)
- .flatMap(r ->
r).collect(Collectors.toMap(RunningRule::getRuleName, r -> r));
- JsonObject runningRuleNames = new JsonObject();
- JsonArray nameList = new JsonArray();
- runningRuleNames.add("ruleNames", nameList);
- runningRules.keySet().forEach(nameList::add);
- return HttpResponse.of(MediaType.JSON_UTF_8,
gson.toJson(runningRuleNames));
+ String result = GSON.toJson(getQueryService().getAlarmRules());
+ return HttpResponse.of(MediaType.JSON_UTF_8, result);
}
- @Get("/status/alarm/{ruleName}")
- public HttpResponse getAlarmRuleByName(@Param("ruleName") String ruleName)
{
- Map<String, RunningRule> runningRules =
getAlarmRulesWatcher().getRunningContext().values().stream().flatMap(List::stream)
-
.collect(Collectors.toMap(RunningRule::getRuleName, r -> r));
- RunningRule rule = runningRules.get(ruleName);
- if (rule == null) {
- return HttpResponse.of(MediaType.JSON_UTF_8, "{}");
- }
- JsonObject runningRuleInfo = new JsonObject();
- runningRuleInfo.addProperty("ruleName", rule.getRuleName());
- runningRuleInfo.addProperty("expression", rule.getExpression());
- runningRuleInfo.addProperty("period", rule.getPeriod());
- runningRuleInfo.addProperty("silentPeriod", rule.getSilencePeriod());
- runningRuleInfo.addProperty("additonalPeriod",
rule.getAdditionalPeriod());
-
- JsonArray includeNameList = new JsonArray();
- runningRuleInfo.add("includeNames", includeNameList);
- rule.getIncludeNames().forEach(includeNameList::add);
-
- JsonArray excludeNameList = new JsonArray();
- runningRuleInfo.add("excludeNames", excludeNameList);
- rule.getExcludeNames().forEach(excludeNameList::add);
-
- runningRuleInfo.addProperty("includeNamesRegex",
rule.getExcludeNamesRegex() == null ? "" :
rule.getIncludeNamesRegex().toString());
- runningRuleInfo.addProperty("excludeNamesRegex",
rule.getExcludeNamesRegex() == null ? "" :
rule.getExcludeNamesRegex().toString());
-
- JsonArray affectedEntities = new JsonArray();
- runningRuleInfo.add("affectedEntities", affectedEntities);
- JsonArray msgFormatter = new JsonArray();
- rule.getWindows().keySet().forEach(e -> {
- JsonObject entity = new JsonObject();
- entity.addProperty("scope", e.getScope());
- entity.addProperty("name", e.getName());
- affectedEntities.add(entity);
- JsonObject msg = new JsonObject();
- msg.addProperty(e.getName(), rule.getFormatter().format(e));
- msgFormatter.add(msg);
- });
-
- JsonArray tagList = new JsonArray();
- runningRuleInfo.add("tags", tagList);
- rule.getTags().forEach(tag -> {
- JsonObject tagInfo = new JsonObject();
- tagInfo.addProperty("key", tag.getKey());
- tagInfo.addProperty("value", tag.getValue());
- tagList.add(tagInfo);
- });
-
- JsonArray hookList = new JsonArray();
- runningRuleInfo.add("hooks", hookList);
- rule.getHooks().forEach(hookList::add);
-
- JsonArray includeMetricList = new JsonArray();
- runningRuleInfo.add("includeMetrics", includeMetricList);
- rule.getIncludeMetrics().forEach(includeMetricList::add);
-
- runningRuleInfo.add("formattedMessages", msgFormatter);
-
- return HttpResponse.of(MediaType.JSON_UTF_8,
runningRuleInfo.toString());
+ @Get("/status/alarm/{ruleId}")
+ public HttpResponse getAlarmRuleByName(@Param("ruleId") String ruleName) {
+ String result =
GSON.toJson(getQueryService().getAlarmRuleById(ruleName));
+ return HttpResponse.of(MediaType.JSON_UTF_8, result);
}
- @Get("/status/alarm/{ruleName}/{entityName}")
- public HttpResponse getAlarmRuleContext(@Param("ruleName") String
ruleName, @Param("entityName") String entityName) {
- Map<String, RunningRule> runningRules =
getAlarmRulesWatcher().getRunningContext().values().stream().flatMap(List::stream)
-
.collect(Collectors.toMap(RunningRule::getRuleName, r -> r));
- RunningRule rule = runningRules.get(ruleName);
- if (rule == null) {
- return HttpResponse.of(MediaType.JSON_UTF_8, "{}");
- }
- Map<AlarmEntity, RunningRule.Window> windows = rule.getWindows();
- RunningRule.Window window = windows.keySet().stream().filter(e ->
e.getName().equals(entityName)).map(windows::get)
- .findFirst().orElse(null);
- JsonObject runningContext = new JsonObject();
- if (window == null) {
- return HttpResponse.of(MediaType.JSON_UTF_8,
runningContext.toString());
- }
-
- runningContext.addProperty("expression", rule.getExpression());
- runningContext.addProperty("endTime", window.getEndTime().toString());
- runningContext.addProperty("additionalPeriod",
window.getAdditionalPeriod());
- runningContext.addProperty("size", window.getSize());
- runningContext.addProperty("silenceCountdown",
window.getSilenceCountdown());
-
- JsonArray metricValues = new JsonArray();
- runningContext.add("windowValues", metricValues);
-
- window.scanWindowValues(values -> {
- for (int i = 0; i < values.size(); i++) {
- JsonObject index = new JsonObject();
- JsonArray metrics = new JsonArray();
- metricValues.add(index);
- index.addProperty("index", i);
- index.add("metrics", metrics);
- Map<String, Metrics> m = values.get(i);
- if (null != m) {
- m.forEach((name, metric) -> {
- JsonObject metricValue = new JsonObject();
- metricValue.addProperty("timeBucket",
metric.getTimeBucket());
- metricValue.addProperty("name", name);
- String value = "";
- if (metric instanceof LongValueHolder) {
- value = Long.toString(((LongValueHolder)
metric).getValue());
- } else if (metric instanceof IntValueHolder) {
- value = Integer.toString(((IntValueHolder)
metric).getValue());
- } else if (metric instanceof DoubleValueHolder) {
- value = Double.toString(((DoubleValueHolder)
metric).getValue());
- } else if (metric instanceof LabeledValueHolder) {
- value = ((LabeledValueHolder)
metric).getValue().toString();
- }
- metricValue.addProperty("value", value);
- metrics.add(metricValue);
- });
- }
- }
- });
-
- runningContext.add("mqeMetricsSnapshot",
window.getMqeMetricsSnapshot());
- return HttpResponse.of(MediaType.JSON_UTF_8,
gson.toJson(runningContext));
+ @Get("/status/alarm/{ruleId}/{entityName}")
+ public HttpResponse getAlarmRuleContext(@Param("ruleId") String ruleId,
@Param("entityName") String entityName) {
+ String result =
GSON.toJson(getQueryService().getAlarmRuleContext(ruleId, entityName));
+ return HttpResponse.of(MediaType.JSON_UTF_8, result);
}
}
diff --git
a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/AlarmStatusQueryService.java
b/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/AlarmStatusQueryService.java
new file mode 100644
index 0000000000..f467af3505
--- /dev/null
+++
b/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/AlarmStatusQueryService.java
@@ -0,0 +1,188 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.oap.query.debug;
+
+import com.google.gson.Gson;
+import java.util.List;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.oap.server.core.CoreModule;
+import org.apache.skywalking.oap.server.core.alarm.AlarmModule;
+import org.apache.skywalking.oap.server.core.alarm.AlarmRulesWatcherService;
+import org.apache.skywalking.oap.server.core.alarm.AlarmStatusWatcherService;
+import org.apache.skywalking.oap.server.core.alarm.provider.AlarmRulesWatcher;
+import
org.apache.skywalking.oap.server.core.alarm.provider.status.AlarmRuleDetail;
+import
org.apache.skywalking.oap.server.core.alarm.provider.status.AlarmRuleList;
+import
org.apache.skywalking.oap.server.core.alarm.provider.status.AlarmRunningContext;
+import
org.apache.skywalking.oap.server.core.alarm.provider.status.ClusterAlarmStatus;
+import
org.apache.skywalking.oap.server.core.alarm.provider.status.InstanceAlarmStatus;
+import org.apache.skywalking.oap.server.core.remote.client.RemoteClient;
+import org.apache.skywalking.oap.server.core.remote.client.RemoteClientManager;
+import org.apache.skywalking.oap.server.core.remote.client.SelfRemoteClient;
+import org.apache.skywalking.oap.server.core.remote.grpc.proto.AlarmRequest;
+import
org.apache.skywalking.oap.server.core.remote.grpc.proto.RemoteServiceGrpc;
+import org.apache.skywalking.oap.server.core.remote.grpc.proto.StatusRequest;
+import org.apache.skywalking.oap.server.core.remote.grpc.proto.StatusResponse;
+import org.apache.skywalking.oap.server.library.module.ModuleManager;
+import org.apache.skywalking.oap.server.library.module.Service;
+
+@Slf4j
+public class AlarmStatusQueryService implements Service {
+ private final ModuleManager moduleManager;
+ private final static Gson GSON = new Gson();
+ private AlarmRulesWatcher alarmRulesWatcher;
+ private AlarmStatusWatcherService alarmStatusWatcher;
+ private RemoteClientManager remoteClientManager;
+
+ public AlarmStatusQueryService(final ModuleManager manager) {
+ this.moduleManager = manager;
+ }
+
+ private AlarmRulesWatcher getAlarmRulesWatcher() {
+ if (alarmRulesWatcher == null) {
+ alarmRulesWatcher = (AlarmRulesWatcher)
moduleManager.find(AlarmModule.NAME)
+
.provider().getService(AlarmRulesWatcherService.class);
+ }
+ return alarmRulesWatcher;
+ }
+
+ private AlarmStatusWatcherService getAlarmStatusWatcher() {
+ if (alarmStatusWatcher == null) {
+ alarmStatusWatcher = moduleManager.find(AlarmModule.NAME)
+
.provider().getService(AlarmStatusWatcherService.class);
+ }
+ return alarmStatusWatcher;
+ }
+
+ private RemoteClientManager getRemoteClientManager() {
+ if (remoteClientManager == null) {
+ remoteClientManager = moduleManager.find(CoreModule.NAME)
+ .provider()
+
.getService(RemoteClientManager.class);
+ }
+ return remoteClientManager;
+ }
+
+ public ClusterAlarmStatus<InstanceAlarmStatus<AlarmRuleList>>
getAlarmRules() {
+ ClusterAlarmStatus<InstanceAlarmStatus<AlarmRuleList>> result = new
ClusterAlarmStatus<>();
+ List<RemoteClient> list = getRemoteClientManager().getRemoteClient();
+ for (RemoteClient remoteClient : list) {
+ String rulesInfo = null;
+ String errorMsg = null;
+ try {
+ if (remoteClient instanceof SelfRemoteClient) {
+ rulesInfo = getAlarmStatusWatcher().getAlarmRules();
+ } else {
+ // get from remote oap in the cluster
+ RemoteServiceGrpc.RemoteServiceBlockingStub stub =
RemoteServiceGrpc.newBlockingStub(
+ remoteClient.getChannel());
+ AlarmRequest alarmRequest = AlarmRequest.newBuilder()
+
.setRequestType(AlarmRequest.RequestType.GET_ALARM_RULES)
+ .build();
+ StatusResponse statusResponse = stub.syncStatus(
+
StatusRequest.newBuilder().setAlarmRequest(alarmRequest).build());
+ rulesInfo = statusResponse.getAlarmStatus();
+ }
+ } catch (Exception e) {
+ log.warn("Failed to get alarm rule list.", e);
+ errorMsg = e.getMessage();
+ }
+ AlarmRuleList alarmRuleList = GSON.fromJson(rulesInfo,
AlarmRuleList.class);
+ InstanceAlarmStatus<AlarmRuleList> instanceAlarmStatus = new
InstanceAlarmStatus<>();
+ instanceAlarmStatus.setStatus(alarmRuleList);
+
instanceAlarmStatus.setAddress(remoteClient.getAddress().toString());
+ instanceAlarmStatus.setErrorMsg(errorMsg);
+ result.getOapInstances().add(instanceAlarmStatus);
+ }
+ return result;
+ }
+
+ public ClusterAlarmStatus<InstanceAlarmStatus<AlarmRuleDetail>>
getAlarmRuleById(String ruleId) {
+ ClusterAlarmStatus<InstanceAlarmStatus<AlarmRuleDetail>> result = new
ClusterAlarmStatus<>();
+ List<RemoteClient> list = getRemoteClientManager().getRemoteClient();
+ for (RemoteClient remoteClient : list) {
+ String ruleDetail = null;
+ String errorMsg = null;
+ try {
+ if (remoteClient instanceof SelfRemoteClient) {
+ ruleDetail =
getAlarmStatusWatcher().getAlarmRuleById(ruleId);
+ } else {
+ // get from remote oap in the cluster
+ RemoteServiceGrpc.RemoteServiceBlockingStub stub =
RemoteServiceGrpc.newBlockingStub(
+ remoteClient.getChannel());
+ AlarmRequest alarmRequest = AlarmRequest.newBuilder()
+ .setRequestType(
+
AlarmRequest.RequestType.GET_ALARM_RULE_BY_ID)
+ .setRuleId(ruleId)
+ .build();
+ StatusResponse statusResponse = stub.syncStatus(
+
StatusRequest.newBuilder().setAlarmRequest(alarmRequest).build());
+ ruleDetail = statusResponse.getAlarmStatus();
+ }
+ } catch (Exception e) {
+ log.warn("Failed to get alarm rule detail by ID: {}.", ruleId,
e);
+ errorMsg = e.getMessage();
+ }
+
+ AlarmRuleDetail alarmRuleDetail = GSON.fromJson(ruleDetail,
AlarmRuleDetail.class);
+ InstanceAlarmStatus<AlarmRuleDetail> instanceAlarmRuleDetail = new
InstanceAlarmStatus<>();
+ instanceAlarmRuleDetail.setStatus(alarmRuleDetail);
+
instanceAlarmRuleDetail.setAddress(remoteClient.getAddress().toString());
+ instanceAlarmRuleDetail.setErrorMsg(errorMsg);
+ result.getOapInstances().add(instanceAlarmRuleDetail);
+ }
+ return result;
+ }
+
+ public ClusterAlarmStatus<InstanceAlarmStatus<AlarmRunningContext>>
getAlarmRuleContext(String ruleId, String entityName) {
+ ClusterAlarmStatus<InstanceAlarmStatus<AlarmRunningContext>> result =
new ClusterAlarmStatus<>();
+ List<RemoteClient> list = getRemoteClientManager().getRemoteClient();
+ for (RemoteClient remoteClient : list) {
+ String context = null;
+ String errorMsg = null;
+ try {
+ if (remoteClient instanceof SelfRemoteClient) {
+ context =
getAlarmStatusWatcher().getAlarmRuleContext(ruleId, entityName);
+ } else {
+ // get from remote oap in the cluster
+ RemoteServiceGrpc.RemoteServiceBlockingStub stub =
RemoteServiceGrpc.newBlockingStub(
+ remoteClient.getChannel());
+ AlarmRequest alarmRequest = AlarmRequest.newBuilder()
+ .setRequestType(
+
AlarmRequest.RequestType.GET_ALARM_RULE_CONTEXT)
+ .setRuleId(ruleId)
+
.setEntityName(entityName)
+ .build();
+ StatusResponse statusResponse = stub.syncStatus(
+
StatusRequest.newBuilder().setAlarmRequest(alarmRequest).build());
+ context = statusResponse.getAlarmStatus();
+ }
+ } catch (Exception e) {
+ log.warn("Failed to get alarm running context by ruleId: {}
and entityName: {}.", ruleId, entityName, e);
+ errorMsg = e.getMessage();
+ }
+ AlarmRunningContext alarmRunningContext = GSON.fromJson(context,
AlarmRunningContext.class);
+ InstanceAlarmStatus<AlarmRunningContext> runningContext = new
InstanceAlarmStatus<>();
+ runningContext.setStatus(alarmRunningContext);
+ runningContext.setAddress(remoteClient.getAddress().toString());
+ runningContext.setErrorMsg(errorMsg);
+ result.getOapInstances().add(runningContext);
+ }
+ return result;
+ }
+}
diff --git
a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/StatusQueryModule.java
b/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/StatusQueryModule.java
index 6375966d8d..1fd3494cb4 100644
---
a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/StatusQueryModule.java
+++
b/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/StatusQueryModule.java
@@ -28,6 +28,6 @@ public class StatusQueryModule extends ModuleDefine {
}
public Class[] services() {
- return new Class[0];
+ return new Class[] {AlarmStatusQueryService.class};
}
}
diff --git
a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/StatusQueryProvider.java
b/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/StatusQueryProvider.java
index 5d1f84fb74..1527e3fc36 100644
---
a/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/StatusQueryProvider.java
+++
b/oap-server/server-query-plugin/status-query-plugin/src/main/java/org/apache/skywalking/oap/query/debug/StatusQueryProvider.java
@@ -56,7 +56,7 @@ public class StatusQueryProvider extends ModuleProvider {
}
public void prepare() throws ServiceNotProvidedException {
-
+ this.registerServiceImplementation(AlarmStatusQueryService.class, new
AlarmStatusQueryService(getManager()));
}
public void start() throws ServiceNotProvidedException {
diff --git a/oap-server/server-starter/src/main/resources/alarm-settings.yml
b/oap-server/server-starter/src/main/resources/alarm-settings.yml
index b764564f7e..268a565b57 100755
--- a/oap-server/server-starter/src/main/resources/alarm-settings.yml
+++ b/oap-server/server-starter/src/main/resources/alarm-settings.yml
@@ -15,7 +15,7 @@
# Sample alarm rules.
rules:
- # Rule unique name, must be ended with `_rule`.
+ # Rule unique id, must be ended with `_rule`.
# endpoint_percent_rule:
# # A MQE expression, the result type must be `SINGLE_VALUE` and the root
operation of the expression must be a Compare Operation
# which provides `1`(true) or `0`(false) result. When the result is
`1`(true), the alarm will be triggered.