Compare commits

..
Author SHA1 Message Date
Zhang Yuxuan f3e6825d0c Merge branch 'master' into fix-dispatch-error 2025-03-15 21:41:25 +08:00
tomsun28 e7e38f3aa3 [bugfix] fix collect dispatch error
Signed-off-by: tomsun28 <tomsun28@outlook.com>
2025-03-15 18:54:06 +08:00
670 changed files with 7501 additions and 23142 deletions
-81
View File
@@ -2325,87 +2325,6 @@
"contributions": [
"code"
]
},
{
"login": "JuJinPark",
"name": "jujin",
"avatar_url": "https://avatars.githubusercontent.com/u/44892459?v=4",
"profile": "https://github.com/JuJinPark",
"contributions": [
"code"
]
},
{
"login": "LL-LIN",
"name": "LL-LIN",
"avatar_url": "https://avatars.githubusercontent.com/u/43002118?v=4",
"profile": "https://github.com/LL-LIN",
"contributions": [
"code"
]
},
{
"login": "bigcyy",
"name": "Yang Chen",
"avatar_url": "https://avatars.githubusercontent.com/u/73413979?v=4",
"profile": "https://bigcyy.github.io/",
"contributions": [
"code"
]
},
{
"login": "sarthakeash",
"name": "Sarthak Arora",
"avatar_url": "https://avatars.githubusercontent.com/u/74091160?v=4",
"profile": "https://github.com/sarthakeash",
"contributions": [
"code"
]
},
{
"login": "PengJingzhao",
"name": "彭镜肇",
"avatar_url": "https://avatars.githubusercontent.com/u/97368949?v=4",
"profile": "https://github.com/PengJingzhao",
"contributions": [
"code"
]
},
{
"login": "gagaradio",
"name": "Walter Jia",
"avatar_url": "https://avatars.githubusercontent.com/u/18532370?v=4",
"profile": "https://github.com/gagaradio",
"contributions": [
"code"
]
},
{
"login": "boyucjz",
"name": "boyucjz",
"avatar_url": "https://avatars.githubusercontent.com/u/18730041?v=4",
"profile": "https://github.com/boyucjz",
"contributions": [
"code"
]
},
{
"login": "Cyanty",
"name": "Cyanty",
"avatar_url": "https://avatars.githubusercontent.com/u/153884653?v=4",
"profile": "https://github.com/Cyanty",
"contributions": [
"code"
]
},
{
"login": "KevinLLF",
"name": "Jay丿167",
"avatar_url": "https://avatars.githubusercontent.com/u/85452733?v=4",
"profile": "https://github.com/KevinLLF",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,
-2
View File
@@ -17,9 +17,7 @@
/home export-ignore
/hip export-ignore
/template-marketplace export-ignore
/.github export-ignore
/.idea export-ignore
/.devcontainer export-ignore
.gitattributes export-ignore
.gitignore export-ignore
-2
View File
@@ -31,8 +31,6 @@ name: "CodeQL"
on:
schedule:
- cron: '21 13 * * 4'
push:
branches: [ action* ]
jobs:
analyze:
+1 -1
View File
@@ -19,7 +19,7 @@ name: DOC CI
on:
push:
branches: [ master, dev, action* ]
branches: [ master, dev ]
paths:
- 'home/**'
pull_request:
+1 -2
View File
@@ -17,8 +17,7 @@
name: 'HertzBeat Doc Pdf Builder'
on:
push:
branches: [ page_build* ]
page_build
jobs:
build:
+1 -1
View File
@@ -22,7 +22,7 @@ name: Frontend CI
on:
push:
branches: [ master, dev, action* ]
branches: [ master, dev ]
paths:
- 'web-app/**'
pull_request:
+1 -1
View File
@@ -22,7 +22,7 @@ name: License Checker
on:
push:
branches: [ master, dev, action* ]
branches: [ master, dev ]
pull_request:
branches: [ master, dev ]
+1 -1
View File
@@ -42,7 +42,7 @@ jobs:
run: |
mvn clean install
mvn clean package -Prelease -DskipTests
cd hertzbeat-collector
cd collector
mvn clean package -Pcluster -DskipTests
- uses: docker/setup-qemu-action@v3
-3
View File
@@ -13,7 +13,6 @@ mvnw.bat
package-lock.json
*.zip
jdk/
data/
### STS ###
.apt_generated
@@ -55,5 +54,3 @@ node_modules
application-dev.yml
application-mysql.yml
application-pg.yml
/package.json
/yarn.lock
+1 -1
View File
@@ -1,5 +1,5 @@
Apache HertzBeat (incubating)
Copyright 2024-2025 The Apache Software Foundation
Copyright 2024 The Apache Software Foundation
This product includes software developed at
The Apache Software Foundation (http://www.apache.org/).
-11
View File
@@ -510,17 +510,6 @@ Thanks to these wonderful people, welcome to join us:
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yasminvo"><img src="https://avatars.githubusercontent.com/u/107528848?v=4?s=100" width="100px;" alt="yasminvo"/><br /><sub><b>yasminvo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yasminvo" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/notbugggg"><img src="https://avatars.githubusercontent.com/u/147966331?v=4?s=100" width="100px;" alt="不关银渐层的事哦"/><br /><sub><b>不关银渐层的事哦</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=notbugggg" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yyahang"><img src="https://avatars.githubusercontent.com/u/90464876?v=4?s=100" width="100px;" alt="yyahang"/><br /><sub><b>yyahang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yyahang" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/JuJinPark"><img src="https://avatars.githubusercontent.com/u/44892459?v=4?s=100" width="100px;" alt="jujin"/><br /><sub><b>jujin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=JuJinPark" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LL-LIN"><img src="https://avatars.githubusercontent.com/u/43002118?v=4?s=100" width="100px;" alt="LL-LIN"/><br /><sub><b>LL-LIN</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LL-LIN" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://bigcyy.github.io/"><img src="https://avatars.githubusercontent.com/u/73413979?v=4?s=100" width="100px;" alt="Yang Chen"/><br /><sub><b>Yang Chen</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=bigcyy" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sarthakeash"><img src="https://avatars.githubusercontent.com/u/74091160?v=4?s=100" width="100px;" alt="Sarthak Arora"/><br /><sub><b>Sarthak Arora</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=sarthakeash" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/PengJingzhao"><img src="https://avatars.githubusercontent.com/u/97368949?v=4?s=100" width="100px;" alt="彭镜肇"/><br /><sub><b>彭镜肇</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=PengJingzhao" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gagaradio"><img src="https://avatars.githubusercontent.com/u/18532370?v=4?s=100" width="100px;" alt="Walter Jia"/><br /><sub><b>Walter Jia</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=gagaradio" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/boyucjz"><img src="https://avatars.githubusercontent.com/u/18730041?v=4?s=100" width="100px;" alt="boyucjz"/><br /><sub><b>boyucjz</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=boyucjz" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Cyanty"><img src="https://avatars.githubusercontent.com/u/153884653?v=4?s=100" width="100px;" alt="Cyanty"/><br /><sub><b>Cyanty</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Cyanty" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/KevinLLF"><img src="https://avatars.githubusercontent.com/u/85452733?v=4?s=100" width="100px;" alt="Jay丿167"/><br /><sub><b>Jay丿167</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=KevinLLF" title="Code">💻</a></td>
</tr>
</tbody>
</table>
-11
View File
@@ -509,17 +509,6 @@ Thanks these wonderful people, welcome to join us:
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yasminvo"><img src="https://avatars.githubusercontent.com/u/107528848?v=4?s=100" width="100px;" alt="yasminvo"/><br /><sub><b>yasminvo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yasminvo" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/notbugggg"><img src="https://avatars.githubusercontent.com/u/147966331?v=4?s=100" width="100px;" alt="不关银渐层的事哦"/><br /><sub><b>不关银渐层的事哦</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=notbugggg" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yyahang"><img src="https://avatars.githubusercontent.com/u/90464876?v=4?s=100" width="100px;" alt="yyahang"/><br /><sub><b>yyahang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yyahang" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/JuJinPark"><img src="https://avatars.githubusercontent.com/u/44892459?v=4?s=100" width="100px;" alt="jujin"/><br /><sub><b>jujin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=JuJinPark" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LL-LIN"><img src="https://avatars.githubusercontent.com/u/43002118?v=4?s=100" width="100px;" alt="LL-LIN"/><br /><sub><b>LL-LIN</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LL-LIN" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://bigcyy.github.io/"><img src="https://avatars.githubusercontent.com/u/73413979?v=4?s=100" width="100px;" alt="Yang Chen"/><br /><sub><b>Yang Chen</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=bigcyy" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sarthakeash"><img src="https://avatars.githubusercontent.com/u/74091160?v=4?s=100" width="100px;" alt="Sarthak Arora"/><br /><sub><b>Sarthak Arora</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=sarthakeash" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/PengJingzhao"><img src="https://avatars.githubusercontent.com/u/97368949?v=4?s=100" width="100px;" alt="彭镜肇"/><br /><sub><b>彭镜肇</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=PengJingzhao" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gagaradio"><img src="https://avatars.githubusercontent.com/u/18532370?v=4?s=100" width="100px;" alt="Walter Jia"/><br /><sub><b>Walter Jia</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=gagaradio" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/boyucjz"><img src="https://avatars.githubusercontent.com/u/18730041?v=4?s=100" width="100px;" alt="boyucjz"/><br /><sub><b>boyucjz</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=boyucjz" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Cyanty"><img src="https://avatars.githubusercontent.com/u/153884653?v=4?s=100" width="100px;" alt="Cyanty"/><br /><sub><b>Cyanty</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Cyanty" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/KevinLLF"><img src="https://avatars.githubusercontent.com/u/85452733?v=4?s=100" width="100px;" alt="Jay丿167"/><br /><sub><b>Jay丿167</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=KevinLLF" title="Code">💻</a></td>
</tr>
</tbody>
</table>
-5
View File
@@ -73,11 +73,6 @@
"field": "successCode",
"type": 4,
"paramValue": "200, 201"
},
{
"field": "enableUrlEncoding",
"type": 1,
"paramValue": true
}
]
}
@@ -17,14 +17,11 @@
package org.apache.hertzbeat.alert.calculate;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -65,14 +62,12 @@ public class RealTimeAlertCalculator {
private static final String KEY_PRIORITY = "__priority__";
private static final String KEY_CODE = "__code__";
private static final String KEY_AVAILABLE = "__available__";
private static final String KEY_LABELS = "__labels__";
private static final String UP = "up";
private static final String DOWN = "down";
private static final String KEY_ROW = "__row__";
private static final Pattern APP_PATTERN = Pattern.compile("equals\\(__app__,\"([^\"]+)\"\\)");
private static final Pattern AVAILABLE_PATTERN = Pattern.compile("equals\\(__available__,\"([^\"]+)\"\\)");
private static final Pattern LABEL_PATTERN = Pattern.compile("contains\\(__labels__,\\s*\"([^\"]+)\"\\)");
private static final Pattern INSTANCE_PATTERN = Pattern.compile("equals\\(__instance__,\"(\\d+)\"\\)");
private static final Pattern METRICS_PATTERN = Pattern.compile("equals\\(__metrics__,\"([^\"]+)\"\\)");
@@ -141,8 +136,8 @@ public class RealTimeAlertCalculator {
Map<String, String> labels = metricsData.getLabels();
Map<String, String> annotations = metricsData.getAnnotations();
List<AlertDefine> thresholds = this.alertDefineService.getRealTimeAlertDefines();
// Filter thresholds by app, metrics, labels and instance
thresholds = filterThresholdsByAppAndMetrics(thresholds, app, metrics, labels, instance, priority);
// Filter thresholds by app, metrics and instance
thresholds = filterThresholdsByAppAndMetrics(thresholds, app, metrics, instance, priority);
if (thresholds.isEmpty()) {
return;
}
@@ -154,7 +149,6 @@ public class RealTimeAlertCalculator {
commonContext.put(KEY_PRIORITY, priority);
commonContext.put(KEY_CODE, code);
commonContext.put(KEY_METRICS, metrics);
commonContext.put(KEY_LABELS, String.join(",", kvLabelsToKvStringSet(labels)));
if (priority == 0) {
commonContext.put(KEY_AVAILABLE, metricsData.getCode() == CollectRep.Code.SUCCESS ? UP : DOWN);
}
@@ -270,7 +264,7 @@ public class RealTimeAlertCalculator {
* @param priority Current priority
* @return Filtered alert definitions
*/
private List<AlertDefine> filterThresholdsByAppAndMetrics(List<AlertDefine> thresholds, String app, String metrics, Map<String, String> labels, String instance, int priority) {
private List<AlertDefine> filterThresholdsByAppAndMetrics(List<AlertDefine> thresholds, String app, String metrics, String instance, int priority) {
return thresholds.stream()
.filter(define -> {
if (StringUtils.isBlank(define.getExpr())) {
@@ -300,29 +294,19 @@ public class RealTimeAlertCalculator {
// Extract and check instance - optional with multiple values
Matcher instanceMatcher = INSTANCE_PATTERN.matcher(expr);
Matcher labelMatcher = LABEL_PATTERN.matcher(expr);
// If no instance and instance labels specified in expr, accept all instances
if (!instanceMatcher.find() && !labelMatcher.find()) {
// If no instance specified in expr, accept all instances
if (!instanceMatcher.find()) {
return true;
}
// Reset matcher to check all instances
instanceMatcher.reset();
labelMatcher.reset();
// If instances specified, current instance must match one of them
while (instanceMatcher.find()) {
if (Objects.equals(instance, instanceMatcher.group(1))) {
return true;
}
}
// If instance labels specified, current instance must match one of them
Set<String> labelKvStringSet = kvLabelsToKvStringSet(labels);
while (labelMatcher.find()) {
String label = labelMatcher.group(1);
if (labelKvStringSet.contains(label)) {
return true;
}
}
return false;
})
.collect(Collectors.toList());
@@ -433,13 +417,4 @@ public class RealTimeAlertCalculator {
return Arrays.hashCode(keyList.toArray(new String[0])) + "-"
+ Arrays.hashCode(valueList.toArray(new String[0]));
}
private Set<String> kvLabelsToKvStringSet(Map<String, String> labels) {
if (labels == null || labels.isEmpty()) {
return Collections.singleton("");
}
return labels.entrySet().stream()
.map(item -> item.getKey() + ":" + item.getValue())
.collect(Collectors.toSet());
}
}
@@ -17,39 +17,30 @@
package org.apache.hertzbeat.alert.config;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Alibaba Cloud SMS configuration properties
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AlibabaSmsProperties {
/**
* Alibaba Cloud access key id
*/
@NotBlank(message = "AccessKeyId cannot be empty")
private String accessKeyId;
/**
* Alibaba Cloud access key secret
*/
@NotBlank(message = "AccessKeySecret cannot be empty")
private String accessKeySecret;
/**
* SMS signature
*/
@NotBlank(message = "SignName cannot be empty")
private String signName;
/**
* SMS template Code
*/
@NotBlank(message = "TemplateCode cannot be null")
private String templateCode;
}
@@ -17,7 +17,6 @@
package org.apache.hertzbeat.alert.config;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
/**
@@ -28,19 +27,16 @@ public class AwsSmsProperties {
/**
* AWS Cloud access key id
*/
@NotBlank(message = "AccessKeyId cannot be empty")
private String accessKeyId;
/**
* AWS Cloud access key secret
*/
@NotBlank(message = "AccessKeySecret cannot be empty")
private String accessKeySecret;
/**
* AWS Cloud region
*/
@NotBlank(message = "region cannot be empty")
private String region;
}
@@ -17,10 +17,7 @@
package org.apache.hertzbeat.alert.config;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@@ -28,8 +25,6 @@ import org.springframework.stereotype.Component;
* SMS configuration
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
@ConfigurationProperties(prefix = "alerter.sms")
public class SmsConfig {
@@ -42,7 +37,6 @@ public class SmsConfig {
/**
* sms service provider
*/
@NotBlank(message = "Type cannot be empty")
private String type;
/**
@@ -64,12 +58,6 @@ public class SmsConfig {
* Aws configuration
*/
private AwsSmsProperties aws;
/**
* Twilio SMS configuration
*/
private TwilioSmsProperties twilio;
/**
* Smslocal SMS configuration
*/
@@ -18,16 +18,12 @@
package org.apache.hertzbeat.alert.config;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Smslocal SMS Properties
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SmslocalSmsProperties {
/**
* SmsLocal account api key
@@ -17,45 +17,35 @@
package org.apache.hertzbeat.alert.config;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Tencent Cloud SMS properties
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TencentSmsProperties {
/**
* Tencent cloud account secret id
*/
@NotBlank(message = "SecretId cannot be empty")
private String secretId;
/**
* Tencent cloud account secret key
*/
@NotBlank(message = "SecretKey cannot be empty")
private String secretKey;
/**
* SMS app id
*/
@NotBlank(message = "AppId cannot be null")
private String appId;
/**
* SMS signature
*/
@NotBlank(message = "SignName cannot be empty")
private String signName;
/**
* SMS template ID
*/
@NotBlank(message = "templateId cannot be null")
private String templateId;
}
@@ -17,22 +17,16 @@
package org.apache.hertzbeat.alert.config;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* UniSMS properties
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UniSmsProperties {
/**
* UniSMS access key id
*/
@NotBlank(message = "accessKeyId cannot be empty")
private String accessKeyId;
/**
@@ -43,18 +37,15 @@ public class UniSmsProperties {
/**
* SMS signature
*/
@NotBlank(message = "signature cannot be null")
private String signature;
/**
* SMS template ID
*/
@NotBlank(message = "templateId cannot be null")
private String templateId;
/**
* Authentication mode: simple or hmac, default is simple
*/
@NotBlank(message = "authMode cannot be null")
private String authMode = "simple";
}
@@ -118,11 +118,11 @@ public class TencentCloudExternAlert {
/**
* Transaction alarm
*/
public static final String EVENT = "event";
private static final String EVENT = "event";
/**
* Indicator alarm
*/
public static final String METRIC = "metric";
private static final String METRIC = "metric";
}
@@ -1,113 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Generated from AlertExpression.g4 by ANTLR 4.13.2
package org.apache.hertzbeat.alert.expr;
import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor;
/**
* This class provides an empty implementation of {@link AlertExpressionVisitor},
* which can be extended to create a visitor which only needs to handle a subset
* of the available methods.
*
* @param <T> The return type of the visit operation. Use {@link Void} for
* operations with no return type.
*/
@SuppressWarnings("CheckReturnValue")
public class AlertExpressionBaseVisitor<T> extends AbstractParseTreeVisitor<T> implements AlertExpressionVisitor<T> {
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitExpression(AlertExpressionParser.ExpressionContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitAndExpr(AlertExpressionParser.AndExprContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitQueryExpr(AlertExpressionParser.QueryExprContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitComparisonExpr(AlertExpressionParser.ComparisonExprContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitUnlessExpr(AlertExpressionParser.UnlessExprContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitLiteralExpr(AlertExpressionParser.LiteralExprContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitParenExpr(AlertExpressionParser.ParenExprContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitOrExpr(AlertExpressionParser.OrExprContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitIdentifier(AlertExpressionParser.IdentifierContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitNumber(AlertExpressionParser.NumberContext ctx) { return visitChildren(ctx); }
}
@@ -1,305 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.expr;
import org.apache.hertzbeat.warehouse.db.QueryExecutor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* Alert expression visitor implement
*/
public class AlertExpressionEvalVisitor extends AlertExpressionBaseVisitor<List<Map<String, Object>>> {
private static final String THRESHOLD = "__threshold__";
private static final String VALUE = "__value__";
private final QueryExecutor executor;
public AlertExpressionEvalVisitor(QueryExecutor executor) {
this.executor = executor;
}
@Override
public List<Map<String, Object>> visitExpression(AlertExpressionParser.ExpressionContext ctx) {
return visit(ctx.expr());
}
@Override
public List<Map<String, Object>> visitParenExpr(AlertExpressionParser.ParenExprContext ctx) {
return visit(ctx.expr());
}
@Override
public List<Map<String, Object>> visitComparisonExpr(AlertExpressionParser.ComparisonExprContext ctx) {
List<Map<String, Object>> leftResult = visit(ctx.left);
List<Map<String, Object>> rightResult = visit(ctx.right);
if (rightResult.size() == 1 && rightResult.get(0).containsKey(THRESHOLD)) {
double threshold = (double) rightResult.get(0).get(THRESHOLD);
String operator = ctx.op.getText();
List<Map<String, Object>> result = new ArrayList<>();
for (Map<String, Object> item : leftResult) {
Object queryValues = item.get(VALUE);
if (queryValues == null) {
// ignore the query result data is empty
continue;
}
// queryValues may be a list of values, or a single value
Object matchValue = evaluateCondition(queryValues, operator, threshold);
item.put(VALUE, matchValue);
// if matchValue is null, mean not match the threshold
// if not null, mean match the threshold
result.add(new HashMap<>(item));
}
return result;
}
return new LinkedList<>();
}
@Override
public List<Map<String, Object>> visitAndExpr(AlertExpressionParser.AndExprContext ctx) {
List<Map<String, Object>> leftOperand = visit(ctx.left);
List<Map<String, Object>> rightOperand = visit(ctx.right);
Map<String, Object> leftMap = null;
boolean leftMatch = false;
Map<String, Object> rightMap = null;
boolean rightMatch = false;
for (Map<String, Object> item : leftOperand) {
if (leftMap == null) {
leftMap = item;
}
if (item.get(VALUE) != null) {
leftMap = item;
leftMatch = true;
break;
}
}
for (Map<String, Object> item : rightOperand) {
if (rightMap == null) {
rightMap = item;
}
if (item.get(VALUE) != null) {
rightMap = item;
rightMatch = true;
break;
}
}
if (leftMatch && rightMatch) {
rightMap.putAll(leftMap);
return new LinkedList<>(List.of(rightMap));
} else if (leftMap != null) {
leftMap.put(VALUE, null);
return new LinkedList<>(List.of(leftMap));
} else if (rightMap != null) {
rightMap.put(VALUE, null);
return new LinkedList<>(List.of(rightMap));
}
return new LinkedList<>();
}
@Override
public List<Map<String, Object>> visitOrExpr(AlertExpressionParser.OrExprContext ctx) {
List<Map<String, Object>> leftOperand = visit(ctx.left);
List<Map<String, Object>> rightOperand = visit(ctx.right);
Map<String, Object> leftMap = null;
boolean leftMatch = false;
Map<String, Object> rightMap = null;
boolean rightMatch = false;
for (Map<String, Object> item : leftOperand) {
if (leftMap == null) {
leftMap = item;
}
if (item.get(VALUE) != null) {
leftMap = item;
leftMatch = true;
break;
}
}
for (Map<String, Object> item : rightOperand) {
if (rightMap == null) {
rightMap = item;
}
if (item.get(VALUE) != null) {
rightMap = item;
rightMatch = true;
break;
}
}
if (leftMatch && rightMatch) {
rightMap.putAll(leftMap);
return new LinkedList<>(List.of(rightMap));
} else if (leftMatch) {
return new LinkedList<>(List.of(leftMap));
} else if (rightMatch) {
return new LinkedList<>(List.of(rightMap));
} else {
if (leftMap != null && rightMap != null) {
rightMap.putAll(leftMap);
return new LinkedList<>(List.of(rightMap));
} else if (leftMap != null) {
return new LinkedList<>(List.of(leftMap));
} else if (rightMap != null) {
return new LinkedList<>(List.of(rightMap));
}
}
return new LinkedList<>();
}
@Override
public List<Map<String, Object>> visitUnlessExpr(AlertExpressionParser.UnlessExprContext ctx) {
List<Map<String, Object>> leftOperand = visit(ctx.left);
List<Map<String, Object>> rightOperand = visit(ctx.right);
Map<String, Object> leftMap = null;
boolean leftMatch = false;
Map<String, Object> rightMap = null;
boolean rightMatch = false;
for (Map<String, Object> item : leftOperand) {
if (leftMap == null) {
leftMap = item;
}
if (item.get(VALUE) != null) {
leftMap = item;
leftMatch = true;
break;
}
}
for (Map<String, Object> item : rightOperand) {
if (rightMap == null) {
rightMap = item;
}
if (item.get(VALUE) != null) {
rightMap = item;
rightMatch = true;
break;
}
}
if (leftMatch && !rightMatch) {
return new LinkedList<>(List.of(leftMap));
} else {
if (leftMap != null) {
leftMap.put(VALUE, null);
return new LinkedList<>(List.of(leftMap));
} else {
if (rightMap != null) {
rightMap.put(VALUE, null);
return new LinkedList<>(List.of(rightMap));
}
}
}
return new LinkedList<>();
}
@Override
public List<Map<String, Object>> visitQueryExpr(AlertExpressionParser.QueryExprContext ctx) {
String query = ctx.identifier().getText();
return executor.execute(query);
}
@Override
public List<Map<String, Object>> visitLiteralExpr(AlertExpressionParser.LiteralExprContext ctx) {
double value = Double.parseDouble(ctx.number().getText());
List<Map<String, Object>> numAsList = new ArrayList<>();
Map<String, Object> valueMap = new HashMap<>();
valueMap.put(THRESHOLD, value);
numAsList.add(valueMap);
return numAsList;
}
private Object evaluateCondition(Object value, String operator, Double threshold) {
// value may be a list of values, or a single value
switch (operator) {
case ">":
// if value is list, return the max value
if (value instanceof List<?> values) {
Double doubleValue = values.stream().map(v -> Double.valueOf(v.toString())).max(Double::compareTo).orElse(null);
if (doubleValue != null) {
return doubleValue > threshold ? doubleValue : null;
} else {
return null;
}
} else {
return Double.parseDouble(value.toString()) > threshold ? value : null;
}
case ">=":
if (value instanceof List<?> values) {
Double doubleValue = values.stream().map(v -> Double.valueOf(v.toString())).max(Double::compareTo).orElse(null);
if (doubleValue != null) {
return doubleValue >= threshold ? doubleValue : null;
} else {
return null;
}
} else {
return Double.parseDouble(value.toString()) >= threshold ? value : null;
}
case "<":
if (value instanceof List<?> values) {
Double doubleValue = values.stream().map(v -> Double.valueOf(v.toString())).min(Double::compareTo).orElse(null);
if (doubleValue != null) {
return doubleValue < threshold ? doubleValue : null;
} else {
return null;
}
} else {
return Double.parseDouble(value.toString()) < threshold ? value : null;
}
case "<=":
if (value instanceof List<?> values) {
Double doubleValue = values.stream().map(v -> Double.valueOf(v.toString())).min(Double::compareTo).orElse(null);
if (doubleValue != null) {
return doubleValue <= threshold ? doubleValue : null;
} else {
return null;
}
} else {
return Double.parseDouble(value.toString()) <= threshold ? value : null;
}
case "==":
if (value instanceof List<?> values) {
for (Object v : values) {
if (v.equals(threshold)) {
return v;
}
}
return null;
} else {
return value.equals(threshold) ? value : null;
}
case "!=":
if (value instanceof List<?> values) {
for (Object v : values) {
if (v.equals(threshold)) {
return null;
}
}
return value;
} else {
return value.equals(threshold) ? null : value;
}
default:
// unsupported operator todo add more operator
return null;
}
}
}
@@ -1,193 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Generated from AlertExpression.g4 by ANTLR 4.13.2
package org.apache.hertzbeat.alert.expr;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.Lexer;
import org.antlr.v4.runtime.RuntimeMetaData;
import org.antlr.v4.runtime.Vocabulary;
import org.antlr.v4.runtime.VocabularyImpl;
import org.antlr.v4.runtime.atn.ATN;
import org.antlr.v4.runtime.atn.ATNDeserializer;
import org.antlr.v4.runtime.atn.LexerATNSimulator;
import org.antlr.v4.runtime.atn.PredictionContextCache;
import org.antlr.v4.runtime.dfa.DFA;
@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast", "CheckReturnValue", "this-escape"})
public class AlertExpressionLexer extends Lexer {
static { RuntimeMetaData.checkVersion("4.13.2", RuntimeMetaData.VERSION); }
protected static final DFA[] _decisionToDFA;
protected static final PredictionContextCache _sharedContextCache =
new PredictionContextCache();
public static final int
AND=1, OR=2, UNLESS=3, GT=4, GE=5, LT=6, LE=7, EQ=8, NE=9, LPAREN=10,
RPAREN=11, IDENTIFIER=12, NUMBER=13, WS=14;
public static String[] channelNames = {
"DEFAULT_TOKEN_CHANNEL", "HIDDEN"
};
public static String[] modeNames = {
"DEFAULT_MODE"
};
private static String[] makeRuleNames() {
return new String[] {
"AND", "OR", "UNLESS", "GT", "GE", "LT", "LE", "EQ", "NE", "LPAREN",
"RPAREN", "IDENTIFIER", "NUMBER", "WS"
};
}
public static final String[] ruleNames = makeRuleNames();
private static String[] makeLiteralNames() {
return new String[] {
null, "'and'", "'or'", "'unless'", "'>'", "'>='", "'<'", "'<='", "'=='",
"'!='", "'('", "')'"
};
}
private static final String[] _LITERAL_NAMES = makeLiteralNames();
private static String[] makeSymbolicNames() {
return new String[] {
null, "AND", "OR", "UNLESS", "GT", "GE", "LT", "LE", "EQ", "NE", "LPAREN",
"RPAREN", "IDENTIFIER", "NUMBER", "WS"
};
}
private static final String[] _SYMBOLIC_NAMES = makeSymbolicNames();
public static final Vocabulary VOCABULARY = new VocabularyImpl(_LITERAL_NAMES, _SYMBOLIC_NAMES);
/**
* @deprecated Use {@link #VOCABULARY} instead.
*/
@Deprecated
public static final String[] tokenNames;
static {
tokenNames = new String[_SYMBOLIC_NAMES.length];
for (int i = 0; i < tokenNames.length; i++) {
tokenNames[i] = VOCABULARY.getLiteralName(i);
if (tokenNames[i] == null) {
tokenNames[i] = VOCABULARY.getSymbolicName(i);
}
if (tokenNames[i] == null) {
tokenNames[i] = "<INVALID>";
}
}
}
@Override
@Deprecated
public String[] getTokenNames() {
return tokenNames;
}
@Override
public Vocabulary getVocabulary() {
return VOCABULARY;
}
public AlertExpressionLexer(CharStream input) {
super(input);
_interp = new LexerATNSimulator(this,_ATN,_decisionToDFA,_sharedContextCache);
}
@Override
public String getGrammarFileName() { return "AlertExpression.g4"; }
@Override
public String[] getRuleNames() { return ruleNames; }
@Override
public String getSerializedATN() { return _serializedATN; }
@Override
public String[] getChannelNames() { return channelNames; }
@Override
public String[] getModeNames() { return modeNames; }
@Override
public ATN getATN() { return _ATN; }
public static final String _serializedATN =
"\u0004\u0000\u000eZ\u0006\uffff\uffff\u0002\u0000\u0007\u0000\u0002\u0001"+
"\u0007\u0001\u0002\u0002\u0007\u0002\u0002\u0003\u0007\u0003\u0002\u0004"+
"\u0007\u0004\u0002\u0005\u0007\u0005\u0002\u0006\u0007\u0006\u0002\u0007"+
"\u0007\u0007\u0002\b\u0007\b\u0002\t\u0007\t\u0002\n\u0007\n\u0002\u000b"+
"\u0007\u000b\u0002\f\u0007\f\u0002\r\u0007\r\u0001\u0000\u0001\u0000\u0001"+
"\u0000\u0001\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0002\u0001"+
"\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001"+
"\u0003\u0001\u0003\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0005\u0001"+
"\u0005\u0001\u0006\u0001\u0006\u0001\u0006\u0001\u0007\u0001\u0007\u0001"+
"\u0007\u0001\b\u0001\b\u0001\b\u0001\t\u0001\t\u0001\n\u0001\n\u0001\u000b"+
"\u0001\u000b\u0005\u000bB\b\u000b\n\u000b\f\u000bE\t\u000b\u0001\f\u0004"+
"\fH\b\f\u000b\f\f\fI\u0001\f\u0001\f\u0004\fN\b\f\u000b\f\f\fO\u0003\f"+
"R\b\f\u0001\r\u0004\rU\b\r\u000b\r\f\rV\u0001\r\u0001\r\u0000\u0000\u000e"+
"\u0001\u0001\u0003\u0002\u0005\u0003\u0007\u0004\t\u0005\u000b\u0006\r"+
"\u0007\u000f\b\u0011\t\u0013\n\u0015\u000b\u0017\f\u0019\r\u001b\u000e"+
"\u0001\u0000\u0004\u0003\u0000AZ__az\n\u0000\"\"..09==A[]]__a{}}\u8000"+
"\uff5e\u8000\uff5e\u0001\u000009\u0003\u0000\t\n\r\r ^\u0000\u0001\u0001"+
"\u0000\u0000\u0000\u0000\u0003\u0001\u0000\u0000\u0000\u0000\u0005\u0001"+
"\u0000\u0000\u0000\u0000\u0007\u0001\u0000\u0000\u0000\u0000\t\u0001\u0000"+
"\u0000\u0000\u0000\u000b\u0001\u0000\u0000\u0000\u0000\r\u0001\u0000\u0000"+
"\u0000\u0000\u000f\u0001\u0000\u0000\u0000\u0000\u0011\u0001\u0000\u0000"+
"\u0000\u0000\u0013\u0001\u0000\u0000\u0000\u0000\u0015\u0001\u0000\u0000"+
"\u0000\u0000\u0017\u0001\u0000\u0000\u0000\u0000\u0019\u0001\u0000\u0000"+
"\u0000\u0000\u001b\u0001\u0000\u0000\u0000\u0001\u001d\u0001\u0000\u0000"+
"\u0000\u0003!\u0001\u0000\u0000\u0000\u0005$\u0001\u0000\u0000\u0000\u0007"+
"+\u0001\u0000\u0000\u0000\t-\u0001\u0000\u0000\u0000\u000b0\u0001\u0000"+
"\u0000\u0000\r2\u0001\u0000\u0000\u0000\u000f5\u0001\u0000\u0000\u0000"+
"\u00118\u0001\u0000\u0000\u0000\u0013;\u0001\u0000\u0000\u0000\u0015="+
"\u0001\u0000\u0000\u0000\u0017?\u0001\u0000\u0000\u0000\u0019G\u0001\u0000"+
"\u0000\u0000\u001bT\u0001\u0000\u0000\u0000\u001d\u001e\u0005a\u0000\u0000"+
"\u001e\u001f\u0005n\u0000\u0000\u001f \u0005d\u0000\u0000 \u0002\u0001"+
"\u0000\u0000\u0000!\"\u0005o\u0000\u0000\"#\u0005r\u0000\u0000#\u0004"+
"\u0001\u0000\u0000\u0000$%\u0005u\u0000\u0000%&\u0005n\u0000\u0000&\'"+
"\u0005l\u0000\u0000\'(\u0005e\u0000\u0000()\u0005s\u0000\u0000)*\u0005"+
"s\u0000\u0000*\u0006\u0001\u0000\u0000\u0000+,\u0005>\u0000\u0000,\b\u0001"+
"\u0000\u0000\u0000-.\u0005>\u0000\u0000./\u0005=\u0000\u0000/\n\u0001"+
"\u0000\u0000\u000001\u0005<\u0000\u00001\f\u0001\u0000\u0000\u000023\u0005"+
"<\u0000\u000034\u0005=\u0000\u00004\u000e\u0001\u0000\u0000\u000056\u0005"+
"=\u0000\u000067\u0005=\u0000\u00007\u0010\u0001\u0000\u0000\u000089\u0005"+
"!\u0000\u00009:\u0005=\u0000\u0000:\u0012\u0001\u0000\u0000\u0000;<\u0005"+
"(\u0000\u0000<\u0014\u0001\u0000\u0000\u0000=>\u0005)\u0000\u0000>\u0016"+
"\u0001\u0000\u0000\u0000?C\u0007\u0000\u0000\u0000@B\u0007\u0001\u0000"+
"\u0000A@\u0001\u0000\u0000\u0000BE\u0001\u0000\u0000\u0000CA\u0001\u0000"+
"\u0000\u0000CD\u0001\u0000\u0000\u0000D\u0018\u0001\u0000\u0000\u0000"+
"EC\u0001\u0000\u0000\u0000FH\u0007\u0002\u0000\u0000GF\u0001\u0000\u0000"+
"\u0000HI\u0001\u0000\u0000\u0000IG\u0001\u0000\u0000\u0000IJ\u0001\u0000"+
"\u0000\u0000JQ\u0001\u0000\u0000\u0000KM\u0005.\u0000\u0000LN\u0007\u0002"+
"\u0000\u0000ML\u0001\u0000\u0000\u0000NO\u0001\u0000\u0000\u0000OM\u0001"+
"\u0000\u0000\u0000OP\u0001\u0000\u0000\u0000PR\u0001\u0000\u0000\u0000"+
"QK\u0001\u0000\u0000\u0000QR\u0001\u0000\u0000\u0000R\u001a\u0001\u0000"+
"\u0000\u0000SU\u0007\u0003\u0000\u0000TS\u0001\u0000\u0000\u0000UV\u0001"+
"\u0000\u0000\u0000VT\u0001\u0000\u0000\u0000VW\u0001\u0000\u0000\u0000"+
"WX\u0001\u0000\u0000\u0000XY\u0006\r\u0000\u0000Y\u001c\u0001\u0000\u0000"+
"\u0000\u0006\u0000CIOQV\u0001\u0006\u0000\u0000";
public static final ATN _ATN =
new ATNDeserializer().deserialize(_serializedATN.toCharArray());
static {
_decisionToDFA = new DFA[_ATN.getNumberOfDecisions()];
for (int i = 0; i < _ATN.getNumberOfDecisions(); i++) {
_decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i);
}
}
}
@@ -1,570 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Generated from AlertExpression.g4 by ANTLR 4.13.2
package org.apache.hertzbeat.alert.expr;
import org.antlr.v4.runtime.FailedPredicateException;
import org.antlr.v4.runtime.NoViableAltException;
import org.antlr.v4.runtime.Parser;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.RuleContext;
import org.antlr.v4.runtime.RuntimeMetaData;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.TokenStream;
import org.antlr.v4.runtime.Vocabulary;
import org.antlr.v4.runtime.VocabularyImpl;
import org.antlr.v4.runtime.atn.ATN;
import org.antlr.v4.runtime.atn.ATNDeserializer;
import org.antlr.v4.runtime.atn.ParserATNSimulator;
import org.antlr.v4.runtime.atn.PredictionContextCache;
import org.antlr.v4.runtime.dfa.DFA;
import org.antlr.v4.runtime.tree.ParseTreeVisitor;
import org.antlr.v4.runtime.tree.TerminalNode;
import java.util.List;
@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast", "CheckReturnValue", "this-escape"})
public class AlertExpressionParser extends Parser {
static { RuntimeMetaData.checkVersion("4.13.2", RuntimeMetaData.VERSION); }
protected static final DFA[] _decisionToDFA;
protected static final PredictionContextCache _sharedContextCache =
new PredictionContextCache();
public static final int
AND=1, OR=2, UNLESS=3, GT=4, GE=5, LT=6, LE=7, EQ=8, NE=9, LPAREN=10,
RPAREN=11, IDENTIFIER=12, NUMBER=13, WS=14;
public static final int
RULE_expression = 0, RULE_expr = 1, RULE_identifier = 2, RULE_number = 3;
private static String[] makeRuleNames() {
return new String[] {
"expression", "expr", "identifier", "number"
};
}
public static final String[] ruleNames = makeRuleNames();
private static String[] makeLiteralNames() {
return new String[] {
null, "'and'", "'or'", "'unless'", "'>'", "'>='", "'<'", "'<='", "'=='",
"'!='", "'('", "')'"
};
}
private static final String[] _LITERAL_NAMES = makeLiteralNames();
private static String[] makeSymbolicNames() {
return new String[] {
null, "AND", "OR", "UNLESS", "GT", "GE", "LT", "LE", "EQ", "NE", "LPAREN",
"RPAREN", "IDENTIFIER", "NUMBER", "WS"
};
}
private static final String[] _SYMBOLIC_NAMES = makeSymbolicNames();
public static final Vocabulary VOCABULARY = new VocabularyImpl(_LITERAL_NAMES, _SYMBOLIC_NAMES);
/**
* @deprecated Use {@link #VOCABULARY} instead.
*/
@Deprecated
public static final String[] tokenNames;
static {
tokenNames = new String[_SYMBOLIC_NAMES.length];
for (int i = 0; i < tokenNames.length; i++) {
tokenNames[i] = VOCABULARY.getLiteralName(i);
if (tokenNames[i] == null) {
tokenNames[i] = VOCABULARY.getSymbolicName(i);
}
if (tokenNames[i] == null) {
tokenNames[i] = "<INVALID>";
}
}
}
@Override
@Deprecated
public String[] getTokenNames() {
return tokenNames;
}
@Override
public Vocabulary getVocabulary() {
return VOCABULARY;
}
@Override
public String getGrammarFileName() { return "AlertExpression.g4"; }
@Override
public String[] getRuleNames() { return ruleNames; }
@Override
public String getSerializedATN() { return _serializedATN; }
@Override
public ATN getATN() { return _ATN; }
public AlertExpressionParser(TokenStream input) {
super(input);
_interp = new ParserATNSimulator(this,_ATN,_decisionToDFA,_sharedContextCache);
}
@SuppressWarnings("CheckReturnValue")
public static class ExpressionContext extends ParserRuleContext {
public ExprContext expr() {
return getRuleContext(ExprContext.class,0);
}
public TerminalNode EOF() { return getToken(AlertExpressionParser.EOF, 0); }
public ExpressionContext(ParserRuleContext parent, int invokingState) {
super(parent, invokingState);
}
@Override public int getRuleIndex() { return RULE_expression; }
@Override
public <T> T accept(ParseTreeVisitor<? extends T> visitor) {
if ( visitor instanceof AlertExpressionVisitor ) return ((AlertExpressionVisitor<? extends T>)visitor).visitExpression(this);
else return visitor.visitChildren(this);
}
}
public final ExpressionContext expression() throws RecognitionException {
ExpressionContext _localctx = new ExpressionContext(_ctx, getState());
enterRule(_localctx, 0, RULE_expression);
try {
enterOuterAlt(_localctx, 1);
{
setState(8);
expr(0);
setState(9);
match(EOF);
}
}
catch (RecognitionException re) {
_localctx.exception = re;
_errHandler.reportError(this, re);
_errHandler.recover(this, re);
}
finally {
exitRule();
}
return _localctx;
}
@SuppressWarnings("CheckReturnValue")
public static class ExprContext extends ParserRuleContext {
public ExprContext(ParserRuleContext parent, int invokingState) {
super(parent, invokingState);
}
@Override public int getRuleIndex() { return RULE_expr; }
public ExprContext() { }
public void copyFrom(ExprContext ctx) {
super.copyFrom(ctx);
}
}
@SuppressWarnings("CheckReturnValue")
public static class AndExprContext extends ExprContext {
public ExprContext left;
public ExprContext right;
public TerminalNode AND() { return getToken(AlertExpressionParser.AND, 0); }
public List<ExprContext> expr() {
return getRuleContexts(ExprContext.class);
}
public ExprContext expr(int i) {
return getRuleContext(ExprContext.class,i);
}
public AndExprContext(ExprContext ctx) { copyFrom(ctx); }
@Override
public <T> T accept(ParseTreeVisitor<? extends T> visitor) {
if ( visitor instanceof AlertExpressionVisitor ) return ((AlertExpressionVisitor<? extends T>)visitor).visitAndExpr(this);
else return visitor.visitChildren(this);
}
}
@SuppressWarnings("CheckReturnValue")
public static class QueryExprContext extends ExprContext {
public IdentifierContext identifier() {
return getRuleContext(IdentifierContext.class,0);
}
public QueryExprContext(ExprContext ctx) { copyFrom(ctx); }
@Override
public <T> T accept(ParseTreeVisitor<? extends T> visitor) {
if ( visitor instanceof AlertExpressionVisitor ) return ((AlertExpressionVisitor<? extends T>)visitor).visitQueryExpr(this);
else return visitor.visitChildren(this);
}
}
@SuppressWarnings("CheckReturnValue")
public static class ComparisonExprContext extends ExprContext {
public ExprContext left;
public Token op;
public ExprContext right;
public List<ExprContext> expr() {
return getRuleContexts(ExprContext.class);
}
public ExprContext expr(int i) {
return getRuleContext(ExprContext.class,i);
}
public TerminalNode GE() { return getToken(AlertExpressionParser.GE, 0); }
public TerminalNode LE() { return getToken(AlertExpressionParser.LE, 0); }
public TerminalNode GT() { return getToken(AlertExpressionParser.GT, 0); }
public TerminalNode LT() { return getToken(AlertExpressionParser.LT, 0); }
public TerminalNode EQ() { return getToken(AlertExpressionParser.EQ, 0); }
public TerminalNode NE() { return getToken(AlertExpressionParser.NE, 0); }
public ComparisonExprContext(ExprContext ctx) { copyFrom(ctx); }
@Override
public <T> T accept(ParseTreeVisitor<? extends T> visitor) {
if ( visitor instanceof AlertExpressionVisitor ) return ((AlertExpressionVisitor<? extends T>)visitor).visitComparisonExpr(this);
else return visitor.visitChildren(this);
}
}
@SuppressWarnings("CheckReturnValue")
public static class UnlessExprContext extends ExprContext {
public ExprContext left;
public ExprContext right;
public TerminalNode UNLESS() { return getToken(AlertExpressionParser.UNLESS, 0); }
public List<ExprContext> expr() {
return getRuleContexts(ExprContext.class);
}
public ExprContext expr(int i) {
return getRuleContext(ExprContext.class,i);
}
public UnlessExprContext(ExprContext ctx) { copyFrom(ctx); }
@Override
public <T> T accept(ParseTreeVisitor<? extends T> visitor) {
if ( visitor instanceof AlertExpressionVisitor ) return ((AlertExpressionVisitor<? extends T>)visitor).visitUnlessExpr(this);
else return visitor.visitChildren(this);
}
}
@SuppressWarnings("CheckReturnValue")
public static class LiteralExprContext extends ExprContext {
public NumberContext number() {
return getRuleContext(NumberContext.class,0);
}
public LiteralExprContext(ExprContext ctx) { copyFrom(ctx); }
@Override
public <T> T accept(ParseTreeVisitor<? extends T> visitor) {
if ( visitor instanceof AlertExpressionVisitor ) return ((AlertExpressionVisitor<? extends T>)visitor).visitLiteralExpr(this);
else return visitor.visitChildren(this);
}
}
@SuppressWarnings("CheckReturnValue")
public static class ParenExprContext extends ExprContext {
public TerminalNode LPAREN() { return getToken(AlertExpressionParser.LPAREN, 0); }
public ExprContext expr() {
return getRuleContext(ExprContext.class,0);
}
public TerminalNode RPAREN() { return getToken(AlertExpressionParser.RPAREN, 0); }
public ParenExprContext(ExprContext ctx) { copyFrom(ctx); }
@Override
public <T> T accept(ParseTreeVisitor<? extends T> visitor) {
if ( visitor instanceof AlertExpressionVisitor ) return ((AlertExpressionVisitor<? extends T>)visitor).visitParenExpr(this);
else return visitor.visitChildren(this);
}
}
@SuppressWarnings("CheckReturnValue")
public static class OrExprContext extends ExprContext {
public ExprContext left;
public ExprContext right;
public TerminalNode OR() { return getToken(AlertExpressionParser.OR, 0); }
public List<ExprContext> expr() {
return getRuleContexts(ExprContext.class);
}
public ExprContext expr(int i) {
return getRuleContext(ExprContext.class,i);
}
public OrExprContext(ExprContext ctx) { copyFrom(ctx); }
@Override
public <T> T accept(ParseTreeVisitor<? extends T> visitor) {
if ( visitor instanceof AlertExpressionVisitor ) return ((AlertExpressionVisitor<? extends T>)visitor).visitOrExpr(this);
else return visitor.visitChildren(this);
}
}
public final ExprContext expr() throws RecognitionException {
return expr(0);
}
private ExprContext expr(int _p) throws RecognitionException {
ParserRuleContext _parentctx = _ctx;
int _parentState = getState();
ExprContext _localctx = new ExprContext(_ctx, _parentState);
ExprContext _prevctx = _localctx;
int _startState = 2;
enterRecursionRule(_localctx, 2, RULE_expr, _p);
int _la;
try {
int _alt;
enterOuterAlt(_localctx, 1);
{
setState(18);
_errHandler.sync(this);
switch (_input.LA(1)) {
case LPAREN:
{
_localctx = new ParenExprContext(_localctx);
_ctx = _localctx;
_prevctx = _localctx;
setState(12);
match(LPAREN);
setState(13);
expr(0);
setState(14);
match(RPAREN);
}
break;
case IDENTIFIER:
{
_localctx = new QueryExprContext(_localctx);
_ctx = _localctx;
_prevctx = _localctx;
setState(16);
identifier();
}
break;
case NUMBER:
{
_localctx = new LiteralExprContext(_localctx);
_ctx = _localctx;
_prevctx = _localctx;
setState(17);
number();
}
break;
default:
throw new NoViableAltException(this);
}
_ctx.stop = _input.LT(-1);
setState(34);
_errHandler.sync(this);
_alt = getInterpreter().adaptivePredict(_input,2,_ctx);
while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) {
if ( _alt==1 ) {
if ( _parseListeners!=null ) triggerExitRuleEvent();
_prevctx = _localctx;
{
setState(32);
_errHandler.sync(this);
switch ( getInterpreter().adaptivePredict(_input,1,_ctx) ) {
case 1:
{
_localctx = new ComparisonExprContext(new ExprContext(_parentctx, _parentState));
((ComparisonExprContext)_localctx).left = _prevctx;
pushNewRecursionContext(_localctx, _startState, RULE_expr);
setState(20);
if (!(precpred(_ctx, 6))) throw new FailedPredicateException(this, "precpred(_ctx, 6)");
setState(21);
((ComparisonExprContext)_localctx).op = _input.LT(1);
_la = _input.LA(1);
if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & 1008L) != 0)) ) {
((ComparisonExprContext)_localctx).op = (Token)_errHandler.recoverInline(this);
}
else {
if ( _input.LA(1)==Token.EOF ) matchedEOF = true;
_errHandler.reportMatch(this);
consume();
}
setState(22);
((ComparisonExprContext)_localctx).right = expr(7);
}
break;
case 2:
{
_localctx = new AndExprContext(new ExprContext(_parentctx, _parentState));
((AndExprContext)_localctx).left = _prevctx;
pushNewRecursionContext(_localctx, _startState, RULE_expr);
setState(23);
if (!(precpred(_ctx, 5))) throw new FailedPredicateException(this, "precpred(_ctx, 5)");
setState(24);
match(AND);
setState(25);
((AndExprContext)_localctx).right = expr(6);
}
break;
case 3:
{
_localctx = new UnlessExprContext(new ExprContext(_parentctx, _parentState));
((UnlessExprContext)_localctx).left = _prevctx;
pushNewRecursionContext(_localctx, _startState, RULE_expr);
setState(26);
if (!(precpred(_ctx, 4))) throw new FailedPredicateException(this, "precpred(_ctx, 4)");
setState(27);
match(UNLESS);
setState(28);
((UnlessExprContext)_localctx).right = expr(5);
}
break;
case 4:
{
_localctx = new OrExprContext(new ExprContext(_parentctx, _parentState));
((OrExprContext)_localctx).left = _prevctx;
pushNewRecursionContext(_localctx, _startState, RULE_expr);
setState(29);
if (!(precpred(_ctx, 3))) throw new FailedPredicateException(this, "precpred(_ctx, 3)");
setState(30);
match(OR);
setState(31);
((OrExprContext)_localctx).right = expr(4);
}
break;
}
}
}
setState(36);
_errHandler.sync(this);
_alt = getInterpreter().adaptivePredict(_input,2,_ctx);
}
}
}
catch (RecognitionException re) {
_localctx.exception = re;
_errHandler.reportError(this, re);
_errHandler.recover(this, re);
}
finally {
unrollRecursionContexts(_parentctx);
}
return _localctx;
}
@SuppressWarnings("CheckReturnValue")
public static class IdentifierContext extends ParserRuleContext {
public TerminalNode IDENTIFIER() { return getToken(AlertExpressionParser.IDENTIFIER, 0); }
public IdentifierContext(ParserRuleContext parent, int invokingState) {
super(parent, invokingState);
}
@Override public int getRuleIndex() { return RULE_identifier; }
@Override
public <T> T accept(ParseTreeVisitor<? extends T> visitor) {
if ( visitor instanceof AlertExpressionVisitor ) return ((AlertExpressionVisitor<? extends T>)visitor).visitIdentifier(this);
else return visitor.visitChildren(this);
}
}
public final IdentifierContext identifier() throws RecognitionException {
IdentifierContext _localctx = new IdentifierContext(_ctx, getState());
enterRule(_localctx, 4, RULE_identifier);
try {
enterOuterAlt(_localctx, 1);
{
setState(37);
match(IDENTIFIER);
}
}
catch (RecognitionException re) {
_localctx.exception = re;
_errHandler.reportError(this, re);
_errHandler.recover(this, re);
}
finally {
exitRule();
}
return _localctx;
}
@SuppressWarnings("CheckReturnValue")
public static class NumberContext extends ParserRuleContext {
public TerminalNode NUMBER() { return getToken(AlertExpressionParser.NUMBER, 0); }
public NumberContext(ParserRuleContext parent, int invokingState) {
super(parent, invokingState);
}
@Override public int getRuleIndex() { return RULE_number; }
@Override
public <T> T accept(ParseTreeVisitor<? extends T> visitor) {
if ( visitor instanceof AlertExpressionVisitor ) return ((AlertExpressionVisitor<? extends T>)visitor).visitNumber(this);
else return visitor.visitChildren(this);
}
}
public final NumberContext number() throws RecognitionException {
NumberContext _localctx = new NumberContext(_ctx, getState());
enterRule(_localctx, 6, RULE_number);
try {
enterOuterAlt(_localctx, 1);
{
setState(39);
match(NUMBER);
}
}
catch (RecognitionException re) {
_localctx.exception = re;
_errHandler.reportError(this, re);
_errHandler.recover(this, re);
}
finally {
exitRule();
}
return _localctx;
}
public boolean sempred(RuleContext _localctx, int ruleIndex, int predIndex) {
switch (ruleIndex) {
case 1:
return expr_sempred((ExprContext)_localctx, predIndex);
}
return true;
}
private boolean expr_sempred(ExprContext _localctx, int predIndex) {
switch (predIndex) {
case 0:
return precpred(_ctx, 6);
case 1:
return precpred(_ctx, 5);
case 2:
return precpred(_ctx, 4);
case 3:
return precpred(_ctx, 3);
}
return true;
}
public static final String _serializedATN =
"\u0004\u0001\u000e*\u0002\u0000\u0007\u0000\u0002\u0001\u0007\u0001\u0002"+
"\u0002\u0007\u0002\u0002\u0003\u0007\u0003\u0001\u0000\u0001\u0000\u0001"+
"\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001"+
"\u0001\u0001\u0001\u0003\u0001\u0013\b\u0001\u0001\u0001\u0001\u0001\u0001"+
"\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001"+
"\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0005\u0001!\b\u0001\n\u0001"+
"\f\u0001$\t\u0001\u0001\u0002\u0001\u0002\u0001\u0003\u0001\u0003\u0001"+
"\u0003\u0000\u0001\u0002\u0004\u0000\u0002\u0004\u0006\u0000\u0001\u0001"+
"\u0000\u0004\t+\u0000\b\u0001\u0000\u0000\u0000\u0002\u0012\u0001\u0000"+
"\u0000\u0000\u0004%\u0001\u0000\u0000\u0000\u0006\'\u0001\u0000\u0000"+
"\u0000\b\t\u0003\u0002\u0001\u0000\t\n\u0005\u0000\u0000\u0001\n\u0001"+
"\u0001\u0000\u0000\u0000\u000b\f\u0006\u0001\uffff\uffff\u0000\f\r\u0005"+
"\n\u0000\u0000\r\u000e\u0003\u0002\u0001\u0000\u000e\u000f\u0005\u000b"+
"\u0000\u0000\u000f\u0013\u0001\u0000\u0000\u0000\u0010\u0013\u0003\u0004"+
"\u0002\u0000\u0011\u0013\u0003\u0006\u0003\u0000\u0012\u000b\u0001\u0000"+
"\u0000\u0000\u0012\u0010\u0001\u0000\u0000\u0000\u0012\u0011\u0001\u0000"+
"\u0000\u0000\u0013\"\u0001\u0000\u0000\u0000\u0014\u0015\n\u0006\u0000"+
"\u0000\u0015\u0016\u0007\u0000\u0000\u0000\u0016!\u0003\u0002\u0001\u0007"+
"\u0017\u0018\n\u0005\u0000\u0000\u0018\u0019\u0005\u0001\u0000\u0000\u0019"+
"!\u0003\u0002\u0001\u0006\u001a\u001b\n\u0004\u0000\u0000\u001b\u001c"+
"\u0005\u0003\u0000\u0000\u001c!\u0003\u0002\u0001\u0005\u001d\u001e\n"+
"\u0003\u0000\u0000\u001e\u001f\u0005\u0002\u0000\u0000\u001f!\u0003\u0002"+
"\u0001\u0004 \u0014\u0001\u0000\u0000\u0000 \u0017\u0001\u0000\u0000\u0000"+
" \u001a\u0001\u0000\u0000\u0000 \u001d\u0001\u0000\u0000\u0000!$\u0001"+
"\u0000\u0000\u0000\" \u0001\u0000\u0000\u0000\"#\u0001\u0000\u0000\u0000"+
"#\u0003\u0001\u0000\u0000\u0000$\"\u0001\u0000\u0000\u0000%&\u0005\f\u0000"+
"\u0000&\u0005\u0001\u0000\u0000\u0000\'(\u0005\r\u0000\u0000(\u0007\u0001"+
"\u0000\u0000\u0000\u0003\u0012 \"";
public static final ATN _ATN =
new ATNDeserializer().deserialize(_serializedATN.toCharArray());
static {
_decisionToDFA = new DFA[_ATN.getNumberOfDecisions()];
for (int i = 0; i < _ATN.getNumberOfDecisions(); i++) {
_decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i);
}
}
}
@@ -1,109 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Generated from AlertExpression.g4 by ANTLR 4.13.2
package org.apache.hertzbeat.alert.expr;
import org.antlr.v4.runtime.tree.ParseTreeVisitor;
/**
* This interface defines a complete generic visitor for a parse tree produced
* by {@link AlertExpressionParser}.
*
* @param <T> The return type of the visit operation. Use {@link Void} for
* operations with no return type.
*/
public interface AlertExpressionVisitor<T> extends ParseTreeVisitor<T> {
/**
* Visit a parse tree produced by {@link AlertExpressionParser#expression}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitExpression(AlertExpressionParser.ExpressionContext ctx);
/**
* Visit a parse tree produced by the {@code AndExpr}
* labeled alternative in AlertExpressionParser#expr
* @param ctx the parse tree
* @return the visitor result
*/
T visitAndExpr(AlertExpressionParser.AndExprContext ctx);
/**
* Visit a parse tree produced by the {@code QueryExpr}
* labeled alternative in AlertExpressionParser#expr
* @param ctx the parse tree
* @return the visitor result
*/
T visitQueryExpr(AlertExpressionParser.QueryExprContext ctx);
/**
* Visit a parse tree produced by the {@code ComparisonExpr}
* labeled alternative in AlertExpressionParser#expr
* @param ctx the parse tree
* @return the visitor result
*/
T visitComparisonExpr(AlertExpressionParser.ComparisonExprContext ctx);
/**
* Visit a parse tree produced by the {@code UnlessExpr}
* labeled alternative in AlertExpressionParser#expr
* @param ctx the parse tree
* @return the visitor result
*/
T visitUnlessExpr(AlertExpressionParser.UnlessExprContext ctx);
/**
* Visit a parse tree produced by the {@code LiteralExpr}
* labeled alternative in AlertExpressionParser#expr
* @param ctx the parse tree
* @return the visitor result
*/
T visitLiteralExpr(AlertExpressionParser.LiteralExprContext ctx);
/**
* Visit a parse tree produced by the {@code ParenExpr}
* labeled alternative in AlertExpressionParser#expr
* @param ctx the parse tree
* @return the visitor result
*/
T visitParenExpr(AlertExpressionParser.ParenExprContext ctx);
/**
* Visit a parse tree produced by the {@code OrExpr}
* labeled alternative in AlertExpressionParser#expr
* @param ctx the parse tree
* @return the visitor result
*/
T visitOrExpr(AlertExpressionParser.OrExprContext ctx);
/**
* Visit a parse tree produced by {@link AlertExpressionParser#identifier}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitIdentifier(AlertExpressionParser.IdentifierContext ctx);
/**
* Visit a parse tree produced by {@link AlertExpressionParser#number}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitNumber(AlertExpressionParser.NumberContext ctx);
}
@@ -48,7 +48,6 @@ final class TelegramBotAlertNotifyHandlerImpl extends AbstractAlertNotifyHandler
TelegramBotNotifyDTO notifyBody = TelegramBotNotifyDTO.builder()
.chatId(receiver.getTgUserId())
.text(renderContent(noticeTemplate, alert))
.messageThreadId(receiver.getTgMessageThreadId())
.disableWebPagePreview(true)
.build();
HttpHeaders headers = new HttpHeaders();
@@ -85,8 +84,6 @@ final class TelegramBotAlertNotifyHandlerImpl extends AbstractAlertNotifyHandler
private String text;
@JsonProperty("disable_web_page_preview")
private Boolean disableWebPagePreview;
@JsonProperty("message_thread_id")
private String messageThreadId;
}
@NoArgsConstructor
@@ -23,7 +23,6 @@ import org.apache.hertzbeat.alert.config.SmsConfig;
import org.apache.hertzbeat.alert.service.impl.SmsLocalSmsClientImpl;
import org.apache.hertzbeat.alert.service.impl.AwsSmsClientImpl;
import org.apache.hertzbeat.alert.service.impl.TencentSmsClientImpl;
import org.apache.hertzbeat.alert.service.impl.TwilioSmsClientImpl;
import org.apache.hertzbeat.alert.service.impl.UniSmsClientImpl;
import org.apache.hertzbeat.alert.service.impl.AlibabaSmsClientImpl;
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
@@ -36,7 +35,6 @@ import org.springframework.stereotype.Component;
import static org.apache.hertzbeat.common.constants.SmsConstants.ALIBABA;
import static org.apache.hertzbeat.common.constants.SmsConstants.AWS;
import static org.apache.hertzbeat.common.constants.SmsConstants.TENCENT;
import static org.apache.hertzbeat.common.constants.SmsConstants.TWILIO;
import static org.apache.hertzbeat.common.constants.SmsConstants.UNISMS;
import static org.apache.hertzbeat.common.constants.SmsConstants.SMSLOCAL;
@@ -143,12 +141,9 @@ public class SmsClientFactory {
case AWS:
currentSmsClient = new AwsSmsClientImpl(smsConfig.getAws());
break;
case TWILIO:
currentSmsClient = new TwilioSmsClientImpl(smsConfig.getTwilio());
break;
default:
log.warn("[SmsClientFactory] Unsupported SMS provider type: {}", smsConfig.getType());
break;
}
}
}
}
@@ -17,26 +17,21 @@
package org.apache.hertzbeat.alert.service.impl;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.Getter;
import lombok.Setter;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Stack;
import lombok.extern.slf4j.Slf4j;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import org.apache.hertzbeat.alert.expr.AlertExpressionEvalVisitor;
import org.apache.hertzbeat.alert.expr.AlertExpressionLexer;
import org.apache.hertzbeat.alert.expr.AlertExpressionParser;
import org.apache.hertzbeat.alert.service.DataSourceService;
import org.apache.hertzbeat.warehouse.db.QueryExecutor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.util.StringUtils;
/**
* datasource service
@@ -44,25 +39,14 @@ import java.util.concurrent.TimeUnit;
@Service
@Slf4j
public class DataSourceServiceImpl implements DataSourceService {
@Setter
@Autowired(required = false)
private List<QueryExecutor> executors;
@Getter
private final Cache<String, ParseTree> expressionCache = Caffeine.newBuilder()
.maximumSize(256)
.expireAfterAccess(1, TimeUnit.HOURS)
.recordStats()
.build();
@Getter
private final Cache<String, CommonTokenStream> tokenStreamCache = Caffeine.newBuilder()
.maximumSize(512)
.expireAfterWrite(30, TimeUnit.MINUTES)
.recordStats()
.build();
private static final Pattern EXPR_TOKEN = Pattern.compile("\\(|\\)|[a-zA-Z_][a-zA-Z0-9_={}\\[\\]\".]*|\\d+(\\.\\d+)?|>=|<=|==|!=|>|<|and|or|unless");
private static final String THRESHOLD = "__threshold__";
private static final String VALUE = "__value__";
@Override
public List<Map<String, Object>> calculate(String datasource, String expr) {
if (!StringUtils.hasText(expr)) {
@@ -72,7 +56,6 @@ public class DataSourceServiceImpl implements DataSourceService {
throw new IllegalArgumentException("No query executor found");
}
QueryExecutor executor = executors.stream().filter(e -> e.support(datasource)).findFirst().orElse(null);
if (executor == null) {
throw new IllegalArgumentException("Unsupported datasource: " + datasource);
}
@@ -87,18 +70,278 @@ public class DataSourceServiceImpl implements DataSourceService {
}
private List<Map<String, Object>> evaluate(String expr, QueryExecutor executor) {
ParseTree tree = expressionCache.get(expr, e -> {
CommonTokenStream tokens = tokenStreamCache.get(e, this::createTokenStream);
AlertExpressionParser parser = new AlertExpressionParser(tokens);
return parser.expr();
});
AlertExpressionEvalVisitor visitor = new AlertExpressionEvalVisitor(executor);
return visitor.visit(tree);
Stack<List<Map<String, Object>>> values = new Stack<>();
Stack<String> operators = new Stack<>();
Matcher matcher = EXPR_TOKEN.matcher(expr);
List<String> tokens = new ArrayList<>();
while (matcher.find()) {
tokens.add(matcher.group());
}
for (String token : tokens) {
if (token.equals("(")) {
operators.push(token);
} else if (token.equals(")")) {
while (!operators.isEmpty() && !operators.peek().equals("(")) {
applyOperator(values, operators.pop());
}
// remove the left parenthesis
operators.pop();
} else if (token.matches(">=|<=|==|!=|>|<")) {
operators.push(token);
} else if (token.equals("and") || token.equals("or") || token.equals("unless")) {
while (!operators.isEmpty() && precedence(operators.peek()) >= precedence(token)) {
applyOperator(values, operators.pop());
}
operators.push(token);
} else if (token.matches("\\d+(\\.\\d+)?")) {
double value = Double.parseDouble(token);
List<Map<String, Object>> numAsList = new ArrayList<>();
numAsList.add(Map.of(THRESHOLD, value));
values.push(numAsList);
} else if (token.matches("[a-zA-Z_][a-zA-Z0-9_={}\\[\\]\".]*")) {
List<Map<String, Object>> results = executor.execute(token);
values.push(results);
}
}
while (!operators.isEmpty()) {
applyOperator(values, operators.pop());
}
return values.isEmpty() ? new LinkedList<>() : values.pop();
}
private CommonTokenStream createTokenStream(String expr) {
AlertExpressionLexer lexer = new AlertExpressionLexer(CharStreams.fromString(expr));
return new CommonTokenStream(lexer);
private int precedence(String op) {
return switch (op) {
case "or" -> 1;
case "unless" -> 2;
case "and" -> 3;
case ">", "<", ">=", "<=", "==", "!=" -> 4;
default -> 0;
};
}
}
private void applyOperator(Stack<List<Map<String, Object>>> values, String op) {
if (values.size() < 2) {
return;
};
List<Map<String, Object>> rightOperand = values.pop();
List<Map<String, Object>> leftOperand = values.pop();
if (rightOperand.size() == 1 && rightOperand.get(0).containsKey(THRESHOLD)) {
double threshold = (double) rightOperand.get(0).get(THRESHOLD);
List<Map<String, Object>> result = new ArrayList<>();
for (Map<String, Object> item : leftOperand) {
Object queryValues = item.get(VALUE);
if (queryValues == null) {
// ignore the query result data is empty
continue;
}
// queryValues may be a list of values, or a single value
Object matchValue = evaluateCondition(queryValues, op, threshold);
item.put(VALUE, matchValue);
// if matchValue is null, mean not match the threshold
// if not null, mean match the threshold
result.add(new HashMap<>(item));
}
if (!result.isEmpty()) {
values.push(result);
}
return;
}
Map<String, Object> leftMap = null;
boolean leftMatch = false;
Map<String, Object> rightMap = null;
boolean rightMatch = false;
switch (op) {
case "and" -> {
for (Map<String, Object> item : leftOperand) {
if (leftMap == null) {
leftMap = item;
}
if (item.get(VALUE) != null) {
leftMap = item;
leftMatch = true;
break;
}
}
for (Map<String, Object> item : rightOperand) {
if (rightMap == null) {
rightMap = item;
}
if (item.get(VALUE) != null) {
rightMap = item;
rightMatch = true;
break;
}
}
if (leftMatch && rightMatch) {
rightMap.putAll(leftMap);
values.push(new LinkedList<>(List.of(rightMap)));
} else if (leftMap != null) {
leftMap.put(VALUE, null);
values.push(new LinkedList<>(List.of(leftMap)));
} else if (rightMap != null) {
rightMap.put(VALUE, null);
values.push(new LinkedList<>(List.of(rightMap)));
}
}
case "or" -> {
for (Map<String, Object> item : leftOperand) {
if (leftMap == null) {
leftMap = item;
}
if (item.get(VALUE) != null) {
leftMap = item;
leftMatch = true;
break;
}
}
for (Map<String, Object> item : rightOperand) {
if (rightMap == null) {
rightMap = item;
}
if (item.get(VALUE) != null) {
rightMap = item;
rightMatch = true;
break;
}
}
if (leftMatch && rightMatch) {
rightMap.putAll(leftMap);
values.push(new LinkedList<>(List.of(rightMap)));
} else if (leftMatch) {
values.push(new LinkedList<>(List.of(leftMap)));
} else if (rightMatch) {
values.push(new LinkedList<>(List.of(rightMap)));
} else {
if (leftMap != null && rightMap != null) {
rightMap.putAll(leftMap);
values.push(new LinkedList<>(List.of(rightMap)));
} else if (leftMap != null) {
values.push(new LinkedList<>(List.of(leftMap)));
} else if (rightMap != null){
values.push(new LinkedList<>(List.of(rightMap)));
}
}
}
case "unless" -> {
for (Map<String, Object> item : leftOperand) {
if (leftMap == null) {
leftMap = item;
}
if (item.get(VALUE) != null) {
leftMap = item;
leftMatch = true;
break;
}
}
for (Map<String, Object> item : rightOperand) {
if (rightMap == null) {
rightMap = item;
}
if (item.get(VALUE) != null) {
rightMap = item;
rightMatch = true;
break;
}
}
if (leftMatch && !rightMatch) {
values.push(new LinkedList<>(List.of(leftMap)));
} else {
if (leftMap != null) {
leftMap.put(VALUE, null);
values.push(new LinkedList<>(List.of(leftMap)));
} else {
if (rightMap != null) {
rightMap.put(VALUE, null);
values.push(new LinkedList<>(List.of(rightMap)));
}
}
}
}
default -> throw new IllegalArgumentException("Unsupported operator: " + op);
}
}
private Object evaluateCondition(Object value, String operator, Double threshold) {
// value may be a list of values, or a single value
switch (operator) {
case ">":
// if value is list, return the max value
if (value instanceof List<?> values) {
Double doubleValue = values.stream().map(v -> Double.valueOf(v.toString()))
.max(Double::compareTo).orElse(null);
if (doubleValue != null) {
return doubleValue > threshold ? doubleValue : null;
} else {
return null;
}
} else {
return Double.parseDouble(value.toString()) > threshold ? value : null;
}
case ">=":
if (value instanceof List<?> values) {
Double doubleValue = values.stream().map(v -> Double.valueOf(v.toString()))
.max(Double::compareTo).orElse(null);
if (doubleValue != null) {
return doubleValue >= threshold ? doubleValue : null;
} else {
return null;
}
} else {
return Double.parseDouble(value.toString()) >= threshold ? value : null;
}
case "<":
if (value instanceof List<?> values) {
Double doubleValue = values.stream().map(v -> Double.valueOf(v.toString()))
.min(Double::compareTo).orElse(null);
if (doubleValue != null) {
return doubleValue < threshold ? doubleValue : null;
} else {
return null;
}
} else {
return Double.parseDouble(value.toString()) < threshold ? value : null;
}
case "<=":
if (value instanceof List<?> values) {
Double doubleValue = values.stream().map(v -> Double.valueOf(v.toString()))
.min(Double::compareTo).orElse(null);
if (doubleValue != null) {
return doubleValue <= threshold ? doubleValue : null;
} else {
return null;
}
} else {
return Double.parseDouble(value.toString()) <= threshold ? value : null;
}
case "==":
if (value instanceof List<?> values) {
for (Object v : values) {
if (v.equals(threshold)) {
return v;
}
}
return null;
} else {
return value.equals(threshold) ? value : null;
}
case "!=":
if (value instanceof List<?> values) {
for (Object v : values) {
if (v.equals(threshold)) {
return null;
}
}
return value;
} else {
return value.equals(threshold) ? null : value;
}
default:
// unsupported operator todo add more operator
return null;
}
}
public void setExecutors(List<QueryExecutor> mockExecutor) {
this.executors = mockExecutor;
}
}
@@ -18,22 +18,14 @@
package org.apache.hertzbeat.alert.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.hertzbeat.alert.dto.TencentCloudExternAlert;
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
import org.apache.hertzbeat.alert.service.ExternAlertService;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* Default external alarm service impl
*/
@@ -51,7 +43,8 @@ public class TencentExternAlertService implements ExternAlertService {
log.warn("parse extern alert content failed! content: {}", content);
return;
}
SingleAlert alert = new TencentCloudAlertConverter().convert(report);
// todo convert TenCloudAlertReport to SingleAlert
SingleAlert alert = new SingleAlert();
alarmCommonReduce.reduceAndSendAlarm(alert);
}
@@ -59,169 +52,4 @@ public class TencentExternAlertService implements ExternAlertService {
public String supportSource() {
return "tencent";
}
/**
* Converter: TencentCloud alert to SingleAlert
*/
public static class TencentCloudAlertConverter {
/**
* Metric alert content template
*/
private final String metricTemplate = "Tencent Cloud Metric Alert: {metricShowName} {calcType} {calcValue}{calcUnit}, Current: {currentValue}{unit}";
/**
* Event alert content template
*/
private final String eventTemplate = "Tencent Cloud Event Alert: {productShowName} - {eventShowName}";
/**
* Convert TencentCloud alert to SingleAlert
*/
public SingleAlert convert(TencentCloudExternAlert tencentAlert) {
// Building basic information
SingleAlert.SingleAlertBuilder builder = SingleAlert.builder()
.status(convertStatus(tencentAlert.getAlarmStatus()))
.startAt(parseTime(tencentAlert.getFirstOccurTime()))
.activeAt(parseTime(tencentAlert.getFirstOccurTime()))
.triggerTimes(1);
// If it is a recovery state, set the end time.
if ("0".equals(tencentAlert.getAlarmStatus())) {
builder.endAt(parseTime(tencentAlert.getRecoverTime()));
}
// Build labels
Map<String, String> labels = new HashMap<>();
buildLabels(labels, tencentAlert);
// Build annotations
Map<String, String> annotations = new HashMap<>();
buildAnnotations(annotations, tencentAlert);
// Build content
String content = generateContent(tencentAlert);
return builder
.labels(labels)
.annotations(annotations)
.content(content)
.build();
}
private void buildLabels(Map<String, String> labels, TencentCloudExternAlert alert) {
labels.put("__source__", "tencent_cloud");
labels.put("alert_type", alert.getAlarmType());
if (TencentCloudExternAlert.METRIC.equals(alert.getAlarmType())) {
labels.put("metric_name", alert.getAlarmPolicyInfo().getConditions().getMetricName());
labels.put("namespace", alert.getAlarmObjInfo().getNamespace());
} else {
labels.put("event_name", alert.getAlarmPolicyInfo().getConditions().getEventName());
labels.put("product_name", alert.getAlarmPolicyInfo().getConditions().getProductName());
}
}
private void buildAnnotations(Map<String, String> annotations, TencentCloudExternAlert alert) {
TencentCloudExternAlert.AlarmPolicyInfo alarmPolicyInfo = alert.getAlarmPolicyInfo();
TencentCloudExternAlert.AlarmObjInfo alarmObjInfo = alert.getAlarmObjInfo();
TencentCloudExternAlert.Dimensions dimensions = alert.getAlarmObjInfo().getDimensions();
putIfNotNull(annotations, "policy_id", alarmPolicyInfo.getPolicyId());
putIfNotNull(annotations, "policy_type", alarmPolicyInfo.getPolicyType());
putIfNotNull(annotations, "policy_name", alarmPolicyInfo.getPolicyName());
putIfNotNull(annotations, "policy_type_cname", alarmPolicyInfo.getPolicyTypeCname());
putIfNotNull(annotations, "namespace", alarmObjInfo.getNamespace());
putIfNotNull(annotations, "region", alarmObjInfo.getRegion());
putIfNotNull(annotations, "app_id", alarmObjInfo.getAppId());
putIfNotNull(annotations, "uin", alarmObjInfo.getUin());
putIfNotNull(annotations, "instance_id", dimensions.getUnInstanceId());
putIfNotNull(annotations, "obj_id", dimensions.getObjId());
}
/**
* Generate alert content with template
*/
private String generateContent(TencentCloudExternAlert alert) {
if (TencentCloudExternAlert.METRIC.equals(alert.getAlarmType())) {
return generateMetricContent(alert);
} else {
return generateEventContent(alert);
}
}
/**
* Generate metric alert content
*/
private String generateMetricContent(TencentCloudExternAlert alert) {
TencentCloudExternAlert.Conditions conditions = alert.getAlarmPolicyInfo().getConditions();
Map<String, String> params = new HashMap<>();
params.put("metricShowName", conditions.getMetricShowName());
if (StringUtils.isNotEmpty(conditions.getCalcType())
&& StringUtils.isNotEmpty(conditions.getCalcValue())) {
params.put("calcType", conditions.getCalcType());
params.put("calcValue", conditions.getCalcValue());
params.put("calcUnit", Objects.toString(conditions.getCalcUnit(), ""));
} else {
params.put("calcType", "");
params.put("calcValue", "N/A");
params.put("calcUnit", "");
}
if (StringUtils.isNotEmpty(conditions.getCurrentValue())) {
params.put("currentValue", conditions.getCurrentValue());
params.put("unit", Objects.toString(conditions.getUnit(), ""));
} else {
params.put("currentValue", "N/A");
params.put("unit", "");
}
return replacePlaceholders(metricTemplate, params);
}
/**
* Generate event alert content
*/
private String generateEventContent(TencentCloudExternAlert alert) {
TencentCloudExternAlert.Conditions conditions = alert.getAlarmPolicyInfo().getConditions();
Map<String, String> params = new HashMap<>();
params.put("productShowName", conditions.getProductShowName());
params.put("eventShowName", conditions.getEventShowName());
return replacePlaceholders(eventTemplate, params);
}
/**
* Replace placeholders in template with actual values
*/
private String replacePlaceholders(String template, Map<String, String> params) {
String result = template;
for (Map.Entry<String, String> entry : params.entrySet()) {
result = result.replace("{" + entry.getKey() + "}", Objects.toString(entry.getValue(), "NULL"));
}
return result;
}
private String convertStatus(String alarmStatus) {
return "1".equals(alarmStatus) ? CommonConstants.ALERT_STATUS_FIRING : CommonConstants.ALERT_STATUS_RESOLVED;
}
private Long parseTime(String timeStr) {
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.parse(timeStr).getTime();
} catch (ParseException e) {
log.error("Failed to parse time: {}", timeStr);
throw new IllegalArgumentException("Failed to parse time: " + timeStr, e);
}
}
private void putIfNotNull(Map<String, String> map, String key, String value){
if (StringUtils.isNotEmpty(value)){
map.put(key, value);
}
}
}
}
@@ -17,7 +17,6 @@
package org.apache.hertzbeat.alert.service.impl;
import jakarta.xml.bind.DatatypeConverter;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.config.TencentSmsProperties;
import org.apache.hertzbeat.alert.service.SmsClient;
@@ -35,6 +34,7 @@ import org.apache.http.util.EntityUtils;
import org.apache.hertzbeat.common.util.JsonUtil;
import com.fasterxml.jackson.databind.JsonNode;
import javax.xml.bind.DatatypeConverter;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
@@ -1,171 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.service.impl;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.config.TwilioSmsProperties;
import org.apache.hertzbeat.alert.service.SmsClient;
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
import org.apache.hertzbeat.common.support.exception.SendMessageException;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import java.net.URI;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import static org.apache.hertzbeat.common.constants.SmsConstants.TWILIO;
/**
* Twilio SMS Client Implementation<br>
* API doc: <a href=
* "https://www.twilio.com/docs/sms/api">https://www.twilio.com/docs/sms/api</a>
*/
@Slf4j
public class TwilioSmsClientImpl implements SmsClient {
private static final String API_URL_FORMAT = "https://api.twilio.com/2010-04-01/Accounts/%s/Messages.json";
private static final String MESSAGE_TEMPLATE = "Instance: %s, Priority: %s, Content: %s";
private final String accountSid;
private final String authToken;
private final String twilioPhoneNumber;
public TwilioSmsClientImpl(TwilioSmsProperties config) {
if (config != null) {
this.accountSid = config.getAccountSid();
this.authToken = config.getAuthToken();
this.twilioPhoneNumber = config.getTwilioPhoneNumber();
} else {
this.accountSid = "";
this.authToken = "";
this.twilioPhoneNumber = "";
}
}
@Override
public void sendMessage(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
String instance = null;
String priority = null;
String content = null;
if (alert.getCommonLabels() != null) {
instance = alert.getCommonLabels().get("instance") == null ? alert.getGroupKey()
: alert.getCommonLabels().get("instance");
priority = alert.getCommonLabels().get("priority") == null ? "unknown"
: alert.getCommonLabels().get("priority");
content = alert.getCommonAnnotations().get("summary");
content = content == null ? alert.getCommonAnnotations().get("description") : content;
if (content == null) {
content = alert.getCommonAnnotations().values().stream().findFirst().orElse(null);
}
}
this.send(receiver.getPhone(), createMessage(instance, priority, content));
}
private String createMessage(String instance, String priority, String content) {
return String.format(MESSAGE_TEMPLATE, instance, priority, content);
}
private void send(String phoneNumber, String message) {
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
String endpoint = String.format(API_URL_FORMAT, accountSid);
URI requestUri = new URI(endpoint);
HttpPost httpPost = createHttpPost(requestUri, phoneNumber, message);
log.info("Sending Twilio SMS request to {}", requestUri);
executeRequest(httpClient, httpPost, phoneNumber);
} catch (Exception e) {
log.warn("Failed to send SMS: {}", e.getMessage());
throw new SendMessageException(e.getMessage());
}
}
private HttpPost createHttpPost(URI requestUri, String toNumber, String message) {
HttpPost httpPost = new HttpPost(requestUri);
String auth = accountSid + ":" + authToken;
String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes());
httpPost.setHeader("Authorization", "Basic " + encodedAuth);
List<BasicNameValuePair> parameters = new ArrayList<>();
parameters.add(new BasicNameValuePair("To", toNumber));
parameters.add(new BasicNameValuePair("From", twilioPhoneNumber));
parameters.add(new BasicNameValuePair("Body", message));
try {
httpPost.setEntity(new UrlEncodedFormEntity(parameters));
return httpPost;
} catch (Exception e) {
log.error("Failed to create HTTP request: {}", e.getMessage());
throw new SendMessageException(e.getMessage());
}
}
private void executeRequest(CloseableHttpClient httpClient, HttpPost httpPost, String phoneNumber)
throws Exception {
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
int statusCode = response.getStatusLine().getStatusCode();
String responseBody = EntityUtils.toString(response.getEntity());
log.info("SMS response status: {}, body: {}", statusCode, responseBody);
if (statusCode < 200 || statusCode >= 300) {
if (responseBody.contains("21608")) {
throw new SendMessageException(
"The Twilio trial account can only send SMS to verified phone numbers");
} else {
throw new SendMessageException(
"HTTP request failed with status code: " + statusCode + ", response: " + responseBody);
}
}
JsonNode jsonResponse = JsonUtil.fromJson(responseBody);
if (jsonResponse == null) {
throw new SendMessageException(statusCode + ":" + responseBody);
}
JsonNode sidNode = jsonResponse.get("sid");
if (sidNode == null) {
throw new SendMessageException(statusCode + ":" + responseBody);
}
String sid = sidNode.asText();
log.info("Successfully sent SMS to phone: {}, sid: {}", phoneNumber, sid);
}
}
@Override
public String getType() {
return TWILIO;
}
@Override
public boolean checkConfig() {
return !(accountSid.isBlank() || authToken.isBlank() || twilioPhoneNumber.isBlank());
}
}
@@ -1,57 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
grammar AlertExpression;
expression
: expr EOF
;
expr
: '(' expr ')' # ParenExpr
| left=expr op=('>='|'<='|'>'|'<'|'=='|'!=') right=expr # ComparisonExpr
| left=expr 'and' right=expr # AndExpr
| left=expr 'unless' right=expr # UnlessExpr
| left=expr 'or' right=expr # OrExpr
| identifier # QueryExpr
| number # LiteralExpr
;
// Lexer rules
AND : 'and' ;
OR : 'or' ;
UNLESS : 'unless' ;
GT : '>' ;
GE : '>=' ;
LT : '<' ;
LE : '<=' ;
EQ : '==' ;
NE : '!=' ;
LPAREN : '(' ;
RPAREN : ')' ;
identifier
: IDENTIFIER
;
number
: NUMBER
;
IDENTIFIER : [a-zA-Z_] [a-zA-Z0-9_={}[\]".]*;
NUMBER : [0-9]+ ('.' [0-9]+)? ;
WS : [ \t\r\n]+ -> skip ;
@@ -1,133 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.controller;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
import org.apache.hertzbeat.alert.service.AlertInhibitService;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.alerter.AlertInhibit;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import java.util.HashMap;
/**
* test case for {@link AlertInhibitControllerTest}
*/
@ExtendWith(MockitoExtension.class)
public class AlertInhibitControllerTest {
private MockMvc mockMvc;
@Mock
private AlertInhibitService alertInhibitService;
@InjectMocks
private AlertInhibitController alertInhibitController;
private AlertInhibit alertInhibit;
@BeforeEach
void setUp() {
this.mockMvc = standaloneSetup(alertInhibitController).build();
HashMap<String, String> sourceLabels = new HashMap<>();
HashMap<String, String> targetLabels = new HashMap<>();
alertInhibit = AlertInhibit.builder()
.id(1L)
.name("test")
.sourceLabels(sourceLabels)
.targetLabels(targetLabels)
.creator("test")
.build();
}
@Test
void testAddNewAlertInhibit() throws Exception {
doNothing().when(alertInhibitService).validate(any(AlertInhibit.class), eq(false));
doNothing().when(alertInhibitService).addAlertInhibit(any(AlertInhibit.class));
mockMvc.perform(post("/api/alert/inhibit")
.contentType(MediaType.APPLICATION_JSON)
.content(JsonUtil.toJson(alertInhibit)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andExpect(jsonPath("$.msg").value("Add success"));
}
@Test
void testModifyAlertInhibit() throws Exception {
doNothing().when(alertInhibitService).validate(any(AlertInhibit.class), eq(true));
doNothing().when(alertInhibitService).modifyAlertInhibit(any(AlertInhibit.class));
mockMvc.perform(put("/api/alert/inhibit")
.contentType(MediaType.APPLICATION_JSON)
.content(JsonUtil.toJson(alertInhibit)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andExpect(jsonPath("$.msg").value("Modify success"));
}
@Test
void testGetAlertInhibitExists() throws Exception {
when(alertInhibitService.getAlertInhibit(1L)).thenReturn(alertInhibit);
mockMvc.perform(get("/api/alert/inhibit/{id}", 1L)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.id").value(alertInhibit.getId()));
}
@Test
void testGetAlertInhibitNotExists() throws Exception {
when(alertInhibitService.getAlertInhibit(1L)).thenReturn(null);
mockMvc.perform(get("/api/alert/inhibit/{id}", 1L)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.MONITOR_NOT_EXIST_CODE))
.andExpect(jsonPath("$.msg").value("AlertInhibit not exist."));
}
}
@@ -1,181 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.expr;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.apache.hertzbeat.warehouse.db.QueryExecutor;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.Mockito.when;
/**
* test case for {@link AlertExpressionEvalVisitor}
*/
class AlertExpressionEvalVisitorTest {
private QueryExecutor mockExecutor;
private AlertExpressionEvalVisitor visitor;
@BeforeEach
void setUp() {
mockExecutor = Mockito.mock(QueryExecutor.class);
visitor = new AlertExpressionEvalVisitor(mockExecutor);
}
@Test
void testGreaterThan() {
when(mockExecutor.execute("cpu")).thenReturn(List.of(new HashMap<>(Map.of("__value__", 80.0))));
List<Map<String, Object>> result = evaluate("cpu > 70");
assertEquals(1, result.size());
assertEquals(80.0, result.get(0).get("__value__"));
}
@Test
void testGreaterThanWithInteger() {
when(mockExecutor.execute("cpu")).thenReturn(List.of(new HashMap<>(Map.of("__value__", 80))));
List<Map<String, Object>> result = evaluate("cpu > 70");
assertEquals(1, result.size());
assertEquals(80, result.get(0).get("__value__"));
}
@Test
void testLessThan() {
when(mockExecutor.execute("memory")).thenReturn(List.of(new HashMap<>(Map.of("__value__", 65.0))));
List<Map<String, Object>> result = evaluate("memory < 70");
assertEquals(1, result.size());
assertEquals(65.0, result.get(0).get("__value__"));
}
@Test
void testEqualWithTolerance() {
when(mockExecutor.execute("disk")).thenReturn(List.of(new HashMap<>(Map.of("__value__", 99.999))));
List<Map<String, Object>> result = evaluate("disk == 100");
assertEquals(1, result.size());
assertNull(result.get(0).get("__value__"));
}
@Test
void testNotEqual() {
when(mockExecutor.execute("network")).thenReturn(List.of(new HashMap<>(Map.of("__value__", 50.0))));
List<Map<String, Object>> result = evaluate("network != 60");
assertEquals(1, result.size());
assertEquals(50.0, result.get(0).get("__value__"));
}
@Test
void testExactlyEqual() {
when(mockExecutor.execute("threshold")).thenReturn(List.of(new HashMap<>(Map.of("__value__", 100.0))));
List<Map<String, Object>> result = evaluate("threshold == 100");
assertEquals(1, result.size());
assertEquals(100.0, result.get(0).get("__value__"));
}
@Test
void testMaxValueBoundary() {
when(mockExecutor.execute("max_val")).thenReturn(List.of(new HashMap<>(Map.of("__value__", Double.MAX_VALUE))));
List<Map<String, Object>> result = evaluate("max_val > 100");
assertEquals(1, result.size());
assertEquals(Double.MAX_VALUE, result.get(0).get("__value__"));
}
@Test
void testMinValueBoundary() {
when(mockExecutor.execute("min_val")).thenReturn(List.of(new HashMap<>(Map.of("__value__", Double.MIN_VALUE))));
List<Map<String, Object>> result = evaluate("min_val > 0");
assertEquals(1, result.size());
assertEquals(Double.MIN_VALUE, result.get(0).get("__value__"));
}
@Test
void testEmptyMetricName() {
when(mockExecutor.execute("")).thenReturn(List.of(new HashMap<>(Map.of("__value__", 50.0))));
List<Map<String, Object>> result = evaluate(" > 40");
assertEquals(1, result.size());
assertNull(result.get(0).get("__value__"));
}
@Test
void testListValueWithMax() {
when(mockExecutor.execute("multi_val")).thenReturn(List.of(new HashMap<>(Map.of("__value__", List.of(10.0, 20.0, 30.0)))));
List<Map<String, Object>> result = evaluate("multi_val > 25");
assertEquals(1, result.size());
assertEquals(30.0, result.get(0).get("__value__"));
}
@Test
void testListValueWithMin() {
when(mockExecutor.execute("multi_val")).thenReturn(List.of(new HashMap<>(Map.of("__value__", List.of(10.0, 20.0, 30.0)))));
List<Map<String, Object>> result = evaluate("multi_val < 15");
assertEquals(1, result.size());
assertEquals(10.0, result.get(0).get("__value__"));
}
@Test
void testEmptyListValue() {
when(mockExecutor.execute("empty_list")).thenReturn(List.of(new HashMap<>(Map.of("__value__", List.of()))));
List<Map<String, Object>> result = evaluate("empty_list > 50");
assertEquals(1, result.size());
assertNull(result.get(0).get("__value__"));
}
@Test
void testNestedParentheses() {
when(mockExecutor.execute("a")).thenReturn(List.of(new HashMap<>(Map.of("__value__", 10.0))));
when(mockExecutor.execute("b")).thenReturn(List.of(new HashMap<>(Map.of("__value__", 20.0))));
when(mockExecutor.execute("c")).thenReturn(List.of(new HashMap<>(Map.of("__value__", 30.0))));
List<Map<String, Object>> result = evaluate("(a > 5) and (b > 15 or c < 25)");
assertEquals(1, result.size());
assertEquals(10.0, result.get(0).get("__value__"));
}
@Test
void testMultipleUnlessConditions() {
when(mockExecutor.execute("metric1")).thenReturn(List.of(new HashMap<>(Map.of("__value__", 40.0))));
when(mockExecutor.execute("metric2")).thenReturn(List.of(new HashMap<>(Map.of("__value__", 50.0))));
when(mockExecutor.execute("metric3")).thenReturn(List.of(new HashMap<>(Map.of("__value__", 60.0))));
List<Map<String, Object>> result = evaluate("metric1 > 30 unless metric2 > 45 unless metric3 < 70");
assertEquals(1, result.size());
assertNull(result.get(0).get("__value__"));
}
@Test
void testComplexExpressionWithMixedOperators() {
when(mockExecutor.execute("cpu_temp")).thenReturn(List.of(new HashMap<>(Map.of("__value__", 75.0))));
when(mockExecutor.execute("gpu_temp")).thenReturn(List.of(new HashMap<>(Map.of("__value__", 85.0))));
when(mockExecutor.execute("fan_speed")).thenReturn(List.of(new HashMap<>(Map.of("__value__", 2000.0))));
List<Map<String, Object>> result = evaluate("(cpu_temp > 70 and gpu_temp < 90) or fan_speed > 1500");
assertEquals(1, result.size());
assertEquals(75.0, result.get(0).get("__value__"));
}
private List<Map<String, Object>> evaluate(String expression) {
AlertExpressionLexer lexer = new AlertExpressionLexer(CharStreams.fromString(expression));
AlertExpressionParser parser = new AlertExpressionParser(new CommonTokenStream(lexer));
return visitor.visit(parser.expression());
}
}
@@ -15,23 +15,38 @@
* limitations under the License.
*/
/*
* 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.hertzbeat.alert.service;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import java.util.HashMap;
import com.github.benmanes.caffeine.cache.Cache;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import org.apache.hertzbeat.alert.service.impl.DataSourceServiceImpl;
import org.apache.hertzbeat.warehouse.db.QueryExecutor;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Map;
import org.mockito.Mockito;
import org.apache.hertzbeat.warehouse.db.QueryExecutor;
/**
* test case for {@link DataSourceService}
@@ -529,91 +544,4 @@ class DataSourceServiceTest {
assertEquals(1, result.size());
assertEquals(200.0, result.get(0).get("__value__"));
}
@Test
void expressionCacheShouldHitForSameExpression() {
List<Map<String, Object>> prometheusData = List.of(
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
);
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
Mockito.when(mockExecutor.execute(Mockito.anyString())).thenReturn(prometheusData);
dataSourceService.setExecutors(List.of(mockExecutor));
String expr = "node_cpu_seconds_total > 50";
Cache<String, ParseTree> expressionCache = dataSourceService.getExpressionCache();
expressionCache.invalidateAll();
long beforeHits = expressionCache.stats().hitCount();
dataSourceService.calculate("promql", expr); //first execution miss cache
dataSourceService.calculate("promql", expr); //second execution hits cache
long actualHits = expressionCache.stats().hitCount() - beforeHits;
assertEquals(1, actualHits, "expression cache should hit but miss");
}
@Test
void tokenCacheShouldHitForSameExpression() {
List<Map<String, Object>> prometheusData = List.of(
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
);
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
Mockito.when(mockExecutor.execute(Mockito.anyString())).thenReturn(prometheusData);
dataSourceService.setExecutors(List.of(mockExecutor));
String expr = "node_cpu_seconds_total > 50";
Cache<String, ParseTree> expressionCache = dataSourceService.getExpressionCache();
Cache<String, CommonTokenStream> tokenStreamCache = dataSourceService.getTokenStreamCache();
expressionCache.invalidateAll();
tokenStreamCache.invalidateAll();
long beforeHits = tokenStreamCache.stats().hitCount();
dataSourceService.calculate("promql", expr);
expressionCache.invalidateAll();
dataSourceService.calculate("promql", expr);
long actualHits = tokenStreamCache.stats().hitCount() - beforeHits;
assertEquals(1, actualHits, "expression cache should hit but miss");
}
@Test
void expressCacheShouldNotHitForDifferentExpressions() {
List<Map<String, Object>> prometheusData = List.of(
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
);
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
Mockito.when(mockExecutor.execute(Mockito.anyString())).thenReturn(prometheusData);
dataSourceService.setExecutors(List.of(mockExecutor));
dataSourceService.calculate("promql", "node_cpu_seconds_total > 50");
Cache<String, ParseTree> expressionCache = dataSourceService.getExpressionCache();
Cache<String, CommonTokenStream> tokenStreamCache = dataSourceService.getTokenStreamCache();
expressionCache.invalidateAll();
tokenStreamCache.invalidateAll();
String expr1 = "node_cpu_seconds_total > 30";
String expr2 = "node_cpu_seconds_total > 50";
long beforeHits = expressionCache.stats().hitCount();
dataSourceService.calculate("promql", expr1);
dataSourceService.calculate("promql", expr2);
long actualHits = expressionCache.stats().hitCount() - beforeHits;
assertEquals(0, actualHits, "expression cache should miss but hit");
}
@Test
void tokenCacheShouldNotHitForDifferentExpressions() {
List<Map<String, Object>> prometheusData = List.of(
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555, "instance", "node2"))
);
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
Mockito.when(mockExecutor.execute(Mockito.anyString())).thenReturn(prometheusData);
dataSourceService.setExecutors(List.of(mockExecutor));
dataSourceService.calculate("promql", "node_cpu_seconds_total > 50");
Cache<String, ParseTree> expressionCache = dataSourceService.getExpressionCache();
Cache<String, CommonTokenStream> tokenStreamCache = dataSourceService.getTokenStreamCache();
expressionCache.invalidateAll();
tokenStreamCache.invalidateAll();
String expr1 = "node_cpu_seconds_total > 30";
String expr2 = "node_cpu_seconds_total > 50";
long beforeHits = tokenStreamCache.stats().hitCount();
dataSourceService.calculate("promql", expr1);
dataSourceService.calculate("promql", expr2);
long actualHits = tokenStreamCache.stats().hitCount() - beforeHits;
assertEquals(0, actualHits, "expression cache should miss but hit");
}
}
@@ -1,64 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.service;
import org.apache.hertzbeat.alert.config.TwilioSmsProperties;
import org.apache.hertzbeat.alert.service.impl.TwilioSmsClientImpl;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* Test case for {@link TwilioSmsClientImpl}
*/
class TwilioSmsClientImplTest {
@Test
void getType() {
TwilioSmsProperties twilioSmsProperties = new TwilioSmsProperties();
twilioSmsProperties.setAccountSid("accountSid");
twilioSmsProperties.setAuthToken("authToken");
twilioSmsProperties.setTwilioPhoneNumber("twilioPhoneNumber");
TwilioSmsClientImpl twilioSmsClient = new TwilioSmsClientImpl(twilioSmsProperties);
assertEquals("twilio", twilioSmsClient.getType());
}
@ParameterizedTest
@CsvSource({
"accountSid, authToken, twilioPhoneNumber, true",
"accountSid, authToken, '', false",
"accountSid, '', twilioPhoneNumber, false",
"accountSid, '', '', false",
"'', authToken, twilioPhoneNumber, false",
"'', authToken, '', false",
"'', '', twilioPhoneNumber, false",
"'', '', '', false",
})
void checkConfig(String accountSid, String authToken, String twilioPhoneNumber, boolean expected) {
TwilioSmsProperties twilioSmsProperties = new TwilioSmsProperties();
twilioSmsProperties.setAccountSid(accountSid);
twilioSmsProperties.setAuthToken(authToken);
twilioSmsProperties.setTwilioPhoneNumber(twilioPhoneNumber);
TwilioSmsClientImpl twilioSmsClient = new TwilioSmsClientImpl(twilioSmsProperties);
assertEquals(expected, twilioSmsClient.checkConfig());
}
}
@@ -155,11 +155,6 @@
<artifactId>plc4j-driver-modbus</artifactId>
<version>0.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.plc4x</groupId>
<artifactId>plc4j-driver-s7</artifactId>
<version>0.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-sftp</artifactId>
@@ -63,25 +63,6 @@ public class JdbcCommonCollect extends AbstractCollect {
private static final String[] VULNERABLE_KEYWORDS = {"allowLoadLocalInfile", "allowLoadLocalInfileInPath", "useLocalInfile"};
private static final String[] BLACK_LIST = {
// dangerous SQL commands - may cause database structure damage or data leakage
"create trigger", "create alias", "runscript from", "shutdown", "drop table",
"drop database", "create function", "alter system", "grant all", "revoke all",
// file IO related - may cause server files to be read or written
"allowloadlocalinfile", "allowloadlocalinfileinpath", "uselocalinfile",
// code execution related - may result in remote code execution
"init=", "javaobjectserializer=", "runscript", "serverstatusdiffinterceptor",
"queryinterceptors=", "statementinterceptors=", "exceptioninterceptors=",
// multiple statement execution - may lead to SQL injection
"allowmultiqueries",
// deserialization related - may result in remote code execution
"autodeserialize", "detectcustomcollations",
};
private final GlobalConnectionCache connectionCommonCache = GlobalConnectionCache.getInstance();
@@ -350,24 +331,17 @@ public class JdbcCommonCollect extends AbstractCollect {
if (Objects.nonNull(jdbcProtocol.getUrl())
&& !Objects.equals("", jdbcProtocol.getUrl())
&& jdbcProtocol.getUrl().startsWith("jdbc")) {
// limit url length
if (jdbcProtocol.getUrl().length() > 2048) {
throw new IllegalArgumentException("JDBC URL length exceeds maximum limit of 2048 characters");
// convert the URL to lowercase for case-insensitive checking
String url = jdbcProtocol.getUrl().toLowerCase();
// check whether the parameter is valid
if (url.contains("create trigger") || url.contains("create alias") || url.contains("runscript from")
|| url.contains("allowloadlocalinfile") || url.contains("allowloadlocalinfileinpath")
|| url.contains("uselocalinfile") || url.contains("autodeserialize") || url.contains("detectcustomcollations")
|| url.contains("serverstatusdiffinterceptor")) {
throw new IllegalArgumentException("Invalid JDBC URL: contains malicious characters.");
}
// remove special characters
String cleanedUrl = jdbcProtocol.getUrl().replaceAll("[\\x00-\\x1F\\x7F]", "");
String url = cleanedUrl.toLowerCase();
// backlist check
for (String keyword : BLACK_LIST) {
if (url.contains(keyword)) {
throw new IllegalArgumentException("Invalid JDBC URL: contains potentially malicious parameter: " + keyword);
}
}
// url format check
if (!url.matches("^jdbc:[a-zA-Z0-9]+://[^\\s]+$")) {
throw new IllegalArgumentException("Invalid JDBC URL format");
}
return cleanedUrl;
// when has config jdbc url, use it
return jdbcProtocol.getUrl();
}
return switch (jdbcProtocol.getPlatform()) {
case "mysql", "mariadb" -> "jdbc:mysql://" + host + ":" + port
@@ -18,14 +18,11 @@
package org.apache.hertzbeat.collector.collect.http;
import static org.apache.hertzbeat.common.constants.SignConstants.RIGHT_DASH;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.StringReader;
import java.net.ConnectException;
@@ -37,22 +34,19 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.net.ssl.SSLException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.collector.collect.AbstractCollect;
import org.apache.hertzbeat.collector.collect.common.http.CommonHttpClient;
import org.apache.hertzbeat.collector.collect.http.promethus.AbstractPrometheusParse;
import org.apache.hertzbeat.collector.collect.http.promethus.PrometheusParseCreator;
import org.apache.hertzbeat.collector.collect.prometheus.parser.MetricFamily;
import org.apache.hertzbeat.collector.collect.prometheus.parser.OnlineParser;
import org.apache.hertzbeat.collector.collect.http.promethus.exporter.ExporterParser;
import org.apache.hertzbeat.collector.collect.http.promethus.exporter.MetricFamily;
import org.apache.hertzbeat.collector.constants.CollectorConstants;
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
import org.apache.hertzbeat.collector.util.CollectUtil;
@@ -69,9 +63,11 @@ import org.apache.hertzbeat.common.util.CommonUtil;
import org.apache.hertzbeat.common.util.IpDomainUtil;
import org.apache.http.Header;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpHost;
import org.apache.http.HttpStatus;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.AuthCache;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.RequestConfig;
@@ -81,6 +77,8 @@ import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.auth.DigestScheme;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
@@ -88,30 +86,20 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriUtils;
import org.xml.sax.InputSource;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.HashSet;
import java.util.ArrayList;
import java.util.Collections;
/**
* http https collect
*/
@Slf4j
public class HttpCollectImpl extends AbstractCollect {
private final Set<Integer> defaultSuccessStatusCodes = Set.of(
HttpStatus.SC_OK,
HttpStatus.SC_CREATED,
HttpStatus.SC_ACCEPTED,
HttpStatus.SC_MULTIPLE_CHOICES,
HttpStatus.SC_MOVED_PERMANENTLY,
HttpStatus.SC_MOVED_TEMPORARILY);
private static final Map<Long, ExporterParser> EXPORTER_PARSER_TABLE = new ConcurrentHashMap<>();
private final Set<Integer> defaultSuccessStatusCodes = Stream.of(HttpStatus.SC_OK, HttpStatus.SC_CREATED,
HttpStatus.SC_ACCEPTED, HttpStatus.SC_MULTIPLE_CHOICES, HttpStatus.SC_MOVED_PERMANENTLY,
HttpStatus.SC_MOVED_TEMPORARILY).collect(Collectors.toSet());
@Override
public void preCheck(Metrics metrics) throws IllegalArgumentException {
@@ -144,11 +132,10 @@ public class HttpCollectImpl extends AbstractCollect {
builder.setMsg(NetworkConstants.STATUS_CODE + SignConstants.BLANK + statusCode);
return;
}
/*
this could create large objects, potentially impacting JVM memory space significantly.
Option 1: Parse using InputStream, but this requires significant code changes;
Option 2: Manually trigger garbage collection, similar to how it's done in Dubbo for large inputs.
*/
// todo This code converts an InputStream directly to a String. For large data in Prometheus exporters,
// this could create large objects, potentially impacting JVM memory space significantly.
// Option 1: Parse using InputStream, but this requires significant code changes;
// Option 2: Manually trigger garbage collection, similar to how it's done in Dubbo for large inputs.
String resp = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
if (!StringUtils.hasText(resp)) {
log.info("http response entity is empty, status: {}.", statusCode);
@@ -162,17 +149,15 @@ public class HttpCollectImpl extends AbstractCollect {
case DispatchConstants.PARSE_PROM_QL ->
parseResponseByPromQl(resp, metrics.getAliasFields(), metrics.getHttp(), builder);
case DispatchConstants.PARSE_PROMETHEUS ->
parseResponseByPrometheusExporter(response.getEntity().getContent(), metrics.getAliasFields(), builder);
parseResponseByPrometheusExporter(resp, metrics.getAliasFields(), builder);
case DispatchConstants.PARSE_XML_PATH ->
parseResponseByXmlPath(resp, metrics, builder, responseTime);
parseResponseByXmlPath(resp, metrics.getAliasFields(), metrics.getHttp(), builder);
case DispatchConstants.PARSE_WEBSITE ->
parseResponseByWebsite(resp, metrics, metrics.getHttp(), builder, responseTime);
case DispatchConstants.PARSE_SITE_MAP ->
parseResponseBySiteMap(resp, metrics.getAliasFields(), builder);
case DispatchConstants.PARSE_HEADER ->
parseResponseByHeader(builder, metrics.getAliasFields(), response);
case DispatchConstants.PARSE_CONFIG ->
parseResponseByConfig(resp, metrics.getAliasFields(), metrics.getHttp(), builder, responseTime);
default ->
parseResponseByDefault(resp, metrics.getAliasFields(), metrics.getHttp(), builder, responseTime);
}
@@ -343,207 +328,8 @@ public class HttpCollectImpl extends AbstractCollect {
}
}
private void parseResponseByXmlPath(String resp, Metrics metrics,
CollectRep.MetricsData.Builder builder, Long responseTime) {
HttpProtocol http = metrics.getHttp();
List<String> aliasFields = metrics.getAliasFields();
String xpathExpression = http.getParseScript();
if (!StringUtils.hasText(xpathExpression)) {
log.warn("Http collect parse type is xmlPath, but the xpath expression is empty.");
builder.setCode(CollectRep.Code.FAIL);
builder.setMsg("XPath expression is empty");
return;
}
int keywordNum = CollectUtil.countMatchKeyword(resp, http.getKeyword());
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
DocumentBuilder db = dbf.newDocumentBuilder();
Document document = db.parse(new InputSource(new StringReader(resp)));
XPathFactory xpathFactory = XPathFactory.newInstance();
XPath xpath = xpathFactory.newXPath();
NodeList nodeList = (NodeList) xpath.evaluate(xpathExpression, document, XPathConstants.NODESET);
if (nodeList == null || nodeList.getLength() == 0) {
log.debug("XPath expression '{}' returned no nodes.", xpathExpression);
boolean requestedSummaryFields = aliasFields.stream()
.anyMatch(alias -> NetworkConstants.RESPONSE_TIME.equalsIgnoreCase(alias)
|| CollectorConstants.KEYWORD.equalsIgnoreCase(alias));
if (requestedSummaryFields) {
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
for (String alias : aliasFields) {
if (NetworkConstants.RESPONSE_TIME.equalsIgnoreCase(alias)) {
valueRowBuilder.addColumn(responseTime.toString());
} else if (CollectorConstants.KEYWORD.equalsIgnoreCase(alias)) {
valueRowBuilder.addColumn(Integer.toString(keywordNum));
} else {
valueRowBuilder.addColumn(CommonConstants.NULL_VALUE);
}
}
builder.addValueRow(valueRowBuilder.build());
}
return;
}
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
for (String alias : aliasFields) {
if (NetworkConstants.RESPONSE_TIME.equalsIgnoreCase(alias)) {
valueRowBuilder.addColumn(responseTime.toString());
} else if (CollectorConstants.KEYWORD.equalsIgnoreCase(alias)) {
valueRowBuilder.addColumn(Integer.toString(keywordNum));
} else {
try {
String value = (String) xpath.evaluate(alias, node, XPathConstants.STRING);
valueRowBuilder.addColumn(StringUtils.hasText(value) ? value : CommonConstants.NULL_VALUE);
} catch (XPathExpressionException e) {
log.warn("Failed to evaluate XPath '{}' for node [{}]: {}", alias, node.getNodeName(), e.getMessage());
valueRowBuilder.addColumn(CommonConstants.NULL_VALUE);
}
}
}
builder.addValueRow(valueRowBuilder.build());
}
} catch (Exception e) {
log.warn("Failed to parse XML response with XPath '{}': {}", xpathExpression, e.getMessage(), e);
builder.setCode(CollectRep.Code.FAIL);
builder.setMsg("Failed to parse XML response: " + e.getMessage());
}
}
/**
* Parses the response body in Properties/Config format.
* Two modes are supported:
* 1. single-object mode: if http.parseScript is null, aliasFields are treated as indicator names.
* - If there is a locator in the indicator definition, use the locator as the key of the Properties.
* - Otherwise, use aliasField (metric name) as the key for Properties.
* Generate a single row of data.
* 2. array mode: if http.parseScript is not empty (e.g. “users”), treat it as an array base path.
* Treat aliasFields as the attribute name of an array element, and generate a single row of data for each array index. locator is invalid in this mode.
*
* @param resp Response body string
* @param aliasFields List of metrics aliases (i.e., the list of fields in metrics.fields).
* @param http http protocol configuration
* @param builder The metrics data builder.
* @param responseTime response time
*/
private void parseResponseByConfig(String resp, List<String> aliasFields, HttpProtocol http,
CollectRep.MetricsData.Builder builder, Long responseTime) {
if (!StringUtils.hasText(resp)) {
log.warn("Http collect parse type is config, but response body is empty.");
builder.setCode(CollectRep.Code.FAIL);
builder.setMsg("Response body is empty");
return;
}
Properties properties = new Properties();
try (StringReader reader = new StringReader(resp)) {
properties.load(reader);
} catch (IOException e) {
log.warn("Failed to parse config response: {}", e.getMessage(), e);
builder.setCode(CollectRep.Code.FAIL);
builder.setMsg("Failed to parse config response: " + e.getMessage());
return;
}
String arrayBasePath = http.getParseScript();
int keywordNum = CollectUtil.countMatchKeyword(resp, http.getKeyword());
if (!StringUtils.hasText(arrayBasePath)) {
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
for (String alias : aliasFields) {
if (NetworkConstants.RESPONSE_TIME.equalsIgnoreCase(alias)) {
valueRowBuilder.addColumn(responseTime.toString());
} else if (CollectorConstants.KEYWORD.equalsIgnoreCase(alias)) {
valueRowBuilder.addColumn(Integer.toString(keywordNum));
} else {
String value = properties.getProperty(alias);
valueRowBuilder.addColumn(value != null ? value : CommonConstants.NULL_VALUE);
}
}
CollectRep.ValueRow valueRow = valueRowBuilder.build();
if (hasMeaningfulDataInRow(valueRow, aliasFields)) {
builder.addValueRow(valueRow);
} else {
log.warn("No meaningful data found in single config object response for aliasFields: {}", aliasFields);
}
} else {
Pattern pattern = Pattern.compile("^" + Pattern.quote(arrayBasePath) + "\\[(\\d+)]\\.");
Set<Integer> existingIndices = new HashSet<>();
for (String key : properties.stringPropertyNames()) {
Matcher matcher = pattern.matcher(key);
if (matcher.find()) {
try {
int index = Integer.parseInt(matcher.group(1));
existingIndices.add(index);
} catch (NumberFormatException e) {
log.error("Could not parse index from key: {}", key);
}
}
}
if (existingIndices.isEmpty()) {
log.warn("Could not find any array elements for base path '{}' in config response.", arrayBasePath);
return;
}
List<Integer> sortedIndices = new ArrayList<>(existingIndices);
Collections.sort(sortedIndices);
for (int i : sortedIndices) {
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
for (String alias : aliasFields) {
if (NetworkConstants.RESPONSE_TIME.equalsIgnoreCase(alias)) {
valueRowBuilder.addColumn(responseTime.toString());
} else if (CollectorConstants.KEYWORD.equalsIgnoreCase(alias)) {
valueRowBuilder.addColumn(Integer.toString(keywordNum));
} else {
String currentKey = arrayBasePath + "[" + i + "]." + alias;
String value = properties.getProperty(currentKey);
valueRowBuilder.addColumn(value != null ? value : CommonConstants.NULL_VALUE);
}
}
CollectRep.ValueRow valueRow = valueRowBuilder.build();
if (hasMeaningfulDataInRow(valueRow, aliasFields)) {
builder.addValueRow(valueRowBuilder.build());
}
}
}
}
private boolean hasMeaningfulDataInRow(CollectRep.ValueRow valueRow, List<String> aliasFields) {
if (valueRow.getColumnsCount() == 0) {
return false;
}
if (valueRow.getColumnsCount() != aliasFields.size()) {
log.error("Column count ({}) mismatch with aliasFields size ({}) when checking meaningful data.",
valueRow.getColumnsCount(), aliasFields.size());
return false;
}
boolean hasMeaningfulData = false;
for (int i = 0; i < valueRow.getColumnsCount(); i++) {
String columnValue = valueRow.getColumns(i);
String alias = aliasFields.get(i);
if (!CommonConstants.NULL_VALUE.equals(columnValue) && (!NetworkConstants.RESPONSE_TIME.equalsIgnoreCase(alias) && !CollectorConstants.KEYWORD.equalsIgnoreCase(alias))) {
hasMeaningfulData = true;
break;
}
if ((NetworkConstants.RESPONSE_TIME.equalsIgnoreCase(alias) || CollectorConstants.KEYWORD.equalsIgnoreCase(alias)) && !CommonConstants.NULL_VALUE.equals(columnValue)) {
hasMeaningfulData = true;
}
}
return hasMeaningfulData;
private void parseResponseByXmlPath(String resp, List<String> aliasFields, HttpProtocol http,
CollectRep.MetricsData.Builder builder) {
}
private void parseResponseByJsonPath(String resp, List<String> aliasFields, HttpProtocol http,
@@ -600,22 +386,36 @@ public class HttpCollectImpl extends AbstractCollect {
prometheusParser.handle(resp, aliasFields, http, builder);
}
private void parseResponseByPrometheusExporter(InputStream content, List<String> aliasFields, CollectRep.MetricsData.Builder builder) throws IOException {
Map<String, MetricFamily> metricFamilyMap = OnlineParser.parseMetrics(content);
if (metricFamilyMap == null || metricFamilyMap.isEmpty()) {
return;
private void parseResponseByPrometheusExporter(String resp, List<String> aliasFields,
CollectRep.MetricsData.Builder builder) {
if (!EXPORTER_PARSER_TABLE.containsKey(builder.getId())) {
EXPORTER_PARSER_TABLE.put(builder.getId(), new ExporterParser());
}
ExporterParser parser = EXPORTER_PARSER_TABLE.get(builder.getId());
Map<String, MetricFamily> metricFamilyMap = parser.textToMetric(resp);
String metrics = builder.getMetrics();
if (metricFamilyMap.containsKey(metrics)) {
MetricFamily metricFamily = metricFamilyMap.get(metrics);
for (MetricFamily.Metric metric : metricFamily.getMetricList()) {
Map<String, String> labelMap = metric.getLabels()
Map<String, String> labelMap = metric.getLabelPair()
.stream()
.collect(Collectors.toMap(MetricFamily.Label::getName, MetricFamily.Label::getValue));
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
for (String aliasField : aliasFields) {
if ("value".equals(aliasField)) {
valueRowBuilder.addColumn(String.valueOf(metric.getValue()));
if (metric.getCounter() != null) {
valueRowBuilder.addColumn(String.valueOf(metric.getCounter().getValue()));
} else if (metric.getGauge() != null) {
valueRowBuilder.addColumn(String.valueOf(metric.getGauge().getValue()));
} else if (metric.getUntyped() != null) {
valueRowBuilder.addColumn(String.valueOf(metric.getUntyped().getValue()));
} else if (metric.getInfo() != null) {
valueRowBuilder.addColumn(String.valueOf(metric.getInfo().getValue()));
} else if (metric.getSummary() != null) {
valueRowBuilder.addColumn(String.valueOf(metric.getSummary().getValue()));
} else if (metric.getHistogram() != null) {
valueRowBuilder.addColumn(String.valueOf(metric.getHistogram().getValue()));
}
} else {
String columnValue = labelMap.get(aliasField);
valueRowBuilder.addColumn(columnValue == null ? CommonConstants.NULL_VALUE : columnValue);
@@ -672,10 +472,11 @@ public class HttpCollectImpl extends AbstractCollect {
CredentialsProvider provider = new BasicCredentialsProvider();
UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(auth.getDigestAuthUsername(),
auth.getDigestAuthPassword());
AuthScope authScope = new AuthScope(httpProtocol.getHost(), Integer.parseInt(httpProtocol.getPort()));
provider.setCredentials(authScope, credentials);
provider.setCredentials(AuthScope.ANY, credentials);
AuthCache authCache = new BasicAuthCache();
authCache.put(new HttpHost(httpProtocol.getHost(), Integer.parseInt(httpProtocol.getPort())), new DigestScheme());
clientContext.setCredentialsProvider(provider);
clientContext.setAuthCache(authCache);
return clientContext;
}
}
@@ -708,37 +509,13 @@ public class HttpCollectImpl extends AbstractCollect {
}
// params
Map<String, String> params = httpProtocol.getParams();
boolean enableUrlEncoding = Boolean.parseBoolean(httpProtocol.getEnableUrlEncoding());
StringBuilder queryParams = new StringBuilder();
if (params != null && !params.isEmpty()) {
for (Map.Entry<String, String> param : params.entrySet()) {
String key = param.getKey();
String value = param.getValue();
if (!StringUtils.hasText(key)) {
continue;
}
if (!queryParams.isEmpty()) {
queryParams.append("&");
}
if (enableUrlEncoding) {
key = UriUtils.encodeQueryParam(key, "UTF-8");
}
queryParams.append(key);
if (StringUtils.hasText(value)) {
String calculatedValue = TimeExpressionUtil.calculate(value);
if (enableUrlEncoding) {
calculatedValue = UriUtils.encodeQueryParam(calculatedValue, "UTF-8");
}
queryParams.append("=").append(calculatedValue);
if (StringUtils.hasText(param.getValue())) {
requestBuilder.addParameter(param.getKey(), TimeExpressionUtil.calculate(param.getValue()));
}
}
}
// The default request header can be overridden if customized
// keep-alive
requestBuilder.addHeader(HttpHeaders.CONNECTION, NetworkConstants.KEEP_ALIVE);
@@ -748,7 +525,8 @@ public class HttpCollectImpl extends AbstractCollect {
if (headers != null && !headers.isEmpty()) {
for (Map.Entry<String, String> header : headers.entrySet()) {
if (StringUtils.hasText(header.getValue())) {
requestBuilder.addHeader(header.getKey(), header.getValue());
requestBuilder.addHeader(CollectUtil.replaceUriSpecialChar(header.getKey()),
CollectUtil.replaceUriSpecialChar(header.getValue()));
}
}
}
@@ -779,32 +557,14 @@ public class HttpCollectImpl extends AbstractCollect {
// if it has payload, would override post params
if (StringUtils.hasLength(httpProtocol.getPayload()) && (HttpMethod.POST.matches(httpMethod) || HttpMethod.PUT.matches(httpMethod))) {
requestBuilder.setEntity(new StringEntity(TimeExpressionUtil.calculate(httpProtocol.getPayload()), StandardCharsets.UTF_8));
requestBuilder.setEntity(new StringEntity(httpProtocol.getPayload(), StandardCharsets.UTF_8));
}
// uri encode
String uri;
if (enableUrlEncoding) {
// if the url contains parameters directly
if (httpProtocol.getUrl().contains("?")) {
String path = httpProtocol.getUrl().substring(0, httpProtocol.getUrl().indexOf("?"));
String query = httpProtocol.getUrl().substring(httpProtocol.getUrl().indexOf("?") + 1);
uri = UriUtils.encodePath(path, "UTF-8") + "?" + UriUtils.encodeQuery(query, "UTF-8");
} else {
uri = UriUtils.encodePath(httpProtocol.getUrl(), "UTF-8");
}
} else {
uri = httpProtocol.getUrl();
}
// append query params
if (!queryParams.isEmpty()) {
uri += (uri.contains("?") ? "&" : "?") + queryParams;
}
String finalUri;
// uri
String uri = CollectUtil.replaceUriSpecialChar(httpProtocol.getUrl());
if (IpDomainUtil.isHasSchema(httpProtocol.getHost())) {
finalUri = httpProtocol.getHost() + ":" + httpProtocol.getPort() + uri;
requestBuilder.setUri(httpProtocol.getHost() + ":" + httpProtocol.getPort() + uri);
} else {
String ipAddressType = IpDomainUtil.checkIpAddressType(httpProtocol.getHost());
String baseUri = NetworkConstants.IPV6.equals(ipAddressType)
@@ -812,19 +572,12 @@ public class HttpCollectImpl extends AbstractCollect {
: String.format("%s:%s%s", httpProtocol.getHost(), httpProtocol.getPort(), uri);
boolean ssl = Boolean.parseBoolean(httpProtocol.getSsl());
if (ssl) {
finalUri = NetworkConstants.HTTPS_HEADER + baseUri;
requestBuilder.setUri(NetworkConstants.HTTPS_HEADER + baseUri);
} else {
finalUri = NetworkConstants.HTTP_HEADER + baseUri;
requestBuilder.setUri(NetworkConstants.HTTP_HEADER + baseUri);
}
}
try {
requestBuilder.setUri(finalUri);
} catch (IllegalArgumentException e) {
log.warn("Invalid URI with illegal characters: {}. User has disabled URL encoding, not applying any encoding.", finalUri);
throw e;
}
// custom timeout
int timeout = CollectUtil.getTimeout(httpProtocol.getTimeout(), 0);
if (timeout > 0) {
@@ -0,0 +1,432 @@
/*
* 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.hertzbeat.collector.collect.http.promethus.exporter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.collector.collect.http.promethus.ParseException;
import org.apache.hertzbeat.common.util.StrBuffer;
/**
* Resolves the data passed by prometheus's exporter interface http:xxx/metrics
* Reference: prometheus text_parse.go code, entry: TextToMetricFamilies
*/
@Slf4j
public class ExporterParser {
private static final String HELP = "HELP";
private static final String TYPE = "TYPE";
private static final String EOF = "EOF";
private static final String METRIC_NAME_LABEL = ".name";
private static final String QUANTILE_LABEL = "quantile";
private static final String BUCKET_LABEL = "le";
private static final String NAME_LABEL = "__name__";
private static final String SUM_SUFFIX = "_sum";
private static final String COUNT_SUFFIX = "_count";
private static final char LEFT_CURLY_BRACKET = '{';
private static final char RIGHT_CURLY_BRACKET = '}';
private static final char EQUALS = '=';
private static final char QUOTES = '"';
private static final char ENTER = '\n';
private static final char SPACE = ' ';
private static final char COMMA = ',';
private final Lock lock = new ReentrantLock();
private MetricFamily currentMetricFamily;
private String currentQuantile;
private String currentBucket;
public Map<String, MetricFamily> textToMetric(String resp) {
// key: metric name, value: metric family
Map<String, MetricFamily> metricMap = new ConcurrentHashMap<>(10);
lock.lock();
try {
String[] lines = resp.split("\n");
for (String line : lines) {
this.parseLine(metricMap, new StrBuffer(line));
}
return metricMap;
} catch (Exception e) {
log.error("parse prometheus exporter data error, msg: {}", e.getMessage());
} finally {
lock.unlock();
}
return metricMap;
}
private void parseLine(Map<String, MetricFamily> metricMap, StrBuffer buffer) {
buffer.skipBlankTabs();
if (buffer.isEmpty()) {
return;
}
switch (buffer.charAt(0)) {
case '#' -> {
buffer.read();
this.currentMetricFamily = null;
this.parseComment(metricMap, buffer);
}
case ENTER -> {
}
default -> {
this.currentBucket = null;
this.currentQuantile = null;
this.parseMetric(buffer);
}
}
}
private void parseComment(Map<String, MetricFamily> metricMap, StrBuffer buffer) {
buffer.skipBlankTabs();
if (buffer.isEmpty()) {
return;
}
String token = this.readTokenUnitWhitespace(buffer);
if (EOF.equals(token)) {
return;
}
if (!HELP.equals(token) && !TYPE.equals(token)) {
log.error("parse comment error {}, start without {} or {}", buffer.toStr(), HELP, TYPE);
return;
}
String metricName = this.readTokenAsMetricName(buffer);
this.currentMetricFamily = metricMap.computeIfAbsent(metricName, key -> new MetricFamily());
this.currentMetricFamily.setName(metricName);
switch (token) {
case HELP -> this.parseHelp(buffer);
case TYPE -> this.parseType(buffer);
default -> {
}
}
}
private void parseHelp(StrBuffer line) {
line.skipBlankTabs();
this.currentMetricFamily.setHelp(line.toStr());
}
private void parseType(StrBuffer line) {
line.skipBlankTabs();
String type = line.toStr().toLowerCase();
MetricType metricType = MetricType.getType(type);
if (metricType == null) {
throw new ParseException("pare type error");
}
this.currentMetricFamily.setMetricType(metricType);
}
private void parseMetric(StrBuffer buffer) {
String metricName = this.readTokenAsMetricName(buffer);
MetricFamily.Label label = new MetricFamily.Label();
label.setName(METRIC_NAME_LABEL);
label.setValue(metricName);
if (metricName.isEmpty()) {
log.error("error parse metric, metric name is null, line: {}", buffer.toStr());
return;
}
List<MetricFamily.Metric> metricList = this.currentMetricFamily.getMetricList();
if (metricList == null) {
metricList = new ArrayList<>();
this.currentMetricFamily.setMetricList(metricList);
}
// TODO For the time being, the data is displayed in the form of labels. If there is a better chart display method in the future, we will optimize it.
MetricFamily.Metric metric = new MetricFamily.Metric();
metricList.add(metric);
metric.setLabelPair(new ArrayList<>());
metric.getLabelPair().add(label);
this.readLabels(metric, buffer);
}
private void readLabels(MetricFamily.Metric metric, StrBuffer buffer) {
buffer.skipBlankTabs();
if (buffer.isEmpty()) {
return;
}
if (buffer.charAt(0) == LEFT_CURLY_BRACKET) {
buffer.read();
this.startReadLabelName(metric, buffer);
} else {
this.readLabelValue(metric, null, buffer);
}
}
private void startReadLabelName(MetricFamily.Metric metric, StrBuffer buffer) {
buffer.skipBlankTabs();
if (buffer.isEmpty()) {
return;
}
if (buffer.charAt(0) == RIGHT_CURLY_BRACKET) {
buffer.read();
buffer.skipBlankTabs();
if (buffer.isEmpty()) {
return;
}
this.readLabelValue(metric, new MetricFamily.Label(), buffer);
return;
}
String labelName = this.readTokenAsLabelName(buffer);
if (labelName.isEmpty() || labelName.equals(NAME_LABEL)) {
throw new ParseException("invalid label name" + labelName + ", label name size = 0 or label name equals " + NAME_LABEL);
}
MetricFamily.Label label = new MetricFamily.Label();
label.setName(labelName);
if (buffer.read() != EQUALS) {
throw new ParseException("parse error, not match the format of labelName=labelValue");
}
this.startReadLabelValue(metric, label, buffer);
}
private void startReadLabelValue(MetricFamily.Metric metric, MetricFamily.Label label, StrBuffer buffer) {
buffer.skipBlankTabs();
if (buffer.isEmpty()) {
return;
}
char c = buffer.read();
if (c != QUOTES) {
throw new ParseException("expected '\"' at start of label value, line: " + buffer.toStr());
}
String labelValue = this.readTokenAsLabelValue(buffer);
label.setValue(labelValue);
if (!this.isValidLabelValue(labelValue)) {
throw new ParseException("no valid label value: " + labelValue);
}
if (this.currentMetricFamily.getMetricType().equals(MetricType.SUMMARY) && label.getName().equals(QUANTILE_LABEL)) {
this.currentQuantile = labelValue;
} else if (this.currentMetricFamily.getMetricType().equals(MetricType.HISTOGRAM) && label.getName().equals(BUCKET_LABEL)) {
this.currentBucket = labelValue;
}
metric.getLabelPair().add(label);
if (buffer.isEmpty()) {
return;
}
c = buffer.read();
switch (c) {
case COMMA -> this.startReadLabelName(metric, buffer);
case RIGHT_CURLY_BRACKET -> this.readLabelValue(metric, label, buffer);
default -> throw new ParseException("expected '}' or ',' at end of label value, line: " + buffer.toStr());
}
}
private void readLabelValue(MetricFamily.Metric metric, MetricFamily.Label label, StrBuffer buffer) {
buffer.skipBlankTabs();
if (buffer.isEmpty()) {
return;
}
switch (this.currentMetricFamily.getMetricType()) {
case INFO -> {
MetricFamily.Info info = new MetricFamily.Info();
info.setValue(buffer.toDouble());
metric.setInfo(info);
}
case COUNTER -> {
MetricFamily.Counter counter = new MetricFamily.Counter();
counter.setValue(buffer.toDouble());
metric.setCounter(counter);
}
case GAUGE -> {
MetricFamily.Gauge gauge = new MetricFamily.Gauge();
gauge.setValue(buffer.toDouble());
metric.setGauge(gauge);
}
case UNTYPED -> {
MetricFamily.Untyped untyped = new MetricFamily.Untyped();
untyped.setValue(buffer.toDouble());
metric.setUntyped(untyped);
}
case SUMMARY -> {
// For the time being, the data is displayed in the form of labels. If there is a better chart display method in the future, we will optimize it.
MetricFamily.Summary summary = new MetricFamily.Summary();
summary.setValue(buffer.toDouble());
metric.setSummary(summary);
}
case HISTOGRAM -> {
// For the time being, the data is displayed in the form of labels. If there is a better chart display method in the future, we will optimize it.
MetricFamily.Histogram histogram = new MetricFamily.Histogram();
histogram.setValue(buffer.toDouble());
metric.setHistogram(histogram);
}
default -> throw new ParseException("no such type in metricFamily");
}
}
/**
* Reads the token before the first whitespace
*
* @param buffer A line data object
* @return token unit
*/
private String readTokenUnitWhitespace(StrBuffer buffer) {
StringBuilder builder = new StringBuilder();
while (!buffer.isEmpty()) {
char c = buffer.read();
if (c == SPACE) {
break;
}
builder.append(c);
}
return builder.toString();
}
/**
* Gets the name of the metric
*
* @param buffer A line data object
* @return token name
*/
private String readTokenAsMetricName(StrBuffer buffer) {
buffer.skipBlankTabs();
StringBuilder builder = new StringBuilder();
if (this.isValidMetricNameStart(buffer.charAt(0))) {
while (!buffer.isEmpty()) {
char c = buffer.read();
if (!this.isValidMetricNameContinuation(c)) {
buffer.rollback();
break;
}
builder.append(c);
}
return builder.toString();
}
throw new ParseException("parse metric name error");
}
/**
* Gets the name of the label
*
* @param buffer A line data object
* @return label name
*/
private String readTokenAsLabelName(StrBuffer buffer) {
buffer.skipBlankTabs();
StringBuilder builder = new StringBuilder();
char c = buffer.read();
if (this.isValidLabelNameStart(c)) {
builder.append(c);
while (!buffer.isEmpty()) {
c = buffer.read();
if (!this.isValidLabelNameContinuation(c)) {
buffer.rollback();
break;
}
builder.append(c);
}
return builder.toString();
}
throw new ParseException("parse label name error");
}
/**
* Gets the value of the label
*
* @param buffer A line data object
* @return label value
*/
private String readTokenAsLabelValue(StrBuffer buffer) {
StringBuilder builder = new StringBuilder();
boolean escaped = false;
while (!buffer.isEmpty()) {
char c = buffer.read();
// Handle '\\' escape sequences
if (escaped) {
switch (c) {
case QUOTES, '\\' -> builder.append(c);
case 'n' -> builder.append('\n');
default -> throw new ParseException("parse label value error");
}
escaped = false;
} else {
switch (c) {
case QUOTES -> {
return builder.toString();
}
case ENTER -> throw new ParseException("parse label value error, next line");
case '\\' -> escaped = true;
default -> builder.append(c);
}
}
}
return builder.toString();
}
/**
* Checks whether a character conforms to the first character rule for metric names
*
* @param c metric character
* @return true/false
*/
private boolean isValidMetricNameStart(char c) {
return isValidLabelNameStart(c) || c == ':';
}
/**
* Checks whether a character conforms to rules for metric name characters other than the first
*
* @param c metric character
* @return true/false
*/
private boolean isValidMetricNameContinuation(char c) {
return isValidLabelNameContinuation(c) || c == ':';
}
/**
* Checks whether a character conforms to the first character rule for label names
*
* @param c metric character
* @return true/false
*/
private boolean isValidLabelNameStart(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
}
/**
* Checks whether a character conforms to rules for label name characters other than the first
*
* @param c metric character
* @return true/false
*/
private boolean isValidLabelNameContinuation(char c) {
return isValidLabelNameStart(c) || (c >= '0' && c <= '9');
}
/**
* Checks if a string is a valid UTF-8 encoded string
*
* @param s label value
* @return true/false
*/
private boolean isValidLabelValue(String s) {
return s != null;
}
private boolean isSum(String s) {
return s != null && s.endsWith(SUM_SUFFIX);
}
private boolean isCount(String s) {
return s != null && s.endsWith(COUNT_SUFFIX);
}
}
@@ -0,0 +1,252 @@
/*
* 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.hertzbeat.collector.collect.http.promethus.exporter;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
import lombok.ToString;
/**
* MetricFamily.
*/
@Data
@ToString
public class MetricFamily {
/**
* metric name
*/
private String name;
/**
* metric help
*/
private String help;
/**
* metric type
*/
private MetricType metricType;
/**
* Specific metric
*/
private List<Metric> metricList;
/**
* Metric
*/
@Data
public static class Metric {
/**
* Label data, mainly corresponding to the content within {}
*/
private List<Label> labelPair;
/**
* info
*/
private Info info;
/**
* gauge
*/
private Gauge gauge;
/**
* counter
*/
private Counter counter;
/**
* summary
*/
private Summary summary;
/**
* untyped
*/
private Untyped untyped;
/**
* histogram
*/
private Histogram histogram;
/**
* timestampMs
*/
private Long timestampMs;
}
/**
* Label
*/
@Data
public static class Label {
/**
* name
*/
private String name;
/**
* value
*/
private String value;
}
/**
* Info
*/
@Data
public static class Info {
/**
* value
*/
private double value;
}
/**
* Counter
*/
@Data
public static class Counter {
/**
* value
*/
private double value;
// Exemplar
}
/**
* Gauge
*/
@Data
public static class Gauge {
/**
* value
*/
private double value;
}
/**
* untyped
*/
@Data
public static class Untyped {
/**
* value
*/
private double value;
}
/**
* Summary
*/
@Data
public static class Summary {
/**
* value
*/
private double value;
/**
* count
*/
private long count;
/**
* sum
*/
private double sum;
/**
* quantileList
*/
private List<Quantile> quantileList = new ArrayList<>();
}
/**
* Quantile
*/
@Data
public static class Quantile {
/**
* Corresponding to the quantile field in Prometheus
*/
private double xLabel;
/**
* value
*/
private double value;
}
/**
* Histogram
*/
@Data
public static class Histogram {
/**
* value
*/
private double value;
/**
* count
*/
private long count;
/**
* sum
*/
private double sum;
/**
* bucketList
*/
private List<Bucket> bucketList = new ArrayList<>();
}
/**
* Bucket
*/
@Data
public static class Bucket {
/**
* cumulativeCount
*/
private long cumulativeCount;
/**
* upperBound
*/
private double upperBound;
}
}
@@ -0,0 +1,52 @@
/*
* 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.hertzbeat.collector.collect.http.promethus.exporter;
/**
* prometheus metrics type
*/
public enum MetricType {
// for string metric info
INFO("info"),
// Represents a monotonically increasing counter, e.g., counting occurrences
COUNTER("counter"),
// A metric type that can fluctuate up and down, e.g., CPU usage rate
GAUGE("gauge"),
SUMMARY("summary"),
UNTYPED("untyped"),
HISTOGRAM("histogram");
private final String value;
MetricType(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public static MetricType getType(String value) {
for (MetricType metricType : values()) {
if (metricType.getValue().equals(value)) {
return metricType;
}
}
return null;
}
}
@@ -19,7 +19,6 @@ package org.apache.hertzbeat.collector.collect.prometheus;
import static org.apache.hertzbeat.common.constants.SignConstants.RIGHT_DASH;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.net.ConnectException;
import java.net.UnknownHostException;
@@ -35,6 +34,7 @@ import javax.net.ssl.SSLException;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.collector.collect.common.http.CommonHttpClient;
import org.apache.hertzbeat.collector.collect.prometheus.parser.MetricFamily;
import org.apache.hertzbeat.collector.collect.prometheus.parser.TextParser;
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
import org.apache.hertzbeat.collector.util.CollectUtil;
import org.apache.hertzbeat.common.constants.CommonConstants;
@@ -46,7 +46,6 @@ import org.apache.hertzbeat.common.entity.message.CollectRep;
import org.apache.hertzbeat.common.util.Base64Util;
import org.apache.hertzbeat.common.util.CommonUtil;
import org.apache.hertzbeat.common.util.IpDomainUtil;
import org.apache.hertzbeat.collector.collect.prometheus.parser.OnlineParser;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpHost;
import org.apache.http.HttpStatus;
@@ -65,6 +64,7 @@ import org.apache.http.impl.auth.DigestScheme;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
@@ -99,12 +99,25 @@ public class PrometheusAutoCollectImpl {
builder.setMsg(NetworkConstants.STATUS_CODE + SignConstants.BLANK + statusCode);
return null;
}
try {
return parseResponseByPrometheusExporter(response.getEntity().getContent(), builder);
} catch (Exception e) {
log.info("parse error: {}.", e.getMessage(), e);
// todo: The InputStream is directly converted to a String here
// For large data in the Prometheus exporter, this can generate large objects, which could severely impact JVM memory space
// todo: Option one: Use InputStream for parsing, but this requires significant code changes
// Option two: Manually trigger garbage collection, which can be referenced from Dubbo for long i
String resp = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
long collectTime = System.currentTimeMillis();
builder.setTime(collectTime);
if (resp == null || !StringUtils.hasText(resp)) {
log.error("http response content is empty, status: {}.", statusCode);
builder.setCode(CollectRep.Code.FAIL);
builder.setMsg("parse response data error:" + e.getMessage());
builder.setMsg("http response content is empty");
} else {
try {
return parseResponseByPrometheusExporter(resp, metrics.getAliasFields(), builder);
} catch (Exception e) {
log.info("parse error: {}.", e.getMessage(), e);
builder.setCode(CollectRep.Code.FAIL);
builder.setMsg("parse response data error:" + e.getMessage());
}
}
} catch (ClientProtocolException e1) {
String errorMsg = CommonUtil.getMessageFromThrowable(e1);
@@ -155,12 +168,10 @@ public class PrometheusAutoCollectImpl {
}
}
private List<CollectRep.MetricsData> parseResponseByPrometheusExporter(InputStream inputStream, CollectRep.MetricsData.Builder builder) throws IOException {
Map<String, MetricFamily> metricFamilyMap = OnlineParser.parseMetrics(inputStream);
private List<CollectRep.MetricsData> parseResponseByPrometheusExporter(String resp, List<String> aliasFields,
CollectRep.MetricsData.Builder builder) {
Map<String, MetricFamily> metricFamilyMap = TextParser.textToMetricFamilies(resp);
List<CollectRep.MetricsData> metricsDataList = new LinkedList<>();
if (metricFamilyMap == null) {
return metricsDataList;
}
for (Map.Entry<String, MetricFamily> entry : metricFamilyMap.entrySet()) {
builder.clearFields();
builder.clearValues();
@@ -17,17 +17,16 @@
package org.apache.hertzbeat.collector.collect.prometheus.parser;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -37,47 +36,9 @@ import java.util.concurrent.ConcurrentHashMap;
@Slf4j
public class OnlineParser {
private static final Map<Integer, Integer> escapeMap = new HashMap<>(8);
static {
escapeMap.put((int) 'n', (int) '\n');
escapeMap.put((int) 'b', (int) '\b');
escapeMap.put((int) 't', (int) '\t');
escapeMap.put((int) 'r', (int) '\r');
escapeMap.put((int) 'f', (int) '\f');
escapeMap.put((int) '\'', (int) '\'');
escapeMap.put((int) '\"', (int) '\"');
escapeMap.put((int) '\\', (int) '\\');
}
private OnlineParser() {
}
public static Map<String, MetricFamily> parseMetrics(InputStream inputStream) throws IOException {
Map<String, MetricFamily> metricFamilyMap = new ConcurrentHashMap<>(10);
try {
int i = getChar(inputStream);
while (i != -1) {
if (i == '#' || i == '\n') {
skipToLineEnd(inputStream).maybeEol().maybeEof().noElse();
} else {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append((char) i);
parseMetric(inputStream, metricFamilyMap, stringBuilder);
}
i = getChar(inputStream);
}
} catch (FormatException e) {
log.error("prometheus parser failed because of wrong input format. {}", e.getMessage());
return null;
}
return metricFamilyMap;
}
private static class FormatException extends Exception {
public FormatException() {
}
public FormatException() {}
public FormatException(String message) {
super(message);
@@ -156,92 +117,91 @@ public class OnlineParser {
return this.i;
}
private int getInt() {
private int getInt() throws FormatException {
return this.i;
}
}
private static int getChar(InputStream inputStream) throws IOException, FormatException {
private static CharChecker parseOneChar(InputStream inputStream) throws IOException {
int i = inputStream.read();
if (i == '\\') {
i = inputStream.read();
if (escapeMap.containsKey(i)) {
return escapeMap.get(i);
} else {
throw new FormatException("Escape character failed.");
}
} else {
return i;
}
return new CharChecker(i);
}
private static CharChecker parseOneDouble(InputStream inputStream, StringBuilder stringBuilder) throws IOException, FormatException {
int i = getChar(inputStream);
while (i >= '0' && i <= '9' || i >= 'a' && i <= 'z' || i >= 'A' && i <= 'Z' || i == '-' || i == '+' || i == '.') {
private static CharChecker parseOneDouble(InputStream inputStream, StringBuilder stringBuilder) throws IOException {
int i = inputStream.read();
while ((i >= '0' && i <= '9') || (i >= 'a' && i <= 'z') || (i >= 'A' && i <= 'Z') || i == '-' || i == '+' || i == 'e' || i == '.') {
stringBuilder.append((char) i);
i = getChar(inputStream);
i = inputStream.read();
}
return new CharChecker(i);
}
private static CharChecker skipOneLong(InputStream inputStream) throws IOException, FormatException {
int i = getChar(inputStream);
private static CharChecker skipOneLong(InputStream inputStream) throws IOException {
int i = inputStream.read();
while (i >= '0' && i <= '9') {
i = getChar(inputStream);
i = inputStream.read();
}
return new CharChecker(i);
}
private static CharChecker parseMetricName(InputStream inputStream, StringBuilder stringBuilder) throws IOException, FormatException {
int i = getChar(inputStream);
private static CharChecker parseMetricName(InputStream inputStream, StringBuilder stringBuilder) throws IOException {
int i = inputStream.read();
while ((i >= 'a' && i <= 'z') || (i >= 'A' && i <= 'Z') || (i >= '0' && i <= '9') || i == '_' || i == ':') {
stringBuilder.append((char) i);
i = getChar(inputStream);
i = inputStream.read();
}
return new CharChecker(i);
}
private static CharChecker parseLabelName(InputStream inputStream, StringBuilder stringBuilder) throws IOException, FormatException {
int i = getChar(inputStream);
private static CharChecker parseLabelName(InputStream inputStream, StringBuilder stringBuilder) throws IOException {
int i = inputStream.read();
while ((i >= 'a' && i <= 'z') || (i >= 'A' && i <= 'Z') || (i >= '0' && i <= '9') || i == '_') {
stringBuilder.append((char) i);
i = getChar(inputStream);
i = inputStream.read();
}
return new CharChecker(i);
}
private static CharChecker parseLabelValue(InputStream inputStream, StringBuilder stringBuilder) throws IOException, FormatException {
int i = getChar(inputStream);
int i = inputStream.read();
while (i != '"' && i != -1) {
if (i == '\\') {
i = getChar(inputStream);
i = inputStream.read();
switch (i) {
case 'n' -> stringBuilder.append('\n');
case '\\' -> stringBuilder.append('\\');
case '\"' -> stringBuilder.append('\"');
default -> throw new FormatException();
case 'n':
stringBuilder.append('\n');
break;
case '\\':
stringBuilder.append('\\');
break;
case '\"':
stringBuilder.append('\"');
break;
default:
throw new FormatException();
}
} else {
}
else {
stringBuilder.append((char) i);
}
i = getChar(inputStream);
i = inputStream.read();
}
return new CharChecker(i);
}
private static CharChecker skipSpaces(InputStream inputStream) throws IOException, FormatException {
int i = getChar(inputStream);
private static CharChecker skipSpaces(InputStream inputStream) throws IOException {
int i = inputStream.read();
while (i == ' ') {
i = getChar(inputStream);
i = inputStream.read();
}
return new CharChecker(i);
}
private static CharChecker skipToLineEnd(InputStream inputStream) throws IOException, FormatException {
int i = getChar(inputStream);
private static CharChecker skipToLineEnd(InputStream inputStream) throws IOException {
int i = inputStream.read();
while (i != '\n' && i != -1) {
i = getChar(inputStream);
i = inputStream.read();
}
return new CharChecker(i);
}
@@ -296,7 +256,7 @@ public class OnlineParser {
}
private static CharChecker parseMetric(InputStream inputStream, Map<String, MetricFamily> metricFamilyMap, StringBuilder stringBuilder) throws IOException, FormatException {
MetricFamily metricFamily;
MetricFamily metricFamily = null;
MetricFamily.Metric metric = new MetricFamily.Metric();
int i = parseMetricName(inputStream, stringBuilder).maybeSpace().maybeLeftBracket().noElse();
String metricName = stringBuilder.toString();
@@ -307,18 +267,18 @@ public class OnlineParser {
metricFamily.setMetricList(new ArrayList<>());
metricFamily.setName(metricName);
metricFamilyMap.put(metricName, metricFamily);
} else {
}
else {
metricFamily = metricFamilyMap.get(metricName);
}
if (i == ' ') {
i = skipSpaces(inputStream).getInt();
}
List<MetricFamily.Label> labelList = new LinkedList<>();
metric.setLabels(labelList);
if (i == '{') {
List<MetricFamily.Label> labelList = new LinkedList<>();
parseLabels(inputStream, stringBuilder, labelList);
metric.setLabels(labelList);
i = skipSpaces(inputStream).getInt();
}
@@ -344,4 +304,26 @@ public class OnlineParser {
metricFamily.getMetricList().add(metric);
return new CharChecker(i);
}
public static Map<String, MetricFamily> parseMetrics(InputStream inputStream) throws IOException {
Map<String, MetricFamily> metricFamilyMap = new ConcurrentHashMap<>(10);
int i = inputStream.read();
try {
while (i != -1) {
if (i == '#' || i == '\n') {
skipToLineEnd(inputStream).maybeEol().maybeEof().noElse();
} else {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append((char) i);
parseMetric(inputStream, metricFamilyMap, stringBuilder);
}
i = inputStream.read();
}
} catch (FormatException e) {
log.error("prometheus parser failed because of wrong input format. {}", e.getMessage());
return null;
}
return metricFamilyMap;
}
}
@@ -29,10 +29,8 @@ import org.apache.hertzbeat.common.util.StrBuffer;
/**
* Resolves the data passed by prometheus's exporter interface http:xxx/metrics
* Reference: prometheus text_parse.go code, entry: TextToMetricFamilies
* recommend use OnlineParser
*/
@Slf4j
@Deprecated(since = "1.7.0")
public class TextParser {
private static final String NAME_LABEL = "__name__";
private static final char LEFT_CURLY_BRACKET = '{';
@@ -46,6 +44,7 @@ public class TextParser {
/**
* parser prometheus exporter text metrics data
* todo use inputStream bytebuffer instead of resp string
* @param resp txt data
* @return metrics family
*/
@@ -58,13 +58,13 @@ public class PushCollectImpl extends AbstractCollect {
private static final Map<Long, Long> timeMap = new ConcurrentHashMap<>();
// ms
private static final Integer DEFAULT_TIMEOUT = 3000;
private static final Integer timeout = 3000;
private static final Integer SUCCESS_CODE = 200;
// It's hard to determine how long ago the first data collection was, because there's no way to know when the last collection occurred.
// This makes it difficult to avoid re-collecting data after a restart. The default is 30 seconds
private static final Integer FIRST_COLLECT_INTERVAL = 30000;
private static final Integer firstCollectInterval = 30000;
@Override
public void preCheck(Metrics metrics) throws IllegalArgumentException {
@@ -80,7 +80,7 @@ public class PushCollectImpl extends AbstractCollect {
long monitorId = builder.getId();
PushProtocol pushProtocol = metrics.getPush();
Long time = timeMap.getOrDefault(monitorId, curTime - FIRST_COLLECT_INTERVAL);
Long time = timeMap.getOrDefault(monitorId, curTime - firstCollectInterval);
timeMap.put(monitorId, curTime);
HttpContext httpContext = createHttpContext(pushProtocol);
@@ -145,10 +145,10 @@ public class PushCollectImpl extends AbstractCollect {
//requestBuilder.setUri(pushProtocol.getUri());
if (DEFAULT_TIMEOUT > 0) {
if (timeout > 0) {
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(DEFAULT_TIMEOUT)
.setSocketTimeout(DEFAULT_TIMEOUT)
.setConnectTimeout(timeout)
.setSocketTimeout(timeout)
.setRedirectsEnabled(true)
.build();
requestBuilder.setConfig(requestConfig);
@@ -1,95 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.collector.collect.s7;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.collector.collect.plc.AbstractPlcCollectImpl;
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
import org.apache.hertzbeat.common.entity.job.Metrics;
import org.apache.hertzbeat.common.entity.job.protocol.PlcProtocol;
import org.apache.hertzbeat.common.entity.job.protocol.S7Protocol;
import org.apache.hertzbeat.common.entity.message.CollectRep;
import org.apache.plc4x.java.api.PlcConnection;
import org.apache.plc4x.java.api.messages.PlcReadRequest;
import org.springframework.beans.BeanUtils;
import org.springframework.util.StringUtils;
import java.util.List;
/**
* plc collect
*/
@Slf4j
public class S7CollectImpl extends AbstractPlcCollectImpl {
@Override
public void preCheck(Metrics metrics) throws IllegalArgumentException {
S7Protocol s7 = metrics.getS7();
List<String> registerAddressList = s7.getRegisterAddresses();
// check RackId
if (!StringUtils.hasText(s7.getRackId())) {
s7.setRackId("0");
}
// check SlotId
if (!StringUtils.hasText(s7.getSlotId())) {
s7.setSlotId("0");
}
// check controllerType
if (!StringUtils.hasText(s7.getControllerType())) {
s7.setControllerType("S7_1500");
}
if (!StringUtils.hasText(s7.getTimeout())) {
s7.setTimeout("5000");
}
PlcProtocol plc = metrics.getPlc() == null ? new PlcProtocol() : metrics.getPlc();
plc.setRegisterAddresses(registerAddressList);
BeanUtils.copyProperties(s7, plc);
metrics.setPlc(plc);
super.preCheck(metrics);
}
@Override
public void collect(CollectRep.MetricsData.Builder builder, Metrics metrics) {
super.collect(builder, metrics);
}
@Override
public String supportProtocol() {
return DispatchConstants.PROTOCOL_S7;
}
@Override
protected String getConnectionString(Metrics metrics) {
S7Protocol s7Protocol = metrics.getS7();
return "s7://" + s7Protocol.getHost() + ":" + s7Protocol.getPort() + "?remote-rack:" + s7Protocol.getRackId()
+ "&remote-slot:" + s7Protocol.getSlotId() + "&controller-type=" + s7Protocol.getControllerType();
}
@Override
protected PlcReadRequest buildRequest(Metrics metrics, PlcConnection connection) {
S7Protocol s7Protocol = metrics.getS7();
List<String> registerAddressList = s7Protocol.getRegisterAddresses();
// Create a new read request:
PlcReadRequest.Builder requestBuilder = connection.readRequestBuilder();
for (int i = 0; i < registerAddressList.size(); i++) {
String s1 = registerAddressList.get(i);
requestBuilder.addTagAddress(metrics.getS7().getAddressSyntax() + ":" + i, s1);
}
return requestBuilder.build();
}
}
@@ -47,28 +47,31 @@ public class DnsCollectImplTest {
.address("www.google.com")
.timeout("3000")
.port("53")
.tcp("tcp")
.build();
}
@Test
public void testPreCheck() {
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
//metrics is null
dnsCollect.collect(builder, null);
assertEquals(CollectRep.Code.FAIL, builder.getCode());
assertThrows(IllegalArgumentException.class, () -> {
Metrics metrics = new Metrics();
metrics.setName("question");
metrics.setDns(dnsProtocol);
dnsCollect.preCheck(metrics);
});
//invalid DnsProtocol
Metrics metrics = new Metrics();
//query class is blank
assertThrows(IllegalArgumentException.class, () -> {
DnsProtocol dns = DnsProtocol.builder().build();
Metrics metrics = new Metrics();
metrics.setDns(dns);
dnsCollect.preCheck(metrics);
});
//validated DnsProtocol
// no exception throws
assertDoesNotThrow(() -> {
metrics.setName("question");
dnsProtocol.setTcp("tcp");
Metrics metrics = new Metrics();
metrics.setDns(dnsProtocol);
dnsCollect.preCheck(metrics);
});
@@ -77,23 +80,28 @@ public class DnsCollectImplTest {
@Test
public void testCollect() {
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
Metrics metrics0 = Metrics.builder()
.name("question")
.dns(dnsProtocol)
.aliasFields(Collections.singletonList("section"))
.build();
dnsCollect.collect(builder, metrics0);
assertEquals(CollectRep.Code.SUCCESS, builder.getCode());
long monitorId = 666;
String app = "testDNS";
Metrics metrics = new Metrics();
metrics.setName("question");
metrics.setDns(dnsProtocol);
metrics.setAliasFields(Collections.singletonList("section"));
dnsCollect.collect(builder, metrics);
assertNotNull(builder.getValues(0).getColumns(0));
// dns is null, no exception throws
assertDoesNotThrow(() -> {
dnsCollect.collect(builder, null);
});
// metric name is header
Metrics metrics1 = Metrics.builder()
.name("header")
.dns(dnsProtocol)
.aliasFields(Collections.singletonList("section"))
.build();
dnsCollect.collect(builder, metrics1);
assertEquals(CollectRep.Code.SUCCESS, builder.getCode());
assertDoesNotThrow(() -> {
Metrics metrics1 = new Metrics();
metrics1.setName("header");
metrics1.setDns(dnsProtocol);
metrics1.setAliasFields(Collections.singletonList("section"));
dnsCollect.collect(builder, metrics1);
});
}
@Test
@@ -18,14 +18,7 @@
package org.apache.hertzbeat.collector.collect.http;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
import org.apache.hertzbeat.common.entity.job.Metrics;
import org.apache.hertzbeat.common.entity.job.protocol.HttpProtocol;
import org.apache.hertzbeat.common.entity.message.CollectRep;
@@ -48,7 +41,7 @@ class HttpCollectImplTest {
assertThrows(IllegalArgumentException.class, () -> {
httpCollectImpl.preCheck(null);
});
assertThrows(IllegalArgumentException.class, () -> {
Metrics metrics = Metrics.builder().build();
httpCollectImpl.preCheck(metrics);
@@ -72,92 +65,4 @@ class HttpCollectImplTest {
String protocol = httpCollectImpl.supportProtocol();
assert "http".equals(protocol);
}
@Test
void parseResponseByXmlPath() throws Exception {
// Create a sample XML response
String xmlResponse = """
<?xml version="1.0" encoding="UTF-8"?>
<root>
<server>
<name>Server1</name>
<status>Running</status>
<metrics>
<cpu>75.5</cpu>
<memory>1024</memory>
<disk>500</disk>
</metrics>
</server>
<server>
<name>Server2</name>
<status>Stopped</status>
<metrics>
<cpu>0.0</cpu>
<memory>0</memory>
<disk>500</disk>
</metrics>
</server>
</root>
""";
// Set up HttpProtocol with XML path parsing
HttpProtocol http = HttpProtocol.builder()
.parseType(DispatchConstants.PARSE_XML_PATH)
.parseScript("//server") // XPath to select all server nodes
.build();
// Set up Metrics with fields that have XPath expressions
List<Metrics.Field> fields = new ArrayList<>();
fields.add(Metrics.Field.builder().field("name").build());
fields.add(Metrics.Field.builder().field("status").build());
fields.add(Metrics.Field.builder().field("metrics/cpu").build());
fields.add(Metrics.Field.builder().field("metrics/memory").build());
Metrics metrics = Metrics.builder()
.http(http)
.fields(fields)
.aliasFields(Arrays.asList("name", "status", "metrics/cpu", "metrics/memory"))
.build();
// Create a custom builder that captures added rows
List<CollectRep.ValueRow> capturedRows = new ArrayList<>();
CollectRep.MetricsData.Builder builder = new CollectRep.MetricsData.Builder() {
@Override
public CollectRep.MetricsData.Builder addValueRow(CollectRep.ValueRow valueRow) {
capturedRows.add(valueRow);
return super.addValueRow(valueRow);
}
};
// Use reflection to access the private parseResponseByXmlPath method
Method parseMethod = HttpCollectImpl.class.getDeclaredMethod(
"parseResponseByXmlPath",
String.class,
Metrics.class,
CollectRep.MetricsData.Builder.class,
Long.class);
parseMethod.setAccessible(true);
// Call the method
parseMethod.invoke(httpCollectImpl, xmlResponse, metrics, builder, 100L);
// Verify the results
assertEquals(2, capturedRows.size(), "Should have parsed 2 server nodes");
// Check first server
CollectRep.ValueRow firstRow = capturedRows.get(0);
assertEquals(4, firstRow.getColumnsCount(), "First row should have 4 columns");
assertEquals("Server1", firstRow.getColumns(0), "First server name should be Server1");
assertEquals("Running", firstRow.getColumns(1), "First server status should be Running");
assertEquals("75.5", firstRow.getColumns(2), "First server CPU should be 75.5");
assertEquals("1024", firstRow.getColumns(3), "First server memory should be 1024");
// Check second server
CollectRep.ValueRow secondRow = capturedRows.get(1);
assertEquals(4, secondRow.getColumnsCount(), "Second row should have 4 columns");
assertEquals("Server2", secondRow.getColumns(0), "Second server name should be Server2");
assertEquals("Stopped", secondRow.getColumns(1), "Second server status should be Stopped");
assertEquals("0.0", secondRow.getColumns(2), "Second server CPU should be 0.0");
assertEquals("0", secondRow.getColumns(3), "Second server memory should be 0");
}
}
@@ -0,0 +1,79 @@
/*
* 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.hertzbeat.collector.collect.http.promethus.exporter;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.Map;
import org.junit.jupiter.api.Test;
/**
* Test case for {@link ExporterParser}
*/
class ExporterParserTest {
@Test
void textToMetric() {
String resp = """
# HELP disk_total_bytes Total space for path
# TYPE disk_total_bytes gauge
disk_total_bytes{path="C:\\\\hertzbeat\\\\repo\\\\testpath",} 4.29496725504E11
# HELP go_gc_cycles_automatic_gc_cycles_total Count of completed GC cycles generated by the Go runtime.
# TYPE go_gc_cycles_automatic_gc_cycles_total counter
go_gc_cycles_automatic_gc_cycles_total 0
# HELP go_gc_cycles_forced_gc_cycles_total Count of completed GC cycles forced by the application.
# TYPE go_gc_cycles_forced_gc_cycles_total counter
go_gc_cycles_forced_gc_cycles_total 0
# HELP go_gc_cycles_total_gc_cycles_total Count of all completed GC cycles.
# TYPE go_gc_cycles_total_gc_cycles_total counter
go_gc_cycles_total_gc_cycles_total 0
# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 0
go_gc_duration_seconds{quantile="0.25"} 0
go_gc_duration_seconds{quantile="0.5"} 0
go_gc_duration_seconds{quantile="0.75"} 0
go_gc_duration_seconds{quantile="1"} 0
# TYPE jvm info
# HELP jvm VM version info
jvm_info{runtime="OpenJDK Runtime Environment",vendor="Azul Systems, Inc.",version="11.0.13+8-LTS"} 1.0
# TYPE jvm_gc_collection_seconds summary
# HELP jvm_gc_collection_seconds Time spent in a given JVM garbage collector in seconds.
jvm_gc_collection_seconds_count{gc="G1 Young Generation"} 10.0
jvm_gc_collection_seconds_sum{gc="G1 Young Generation"} 0.051
jvm_gc_collection_seconds_count{gc="G1 Old Generation"} 0.0
jvm_gc_collection_seconds_sum{gc="G1 Old Generation"} 0.0
# TYPE resource_group_aggregate_usage_secs summary
resource_group_aggregate_usage_secs{cluster="standalone",quantile="0.5"} 2.69245E-4
resource_group_aggregate_usage_secs{cluster="standalone",quantile="0.9"} 3.49601E-4
resource_group_aggregate_usage_secs_count{cluster="standalone"} 13.0
resource_group_aggregate_usage_secs_sum{cluster="standalone"} 0.004832498
resource_group_aggregate_usage_secs_created{cluster="standalone"} 1.715842140749E9
# TYPE metadata_store_ops_latency_ms histogram
metadata_store_ops_latency_ms_bucket{cluster="standalone",name="metadata-store",type="get",status="success",le="1.0"} 59.0
metadata_store_ops_latency_ms_bucket{cluster="standalone",name="metadata-store",type="get",status="success",le="3.0"} 61.0
metadata_store_ops_latency_ms_bucket{cluster="standalone",name="metadata-store",type="get",status="success",le="5.0"} 61.0
# EOF""";
ExporterParser parser = new ExporterParser();
Map<String, MetricFamily> metricFamilyMap = parser.textToMetric(resp);
assertEquals(5, metricFamilyMap.get("resource_group_aggregate_usage_secs").getMetricList().size());
assertEquals(3, metricFamilyMap.get("metadata_store_ops_latency_ms").getMetricList().size());
assertEquals(5, metricFamilyMap.get("go_gc_duration_seconds").getMetricList().size());
assertEquals(9, metricFamilyMap.size());
}
}
@@ -25,12 +25,8 @@ import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
class OnlineParserTest {
@@ -48,13 +44,13 @@ class OnlineParserTest {
String str = """
# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 2.0209e-05
go_gc_duration_seconds{quantile="0.25"} 6.6917e-05
go_gc_duration_seconds { quantile="0"} 2.0209e-05 321312
go_gc_duration_seconds{ quantile = "0.25" } 6.6917e-05
go_gc_duration_seconds{quantile="0.5"} -Inf
go_gc_duration_seconds{quantile="0.75"} +Inf
go_gc_duration_seconds{ quantile = "0.75"} +Inf
go_gc_duration_seconds{quantile="1"} NaN
go_gc_duration_seconds_sum 0.001134793
go_gc_duration_seconds_count 5
go_gc_duration_seconds_sum 0.001134793 321314
go_gc_duration_seconds_count 5 43
# HELP go_goroutines Number of goroutines that currently exist.
# TYPE go_goroutines gauge
go_goroutines 32
@@ -77,26 +73,7 @@ class OnlineParserTest {
# TYPE go_memstats_gc_sys_bytes gauge
go_memstats_gc_sys_bytes 4.614808e+06""";
InputStream inputStream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
Map<String, MetricFamily> metricFamilyMap1 = OnlineParser.parseMetrics(inputStream);
Map<String, MetricFamily> metricFamilyMap2 = TextParser.textToMetricFamilies(str);
assertNotNull(metricFamilyMap1);
assertNotNull(metricFamilyMap2);
assertEquals(metricFamilyMap1.size(), metricFamilyMap2.size());
metricFamilyMap2.forEach((metricFamilyName, metricFamily2) -> {
if (!metricFamilyMap1.containsKey(metricFamilyName)) {
fail("parse failed, different result from two parser.");
}
MetricFamily metricFamily1 = metricFamilyMap1.get(metricFamilyName);
assertEquals(metricFamily1.getName(), metricFamily1.getName());
Set<Double> metricValueSet = metricFamily2.getMetricList().stream().map(MetricFamily.Metric::getValue).collect(Collectors.toSet());
metricFamily1.getMetricList().forEach(metric -> {
// this is for something different between two algorithms above, and both of them is current on this parsing behavior.
if (!(metric.getValue() == Double.POSITIVE_INFINITY || metric.getValue() == Double.NEGATIVE_INFINITY)) {
if (!metricValueSet.contains(metric.getValue())) {
fail();
}
}
});
});
Map<String, MetricFamily> metricFamilyMap = OnlineParser.parseMetrics(inputStream);
assertNotNull(metricFamilyMap);
}
}
@@ -302,7 +302,7 @@ public class MetricsCollect implements Runnable, Comparable<MetricsCollect> {
value = String.valueOf(objValue);
}
} catch (Exception e) {
log.warn("[calculates execute warning, use original value.] {}", e.getMessage());
log.info("[calculates execute warning] {}.", e.getMessage());
value = Optional.ofNullable(fieldValueMap.get(expression.getSourceText()))
.map(String::valueOf)
.orElse(null);
@@ -29,5 +29,4 @@ org.apache.hertzbeat.collector.collect.mqtt.MqttCollectImpl
org.apache.hertzbeat.collector.collect.ipmi2.IpmiCollectImpl
org.apache.hertzbeat.collector.collect.kafka.KafkaCollectImpl
org.apache.hertzbeat.collector.collect.sd.HttpSdCollectImpl
org.apache.hertzbeat.collector.collect.modbus.ModbusCollectImpl
org.apache.hertzbeat.collector.collect.s7.S7CollectImpl
org.apache.hertzbeat.collector.collect.modbus.ModbusCollectImpl
@@ -39,7 +39,7 @@ spring:
collector:
info:
version: ${COLLECTOR_VERSION:1.7.0}
version: ${COLLECTOR_VERSION:1.6.1}
ip: ${COLLECTOR_IP:}
dispatch:
entrance:
@@ -202,10 +202,6 @@ public interface DispatchConstants {
* Parsing method prometheus exporter data
*/
String PARSE_PROMETHEUS = "prometheus";
/**
* Parse response body as config/properties format
*/
String PARSE_CONFIG = "config";
/**
* prometheus accept header
*/
@@ -231,9 +227,4 @@ public interface DispatchConstants {
* protocol modbus
*/
String PROTOCOL_MODBUS = "modbus";
/**
* protocol modbus
*/
String PROTOCOL_S7 = "s7";
}
@@ -613,8 +613,9 @@ public class HashedWheelTimer implements Timer {
HashedWheelBucket bucket = this.bucket;
if (bucket != null) {
bucket.remove(this);
} else {
timer.pendingTimeouts.decrementAndGet();
}
timer.pendingTimeouts.decrementAndGet();
}
public boolean compareAndSetState(int expected, int state) {
@@ -641,7 +642,6 @@ public class HashedWheelTimer implements Timer {
}
try {
remove();
task.run(this);
} catch (Throwable t) {
if (logger.isWarnEnabled()) {
@@ -718,6 +718,7 @@ public class HashedWheelTimer implements Timer {
while (timeout != null) {
HashedWheelTimeout next = timeout.next;
if (timeout.remainingRounds <= 0) {
next = remove(timeout);
if (timeout.deadline <= deadline) {
timeout.expire();
} else {
@@ -725,7 +726,9 @@ public class HashedWheelTimer implements Timer {
throw new IllegalStateException(String.format(
"timeout.deadline (%d) > deadline (%d)", timeout.deadline, deadline));
}
} else if (!timeout.isCancelled()) {
} else if (timeout.isCancelled()) {
next = remove(timeout);
} else {
timeout.remainingRounds--;
}
timeout = next;
@@ -758,6 +761,7 @@ public class HashedWheelTimer implements Timer {
timeout.prev = null;
timeout.next = null;
timeout.bucket = null;
timeout.timer.pendingTimeouts.decrementAndGet();
return next;
}
@@ -242,7 +242,6 @@ public class KafkaCollectImpl extends AbstractCollect {
boolean isKafkaCommand = SupportedCommand.isKafkaCommand(command);
if (!isKafkaCommand) {
log.error("Unsupported command: {}", command);
builder.setCode(CollectRep.Code.FAIL);
return;
}
@@ -274,7 +273,7 @@ public class KafkaCollectImpl extends AbstractCollect {
}
}
protected AdminClient getAdminClient(KafkaProtocol kafkaProtocol) {
private AdminClient getAdminClient(KafkaProtocol kafkaProtocol) {
CacheIdentifier kafkaAdminClientIdentifier = CacheIdentifier.builder()
.ip(kafkaProtocol.getHost()).port(kafkaProtocol.getPort())
.build();
@@ -17,24 +17,12 @@
package org.apache.hertzbeat.collector.collect.kafka;
import org.apache.hertzbeat.collector.collect.kafka.constants.SupportedCommand;
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
import org.apache.hertzbeat.common.entity.job.Metrics;
import org.apache.hertzbeat.common.entity.job.protocol.KafkaProtocol;
import org.apache.hertzbeat.common.entity.message.CollectRep;
import org.apache.kafka.clients.admin.AdminClient;
import org.apache.kafka.clients.admin.ListTopicsOptions;
import org.apache.kafka.clients.admin.ListTopicsResult;
import org.apache.kafka.common.KafkaFuture;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.HashSet;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -43,25 +31,12 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
/**
* Test case for {@link KafkaCollectImpl}
*/
@ExtendWith(MockitoExtension.class)
public class KafkaCollectTest {
private static final String HOST = "127.0.0.1";
private static final String PORT = "9092";
private KafkaCollectImpl collect;
@Mock
private AdminClient adminClient;
@BeforeEach
public void setUp() throws Exception {
collect = new KafkaCollectImpl(){
@Override
protected AdminClient getAdminClient(KafkaProtocol protocol) {
return adminClient;
}
};
collect = new KafkaCollectImpl();
}
@Test
@@ -84,51 +59,38 @@ public class KafkaCollectTest {
});
// kafka port is null
assertThrows(IllegalArgumentException.class, () -> {
kafka.setHost(HOST);
kafka.setHost("127.0.0.1");
collect.preCheck(metric);
});
// no exception throw
assertDoesNotThrow(() -> {
kafka.setPort(PORT);
kafka.setPort("9092");
collect.preCheck(metric);
});
}
@Test
void testCollect() throws Exception {
Set<String> topicSet = new HashSet<>();
topicSet.add("test-topic");
ListTopicsResult listTopicsResult = Mockito.mock(ListTopicsResult.class);
KafkaFuture<Set<String>> future = Mockito.mock(KafkaFuture.class);
Mockito.when(adminClient.listTopics(Mockito.any(ListTopicsOptions.class))).thenReturn(listTopicsResult);
Mockito.when(listTopicsResult.names()).thenReturn(future);
Mockito.when(future.get()).thenReturn(topicSet);
KafkaProtocol kafka = KafkaProtocol.builder()
.host(HOST)
.port(PORT)
.build();
Metrics metrics = Metrics.builder()
.kclient(kafka)
.build();
// test if not kafka command
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
collect.collect(builder, metrics);
assertEquals(CollectRep.Code.FAIL, builder.getCode());
void collect() {
// metrics is null
assertThrows(NullPointerException.class, () -> {
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
collect.collect(builder, null);
});
KafkaProtocol kafka = KafkaProtocol.builder().host("127.0.0.1").port("9092").build();
Metrics metrics = Metrics.builder().kclient(kafka).build();
//test if not kafka command
assertDoesNotThrow(() -> {
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
collect.collect(builder, metrics);
});
//test kafka command
kafka.setCommand(SupportedCommand.TOPIC_LIST.getCommand());
builder.setCode(CollectRep.Code.forNumber(6));
collect.collect(builder, metrics);
assertDoesNotThrow(() -> {
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
metrics.getKclient().setCommand("topic-list");
collect.collect(builder, metrics);
});
assertEquals(CollectRep.Code.SUCCESS, builder.getCode());
assertEquals(1, builder.getValuesList().size());
assertEquals("test-topic", builder.getValues(0).getColumns(0));
Mockito.verify(adminClient).listTopics(Mockito.any(ListTopicsOptions.class));
}
@Test
@@ -455,19 +455,4 @@ public interface CommonConstants {
* status page incident state resolved
*/
byte STATUS_PAGE_INCIDENT_STATE_RESOLVED = 3;
/**
* status page incident state resolved
*/
byte MONITOR_TYPE_NORMAL = 0;
/**
* status page incident state resolved
*/
byte MONITOR_TYPE_PUSH_AUTO_CREATE = 1;
/**
* status page incident state resolved
*/
byte MONITOR_TYPE_DISCOVERY_AUTO_CREATE = 2;
}
@@ -65,8 +65,6 @@ public interface ConfigConstants {
String INFO = "info";
String GRAFANA = "grafana";
String LOG = "log";
}
}
@@ -35,7 +35,4 @@ public interface SmsConstants {
// Aws cloud SMS
String AWS = "aws";
// Twilio SMS
String TWILIO = "twilio";
}
@@ -122,11 +122,6 @@ public class NoticeReceiver {
example = "779294123", accessMode = READ_WRITE)
private String tgUserId;
@Schema(title = "Telegram message thread id: The notification method is valid for Telegram Bot",
description = "TTelegram message thread id: The notification method is valid for Telegram Bot",
example = "779294123", accessMode = READ_WRITE)
private String tgMessageThreadId;
@Schema(title = "DingTalk,FeiShu,WeWork user id: The notification method is valid for DingTalk,FeiShu,WeWork Bot",
description = "DingTalk,FeiShu,WeWork user id: The notification method is valid for DingTalk,FeiShu,WeWork Bot",
example = "779294123", accessMode = READ_WRITE)
@@ -1,59 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.common.entity.dto.query;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Metric History Range Query Data
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "Query Request Data")
public class DatasourceQuery {
@Schema(title = "Ref Id, unique id for the query")
private String refId;
@Schema(title = "datasource name")
private String datasource;
@Schema(title = "query expr, like prometheus query")
private String expr;
@Schema(title = "query expr type, like promql or sql or influxql")
private String exprType;
@Schema(title = "query range type, like range or instant")
private String timeType;
@Schema(title = "query range start time")
private Long start;
@Schema(title = "query range end time")
private Long end;
@Schema(title = "query time step, like 5m or 1h")
private String step;
}
@@ -1,103 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.common.entity.dto.query;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Metric History Range Query Data
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "Metric Query Data")
public class DatasourceQueryData {
@Schema(title = "Ref Id, unique id for the query")
private String refId;
@Schema(title = "query status code, 200 for success, other for error")
private Integer status;
@Schema(title = "query error message")
private String msg;
@Schema(title = "query result data frames")
private List<SchemaData> frames;
/**
* Schema Data
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public static final class SchemaData {
@Schema(title = "Metric Schema")
private MetricSchema schema;
@Schema(title = "metrics row values, first is the timestamp-ts", example = "[[29,32,44],[32,34,true]]")
private List<Object[]> data;
}
/**
* Metric Schema
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public static final class MetricSchema {
@Schema(title = "Metrics Field")
private List<MetricField> fields;
@Schema(title = "This frame labels")
private Map<String, String> labels;
@Schema(title = "Meta Information")
private Map<String, String> meta;
}
/**
* Metric Field
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public static final class MetricField {
@Schema(title = "Metric Field Name")
private String name;
@Schema(title = "Field Type: number, string, time, bool")
private String type;
@Schema(title = "Field Unit: %, Mb, Kbps etc.")
private String unit;
}
}
@@ -54,7 +54,6 @@ import org.apache.hertzbeat.common.entity.job.protocol.PushProtocol;
import org.apache.hertzbeat.common.entity.job.protocol.RedfishProtocol;
import org.apache.hertzbeat.common.entity.job.protocol.RedisProtocol;
import org.apache.hertzbeat.common.entity.job.protocol.RocketmqProtocol;
import org.apache.hertzbeat.common.entity.job.protocol.S7Protocol;
import org.apache.hertzbeat.common.entity.job.protocol.ScriptProtocol;
import org.apache.hertzbeat.common.entity.job.protocol.SmtpProtocol;
import org.apache.hertzbeat.common.entity.job.protocol.SnmpProtocol;
@@ -271,10 +270,6 @@ public class Metrics {
* Monitoring configuration information using the public modBus protocol
*/
private ModbusProtocol modbus;
/**
* Monitoring configuration information using the public s7 protocol
*/
private S7Protocol s7;
/**
* collector use - Temporarily store subTask metrics response data
*/
@@ -95,12 +95,6 @@ public class HttpProtocol implements CommonRequestProtocol, Protocol {
*/
private List<String> successCodes;
/**
* Whether to enable URL encoding for the path. Default is true.
* When true, the URL path will be encoded. When false, the URL path will not be encoded.
*/
private String enableUrlEncoding = "true";
/**
* authentication information
*/
@@ -91,9 +91,6 @@ public class Monitor {
@Max(4)
private byte status;
@Schema(title = "Task type 0: Normal, 1: push auto create, 2: discovery auto create")
private byte type;
@Schema(title = "task label", example = "{env:test}", accessMode = READ_WRITE)
@Convert(converter = JsonMapAttributeConverter.class)
@Column(length = 4096)
@@ -445,7 +445,15 @@ public final class CollectRep {
if (value != null) {
// Check byte array size, Arrow buffer size is 32768 bytes
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
vector.setSafe(rowIndex, bytes);
if (bytes.length > 32700) {
log.warn("Value too large for Arrow buffer ({}), truncating to 32700 bytes. Meta: {}",
bytes.length, JsonUtil.toJson(metadata));
byte[] truncatedBytes = new byte[32700];
System.arraycopy(bytes, 0, truncatedBytes, 0, 32700);
vector.set(rowIndex, truncatedBytes);
} else {
vector.set(rowIndex, bytes);
}
}
}
}
@@ -49,46 +49,4 @@ public final class TimePeriodUtil {
return Duration.parse("PT" + tokenTime);
}
}
/**
* transform any timestamp to milliseconds
* @param timestamp timestamp
* @return milliseconds
*/
public static long normalizeToMilliseconds(Object timestamp) {
if (timestamp instanceof String timestampStr) {
// string type, may be second, millisecond or decimal second
// eg: "1672531199000", "1672531199", "1672531199.123"
if (timestampStr.contains(".")) {
// contains decimal point, parse as second timestamp
double seconds = Double.parseDouble(timestampStr);
return (long) (seconds * 1000);
} else {
// integer form, determine second or millisecond
long numericTimestamp = Long.parseLong(timestampStr);
return convertNumericTimestamp(numericTimestamp);
}
} else if (timestamp instanceof Number) {
// number eg Integer、Long、Double
if (timestamp instanceof Double || timestamp instanceof Float) {
// float type, treat as second timestamp
double seconds = ((Number) timestamp).doubleValue();
return (long) (seconds * 1000);
} else {
// integer type, directly determine second or millisecond
long numericTimestamp = ((Number) timestamp).longValue();
return convertNumericTimestamp(numericTimestamp);
}
} else {
throw new IllegalArgumentException("Not support this timestamp type: " + timestamp.getClass().getName());
}
}
private static long convertNumericTimestamp(long numericTimestamp) {
if (String.valueOf(numericTimestamp).length() <= 10) {
return numericTimestamp * 1000;
} else {
return numericTimestamp;
}
}
}
@@ -40,7 +40,6 @@ import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import lombok.extern.slf4j.Slf4j;
@@ -72,11 +71,6 @@ public class DashboardService {
*/
@Transactional(rollbackFor = Exception.class)
public ResponseEntity<?> createOrUpdateDashboard(String dashboardJson, Long monitorId) {
if (!grafanaProperties.enabled()) {
log.info("HertzBeat Grafana config not enabled");
throw new RuntimeException("HertzBeat Grafana config not enabled");
}
String token = serviceAccountService.getToken();
String url = grafanaProperties.getPrefix() + grafanaProperties.getUrl() + CREATE_DASHBOARD_API;
@@ -109,12 +103,6 @@ public class DashboardService {
log.error("create dashboard error: {}", response.getStatusCode());
throw new RuntimeException("create dashboard error");
}
} catch (HttpClientErrorException.Forbidden ex) {
log.error("Grafana Access denied to save dashboard", ex);
throw new RuntimeException("Grafana Access denied to save dashboard", ex);
} catch (HttpClientErrorException.NotFound ex){
log.error("Grafana Dashboard not found", ex);
throw new RuntimeException("Grafana Dashboard not found", ex);
} catch (Exception ex) {
log.error("create dashboard error", ex);
throw new RuntimeException("create dashboard error", ex);
@@ -61,11 +61,6 @@ public class DatasourceService {
* Create a new datasource in Grafana.
*/
public void existOrCreateDatasource(String token) {
if (!warehouseProperties.enabled()) {
log.info("HertzBeat VictoriaMetrics config not enabled");
return;
}
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setBearerAuth(token);
-67
View File
@@ -1,67 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Licensed to the Apache Software Foundation (ASF) under one or more
~ contributor license agreements. See the NOTICE file distributed with
~ this work for additional information regarding copyright ownership.
~ The ASF licenses this file to You under the Apache License, Version 2.0
~ (the "License"); you may not use this file except in compliance with
~ the License. You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.hertzbeat</groupId>
<artifactId>hertzbeat</artifactId>
<version>2.0-SNAPSHOT</version>
</parent>
<artifactId>hertzbeat-log</artifactId>
<name>${project.artifactId}</name>
<properties>
<maven-jar-plugin.version>3.2.0</maven-jar-plugin.version>
<maven-assembly-plugin.version>3.3.0</maven-assembly-plugin.version>
</properties>
<dependencies>
<!-- hertzbeat common -->
<dependency>
<groupId>org.apache.hertzbeat</groupId>
<artifactId>hertzbeat-common</artifactId>
</dependency>
<!-- hertzbeat warehouse -->
<dependency>
<groupId>org.apache.hertzbeat</groupId>
<artifactId>hertzbeat-warehouse</artifactId>
</dependency>
<!-- OpenTelemetry -->
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk-logs</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-logback-appender-1.0</artifactId>
</dependency>
</dependencies>
</project>
@@ -1,117 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.hertzbeat.log.config;
import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_NAME;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter;
import io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.logs.SdkLoggerProvider;
import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor;
import io.opentelemetry.sdk.resources.Resource;
import jakarta.annotation.PostConstruct;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.hertzbeat.warehouse.store.history.greptime.GreptimeProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
/**
* OpenTelemetryConfig is responsible for initializing OpenTelemetry with the specified service name and GrepTimeDB endpoint.
* It ensures that the initialization is done in a thread-safe manner and includes authentication for GrepTimeDB.
*/
@Configuration
@Slf4j
public class OpenTelemetryConfig {
@Autowired
private GreptimeProperties greptimeProperties;
/**
* Initializes OpenTelemetry with the given service name and GrepTimeDB endpoint.
* Includes authentication if configured in GreptimeProperties.
*/
@PostConstruct
public void initializeOpenTelemetry() {
if (greptimeProperties == null || !greptimeProperties.enabled()) {
log.info("GrepTimeDB logging is disabled, skipping OpenTelemetry configuration.");
return;
}
try {
Resource resource = Resource.getDefault()
.merge(Resource.builder()
.put(SERVICE_NAME, "HertzBeat")
.build());
Map<String, String> headers = new HashMap<>();
headers.put("X-Greptime-DB-Name", "public");
headers.put("X-Greptime-Log-Table-Name", "hzb_log");
addAuthenticationHeaders(headers);
OtlpHttpLogRecordExporter logExporter = OtlpHttpLogRecordExporter.builder()
.setEndpoint(greptimeProperties.httpEndpoint() + "/v1/otlp/v1/logs")
.setHeaders(()-> headers)
.setTimeout(10, TimeUnit.SECONDS)
.build();
SdkLoggerProvider loggerProvider = SdkLoggerProvider.builder()
.setResource(resource)
.addLogRecordProcessor(
BatchLogRecordProcessor.builder(logExporter)
.setScheduleDelay(1000, TimeUnit.MILLISECONDS)
.setMaxExportBatchSize(512)
.build())
.build();
OpenTelemetry openTelemetry = OpenTelemetrySdk.builder()
.setLoggerProvider(loggerProvider)
.build();
OpenTelemetryAppender.install(openTelemetry);
log.info("OpenTelemetry successfully configured with GrepTimeDB exporter.");
} catch (Exception e) {
log.error("Failed to initialize OpenTelemetry with GrepTimeDB", e);
}
}
/**
* Adds authentication headers to the provided map if username and password are configured.
*
* @param headers the map to which authentication headers will be added
*/
private void addAuthenticationHeaders(Map<String, String> headers) {
if (StringUtils.isNotBlank(greptimeProperties.username())
&& StringUtils.isNotBlank(greptimeProperties.password())) {
String credentials = greptimeProperties.username() + ":" + greptimeProperties.password();
String encodedCredentials = Base64.getEncoder().encodeToString(credentials.getBytes());
headers.put("Authorization", "Basic " + encodedCredentials);
}
}
}
@@ -1,17 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.apache.hertzbeat.log.config.LogAutoConfiguration
-5
View File
@@ -89,11 +89,6 @@
<groupId>org.apache.hertzbeat</groupId>
<artifactId>hertzbeat-grafana</artifactId>
</dependency>
<!-- log -->
<dependency>
<groupId>org.apache.hertzbeat</groupId>
<artifactId>hertzbeat-log</artifactId>
</dependency>
<!-- spring -->
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -28,7 +28,6 @@ import org.springframework.context.annotation.ImportRuntimeHints;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* start up class.
@@ -42,7 +41,6 @@ import org.springframework.scheduling.annotation.EnableScheduling;
@ConfigurationPropertiesScan(basePackages = {"org.apache.hertzbeat"})
@ImportRuntimeHints(HertzbeatRuntimeHintsRegistrar.class)
@EnableAsync
@EnableScheduling
public class Manager {
public static void main(String[] args) {
SpringApplication.run(Manager.class, args);
@@ -15,21 +15,26 @@
* limitations under the License.
*/
package org.apache.hertzbeat.log.config;
package org.apache.hertzbeat.manager.pojo.dto;
import org.apache.hertzbeat.common.constants.ConfigConstants;
import org.apache.hertzbeat.common.constants.SignConstants;
import org.apache.hertzbeat.warehouse.store.history.greptime.GreptimeProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.ComponentScan;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
/**
* Log auto configuration.
* Alibaba Sms Sender configuration dto
*/
@ComponentScan(basePackages = ConfigConstants.PkgConstant.PKG
+ SignConstants.DOT
+ ConfigConstants.FunctionModuleConstants.LOG
)
@EnableConfigurationProperties(GreptimeProperties.class)
public class LogAutoConfiguration {
@Data
public class SmsAlibabaConfig {
@NotBlank(message = "AccessKeyId cannot be empty")
private String accessKeyId;
@NotBlank(message = "AccessKeySecret cannot be empty")
private String accessKeySecret;
@NotBlank(message = "SignName cannot be empty")
private String signName;
@NotBlank(message = "TemplateCode cannot be null")
private String templateCode;
}
@@ -15,21 +15,24 @@
* limitations under the License.
*/
package org.apache.hertzbeat.warehouse.service;
package org.apache.hertzbeat.manager.pojo.dto;
import java.util.List;
import org.apache.hertzbeat.common.entity.dto.query.DatasourceQuery;
import org.apache.hertzbeat.common.entity.dto.query.DatasourceQueryData;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
/**
* metrics data query service
* AWS Sms Sender configuration dto
*/
public interface DatasourceQueryService {
@Data
public class SmsAwsConfig {
@NotBlank(message = "AccessKeyId cannot be empty")
private String accessKeyId;
@NotBlank(message = "AccessKeySecret cannot be empty")
private String accessKeySecret;
@NotBlank(message = "region cannot be empty")
private String region;
/**
* Query metrics data
* @param queries query expr
* @return data
*/
List<DatasourceQueryData> query(List<DatasourceQuery> queries);
}
@@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.apache.hertzbeat.alert.config;
package org.apache.hertzbeat.manager.pojo.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
@@ -23,27 +23,25 @@ import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Twilio SMS configuration properties
* Sms Sender configuration dto
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TwilioSmsProperties {
/**
* Twilio Account SID
*/
@NotBlank(message = "Account SID cannot be empty")
private String accountSid;
@AllArgsConstructor
public class SmsNoticeSender {
@NotBlank(message = "Type cannot be empty")
private String type;
private SmsTencentConfig tencent;
private SmsAlibabaConfig alibaba;
/**
* Twilio Auth Token
*/
@NotBlank(message = "Auth Token cannot be empty")
private String authToken;
private SmsUniSmsConfig unisms;
/**
* Twilio Issued Phone Number
*/
@NotBlank(message = "Twilio Phone Number cannot be empty")
private String twilioPhoneNumber;
}
private SmslocalConfig smslocal;
private SmsAwsConfig aws;
private boolean enable = true;
}
@@ -15,45 +15,34 @@
* limitations under the License.
*/
package org.apache.hertzbeat.common.entity.job.protocol;
package org.apache.hertzbeat.manager.pojo.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* Modbus Protocol
* Tencent Sms Sender configuration dto
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class S7Protocol implements Protocol {
@AllArgsConstructor
public class SmsTencentConfig {
@NotBlank(message = "SecretId cannot be empty")
private String secretId;
/**
* IP ADDRESS OR DOMAIN NAME OF THE PEER HOST
*/
private String host;
/**
* Port number
*/
private String port;
@NotBlank(message = "SecretKey cannot be empty")
private String secretKey;
private String driverName;
@NotBlank(message = "SignName cannot be empty")
private String signName;
private String addressSyntax;
@NotBlank(message = "AppId cannot be null")
private String appId;
private String rackId;
@NotBlank(message = "templateId cannot be null")
private String templateId;
private String slotId;
private String controllerType;
private String timeout;
private List<String> registerAddresses;
}
@@ -0,0 +1,62 @@
/*
* 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.hertzbeat.manager.pojo.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.logging.log4j.core.config.plugins.validation.constraints.NotBlank;
/**
* UniSMS configuration
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SmsUniSmsConfig {
/**
* UniSMS access key id
*/
@NotBlank(message = "accessKeyId cannot be empty")
private String accessKeyId;
/**
* UniSMS access key secret, required for HMAC mode
*/
private String accessKeySecret;
/**
* SMS signature
*/
@NotBlank(message = "signature cannot be null")
private String signature;
/**
* SMS template ID
*/
@NotBlank(message = "templateId cannot be null")
private String templateId;
/**
* Authentication mode: simple or hmac, default is simple
*/
@NotBlank(message = "authMode cannot be null")
private String authMode = "simple";
}
@@ -17,8 +17,22 @@
* under the License.
*/
export class TwilioSmsConfig {
accountSid!: string;
authToken!: string;
twilioPhoneNumber!: string;
package org.apache.hertzbeat.manager.pojo.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Smslocal SMS configuration
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SmslocalConfig {
/**
* Smslocal api key
*/
private String apiKey;
}
@@ -427,7 +427,6 @@ public class CollectorJobScheduler implements CollectorScheduling, CollectJobSch
if (jobId == null) {
return;
}
jobContentCache.remove(jobId);
for (ConsistentHash.Node node : consistentHash.getAllNodes().values()) {
AssignJobs assignJobs = node.getAssignJobs();
if (assignJobs.getPinnedJobs().remove(jobId)
@@ -75,8 +75,7 @@ import org.yaml.snakeyaml.Yaml;
/**
* Monitoring Type Management Implementation
* Temporarily stores the monitoring configuration and parameter configuration in memory,
* and then persists it to a storage system.
* temporarily stores the monitoring configuration and parameter configuration in memory and then stores it in the
*/
@Service
@Order(value = Ordered.HIGHEST_PRECEDENCE)
@@ -531,7 +531,7 @@ public class MonitorServiceImpl implements MonitorService {
MonitorDto monitorDto = new MonitorDto();
List<Param> params = paramDao.findParamsByMonitorId(id);
monitorDto.setParams(params);
if (DispatchConstants.PROTOCOL_PROMETHEUS.equalsIgnoreCase(monitor.getApp()) || monitor.getType() == CommonConstants.MONITOR_TYPE_PUSH_AUTO_CREATE) {
if (DispatchConstants.PROTOCOL_PROMETHEUS.equalsIgnoreCase(monitor.getApp())) {
List<CollectRep.MetricsData> metricsDataList = warehouseService.queryMonitorMetricsData(id);
List<String> metrics = metricsDataList.stream().map(CollectRep.MetricsData::getMetrics).collect(Collectors.toList());
monitorDto.setMetrics(metrics);
@@ -22,10 +22,10 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import java.lang.reflect.Type;
import jakarta.annotation.Resource;
import org.apache.hertzbeat.alert.config.SmsConfig;
import org.apache.hertzbeat.common.constants.GeneralConfigTypeEnum;
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
import org.apache.hertzbeat.common.support.event.SmsConfigChangeEvent;
import org.apache.hertzbeat.manager.pojo.dto.SmsNoticeSender;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
@@ -35,7 +35,7 @@ import org.springframework.stereotype.Service;
*/
@Service
public class SmsGeneralConfigServiceImpl extends AbstractGeneralConfigServiceImpl<SmsConfig> {
public class SmsGeneralConfigServiceImpl extends AbstractGeneralConfigServiceImpl<SmsNoticeSender> {
@Resource
private ApplicationContext applicationContext;
@@ -55,7 +55,7 @@ public class SmsGeneralConfigServiceImpl extends AbstractGeneralConfigServiceImp
* This method is used to handle the sms configuration change event.
*/
@Override
public void handler(SmsConfig smsConfig) {
public void handler(SmsNoticeSender smsNoticeSender) {
applicationContext.publishEvent(new SmsConfigChangeEvent(applicationContext));
}
@@ -69,11 +69,11 @@ public class SmsGeneralConfigServiceImpl extends AbstractGeneralConfigServiceImp
* a TypeReference of NoticeSender type
*/
@Override
public TypeReference<SmsConfig> getTypeReference() {
public TypeReference<SmsNoticeSender> getTypeReference() {
return new TypeReference<>() {
@Override
public Type getType() {
return SmsConfig.class;
return SmsNoticeSender.class;
}
};
}
@@ -72,22 +72,14 @@ warehouse:
rpc-port: 6667
username: root
password: root
# store real-time metrics data, enable only one below
real-time:
redis:
enabled: false
host: 127.0.0.1
port: 6379
password: 123456
memory:
enabled: true
init-size: 16
redis:
enabled: false
# redis mode: single, sentinel, cluster. Default is single
mode: single
# separate each address with comma when using cluster mode, eg: 127.0.0.1:6379,127.0.0.1:6380
address: 127.0.0.1:6379
# enter master name when using sentinel mode
masterName: mymaster
password: 123456
# redis db index, default: DB0
db: 0
scheduler:
server:
@@ -110,4 +102,4 @@ ai:
# api key
api-key:
#At present, only IFLYTEK large model needs to be filled in
api-secret:
api-secret:
@@ -231,10 +231,6 @@ alerter:
access-key-id: YOUR_ACCESS_KEY_ID
access-key-secret: YOUR_ACCESS_KEY_SECRET
region: AWS_REGION_FOR_END_USER_MESSAGING
twilio:
account-sid: YOUR_ACCOUNT_SID
auth-token: YOUR_AUTH_TOKEN
twilio-phone-number: YOUR_TWILIO_PHONE_NUMBER
scheduler:
server:
enabled: true
@@ -17,44 +17,71 @@
-- ensure every sql can rerun without error
-- add repair table sql
REPAIR TABLE HZB_ALERT_DEFINE;
DELIMITER //
CREATE PROCEDURE AddOrDropColumns()
BEGIN
DECLARE col_exists INT;
-- Check and add 'app' column if it does not exist
SELECT COUNT(*) INTO col_exists FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='HZB_ALERT_DEFINE' AND COLUMN_NAME='app';
IF col_exists = 0 THEN
ALTER TABLE HZB_ALERT_DEFINE ADD COLUMN app VARCHAR(255);
END IF;
-- Drop 'app' column if it exists
SELECT COUNT(*) INTO col_exists FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='HZB_ALERT_DEFINE' AND COLUMN_NAME='app';
IF col_exists = 1 THEN
ALTER TABLE HZB_ALERT_DEFINE DROP COLUMN app;
END IF;
-- Check and add 'metric' column if it does not exist
SELECT COUNT(*) INTO col_exists FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='HZB_ALERT_DEFINE' AND COLUMN_NAME='metric';
IF col_exists = 0 THEN
ALTER TABLE HZB_ALERT_DEFINE ADD COLUMN metric VARCHAR(255);
END IF;
-- Drop 'metric' column if it exists
SELECT COUNT(*) INTO col_exists FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='HZB_ALERT_DEFINE' AND COLUMN_NAME='metric';
IF col_exists = 1 THEN
ALTER TABLE HZB_ALERT_DEFINE DROP COLUMN metric;
END IF;
-- Check and add 'field' column if it does not exist
SELECT COUNT(*) INTO col_exists FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='HZB_ALERT_DEFINE' AND COLUMN_NAME='field';
IF col_exists = 0 THEN
ALTER TABLE HZB_ALERT_DEFINE ADD COLUMN field VARCHAR(255);
END IF;
-- Drop 'field' column if it exists
SELECT COUNT(*) INTO col_exists FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='HZB_ALERT_DEFINE' AND COLUMN_NAME='field';
IF col_exists = 1 THEN
ALTER TABLE HZB_ALERT_DEFINE DROP COLUMN field;
END IF;
-- Check and add 'preset' column if it does not exist
SELECT COUNT(*) INTO col_exists FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='HZB_ALERT_DEFINE' AND COLUMN_NAME='preset';
IF col_exists = 0 THEN
ALTER TABLE HZB_ALERT_DEFINE ADD COLUMN preset BOOLEAN;
END IF;
-- Drop 'preset' column if it exists
SELECT COUNT(*) INTO col_exists FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='HZB_ALERT_DEFINE' AND COLUMN_NAME='preset';
IF col_exists = 1 THEN
ALTER TABLE HZB_ALERT_DEFINE DROP COLUMN preset;
END IF;
-- Check and add 'priority' column if it does not exist
SELECT COUNT(*) INTO col_exists FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='HZB_ALERT_DEFINE' AND COLUMN_NAME='priority';
IF col_exists = 0 THEN
ALTER TABLE HZB_ALERT_DEFINE ADD COLUMN priority INTEGER;
END IF;
-- Drop 'priority' column if it exists
SELECT COUNT(*) INTO col_exists FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='HZB_ALERT_DEFINE' AND COLUMN_NAME='priority';
IF col_exists = 1 THEN
ALTER TABLE HZB_ALERT_DEFINE DROP COLUMN priority;
END IF;
-- Check and add 'tags' column if it does not exist
SELECT COUNT(*) INTO col_exists FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='HZB_ALERT_DEFINE' AND COLUMN_NAME='tags';
IF col_exists = 0 THEN
ALTER TABLE HZB_ALERT_DEFINE ADD COLUMN tags VARCHAR(255);
END IF;
-- Drop 'tags' column if it exists
SELECT COUNT(*) INTO col_exists FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='HZB_ALERT_DEFINE' AND COLUMN_NAME='tags';
IF col_exists = 1 THEN
@@ -66,5 +93,4 @@ DELIMITER ;
CALL AddOrDropColumns();
DROP PROCEDURE IF EXISTS AddOrDropColumns;
commit;
DROP PROCEDURE IF EXISTS AddOrDropColumns;
@@ -246,20 +246,6 @@ params:
defaultValue: '200, 201'
# hide param-true or false
hide: true
# field-param field key
- field: enableUrlEncoding
# name-param field display i18n name
name:
zh-CN: 启用URL编码(UTF-8
en-US: Enable URL Encoding(UTF-8)
# type-param field type(most mapping the html input type)
type: boolean
# required-true or false
required: true
# default value
defaultValue: true
# hide param-true or false
hide: true
# collect metrics config list
metrics:
# metrics - summary
@@ -300,8 +286,6 @@ metrics:
method: ^_^httpMethod^_^
# if enabled https
ssl: ^_^ssl^_^
# enable url encoding
enableUrlEncoding: ^_^enableUrlEncoding^_^
# http request payload
payload: ^_^payload^_^
# http request header content
@@ -360,8 +344,6 @@ metrics:
method: GET
# if enabled https
ssl: ^_^ssl^_^
# enable url encoding
enableUrlEncoding: ^_^enableUrlEncoding^_^
authorization:
# http auth type: Basic Auth, Digest Auth, Bearer Token
type: ^_^authType^_^
@@ -1,247 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# The monitoring type categoryservice-application service monitoring db-database monitoring custom-custom monitoring os-operating system monitoring
category: server
# The monitoring type eg: linux windows tomcat mysql aws...
app: dahua
# The monitoring i18n name
name:
zh-CN: 大华
en-US: Dahua
# The description and help of this monitoring type
help:
zh-CN: 通过http接口监控大华设备状态,获取设备健康数据。
en-US: Monitor Dahua devices through http interface to collect health data.
# Input params define for monitoring(render web ui by the definition)
params:
- field: host
name:
zh-CN: 主机Host
en-US: Host
type: host
required: true
- field: port
name:
zh-CN: 端口
en-US: Port
type: number
range: '[0,65535]'
required: true
defaultValue: 80
- field: timeout
name:
zh-CN: 超时时间(ms)
en-US: Timeout(ms)
type: number
range: '[1000,60000]'
required: true
defaultValue: 5000
- field: username
name:
zh-CN: 用户名
en-US: Username
type: text
required: true
- field: password
name:
zh-CN: 密码
en-US: Password
type: password
required: true
- field: ssl
name:
zh-CN: 启用HTTPS
en-US: SSL
type: boolean
required: false
defaultValue: false
# collect metrics config list
metrics:
- name: network_info # 指标集合名称
i18n:
zh-CN: 网络信息
en-US: Network Info
priority: 0
protocol: http
http:
host: ^_^host^_^
port: ^_^port^_^
ssl: ^_^ssl^_^
url: /cgi-bin/configManager.cgi?action=getConfig&name=Network
method: GET
timeout: ^_^timeout^_^
authorization:
type: Digest Auth
digestAuthUsername: ^_^username^_^
digestAuthPassword: ^_^password^_^
parseType: config
fields:
- field: default_interface
type: 1
i18n:
zh-CN: 默认网卡
en-US: Default Interface
- field: domain_name
type: 1
i18n:
zh-CN: 域名
en-US: Domain Name
- field: hostname
type: 1
i18n:
zh-CN: 主机名
en-US: Hostname
- field: eth0_ip_address
type: 1
i18n:
zh-CN: 网卡 eth0 IP地址
en-US: Interface eth0 IP Address
- field: eth0_gateway
type: 1
i18n:
zh-CN: 网卡 eth0 默认网关
en-US: Interface eth0 Default Gateway
- field: eth0_mac_address
type: 1
i18n:
zh-CN: 网卡 eth0 物理地址
en-US: Interface eth0 Physical Address
- field: eth0_subnet_mask
type: 1
i18n:
zh-CN: 网卡 eth0 子网掩码
en-US: Interface eth0 Subnet Mask
- field: eth0_mtu
type: 0
i18n:
zh-CN: 网卡 eth0 MTU
en-US: Interface eth0 MTU
- field: eth0_dns1
type: 1
i18n:
zh-CN: 网卡 eth0 DNS服务器1
en-US: Interface eth0 DNS Server 1
- field: eth0_dns2
type: 1
i18n:
zh-CN: 网卡 eth0 DNS服务器2
en-US: Interface eth0 DNS Server 2
aliasFields:
- table.Network.DefaultInterface
- table.Network.Domain
- table.Network.Hostname
- table.Network.eth0.IPAddress
- table.Network.eth0.DefaultGateway
- table.Network.eth0.PhysicalAddress
- table.Network.eth0.SubnetMask
- table.Network.eth0.MTU
- table.Network.eth0.DnsServers[0]
- table.Network.eth0.DnsServers[1]
calculates:
- default_interface=table.Network.DefaultInterface
- domain_name=table.Network.Domain
- hostname=table.Network.Hostname
- eth0_ip_address=table.Network.eth0.IPAddress
- eth0_gateway=table.Network.eth0.DefaultGateway
- eth0_mac_address=table.Network.eth0.PhysicalAddress
- eth0_subnet_mask=table.Network.eth0.SubnetMask
- eth0_mtu=table.Network.eth0.MTU
- eth0_dns1=table.Network.eth0.DnsServers[0]
- eth0_dns2=table.Network.eth0.DnsServers[1]
- name: user_info
i18n:
zh-CN: 用户信息
en-US: User Info
priority: 0
protocol: http
http:
host: ^_^host^_^
port: ^_^port^_^
ssl: ^_^ssl^_^
url: /cgi-bin/userManager.cgi?action=getActiveUserInfoAll
method: GET
timeout: ^_^timeout^_^
authorization:
type: Digest Auth
digestAuthUsername: ^_^username^_^
digestAuthPassword: ^_^password^_^
parseType: config
parseScript: users
fields:
- field: ClientAddress
type: 1
i18n:
zh-CN: 客户端地址
en-US: ClientAddress
- field: Name
type: 1
i18n:
zh-CN: 客户端用户
en-US: Name
- field: ClientType
type: 1
i18n:
zh-CN: 客户端登录类型
en-US: ClientType
- field: LoginTime
type: 1
i18n:
zh-CN: 客户端登录时间
en-US: LoginTime
- name: ntp_info
i18n:
zh-CN: 校时信息
en-US: Ntp Info
priority: 0
protocol: http
http:
host: ^_^host^_^
port: ^_^port^_^
ssl: ^_^ssl^_^
url: /cgi-bin/configManager.cgi?action=getConfig&name=NTP
method: GET
timeout: ^_^timeout^_^
authorization:
type: Digest Auth
digestAuthUsername: ^_^username^_^
digestAuthPassword: ^_^password^_^
parseType: config
fields:
- field: ntp_address
type: 1
i18n:
zh-CN: 校时服务器
en-US: Ntp Address
- field: ntp_port
type: 1
i18n:
zh-CN: 校时端口
en-US: Ntp Port
- field: ntp_update_period
type: 1
i18n:
zh-CN: 校时间隔
en-US: Ntp Update Period
aliasFields:
- table.NTP.Address
- table.NTP.Port
- table.NTP.UpdatePeriod
calculates:
- ntp_address=table.NTP.Address
- ntp_port=table.NTP.Port
- ntp_update_period=table.NTP.UpdatePeriod
@@ -1,268 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# The monitoring type categoryservice-application service monitoring db-database monitoring custom-custom monitoring os-operating system monitoring
category: server
# The monitoring type eg: linux windows tomcat mysql aws...
app: hikvision_isapi
# The monitoring i18n name
name:
zh-CN: 海康威视 ISAPI
en-US: Hikvision ISAPI
# The description and help of this monitoring type
help:
zh-CN: 通过ISAPI接口监控海康威视设备状态,获取设备健康数据。
en-US: Monitor Hikvision devices through ISAPI interface to collect health data.
# Input params define for monitoring(render web ui by the definition)
params:
- field: host
name:
zh-CN: 主机Host
en-US: Host
type: host
required: true
- field: port
name:
zh-CN: 端口
en-US: Port
type: number
range: '[0,65535]'
required: true
defaultValue: 80
- field: timeout
name:
zh-CN: 超时时间(ms)
en-US: Timeout(ms)
type: number
range: '[1000,60000]'
required: true
defaultValue: 5000
- field: username
name:
zh-CN: 用户名
en-US: Username
type: text
required: true
- field: password
name:
zh-CN: 密码
en-US: Password
type: password
required: true
- field: ssl
name:
zh-CN: 启用HTTPS
en-US: SSL
type: boolean
required: false
defaultValue: false
# collect metrics config list
metrics:
- name: system_info
i18n:
zh-CN: 系统信息
en-US: System Info
priority: 0
protocol: http
http:
host: ^_^host^_^
port: ^_^port^_^
ssl: ^_^ssl^_^
url: /ISAPI/System/deviceInfo
method: GET
timeout: ^_^timeout^_^
authorization:
type: Digest Auth
digestAuthUsername: ^_^username^_^
digestAuthPassword: ^_^password^_^
parseType: xmlPath
parseScript: //DeviceInfo
fields:
- field: deviceName
type: 1
i18n:
zh-CN: 设备名称
en-US: Device Name
- field: deviceID
type: 1
i18n:
zh-CN: 设备ID
en-US: Device ID
- field: firmwareVersion
type: 1
i18n:
zh-CN: 固件版本
en-US: Firmware Version
- field: model
type: 1
i18n:
zh-CN: 设备型号
en-US: Device Model
- field: macAddress
type: 1
i18n:
zh-CN: mac地址
en-US: Mac Address
- name: status
i18n:
zh-CN: 设备状态
en-US: Status
priority: 0
protocol: http
http:
host: ^_^host^_^
port: ^_^port^_^
ssl: ^_^ssl^_^
url: /ISAPI/System/status
method: GET
timeout: ^_^timeout^_^
authorization:
type: Digest Auth
digestAuthUsername: ^_^username^_^
digestAuthPassword: ^_^password^_^
parseType: xmlPath
parseScript: //DeviceStatus
fields:
- field: CPU_utilization
i18n:
zh-CN: CPU 利用率
en-US: CPU Utilization
type: 0
unit: '%'
- field: memory_usage
i18n:
zh-CN: 内存使用量
en-US: Memory Usage
type: 0
unit: MB
- field: memory_available
i18n:
zh-CN: 可用内存
en-US: Memory Available
type: 0
unit: MB
- field: cache_size
i18n:
zh-CN: 缓存大小
en-US: Cache Size
type: 0
unit: MB
- field: net_port_1_speed
i18n:
zh-CN: 网口1速度
en-US: Net Port 1 Speed
type: 0
unit: Mbps
- field: net_port_2_speed
i18n:
zh-CN: 网口2速度
en-US: Net Port 2 Speed
type: 0
unit: Mbps
- field: boot_time
i18n:
zh-CN: 启动时间
en-US: Boot Time
type: 1
- field: device_uptime
i18n:
zh-CN: 运行时长
en-US: Device Uptime
type: 1
- field: last_calibration_time
i18n:
zh-CN: 上次校时时间
en-US: Last Calibration Time
type: 1
- field: last_calibration_time_diff
i18n:
zh-CN: 上次校时时间差
en-US: Last Calibration Time Diff
type: 0
unit: s
- field: avg_upload_time
i18n:
zh-CN: 平均上传耗时
en-US: Avg Upload Time
type: 0
unit: ms
- field: max_upload_time
i18n:
zh-CN: 最大上传耗时
en-US: Max Upload Time
type: 0
unit: ms
- field: min_upload_time
i18n:
zh-CN: 最小上传耗时
en-US: Min Upload Time
type: 0
unit: ms
- field: last_calibration_mode
i18n:
zh-CN: 上次校时模式
en-US: Last Calibration Mode
type: 1
- field: last_calibration_address
i18n:
zh-CN: 上次校时地址
en-US: Last Calibration Address
type: 1
- field: response_time
i18n:
zh-CN: 响应时间
en-US: Response Time
type: 0
unit: ms
aliasFields:
- CPUList/CPU/cpuUtilization
- MemoryList/Memory/memoryUsage
- MemoryList/Memory/memoryAvailable
- MemoryList/Memory/cacheSize
- NetPortStatusList/NetPortStatus[id='1']/workSpeed
- NetPortStatusList/NetPortStatus[id='2']/workSpeed
- bootTime
- deviceUpTime
- lastCalibrationTime
- lastCalibrationTimeDiff
- uploadTimeConsumingList/avgTime
- uploadTimeConsumingList/maxTime
- uploadTimeConsumingList/minTime
- lastCalibrationTimeMode
- lastCalibrationTimeAddress
- responseTime
calculates:
- CPU_utilization=CPUList/CPU/cpuUtilization
- memory_usage=MemoryList/Memory/memoryUsage
- memory_available=MemoryList/Memory/memoryAvailable
- cache_size=MemoryList/Memory/cacheSize
- net_port_1_speed=NetPortStatusList/NetPortStatus[id='1']/workSpeed
- net_port_2_speed=NetPortStatusList/NetPortStatus[id='2']/workSpeed
- boot_time=bootTime
- device_uptime=deviceUpTime
- last_calibration_time=lastCalibrationTime
- last_calibration_time_diff=lastCalibrationTimeDiff
- avg_upload_time=uploadTimeConsumingList/avgTime
- max_upload_time=uploadTimeConsumingList/maxTime
- min_upload_time=uploadTimeConsumingList/minTime
- last_calibration_mode=lastCalibrationTimeMode
- last_calibration_address=lastCalibrationTimeAddress
- response_time=responseTime
units:
- memory_usage=KB->MB
- memory_available=KB->MB
- cache_size=KB->MB
@@ -1,263 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# The monitoring type categoryservice-application service monitoring db-database monitoring mid-middleware custom-custom monitoring os-operating system monitoring
category: service
# The monitoring type eg: linux windows tomcat mysql aws...
app: s7
# The app api i18n name
name:
zh-CN: s7服务器
en-US: s7 Server
# The description and help of this monitoring type
help:
zh-CN: HertzBeat对支持s7协议的服务进行(保持寄存器和线圈)相关指标进行采集
en-US: HertzBeat collects metrics related to maintaining registers and coils for services that support s7 protocol
zh-TW: HertzBeat對支持s7協定的服務進行(保持寄存器和線圈)相關名額進行採集
# Input params define for monitoring(render web ui by the definition)
params:
# field-param field key
- field: host
# name-param field display i18n name
name:
zh-CN: s7服务Host
en-US: s7 Server Host
# type-param field type(most mapping the html input type)
type: host
# required-true or false
required: true
# field-param field key
- field: port
# name-param field display i18n name
name:
zh-CN: 端口
en-US: Port
# type-param field type(most mapping the html input type)
type: number
# when type is number, range is required
range: '[0,65535]'
# required-true or false
required: true
# default value
defaultValue: 102
- field: rackId
# name-param field display i18n name
name:
zh-CN: rackId
en-US: rackId
# type-param field type(most mapping the html input type)
type: text
# required-true or false
required: false
defaultValue: 0
- field: slotId
# name-param field display i18n name
name:
zh-CN: slotId
en-US: slotId
# type-param field type(most mapping the html input type)
type: text
# required-true or false
required: false
defaultValue: 0
- field: controllerType
# name-param field display i18n name
name:
zh-CN: controllerType
en-US: controllerType
# type-param field type(most mapping the html input type)
type: text
# required-true or false
required: false
defaultValue: S7_1500
- field: timeout
name:
zh-CN: 请求超时时间(ms)
en-US: Request Timeout(ms)
type: number
# when type is number, range is required
range: '[400,200000]'
required: false
defaultValue: 6000
hide: true
- field: holdingRegisterAddresses
# name-param field display i18n name
name:
zh-CN: 保持寄存器地址
en-US: Holding Registers address
# type-param field type(most mapping the html input type)
type: array
# param field input placeholder
placeholder: 'Input RegisterAddress'
# required-true or false
required: true
# hide param-true or false
# hide: true
- field: coilRegisterAddresses
# name-param field display i18n name
name:
zh-CN: 线圈寄存器地址
en-US: Coil Register address
# type-param field type(most mapping the html input type)
type: array
# param field input placeholder
placeholder: 'Input RegisterAddress'
# required-true or false
required: true
# hide param-true or false
# hide: true
# collect metrics config list
metrics:
# metrics - summary
- name: holding-register
i18n:
zh-CN: 保持寄存器 统计信息
en-US: holding-register stats
# metrics scheduling priority(0->127)->(high->low), metrics with the same priority will be scheduled in parallel
# priority 0's metrics is availability metrics, it will be scheduled first, only availability metrics collect success will the scheduling continue
priority: 0
# collect metrics content
fields:
- field: responseTime
type: 0
unit: ms
i18n:
zh-CN: 响应时间
en-US: Response Time
# field-metric name, type-metric type(0-number,1-string), unit-metric unit('%','ms','MB'), label-whether it is a metrics label field
- field: address-0
type: 0
i18n:
zh-CN: address-0
en-US: address-0
# field-metric name, type-metric type(0-number,1-string), unit-metric unit('%','ms','MB'), label-whether it is a metrics label field
- field: address-1
type: 0
i18n:
zh-CN: address-1
en-US: address-1
- field: address-2
type: 0
i18n:
zh-CN: address-2
en-US: address-2
- field: address-3
type: 0
i18n:
zh-CN: address-3
en-US: address-3
# 指标别名列表,按照寄存器地址来进行命名的
# 例如 地址为下标为0 ,值为m holding-register:0
# 地址为下标为1,值为m[2] holding-register:1-0 holding-register:1-1
aliasFields:
- responseTime
- holding-register:0
- holding-register:1
- holding-register:2
- holding-register:3
# mapping and conversion expressions, use thesand aliasField above to calculate metrics value# (可选)指标映射转换计算表达式,与上面的别名一起作用,计算出最终需要的指标值# eg: cores=core1+core2, usage=usage, waitTimeallTime-runningTime
calculates:
- responseTime=responseTime
- address-0=holding-register:0
- address-1=holding-register:1
- address-2=holding-register:2
- address-3=holding-register:3
protocol: s7
# the config content when protocol is http
s7:
# host
host: ^_^host^_^
# port
port: ^_^port^_^
driverName: s7
addressSyntax: holding-register
rackId: ^_^rackId^_^
slotId: ^_^slotId^_^
controllerType: ^_^controllerType^_^
timeout: ^_^timeout^_^
registerAddresses: [ ^_^holdingRegisterAddresses^_^ ]
# metrics - summary
- name: coil
i18n:
zh-CN: 线圈 统计信息
en-US: coil stats
# metrics scheduling priority(0->127)->(high->low), metrics with the same priority will be scheduled in parallel
# priority 0's metrics is availability metrics, it will be scheduled first, only availability metrics collect success will the scheduling continue
priority: 1
# collect metrics content
fields:
- field: responseTime
type: 0
unit: ms
i18n:
zh-CN: 响应时间
en-US: Response Time
# field-metric name, type-metric type(0-number,1-string), unit-metric unit('%','ms','MB'), label-whether it is a metrics label field
- field: address-0
type: 0
i18n:
zh-CN: address-0
en-US: address-0
# field-metric name, type-metric type(0-number,1-string), unit-metric unit('%','ms','MB'), label-whether it is a metrics label field
- field: address-1
type: 0
i18n:
zh-CN: address-1
en-US: address-1
- field: address-2
type: 0
i18n:
zh-CN: address-2
en-US: address-2
- field: address-3
type: 0
i18n:
zh-CN: address-3
en-US: address-3
# 指标别名列表,按照寄存器地址来进行命名的
# 例如 地址为下标为0 ,值为m coil:0
# 地址为下标为1,值为m[2] coil:1-0 coil:1-1
aliasFields:
- responseTime
- coil:0
- coil:1
- coil:2
- coil:3
# mapping and conversion expressions, use thesand aliasField above to calculate metrics value# (可选)指标映射转换计算表达式,与上面的别名一起作用,计算出最终需要的指标值# eg: cores=core1+core2, usage=usage, waitTimeallTime-runningTime
calculates:
- responseTime=responseTime
- address-0=coil:0
- address-1=coil:1
- address-2=coil:2
- address-3=coil:3
protocol: s7
# the config content when protocol is http
s7:
# host
host: ^_^host^_^
# port
port: ^_^port^_^
driverName: s7
addressSyntax: coil
rackId: ^_^rackId^_^
slotId: ^_^slotId^_^
controllerType: ^_^controllerType^_^
timeout: ^_^timeout^_^
registerAddresses: [ ^_^coilRegisterAddresses^_^ ]
@@ -1,154 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# The monitoring type categoryservice-application service monitoring db-database monitoring custom-custom monitoring os-operating system monitoring
category: server
# The monitoring type eg: linux windows tomcat mysql aws...
app: uniview
# The monitoring i18n name
name:
zh-CN: 宇视
en-US: Uniview
# The description and help of this monitoring type
help:
zh-CN: 通过HTTP接口监控宇视设备状态,获取设备健康数据。
en-US: Monitor Uniview devices through HTTP interface to collect health data.
# Input params define for monitoring(render web ui by the definition)
params:
- field: host
name:
zh-CN: 主机Host
en-US: Host
type: host
required: true
- field: port
name:
zh-CN: 端口
en-US: Port
type: number
range: '[0,65535]'
required: true
defaultValue: 80
- field: timeout
name:
zh-CN: 超时时间(ms)
en-US: Timeout(ms)
type: number
range: '[1000,60000]'
required: true
defaultValue: 5000
- field: username
name:
zh-CN: 用户名
en-US: Username
type: text
required: true
- field: password
name:
zh-CN: 密码
en-US: Password
type: password
required: true
- field: ssl
name:
zh-CN: 启用HTTPS
en-US: SSL
type: boolean
required: false
defaultValue: false
# collect metrics config list
metrics:
- name: system_info
i18n:
zh-CN: 系统信息
en-US: System Info
priority: 0
protocol: http
http:
host: ^_^host^_^
port: ^_^port^_^
ssl: ^_^ssl^_^
url: /LAPI/V1.0/System/DeviceInfo
method: GET
timeout: ^_^timeout^_^
authorization:
type: Digest Auth
digestAuthUsername: ^_^username^_^
digestAuthPassword: ^_^password^_^
parseType: jsonPath
parseScript: '$.Response.Data'
fields:
- field: DeviceName
type: 1
i18n:
zh-CN: 设备名称
en-US: Device Name
- field: SerialNumber
type: 1
i18n:
zh-CN: 序列号
en-US: SerialNumber
- field: FirmwareVersion
type: 1
i18n:
zh-CN: 固件版本
en-US: Firmware Version
- field: DeviceModel
type: 1
i18n:
zh-CN: 设备型号
en-US: Device Model
- name: ntp_info
i18n:
zh-CN: 校时信息
en-US: NTP Info
priority: 0
protocol: http
http:
host: ^_^host^_^
port: ^_^port^_^
ssl: ^_^ssl^_^
url: /LAPI/V1.0/System/Time/NTP
method: GET
timeout: ^_^timeout^_^
authorization:
type: Digest Auth
digestAuthUsername: ^_^username^_^
digestAuthPassword: ^_^password^_^
parseType: jsonPath
parseScript: '$.Response.Data.NTPServerInfos[0]'
fields:
- field: IPAddress
type: 1
i18n:
zh-CN: IP地址
en-US: IPAddress
- field: Port
type: 1
i18n:
zh-CN: 端口号
en-US: Port
- field: SynchronizeInterval
type: 1
i18n:
zh-CN: 同步间隔
en-US: SynchronizeInterval
- field: Enabled
type: 1
i18n:
zh-CN: 是否启用
en-US: Enabled
@@ -76,26 +76,6 @@
</filter>
</appender>
<!-- OpenTelemetry Appender for shipping logs to GrepTimeDB -->
<appender name="OpenTelemetryAppender" class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender">
<!-- Capture source code information (class name, method name, line number) -->
<captureCodeAttributes>true</captureCodeAttributes>
<!-- Capture MDC context values -->
<captureMdcAttributes>
<pattern>.*</pattern>
</captureMdcAttributes>
<!-- Capture experimental attributes like exception details -->
<captureExperimentalAttributes>true</captureExperimentalAttributes>
<!-- Capture marker attributes -->
<captureMarkerAttribute>true</captureMarkerAttribute>
<!-- Capture logger context attributes -->
<captureLoggerContext>true</captureLoggerContext>
</appender>
<!-- Settings for this logger: for example, all output logs under the org.springframework package must be at level info or above to be output! -->
<!-- This can avoid outputting many common debug information of the spring framework! -->
<logger name="org.springframework" level="info"/>
@@ -110,14 +90,12 @@
<logger name="org.mongodb" level="warn"/>
<logger name="io.greptime" level="warn"/>
<logger name="org.apache.kafka" level="warn"/>
<logger name="io.opentelemetry" level="info"/>
<!-- Production environment configuration -->
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="SystemOutFileAppender"/>
<appender-ref ref="ErrOutFileAppender"/>
<appender-ref ref="OpenTelemetryAppender"/>
</root>
<!-- North log -->
<Logger name="com.obs.services.AbstractClient" level="OFF"
@@ -140,7 +118,6 @@
<appender-ref ref="ConsoleAppender"/>
<appender-ref ref="SystemOutFileAppender"/>
<appender-ref ref="ErrOutFileAppender"/>
<appender-ref ref="OpenTelemetryAppender"/>
</root>
</springProfile>
@@ -150,7 +127,6 @@
<appender-ref ref="ConsoleAppender"/>
<appender-ref ref="SystemOutFileAppender"/>
<appender-ref ref="ErrOutFileAppender"/>
<appender-ref ref="OpenTelemetryAppender"/>
</root>
</springProfile>
@@ -160,8 +136,7 @@
<appender-ref ref="ConsoleAppender"/>
<appender-ref ref="SystemOutFileAppender"/>
<appender-ref ref="ErrOutFileAppender"/>
<appender-ref ref="OpenTelemetryAppender"/>
</root>
</springProfile>
</configuration>
</configuration>
@@ -21,9 +21,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.hertzbeat.alert.config.SmsConfig;
import org.apache.hertzbeat.common.constants.GeneralConfigTypeEnum;
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
import org.apache.hertzbeat.manager.pojo.dto.SmsNoticeSender;
import org.apache.hertzbeat.manager.service.impl.SmsGeneralConfigServiceImpl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -66,9 +66,9 @@ class SmsGeneralConfigServiceTest {
@Test
void testGetTypeReference() {
TypeReference<SmsConfig> typeReference = service.getTypeReference();
TypeReference<SmsNoticeSender> typeReference = service.getTypeReference();
assertNotNull(typeReference);
assertEquals(SmsConfig.class, typeReference.getType());
assertEquals(SmsNoticeSender.class, typeReference.getType());
}
}
-27
View File
@@ -1,27 +0,0 @@
# Hertzbeat-MCP
## Hertzbeat-Log-MCP
Log MCP Service Based on GreptimeDB.
- GreptimeDB log writing needs to be enabled.
## Claude Desktop Integration (stdio)
```json
{
"mcpServers": {
"hertzbeat-mcp": {
"command": "java",
"args": [
"-Dspring.ai.mcp.server.stdio=true",
"-Dspring.main.web-application-type=none",
"-Dlogging.pattern.console=",
"-Dgreptime.url=http://${IP}:4000",
"-jar",
"${PATH}/hertzbeat-mcp-2.0-SNAPSHOT.jar"
]
}
}
}
```
-107
View File
@@ -1,107 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Licensed to the Apache Software Foundation (ASF) under one or more
~ contributor license agreements. See the NOTICE file distributed with
~ this work for additional information regarding copyright ownership.
~ The ASF licenses this file to You under the Apache License, Version 2.0
~ (the "License"); you may not use this file except in compliance with
~ the License. You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.hertzbeat</groupId>
<artifactId>hertzbeat</artifactId>
<version>2.0-SNAPSHOT</version>
</parent>
<artifactId>hertzbeat-mcp</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-ai.version>1.0.0-M6</spring-ai.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.4.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-webflux-spring-boot-starter</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<!-- json path parser-->
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<release>${java.version}</release>
<compilerArgs>
<compilerArg>-parameters</compilerArg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -1,44 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.mcp.server;
import org.apache.hertzbeat.mcp.server.service.LogService;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
/**
* MCP Server Application
*/
@SpringBootApplication
public class McpServerApplication {
public static void main(String[] args) {
SpringApplication.run(McpServerApplication.class, args);
}
@Bean
public ToolCallbackProvider tools(
LogService logService) {
return MethodToolCallbackProvider.builder()
.toolObjects(logService)
.build();
}
}

Some files were not shown because too many files have changed in this diff Show More