Compare commits

..
Author SHA1 Message Date
a-little-fool 5c98843efb correct home's new_committer_process 2025-02-21 17:52:39 +08:00
702 changed files with 6264 additions and 23925 deletions
-63
View File
@@ -2298,69 +2298,6 @@
"contributions": [
"code"
]
},
{
"login": "yasminvo",
"name": "yasminvo",
"avatar_url": "https://avatars.githubusercontent.com/u/107528848?v=4",
"profile": "https://github.com/yasminvo",
"contributions": [
"code"
]
},
{
"login": "notbugggg",
"name": "不关银渐层的事哦",
"avatar_url": "https://avatars.githubusercontent.com/u/147966331?v=4",
"profile": "https://github.com/notbugggg",
"contributions": [
"code"
]
},
{
"login": "yyahang",
"name": "yyahang",
"avatar_url": "https://avatars.githubusercontent.com/u/90464876?v=4",
"profile": "https://github.com/yyahang",
"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"
]
}
],
"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
-7
View File
@@ -1,7 +0,0 @@
FROM gitpod/workspace-full
USER gitpod
RUN bash -c ". /home/gitpod/.sdkman/bin/sdkman-init.sh && \
sdk install java 17.0.3-ms && \
sdk default java 17.0.3-ms"
+2 -3
View File
@@ -1,6 +1,3 @@
image:
file: .gitpod.Dockerfile
tasks:
- name: Run backend
@@ -18,6 +15,8 @@ tasks:
- name: Setup backend
init: |
sdk install java 17.0.11.fx-zulu < /dev/null
sdk default java 17.0.11.fx-zulu < /dev/null
mvn clean install -DskipTests
command: |
gp sync-done setup-backend
+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/).
+2 -11
View File
@@ -12,7 +12,7 @@
[![Discord](https://img.shields.io/badge/Chat-Discord-7289DA?logo=discord)](https://discord.gg/Fb6M73htGr)
[![Reddit](https://img.shields.io/badge/Reddit-Community-7289DA?logo=reddit)](https://www.reddit.com/r/hertzbeat/)
[![Twitter](https://img.shields.io/twitter/follow/hertzbeat1024?logo=twitter)](https://x.com/hertzbeat1024)
[![Twitter](https://img.shields.io/twitter/follow/hertzbeat1024?logo=twitter)](https://twitter.com/hertzbeat1024)
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/8139/badge)](https://www.bestpractices.dev/projects/8139)
[![codecov](https://codecov.io/gh/apache/HertzBeat/branch/master/graph/badge.svg)](https://app.codecov.io/gh/apache/hertzbeat)
[![Docker Pulls](https://img.shields.io/docker/pulls/apache/hertzbeat?style=%20for-the-badge&logo=docker&label=DockerHub%20Download)](https://hub.docker.com/r/apache/hertzbeat)
@@ -507,15 +507,6 @@ Thanks to these wonderful people, welcome to join us:
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/myangle1120"><img src="https://avatars.githubusercontent.com/u/19237013?v=4?s=100" width="100px;" alt="myangle1120"/><br /><sub><b>myangle1120</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=myangle1120" title="Code">💻</a></td>
<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>
</tr>
</tbody>
</table>
@@ -543,7 +534,7 @@ WeChat Public : Search ID `usthecom`.
[Reddit Community](https://www.reddit.com/r/hertzbeat/)
[Follow Us Twitter](https://x.com/hertzbeat1024)
[Follow Us Twitter](https://twitter.com/hertzbeat1024)
[Subscribe YouTube](https://www.youtube.com/channel/UCri75zfWX0GHqJFPENEbLow)
+2 -11
View File
@@ -12,7 +12,7 @@
[![Discord](https://img.shields.io/badge/Chat-Discord-7289DA?logo=discord)](https://discord.gg/Fb6M73htGr)
[![Reddit](https://img.shields.io/badge/Reddit-Community-7289DA?logo=reddit)](https://www.reddit.com/r/hertzbeat/)
[![Twitter](https://img.shields.io/twitter/follow/hertzbeat1024?logo=twitter)](https://x.com/hertzbeat1024)
[![Twitter](https://img.shields.io/twitter/follow/hertzbeat1024?logo=twitter)](https://twitter.com/hertzbeat1024)
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/8139/badge)](https://www.bestpractices.dev/projects/8139)
[![codecov](https://codecov.io/gh/apache/HertzBeat/branch/master/graph/badge.svg)](https://app.codecov.io/gh/apache/hertzbeat)
[![Docker Pulls](https://img.shields.io/docker/pulls/apache/hertzbeat?style=%20for-the-badge&logo=docker&label=DockerHub%20Download)](https://hub.docker.com/r/apache/hertzbeat)
@@ -506,15 +506,6 @@ Thanks these wonderful people, welcome to join us:
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/myangle1120"><img src="https://avatars.githubusercontent.com/u/19237013?v=4?s=100" width="100px;" alt="myangle1120"/><br /><sub><b>myangle1120</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=myangle1120" title="Code">💻</a></td>
<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>
</tr>
</tbody>
</table>
@@ -542,7 +533,7 @@ Thanks these wonderful people, welcome to join us:
[Reddit Community](https://www.reddit.com/r/hertzbeat/)
[Follow Us Twitter](https://x.com/hertzbeat1024)
[Follow Us Twitter](https://twitter.com/hertzbeat1024)
[Subscribe YouTube](https://www.youtube.com/channel/UCri75zfWX0GHqJFPENEbLow)
-5
View File
@@ -73,11 +73,6 @@
"field": "successCode",
"type": 4,
"paramValue": "200, 201"
},
{
"field": "enableUrlEncoding",
"type": 1,
"paramValue": true
}
]
}
@@ -55,8 +55,6 @@ public class RealTimeAlertCalculator {
private static final int CALCULATE_THREADS = 3;
private static final String KEY_INSTANCE = "__instance__";
private static final String KEY_INSTANCE_NAME = "__instancename__";
private static final String KEY_INSTANCE_HOST = "__instancehost__";
private static final String KEY_APP = "__app__";
private static final String KEY_METRICS = "__metrics__";
private static final String KEY_PRIORITY = "__priority__";
@@ -127,14 +125,10 @@ public class RealTimeAlertCalculator {
private void calculate(CollectRep.MetricsData metricsData) {
long currentTimeMilli = System.currentTimeMillis();
String instance = String.valueOf(metricsData.getId());
String instanceName = metricsData.getInstanceName();
String instanceHost = metricsData.getInstanceHost();
String app = metricsData.getApp();
String metrics = metricsData.getMetrics();
int priority = metricsData.getPriority();
int code = metricsData.getCode().getNumber();
Map<String, String> labels = metricsData.getLabels();
Map<String, String> annotations = metricsData.getAnnotations();
List<AlertDefine> thresholds = this.alertDefineService.getRealTimeAlertDefines();
// Filter thresholds by app, metrics and instance
thresholds = filterThresholdsByAppAndMetrics(thresholds, app, metrics, instance, priority);
@@ -143,8 +137,6 @@ public class RealTimeAlertCalculator {
}
Map<String, Object> commonContext = new HashMap<>(8);
commonContext.put(KEY_INSTANCE, instance);
commonContext.put(KEY_INSTANCE_NAME, instanceName);
commonContext.put(KEY_INSTANCE_HOST, instanceHost);
commonContext.put(KEY_APP, app);
commonContext.put(KEY_PRIORITY, priority);
commonContext.put(KEY_CODE, code);
@@ -152,9 +144,6 @@ public class RealTimeAlertCalculator {
if (priority == 0) {
commonContext.put(KEY_AVAILABLE, metricsData.getCode() == CollectRep.Code.SUCCESS ? UP : DOWN);
}
if (labels != null) {
commonContext.putAll(labels);
}
List<CollectRep.Field> fields = metricsData.getFields();
Map<String, Object> fieldValueMap = new HashMap<>(8);
int valueRowCount = metricsData.getValuesCount();
@@ -171,26 +160,21 @@ public class RealTimeAlertCalculator {
if (StringUtils.isBlank(expr)) {
continue;
}
Map<String, String> commonFingerPrints = new HashMap<>(8);
commonFingerPrints.put(CommonConstants.LABEL_INSTANCE, instance);
// here use the alert name as finger, not care the alert name may be changed
commonFingerPrints.put(CommonConstants.LABEL_ALERT_NAME, define.getName());
commonFingerPrints.put(CommonConstants.LABEL_INSTANCE_NAME, instanceName);
commonFingerPrints.put(CommonConstants.LABEL_INSTANCE_HOST, instanceHost);
commonFingerPrints.putAll(define.getLabels());
if (labels != null) {
commonFingerPrints.putAll(labels);
}
{
// trigger the expr before the metrics data, due the available up down or others
try {
boolean match = execAlertExpression(fieldValueMap, expr, true);
try {
Map<String, String> fingerPrints = new HashMap<>(8);
fingerPrints.put(CommonConstants.LABEL_INSTANCE, instance);
// here use the alert name as finger, not care the alert name may be changed
fingerPrints.put(CommonConstants.LABEL_ALERT_NAME, define.getName());
fingerPrints.putAll(define.getLabels());
if (match) {
// If the threshold rule matches, the number of times the threshold has been triggered is determined and an alarm is triggered
afterThresholdRuleMatch(currentTimeMilli, commonFingerPrints, fieldValueMap, define, annotations);
afterThresholdRuleMatch(currentTimeMilli, fingerPrints, fieldValueMap, define);
} else {
handleRecoveredAlert(commonFingerPrints);
handleRecoveredAlert(fingerPrints);
}
// if this threshold pre compile success, ignore blew
continue;
@@ -208,7 +192,9 @@ public class RealTimeAlertCalculator {
fieldValueMap.put(KEY_ROW, valueRowCount);
fieldValueMap.putAll(commonContext);
fingerPrints.clear();
fingerPrints.putAll(commonFingerPrints);
fingerPrints.put(CommonConstants.LABEL_INSTANCE, instance);
fingerPrints.put(CommonConstants.LABEL_ALERT_NAME, define.getName());
fingerPrints.putAll(define.getLabels());
for (int index = 0; index < valueRow.getColumnsList().size(); index++) {
String valueStr = valueRow.getColumns(index);
if (CommonConstants.NULL_VALUE.equals(valueStr)) {
@@ -242,7 +228,7 @@ public class RealTimeAlertCalculator {
boolean match = execAlertExpression(fieldValueMap, expr, false);
try {
if (match) {
afterThresholdRuleMatch(currentTimeMilli, fingerPrints, fieldValueMap, define, annotations);
afterThresholdRuleMatch(currentTimeMilli, fingerPrints, fieldValueMap, define);
} else {
handleRecoveredAlert(fingerPrints);
}
@@ -325,31 +311,20 @@ public class RealTimeAlertCalculator {
pendingAlertMap.remove(fingerprint);
}
private void afterThresholdRuleMatch(long currentTimeMilli, Map<String, String> fingerPrints,
Map<String, Object> fieldValueMap, AlertDefine define, Map<String, String> annotations) {
private void afterThresholdRuleMatch(long currentTimeMilli, Map<String, String> fingerPrints,
Map<String, Object> fieldValueMap, AlertDefine define) {
String fingerprint = calculateFingerprint(fingerPrints);
SingleAlert existingAlert = pendingAlertMap.get(fingerprint);
Map<String, String> labels = new HashMap<>(8);
fieldValueMap.putAll(define.getLabels());
labels.putAll(fingerPrints);
int requiredTimes = define.getTimes() == null ? 1 : define.getTimes();
if (existingAlert == null) {
// First time triggering alert, create new alert and set to pending status
Map<String, String> alertLabels = new HashMap<>(8);
alertLabels.putAll(fingerPrints);
Map<String, String> alertAnnotations = new HashMap<>(8);
if (annotations != null) {
alertAnnotations.putAll(annotations);
}
if (define.getAnnotations() != null) {
alertAnnotations.putAll(define.getAnnotations());
}
// render var content in annotations
for (Map.Entry<String, String> entry : alertAnnotations.entrySet()) {
entry.setValue(AlertTemplateUtil.render(entry.getValue(), fieldValueMap));
}
SingleAlert newAlert = SingleAlert.builder()
.labels(alertLabels)
.annotations(alertAnnotations)
// render var content in content
.labels(labels)
// todo render var content in annotations
.annotations(define.getAnnotations())
.content(AlertTemplateUtil.render(define.getTemplate(), fieldValueMap))
.status(CommonConstants.ALERT_STATUS_PENDING)
.triggerTimes(1)
@@ -19,7 +19,6 @@
package org.apache.hertzbeat.alert.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
@@ -31,7 +30,6 @@ import java.util.concurrent.ConcurrentHashMap;
/**
* SSE manager for alert
*/
@Slf4j
@Component
public class AlertSseManager {
private final Map<Long, SseEmitter> emitters = new ConcurrentHashMap<>();
@@ -40,7 +38,6 @@ public class AlertSseManager {
SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
emitter.onCompletion(() -> removeEmitter(clientId));
emitter.onTimeout(() -> removeEmitter(clientId));
emitter.onError((ex) -> removeEmitter(clientId));
emitters.put(clientId, emitter);
return emitter;
}
@@ -53,11 +50,7 @@ public class AlertSseManager {
.id(String.valueOf(System.currentTimeMillis()))
.name("ALERT_EVENT")
.data(data));
} catch (IOException | IllegalStateException e) {
emitter.complete();
removeEmitter(clientId);
} catch (Exception exception) {
log.error("Failed to broadcast alert data to client: {}", exception.getMessage());
} catch (IOException e) {
emitter.complete();
removeEmitter(clientId);
}
@@ -67,4 +60,4 @@ public class AlertSseManager {
private void removeEmitter(Long clientId) {
emitters.remove(clientId);
}
}
}
@@ -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,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;
/**
@@ -60,18 +54,4 @@ public class SmsConfig {
*/
private UniSmsProperties unisms;
/**
* Aws configuration
*/
private AwsSmsProperties aws;
/**
* Twilio SMS configuration
*/
private TwilioSmsProperties twilio;
/**
* Smslocal SMS configuration
*/
private SmslocalSmsProperties smslocal;
}
@@ -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";
}
@@ -1,66 +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.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* SkyWalking Alert Content Entity
* @see <a href="https://skywalking.apache.org/docs/main/latest/en/setup/backend/backend-alarm/">backend-alarm</a>
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class SkyWalkingExternAlert {
private Long scopeId;
private String scope;
private String name;
private String id0;
private String id1;
private String ruleName;
private String alarmMessage;
private Long startTime;
private List<Tag> tags;
/**
* SkyWalking Tag Entity
*/
@Data
public static class Tag {
private String key;
private String value;
}
}
@@ -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";
}
@@ -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
@@ -20,10 +20,7 @@ package org.apache.hertzbeat.alert.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
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;
@@ -34,11 +31,8 @@ import org.springframework.context.event.EventListener;
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;
/**
* SMS client factory
@@ -55,7 +49,9 @@ public class SmsClientFactory {
private volatile SmsClient currentSmsClient;
public SmsClientFactory(GeneralConfigDao generalConfigDao, ObjectMapper objectMapper, SmsConfig yamlSmsConfig) {
public SmsClientFactory(GeneralConfigDao generalConfigDao,
ObjectMapper objectMapper,
SmsConfig yamlSmsConfig) {
this.generalConfigDao = generalConfigDao;
this.objectMapper = objectMapper;
this.yamlSmsConfig = yamlSmsConfig;
@@ -137,18 +133,9 @@ public class SmsClientFactory {
case ALIBABA:
currentSmsClient = new AlibabaSmsClientImpl(smsConfig.getAlibaba());
break;
case SMSLOCAL:
currentSmsClient = new SmsLocalSmsClientImpl(smsConfig.getSmslocal());
break;
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;
}
}
}
}
@@ -1,287 +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 com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.config.AwsSmsProperties;
import org.apache.hertzbeat.alert.service.SmsClient;
import org.apache.hertzbeat.alert.util.CryptoUtils;
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.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;
import static org.apache.hertzbeat.common.constants.SmsConstants.AWS;
/**
* AWS Cloud SMS Client Implementation<br>
* API doc:<a href="https://docs.aws.amazon.com/pinpoint/latest/apireference_smsvoicev2/API_SendTextMessage.html">https://docs.aws.amazon.com/pinpoint/latest/apireference_smsvoicev2/API_SendTextMessage.html</a><br>
* Signature doc:<a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv-create-signed-request.html"</a>https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv-create-signed-request.html</a>
*/
@Slf4j
public class AwsSmsClientImpl implements SmsClient {
private static final String SERVICE = "sms-voice";
private static final String MESSAGE_TEMPLATE = "Instance: %s, Priority: %s, Content: %s";
private final String accessKey;
private final String secretKey;
private final String region;
public AwsSmsClientImpl(AwsSmsProperties config) {
if (config != null) {
this.accessKey = config.getAccessKeyId();
this.secretKey = config.getAccessKeySecret();
this.region = config.getRegion();
} else {
this.accessKey = "";
this.secretKey = "";
this.region = "";
}
}
@Override
public void sendMessage(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
// Extract alert info
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) {
Map<String, Object> payload = new HashMap<>();
payload.put("MessageBody", message);
payload.put("DestinationPhoneNumber", phoneNumber);
payload.put("MessageType", "TRANSACTIONAL");
Instant now = Instant.now();
String amzDate = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'").withZone(ZoneOffset.UTC).format(now);
String endpoint = "https://" + SERVICE + "." + region + ".amazonaws.com";
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
String payloadInString = new ObjectMapper().writeValueAsString(payload);
URI requestUri = new URI(endpoint);
HttpPost httpPost = createHttpPost(requestUri, amzDate, payloadInString);
log.info("Sending AWS SMS request to {}", requestUri + "," + "headers: " + Arrays.toString(httpPost.getAllHeaders()));
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 amzDate, String payloadInString) throws Exception {
// Define headers once to avoid repetition (Order of headers must be maintained)
Map<String, String> headers = new LinkedHashMap<>();
headers.put("content-type", "application/x-amz-json-1.0");
headers.put("host", SERVICE + "." + this.region + ".amazonaws.com");
headers.put("x-amz-date", amzDate);
headers.put("x-amz-target", "PinpointSMSVoiceV2.SendTextMessage");
// Build authorization header using the same headers
AwsAuthenticationBuilder authBuilder = new AwsAuthenticationBuilder()
.credential(this.accessKey, this.secretKey)
.region(this.region);
headers.forEach(authBuilder::header);
String authorizationHeader = authBuilder.build(requestUri, "POST", SERVICE, payloadInString);
// Create HTTP post and set all headers
HttpPost httpPost = new HttpPost(requestUri);
httpPost.setHeader("Authorization", authorizationHeader);
headers.forEach(httpPost::setHeader);
httpPost.setEntity(new StringEntity(payloadInString, ContentType.APPLICATION_JSON));
return httpPost;
}
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) {
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 responseNode = jsonResponse.get("MessageId");
if (responseNode == null) {
throw new SendMessageException(statusCode + ":" + responseBody);
}
String messageId = responseNode.asText();
log.info("Successfully sent SMS to phone: {}, messageId: {}", phoneNumber, messageId);
}
}
@Override
public String getType() {
return AWS;
}
@Override
public boolean checkConfig() {
return !(accessKey.isBlank() || secretKey.isBlank() || region.isBlank());
}
private static class AwsAuthenticationBuilder {
private String accessKey;
private String secretKey;
private String region;
private String service;
private URI requestUri;
private String method;
private String payload;
private final Map<String, String> headers = new LinkedHashMap<>();
public AwsAuthenticationBuilder region(String region) {
this.region = region;
return this;
}
public AwsAuthenticationBuilder credential(String accessKey, String secretKey) {
this.accessKey = accessKey;
this.secretKey = secretKey;
return this;
}
public AwsAuthenticationBuilder header(String header, String value) {
this.headers.put(header, value);
return this;
}
public String build(URI requestUri, String method, String service, String payload) throws Exception {
this.requestUri = requestUri;
this.method = method;
this.service = service;
this.payload = payload;
return "%s Credential=%s/%s, SignedHeaders=%s, Signature=%s".formatted(
getAlgorithm(),
this.accessKey,
getCredentialScope(),
getSignedHeaders(),
getSignature()
);
}
private String getCredentialScope() {
return getDateStamp() + "/" + region + "/" + service + "/aws4_request";
}
private String getSignedHeaders() {
return String.join(";", headers.keySet());
}
private String getDateTime() {
return headers.get("x-amz-date");
}
private String getDateStamp() {
return getDateTime().substring(0, 8);
}
private String getSignature() {
String canonicalRequest = getCanonicalRequest();
String stringToSign = String.join("\n",
getAlgorithm(),
getDateTime(),
getCredentialScope(),
CryptoUtils.sha256Hex(canonicalRequest));
byte[] signingKey = getSignatureKey(secretKey, getDateStamp(), region, service);
return CryptoUtils.hmacSha256Hex(signingKey, stringToSign);
}
private String getCanonicalRequest() {
return String.join("\n",
method,
requestUri.getPath().isEmpty() ? "/" : requestUri.getPath(),
requestUri.getQuery() != null ? requestUri.getQuery() : "",
getCanonicalHeaders(),
getSignedHeaders(),
CryptoUtils.sha256Hex(payload)
);
}
private String getCanonicalHeaders() {
return headers.entrySet().stream()
.map(entry -> entry.getKey() + ":" + entry.getValue())
.collect(Collectors.joining("\n")) + "\n";
}
private String getAlgorithm() {
return "AWS4-HMAC-SHA256";
}
private byte[] getSignatureKey(String key, String dateStamp, String regionName, String serviceName) {
byte[] secret = ("AWS4" + key).getBytes(StandardCharsets.UTF_8);
byte[] date = CryptoUtils.hmac256(secret, dateStamp);
byte[] region = CryptoUtils.hmac256(date, regionName);
byte[] service = CryptoUtils.hmac256(region, serviceName);
return CryptoUtils.hmac256(service, "aws4_request");
}
}
}
@@ -305,7 +305,7 @@ public class NoticeConfigServiceImpl implements NoticeConfigService, CommandLine
Map<String, String> labels = new HashMap<>(8);
labels.put(CommonConstants.LABEL_INSTANCE, "1000000");
labels.put(CommonConstants.LABEL_ALERT_NAME, "CPU Usage Alert");
labels.put(CommonConstants.LABEL_INSTANCE_HOST, "127.0.0.1");
labels.put(CommonConstants.LABEL_HOST, "127.0.0.1");
Map<String, String> annotations = new HashMap<>(8);
annotations.put("suggest", "Please check the CPU usage of the server");
SingleAlert singleAlert1 = SingleAlert.builder()
@@ -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.alert.service.impl;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.dto.SkyWalkingExternAlert;
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.time.Instant;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* SkyWalking external alarm service impl
*/
@Slf4j
@Service
public class SkyWalkingExternAlertService implements ExternAlertService {
@Autowired
private AlarmCommonReduce alarmCommonReduce;
@Override
public void addExternAlert(String content) {
TypeReference<List<SkyWalkingExternAlert>> typeReference = new TypeReference<>() {};
List<SkyWalkingExternAlert> alerts = JsonUtil.fromJson(content, typeReference);
if (alerts == null || alerts.isEmpty()) {
log.warn("Parse SkyWalking extern alert content failed! content: {}", content);
return;
}
for (SkyWalkingExternAlert alert : alerts) {
SingleAlert singleAlert = SingleAlert.builder()
.content(alert.getAlarmMessage())
.status(CommonConstants.ALERT_STATUS_FIRING)
.activeAt(Instant.now().toEpochMilli())
.startAt(alert.getStartTime() != null ? alert.getStartTime() : Instant.now().toEpochMilli())
.labels(acquireAlertLabels(alert))
.annotations(acquireAlertAnnotations(alert))
.triggerTimes(1)
.build();
alarmCommonReduce.reduceAndSendAlarm(singleAlert);
}
}
@Override
public String supportSource() {
return "skywalking";
}
private Map<String, String> acquireAlertLabels(SkyWalkingExternAlert externAlert){
Map<String, String> labels = new HashMap<>(8);
labels.put("__source__", "skywalking");
List<SkyWalkingExternAlert.Tag> tags = externAlert.getTags();
if (tags == null || tags.isEmpty()){
return labels;
}
tags.forEach(tag -> labels.put(tag.getKey(), tag.getValue()));
return labels;
}
private Map<String, String> acquireAlertAnnotations(SkyWalkingExternAlert externAlert){
Map<String, String> annotations = new HashMap<>(8);
annotations.putIfAbsent("scope", externAlert.getScope());
annotations.putIfAbsent("name", externAlert.getName());
annotations.putIfAbsent("id0", externAlert.getId0());
annotations.putIfAbsent("id1", externAlert.getId1());
annotations.putIfAbsent("ruleName", externAlert.getRuleName());
return annotations;
}
}
@@ -1,146 +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.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.config.SmslocalSmsProperties;
import org.apache.hertzbeat.alert.service.SmsClient;
import org.apache.hertzbeat.common.constants.SmsConstants;
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.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
/**
* Smslocal SMS Client Implement
*/
@Slf4j
public class SmsLocalSmsClientImpl implements SmsClient {
private static final String HOST = "secure.smslocal.com";
private static final String PATH = "/api/service/enterprise-service/external/sms";
private static final String FROM = "Hertzbeat";
private static final String SUCCESS_CODE = "200";
private final SmslocalSmsProperties config;
public SmsLocalSmsClientImpl(SmslocalSmsProperties smslocalSmsProperties) {
this.config = smslocalSmsProperties;
}
@Override
public void sendMessage(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
if (Objects.isNull(receiver) || Objects.isNull(alert)) {
log.warn("receiver and alert can not be null! receiver: {}, alert:{}", receiver, alert);
return;
}
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
String content = alert.getCommonAnnotations().get("summary");
if (Objects.isNull(content) || Objects.isNull(alert.getCommonAnnotations().get("description"))) {
content = alert.getAlerts().get(0).getContent();
}
SmsMessage smsMessage = new SmsMessage(FROM, receiver.getPhone(), content);
String payload = JsonUtil.toJson(smsMessage);
HttpPost httpPost = new HttpPost("https://" + HOST + PATH);
httpPost.setHeader("Content-Type", "application/json; charset=utf-8");
httpPost.setHeader("Token", config.getApiKey());
httpPost.setEntity(new StringEntity(payload, StandardCharsets.UTF_8));
log.debug("Sending SMS request to {}, payload: {}", httpPost.getURI(), payload);
// send http request and handle response
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
int statusCode = response.getStatusLine().getStatusCode();
String responseBody = EntityUtils.toString(response.getEntity());
log.debug("SMS response status: {}, body: {}", statusCode, responseBody);
if (statusCode != 200) {
throw new SendMessageException("HTTP request failed with status code: " + statusCode);
}
JsonNode jsonResponse = JsonUtil.fromJson(responseBody);
JsonNode jsonNode = jsonResponse.get(0);
if (Objects.isNull(jsonNode)) {
log.warn("jsonResponse parse errorCode failed: {}", jsonResponse);
return;
}
String errorCode = jsonNode.get("errorCode").asText();
if (!SUCCESS_CODE.equals(errorCode)) {
String msgid = jsonNode.get("id").asText();
throw new SendMessageException(errorCode + ":" + msgid);
}
log.info("Successfully sent SMS to phone: {}", receiver.getPhone());
}
} catch (Exception e) {
log.error("Failed to send SMS: {}", e.getMessage());
throw new SendMessageException(e.getMessage());
}
}
@Override
public String getType() {
return SmsConstants.SMSLOCAL;
}
@Override
public boolean checkConfig() {
if (Objects.isNull(config) || Objects.isNull(config.getApiKey()) || config.getApiKey().isBlank()) {
log.warn("smslocal properties can not be null: {}", config);
return false;
}
return true;
}
@Getter
@Setter
private static class SmsMessage {
String from;
String to;
String content;
final int datacoding = 0;
final String direction = "mt";
public SmsMessage(String from, String to, String content) {
this.from = from;
this.to = to;
this.content = content;
}
}
}
@@ -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());
}
}
@@ -89,18 +89,7 @@ public class CryptoUtils {
* @return lowercase hexadecimal string
*/
public static String hmacSha256Hex(String key, String data) {
return hmacSha256Hex(hmac256(key.getBytes(UTF8), data), data);
}
/**
* Calculate HMAC-SHA256 signature and convert to lowercase hexadecimal string
* Note: This is necessary as, converting a String to byte[] and back may yield different results due to encoding differences or data loss.
* @param key secret key in byte array
* @param data data to be signed
* @return lowercase hexadecimal string
*/
public static String hmacSha256Hex(byte[] key, String data) {
byte[] hmacResult = hmac256(key, data);
byte[] hmacResult = hmac256(key.getBytes(UTF8), data);
return DatatypeConverter.printHexBinary(hmacResult).toLowerCase();
}
}
@@ -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,69 +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.AwsSmsProperties;
import org.apache.hertzbeat.alert.service.impl.AwsSmsClientImpl;
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 AwsSmsClientImpl}
*/
class AwsSmsClientImplTest {
@Test
void getType() {
AwsSmsProperties awsSmsProperties = new AwsSmsProperties();
awsSmsProperties.setAccessKeyId("accessKeyId");
awsSmsProperties.setAccessKeySecret("accessKeySecret");
awsSmsProperties.setRegion("region");
AwsSmsClientImpl awsSmsClient = new AwsSmsClientImpl(awsSmsProperties);
assertEquals("aws", awsSmsClient.getType());
}
@ParameterizedTest
@CsvSource({
"accessKeyId, accessKeySecret, region, true",
"accessKeyId, accessKeySecret, '', false",
"accessKeyId, '', region, false",
"accessKeyId, accessKeySecret, '', false",
"accessKeyId, '', '', false",
"'', accessKeySecret, region, false",
"'', accessKeySecret, '', false",
"'', '', region, false",
"'', accessKeySecret, '', false",
})
void checkConfig(String accessKeyId, String accessKeySecret, String region, boolean expected) {
AwsSmsProperties awsSmsProperties = new AwsSmsProperties();
awsSmsProperties.setAccessKeyId(accessKeyId);
awsSmsProperties.setAccessKeySecret(accessKeySecret);
awsSmsProperties.setRegion(region);
AwsSmsClientImpl awsSmsClient = new AwsSmsClientImpl(awsSmsProperties);
assertEquals(expected, awsSmsClient.checkConfig());
}
}
@@ -41,12 +41,12 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import java.util.HashMap;
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}
@@ -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>
@@ -88,7 +88,6 @@ public class JdbcCommonCollect extends AbstractCollect {
SshTunnel sshTunnel = jdbcProtocol.getSshTunnel();
int timeout = CollectUtil.getTimeout(jdbcProtocol.getTimeout());
boolean reuseConnection = Boolean.parseBoolean(jdbcProtocol.getReuseConnection());
Statement statement = null;
String databaseUrl;
try {
@@ -100,7 +99,7 @@ public class JdbcCommonCollect extends AbstractCollect {
}
statement = getConnection(jdbcProtocol.getUsername(),
jdbcProtocol.getPassword(), databaseUrl, timeout, reuseConnection);
jdbcProtocol.getPassword(), databaseUrl, timeout);
switch (jdbcProtocol.getQueryType()) {
case QUERY_TYPE_ONE_ROW -> queryOneRow(statement, jdbcProtocol.getSql(), metrics.getAliasFields(), builder, startTime);
case QUERY_TYPE_MULTI_ROW -> queryMultiRow(statement, jdbcProtocol.getSql(), metrics.getAliasFields(), builder, startTime);
@@ -143,20 +142,11 @@ public class JdbcCommonCollect extends AbstractCollect {
builder.setMsg("Query Error: " + errorMessage);
} finally {
if (statement != null) {
Connection connection = null;
try {
connection = statement.getConnection();
statement.close();
} catch (Exception e) {
log.error("Jdbc close statement error: {}", e.getMessage());
}
try {
if (!reuseConnection && connection != null) {
connection.close();
}
} catch (Exception e) {
log.error("Jdbc close connection error: {}", e.getMessage());
}
}
}
}
@@ -167,40 +157,38 @@ public class JdbcCommonCollect extends AbstractCollect {
}
private Statement getConnection(String username, String password, String url, Integer timeout, boolean reuseConnection) throws Exception {
private Statement getConnection(String username, String password, String url, Integer timeout) throws Exception {
CacheIdentifier identifier = CacheIdentifier.builder()
.ip(url)
.username(username).password(password).build();
Optional<AbstractConnection<?>> cacheOption = connectionCommonCache.getCache(identifier, true);
Statement statement = null;
if (reuseConnection) {
Optional<AbstractConnection<?>> cacheOption = connectionCommonCache.getCache(identifier, true);
if (cacheOption.isPresent()) {
JdbcConnect jdbcConnect = (JdbcConnect) cacheOption.get();
if (cacheOption.isPresent()) {
JdbcConnect jdbcConnect = (JdbcConnect) cacheOption.get();
try {
statement = jdbcConnect.getConnection().createStatement();
// set query timeout
int timeoutSecond = timeout / 1000;
timeoutSecond = timeoutSecond <= 0 ? 1 : timeoutSecond;
statement.setQueryTimeout(timeoutSecond);
// set query max row number
statement.setMaxRows(1000);
} catch (Exception e) {
log.info("The jdbc connect from cache, create statement error: {}", e.getMessage());
try {
statement = jdbcConnect.getConnection().createStatement();
// set query timeout
int timeoutSecond = timeout / 1000;
timeoutSecond = timeoutSecond <= 0 ? 1 : timeoutSecond;
statement.setQueryTimeout(timeoutSecond);
// set query max row number
statement.setMaxRows(1000);
} catch (Exception e) {
log.info("The jdbc connect from cache, create statement error: {}", e.getMessage());
try {
if (statement != null) {
statement.close();
}
jdbcConnect.close();
} catch (Exception e2) {
log.error(e2.getMessage());
if (statement != null) {
statement.close();
}
statement = null;
connectionCommonCache.removeCache(identifier);
jdbcConnect.close();
} catch (Exception e2) {
log.error(e2.getMessage());
}
statement = null;
connectionCommonCache.removeCache(identifier);
}
if (statement != null) {
return statement;
}
}
if (statement != null) {
return statement;
}
// renew connection when failed
Connection connection = DriverManager.getConnection(url, username, password);
@@ -210,10 +198,8 @@ public class JdbcCommonCollect extends AbstractCollect {
timeoutSecond = timeoutSecond <= 0 ? 1 : timeoutSecond;
statement.setQueryTimeout(timeoutSecond);
statement.setMaxRows(1000);
if (reuseConnection) {
JdbcConnect jdbcConnect = new JdbcConnect(connection);
connectionCommonCache.addCache(identifier, jdbcConnect);
}
JdbcConnect jdbcConnect = new JdbcConnect(connection);
connectionCommonCache.addCache(identifier, jdbcConnect);
return statement;
}
@@ -40,10 +40,6 @@ 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;
@@ -67,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;
@@ -79,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;
@@ -86,13 +86,10 @@ 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 org.apache.hertzbeat.common.entity.job.Metrics.Field;
import java.util.function.Function;
/**
* http https collect
@@ -123,7 +120,7 @@ public class HttpCollectImpl extends AbstractCollect {
if (CollectionUtils.isEmpty(httpProtocol.getSuccessCodes())) {
httpProtocol.setSuccessCodes(List.of(HttpStatus.SC_OK + ""));
}
HttpContext httpContext = createHttpContext(metrics.getHttp());
HttpUriRequest request = createHttpRequest(metrics.getHttp());
try (CloseableHttpResponse response = CommonHttpClient.getHttpClient().execute(request, httpContext)) {
@@ -154,7 +151,7 @@ public class HttpCollectImpl extends AbstractCollect {
case DispatchConstants.PARSE_PROMETHEUS ->
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 ->
@@ -331,95 +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;
}
Map<String, Field> fieldMap = metrics.getFields().stream()
.collect(Collectors.toMap(Field::getField, Function.identity(), (field1, field2) -> field1));
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 {
Field field = fieldMap.get(alias);
if (field == null || !StringUtils.hasText(field.getXpath())) {
log.warn("No field definition or xpath found for alias '{}' in XML path parsing.", alias);
valueRowBuilder.addColumn(CommonConstants.NULL_VALUE);
continue;
}
String relativeXpath = field.getXpath();
try {
String value = (String) xpath.evaluate(relativeXpath, node, XPathConstants.STRING);
valueRowBuilder.addColumn(StringUtils.hasText(value) ? value : CommonConstants.NULL_VALUE);
} catch (XPathExpressionException e) {
log.warn("Failed to evaluate relative XPath '{}' (from field definition) for node [{}]: {}", relativeXpath, 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());
}
private void parseResponseByXmlPath(String resp, List<String> aliasFields, HttpProtocol http,
CollectRep.MetricsData.Builder builder) {
}
private void parseResponseByJsonPath(String resp, List<String> aliasFields, HttpProtocol http,
@@ -562,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;
}
}
@@ -598,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);
@@ -638,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()));
}
}
}
@@ -669,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)
@@ -702,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) {
@@ -33,7 +33,7 @@ import java.util.stream.Stream;
import javax.net.ssl.SSLException;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.collector.collect.common.http.CommonHttpClient;
import org.apache.hertzbeat.common.entity.dto.MetricFamily;
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;
@@ -173,6 +173,7 @@ public class PrometheusAutoCollectImpl {
Map<String, MetricFamily> metricFamilyMap = TextParser.textToMetricFamilies(resp);
List<CollectRep.MetricsData> metricsDataList = new LinkedList<>();
for (Map.Entry<String, MetricFamily> entry : metricFamilyMap.entrySet()) {
builder.clearMetrics();
builder.clearFields();
builder.clearValues();
String metricsName = entry.getKey();
@@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.apache.hertzbeat.common.entity.dto;
package org.apache.hertzbeat.collector.collect.prometheus.parser;
import java.util.List;
import lombok.Data;
@@ -15,20 +15,18 @@
* limitations under the License.
*/
package org.apache.hertzbeat.common.util;
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 org.apache.hertzbeat.common.entity.dto.MetricFamily;
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;
@@ -38,23 +36,9 @@ import java.util.concurrent.ConcurrentHashMap;
@Slf4j
public class OnlineParser {
private static final Map<Integer, Integer> escapeMap = new HashMap<>();
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 static class FormatException extends Exception {
public FormatException() {
}
public FormatException() {}
public FormatException(String message) {
super(message);
@@ -139,65 +123,51 @@ public class OnlineParser {
}
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;
}
}
private static CharChecker parseOneChar(InputStream inputStream) throws IOException, FormatException {
int i = getChar(inputStream);
return new CharChecker(i);
}
private static CharChecker parseOneDouble(InputStream inputStream, StringBuilder stringBuilder) throws IOException, FormatException {
int i = getChar(inputStream);
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');
@@ -211,26 +181,27 @@ public class OnlineParser {
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,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();
}
@@ -336,8 +307,8 @@ public class OnlineParser {
public static Map<String, MetricFamily> parseMetrics(InputStream inputStream) throws IOException {
Map<String, MetricFamily> metricFamilyMap = new ConcurrentHashMap<>(10);
int i = inputStream.read();
try {
int i = getChar(inputStream);
while (i != -1) {
if (i == '#' || i == '\n') {
skipToLineEnd(inputStream).maybeEol().maybeEof().noElse();
@@ -346,7 +317,7 @@ public class OnlineParser {
stringBuilder.append((char) i);
parseMetric(inputStream, metricFamilyMap, stringBuilder);
}
i = getChar(inputStream);
i = inputStream.read();
}
} catch (FormatException e) {
log.error("prometheus parser failed because of wrong input format. {}", e.getMessage());
@@ -24,7 +24,6 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.collector.collect.http.promethus.ParseException;
import org.apache.hertzbeat.common.entity.dto.MetricFamily;
import org.apache.hertzbeat.common.util.StrBuffer;
/**
@@ -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();
}
}
@@ -84,8 +84,6 @@ public class SnmpCollectImpl extends AbstractCollect {
+ "{1,choice,0#|1#1 hour, |1<{1,number,integer} hours, }"
+ "{2,choice,0#|1#1 minute, |1<{2,number,integer} minutes, }"
+ "{3,choice,0#|1#1 second, |1<{3,number,integer} seconds }";
private static final String DATE_AND_TIME_PATTERN = "%04d-%02d-%02d,%02d:%02d:%02d.%d";
private static final String TIME_ZONE_PATTERN = "%c%02d:%02d";
private final Map<Integer, Snmp> versionSnmpService = new ConcurrentHashMap<>(3);
@@ -307,9 +305,6 @@ public class SnmpCollectImpl extends AbstractCollect {
CharBuffer res = decoder.decode(ByteBuffer.wrap(bytes));
return res.toString();
} catch (Exception e) {
if (isDateAndTimeOctetString(binding)) {
return parseDateAndTime(clearHexStr);
}
return new String(bytes);
}
} catch (Exception e) {
@@ -320,68 +315,6 @@ public class SnmpCollectImpl extends AbstractCollect {
}
}
private static boolean isDateAndTimeOctetString(VariableBinding binding) {
if (!(binding.getVariable() instanceof OctetString)) {
return false;
}
byte[] bytes = HexFormat.of().parseHex(binding.toValueString().replaceAll(HEX_SPLIT, ""));
if (bytes.length != 8 && bytes.length != 11) return false;
int year = ((bytes[0] & 0xFF) << 8) | (bytes[1] & 0xFF);
if (year < 1970 || year > 3000) return false;
int month = bytes[2] & 0xFF;
if (month < 1 || month > 12) return false;
int day = bytes[3] & 0xFF;
if (day < 1 || day > 31) return false;
int hour = bytes[4] & 0xFF;
if (hour > 23) return false;
int minute = bytes[5] & 0xFF;
if (minute > 59) return false;
int second = bytes[6] & 0xFF;
if (second > 59) return false;
int deciSecond = bytes[7] & 0xFF;
if (deciSecond > 99) return false;
if (bytes.length == 11) {
int tzSign = bytes[8] & 0xFF;
if (tzSign != 0x2B && tzSign != 0x2D) return false;
int tzHour = bytes[9] & 0xFF;
if (tzHour > 23) return false;
int tzMinute = bytes[10] & 0xFF;
if (tzMinute > 59) return false;
}
return true;
}
private static String parseDateAndTime(String hexString) {
byte[] bytes = HexFormat.of().parseHex(hexString);
int year = ((bytes[0] & 0xFF) << 8) | (bytes[1] & 0xFF);
int month = bytes[2] & 0xFF;
int day = bytes[3] & 0xFF;
int hour = bytes[4] & 0xFF;
int minute = bytes[5] & 0xFF;
int second = bytes[6] & 0xFF;
int deciSeconds = bytes[7] & 0xFF;
String dateTime = String.format(DATE_AND_TIME_PATTERN,
year, month, day, hour, minute, second, deciSeconds);
if (bytes.length == 11) {
char sign = (char) bytes[8];
int tzHour = bytes[9] & 0xFF;
int tzMinute = bytes[10] & 0xFF;
dateTime += String.format(" " + TIME_ZONE_PATTERN, sign, tzHour, tzMinute);
}
return dateTime;
}
private void snmpClose(Snmp snmp, int version) throws IOException {
snmp.close();
versionSnmpService.remove(version);
@@ -79,12 +79,11 @@ public class SshCollectImpl extends AbstractCollect {
long startTime = System.currentTimeMillis();
SshProtocol sshProtocol = metrics.getSsh();
boolean reuseConnection = Boolean.parseBoolean(sshProtocol.getReuseConnection());
boolean useProxy = Boolean.parseBoolean(sshProtocol.getUseProxy());
int timeout = CollectUtil.getTimeout(sshProtocol.getTimeout(), DEFAULT_TIMEOUT);
ClientChannel channel = null;
ClientSession clientSession = null;
try {
clientSession = getConnectSession(sshProtocol, timeout, reuseConnection, useProxy);
clientSession = getConnectSession(sshProtocol, timeout, reuseConnection);
if (CommonSshBlacklist.isCommandBlacklisted(sshProtocol.getScript())) {
builder.setCode(CollectRep.Code.FAIL);
builder.setMsg("The command is blacklisted: " + sshProtocol.getScript());
@@ -157,7 +156,7 @@ public class SshCollectImpl extends AbstractCollect {
log.error(e.getMessage(), e);
}
}
if (clientSession != null && !reuseConnection && !useProxy) {
if (clientSession != null && !reuseConnection) {
try {
clientSession.close();
} catch (Exception e) {
@@ -281,8 +280,11 @@ public class SshCollectImpl extends AbstractCollect {
connectionCommonCache.removeCache(identifier);
}
private ClientSession getConnectSession(SshProtocol sshProtocol, int timeout, boolean reuseConnection, boolean useProxy)
private ClientSession getConnectSession(SshProtocol sshProtocol, int timeout, boolean reuseConnection)
throws IOException, GeneralSecurityException {
return SshHelper.getConnectSession(sshProtocol, timeout, reuseConnection, useProxy);
return SshHelper.getConnectSession(
sshProtocol.getHost(), sshProtocol.getPort(), sshProtocol.getUsername(), sshProtocol.getPassword(),
sshProtocol.getPrivateKey(), sshProtocol.getPrivateKeyPassphrase(), timeout, reuseConnection
);
}
}
@@ -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").xpath("name").build());
fields.add(Metrics.Field.builder().field("status").xpath("status").build());
fields.add(Metrics.Field.builder().field("cpu").xpath("metrics/cpu").build());
fields.add(Metrics.Field.builder().field("memory").xpath("metrics/memory").build());
Metrics metrics = Metrics.builder()
.http(http)
.fields(fields)
.aliasFields(Arrays.asList("name", "status", "cpu", "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");
}
}
@@ -17,8 +17,6 @@
package org.apache.hertzbeat.collector.collect.prometheus.parser;
import org.apache.hertzbeat.common.entity.dto.MetricFamily;
import org.apache.hertzbeat.common.util.OnlineParser;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@@ -183,18 +183,21 @@ public class CommonDispatcher implements MetricsTaskDispatch, CollectDataDispatc
Job job = timerTask.getJob();
job.constructPriorMetrics();
Set<Metrics> metricsSet = job.getNextCollectMetrics(null, true);
metricsSet.forEach(metrics -> {
MetricsCollect metricsCollect = new MetricsCollect(metrics, timeout, this,
collectorIdentity, unitConvertList);
jobRequestQueue.addJob(metricsCollect);
if (metrics.getPrometheus() != null) {
metricsTimeoutMonitorMap.put(String.valueOf(job.getId()),
new MetricsTime(System.currentTimeMillis(), metrics, timeout));
} else {
metricsTimeoutMonitorMap.put(job.getId() + "-" + metrics.getName(),
new MetricsTime(System.currentTimeMillis(), metrics, timeout));
}
});
// Avoid NullPointerException
if (null != metricsSet){
metricsSet.forEach(metrics -> {
MetricsCollect metricsCollect = new MetricsCollect(metrics, timeout, this,
collectorIdentity, unitConvertList);
jobRequestQueue.addJob(metricsCollect);
if (metrics.getPrometheus() != null) {
metricsTimeoutMonitorMap.put(String.valueOf(job.getId()),
new MetricsTime(System.currentTimeMillis(), metrics, timeout));
} else {
metricsTimeoutMonitorMap.put(job.getId() + "-" + metrics.getName(),
new MetricsTime(System.currentTimeMillis(), metrics, timeout));
}
});
}
}
@Override
@@ -234,6 +237,8 @@ public class CommonDispatcher implements MetricsTaskDispatch, CollectDataDispatc
long spendTime = System.currentTimeMillis() - job.getDispatchTime();
long interval = job.getInterval() - spendTime / 1000;
interval = interval <= 0 ? 0 : interval;
// Reset Construction Execution Metrics Task View
job.constructPriorMetrics();
timerDispatch.cyclicJob(timerJob, interval, TimeUnit.SECONDS);
}
} else if (!metricsSet.isEmpty()) {
@@ -330,6 +335,8 @@ public class CommonDispatcher implements MetricsTaskDispatch, CollectDataDispatc
long spendTime = System.currentTimeMillis() - job.getDispatchTime();
long interval = job.getInterval() - spendTime / 1000;
interval = interval <= 0 ? 0 : interval;
// Reset Construction Execution Metrics Task View
job.constructPriorMetrics();
timerDispatch.cyclicJob(timerJob, interval, TimeUnit.SECONDS);
}
// it is an asynchronous periodic cyclic task, directly response the collected data
@@ -75,18 +75,6 @@ public class MetricsCollect implements Runnable, Comparable<MetricsCollect> {
* metrics configuration
*/
protected Metrics metrics;
/**
* metadata
*/
protected Map<String, String> metadata;
/**
* labels
*/
protected Map<String, String> labels;
/**
* annotations
*/
protected Map<String, String> annotations;
/**
* time wheel timeout
*/
@@ -131,9 +119,6 @@ public class MetricsCollect implements Runnable, Comparable<MetricsCollect> {
this.id = job.getMonitorId();
this.tenantId = job.getTenantId();
this.app = job.getApp();
this.metadata = job.getMetadata();
this.labels = job.getLabels();
this.annotations = job.getAnnotations();
this.collectDataDispatch = collectDataDispatch;
this.isCyclic = job.isCyclic();
this.isSd = job.isSd();
@@ -151,8 +136,9 @@ public class MetricsCollect implements Runnable, Comparable<MetricsCollect> {
this.startTime = System.currentTimeMillis();
setNewThreadName(id, app, startTime, metrics);
CollectRep.MetricsData.Builder response = CollectRep.MetricsData.newBuilder();
response.setApp(app).setId(id).setTenantId(tenantId)
.setLabels(labels).setAnnotations(annotations).addMetadataAll(metadata);
response.setApp(app);
response.setId(id);
response.setTenantId(tenantId);
// for prometheus auto
if (DispatchConstants.PROTOCOL_PROMETHEUS.equalsIgnoreCase(metrics.getProtocol())) {
List<CollectRep.MetricsData> metricsData = PrometheusAutoCollectImpl
@@ -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:
@@ -17,18 +17,13 @@
package org.apache.hertzbeat.collector.collect.common.ssh;
import java.io.InputStream;
import java.security.KeyPair;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.collector.collect.common.cache.AbstractConnection;
import org.apache.hertzbeat.collector.collect.common.cache.CacheIdentifier;
import org.apache.hertzbeat.collector.collect.common.cache.GlobalConnectionCache;
import org.apache.hertzbeat.collector.collect.common.cache.SshConnect;
import org.apache.hertzbeat.collector.util.PrivateKeyUtils;
import org.apache.hertzbeat.common.entity.job.protocol.SshProtocol;
import org.apache.sshd.client.SshClient;
import org.apache.sshd.client.config.hosts.HostConfigEntry;
import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.common.config.keys.FilePasswordProvider;
import org.apache.sshd.common.util.security.SecurityUtils;
@@ -106,101 +101,4 @@ public class SshHelper {
return clientSession;
}
public static ClientSession getConnectSession(SshProtocol sshProtocol, int timeout, boolean reuseConnection, boolean useProxy)
throws IOException, GeneralSecurityException {
CacheIdentifier identifier = CacheIdentifier.builder()
.ip(sshProtocol.getHost()).port(sshProtocol.getPort())
.username(sshProtocol.getUsername()).password(sshProtocol.getPassword())
.build();
ClientSession clientSession = null;
// When using ProxyJump, force connection reuse:
// Apache MINA SSHD will pass the proxy password error to the target host in proxy scenarios, causing the first connection to fail.
// Reusing connections can skip duplicate authentication and avoid this problem.
if (reuseConnection || useProxy) {
Optional<AbstractConnection<?>> cacheOption = CONNECTION_COMMON_CACHE.getCache(identifier, true);
if (cacheOption.isPresent()) {
SshConnect sshConnect = (SshConnect) cacheOption.get();
clientSession = sshConnect.getConnection();
try {
if (clientSession == null || clientSession.isClosed() || clientSession.isClosing()) {
clientSession = null;
CONNECTION_COMMON_CACHE.removeCache(identifier);
}
} catch (Exception e) {
log.warn(e.getMessage());
clientSession = null;
CONNECTION_COMMON_CACHE.removeCache(identifier);
}
}
if (clientSession != null) {
return clientSession;
}
}
SshClient sshClient = CommonSshClient.getSshClient();
HostConfigEntry proxyConfig = new HostConfigEntry();
if (useProxy && StringUtils.hasText(sshProtocol.getProxyHost())) {
String proxySpec = String.format("%s@%s:%d", sshProtocol.getProxyUsername(), sshProtocol.getProxyHost(), Integer.parseInt(sshProtocol.getProxyPort()));
proxyConfig.setHostName(sshProtocol.getHost());
proxyConfig.setHost(sshProtocol.getHost());
proxyConfig.setPort(Integer.parseInt(sshProtocol.getPort()));
proxyConfig.setUsername(sshProtocol.getUsername());
proxyConfig.setProxyJump(proxySpec);
// Apache SSHD requires the password for the proxy to be preloaded into the sshClient instance before connecting
if (StringUtils.hasText(sshProtocol.getProxyPassword())) {
sshClient.addPasswordIdentity(sshProtocol.getProxyPassword());
log.debug("Loaded proxy server password authentication: {}@{}", sshProtocol.getProxyUsername(), sshProtocol.getProxyHost());
}
if (StringUtils.hasText(sshProtocol.getProxyPrivateKey())) {
proxyConfig.setIdentities(List.of(sshProtocol.getProxyPrivateKey()));
log.debug("Proxy private key loaded into HostConfigEntry");
}
}
if (useProxy && StringUtils.hasText(sshProtocol.getProxyHost())) {
try {
clientSession = sshClient.connect(proxyConfig)
.verify(timeout, TimeUnit.MILLISECONDS).getSession();
}
finally {
sshClient.removePasswordIdentity(sshProtocol.getProxyPassword());
}
} else {
clientSession = sshClient.connect(sshProtocol.getUsername(), sshProtocol.getHost(), Integer.parseInt(sshProtocol.getPort()))
.verify(timeout, TimeUnit.MILLISECONDS).getSession();
}
if (StringUtils.hasText(sshProtocol.getPassword())) {
clientSession.addPasswordIdentity(sshProtocol.getPassword());
} else if (StringUtils.hasText(sshProtocol.getPrivateKey())) {
var resourceKey = PrivateKeyUtils.writePrivateKey(sshProtocol.getHost(), sshProtocol.getPrivateKey());
try (InputStream keyStream = new FileInputStream(resourceKey)) {
FilePasswordProvider passwordProvider = (session, resource, index) -> {
if (StringUtils.hasText(sshProtocol.getPrivateKeyPassphrase())) {
return sshProtocol.getPrivateKeyPassphrase();
}
return null;
};
Iterable<KeyPair> keyPairs = SecurityUtils.loadKeyPairIdentities(null, () -> resourceKey, keyStream, passwordProvider);
if (keyPairs != null) {
keyPairs.forEach(clientSession::addPublicKeyIdentity);
} else {
log.error("Failed to load private key pairs from: {}", resourceKey);
}
} catch (IOException e) {
log.error("Error reading private key file: {}", e.getMessage());
}
} // else auth with localhost private public key certificates
// auth
if (!clientSession.auth().verify(timeout, TimeUnit.MILLISECONDS).isSuccess()) {
clientSession.close();
throw new IllegalArgumentException("ssh auth failed.");
}
if (reuseConnection || useProxy) {
SshConnect sshConnect = new SshConnect(clientSession);
CONNECTION_COMMON_CACHE.addCache(identifier, sshConnect);
}
return clientSession;
}
}
@@ -227,9 +227,4 @@ public interface DispatchConstants {
* protocol modbus
*/
String PROTOCOL_MODBUS = "modbus";
/**
* protocol modbus
*/
String PROTOCOL_S7 = "s7";
}
@@ -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;
}
@@ -268,13 +267,11 @@ public class KafkaCollectImpl extends AbstractCollect {
break;
}
} catch (InterruptedException | ExecutionException e) {
builder.setCode(CollectRep.Code.FAIL);
builder.setMsg("Kafka collect error: " + e.getMessage());
log.error("Kafka collect error", e);
}
}
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
@@ -93,14 +93,9 @@ public interface CommonConstants {
String LABEL_ALERT_NAME = "alertname";
/**
* label key: instance host
* label key: host
*/
String LABEL_INSTANCE_HOST = "instancehost";
/**
* label key: instance name
*/
String LABEL_INSTANCE_NAME = "instancename";
String LABEL_HOST = "host";
/**
* Alarm severity label key
@@ -455,19 +450,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;
}
@@ -1,30 +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.constants;
/**
* Import/Export task constants
*/
public interface ImExportTaskConstant {
/**
* If the number of tasks exceeds 100, progress information will broadcast
*/
Integer IMPORT_TASK_PROCESS_THRESHOLD = 100;
}
@@ -1,49 +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.constants;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
/**
* Import/Export task status
*/
@Getter
@AllArgsConstructor
@ToString
public enum ImportTaskStatusEnum {
/**
* In progress
*/
IN_PROGRESS("IN_PROGRESS"),
/**
* Completed
*/
COMPLETED("COMPLETED"),
/**
* Failed
*/
FAILED("FAILED");
private final String value;
}
@@ -1,39 +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.constants;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
/**
* Manager Event Type Enum
*/
@Getter
@AllArgsConstructor
@ToString
public enum ManagerEventTypeEnum {
/**
* IMPORT_TASK_EVENT
*/
IMPORT_TASK_EVENT("IMPORT_TASK_EVENT");
private final String value;
}
@@ -27,16 +27,12 @@ public interface MetricDataConstants {
String UNIT = "unit";
// key in Schema metadata
String ID = "id";
String MONITOR_ID = "id";
String TENANT_ID = "tenantId";
String APP = "app";
String METRICS = "metrics";
String LABELS = "labels";
String ANNOTATIONS = "annotations";
String PRIORITY = "priority";
String TIME = "time";
String CODE = "code";
String MSG = "msg";
String INSTANCE_NAME = "instancename";
String INSTANCE_HOST = "instancehost";
}
@@ -23,19 +23,10 @@ package org.apache.hertzbeat.common.constants;
public interface SmsConstants {
// Tencent cloud SMS
String TENCENT = "tencent";
// Alibaba Cloud SMS
String ALIBABA = "alibaba";
// UniSMS
String UNISMS = "unisms";
// Smslocal SMS
String SMSLOCAL = "smslocal";
// Aws cloud SMS
String AWS = "aws";
// Twilio SMS
String TWILIO = "twilio";
}
@@ -63,7 +63,6 @@ public class GroupAlert {
private Long id;
@Schema(title = "Group Key", example = "HighCPUUsage{alertname=\"HighCPUUsage\", instance=\"server1\"}")
@Column(length = 2048)
private String groupKey;
@Schema(title = "Status", example = "resolved")
@@ -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)
@@ -59,9 +59,8 @@ public class SingleAlert {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Schema(title = "Threshold Id", example = "87584674384", accessMode = READ_ONLY)
private Long id;
@Schema(title = "Fingerprint", example = "alertname:demo")
@Column(length = 2048)
@Schema(title = "Fingerprint", example = "dxsdfdsf")
private String fingerprint;
@Schema(title = "Labels", example = "{\"alertname\": \"HighCPUUsage\", \"priority\": \"critical\", \"instance\": \"343483943\"}")
@@ -75,7 +74,6 @@ public class SingleAlert {
private Map<String, String> annotations;
@Schema(title = "Content", example = "CPU usage is above 80% for the last 5 minutes on instance server1.example.com.")
@Column(length = 4096)
private String content;
@Schema(title = "Status", example = "firing|resolved")
@@ -1,79 +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;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.hertzbeat.common.constants.ImportTaskStatusEnum;
import org.apache.hertzbeat.common.constants.ManagerEventTypeEnum;
import org.apache.hertzbeat.common.constants.NotifyLevelEnum;
import org.springframework.lang.Nullable;
/**
* Import task message
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class ImportTaskMessage extends ManagerMessage {
/**
* Task name
*/
@NotNull
private String taskName;
/**
* Progress, expressed as a percentage
*/
@Nullable
private Integer progress;
/**
* Task Status,
* @see ImportTaskStatusEnum
*/
@NotNull
private String status;
/**
* If Fail, the error message
*/
@Nullable
private String errMsg;
public ImportTaskMessage(String notifyLevel, String managerEventType, String taskName, @Nullable Integer progress, String status, @Nullable String errMsg){
super(notifyLevel, managerEventType);
this.taskName = taskName;
this.progress = progress;
this.status = status;
this.errMsg = errMsg;
}
public static ManagerMessage createInProgressMessage(String taskName, Integer process){
return new ImportTaskMessage(NotifyLevelEnum.INFO.getValue(), ManagerEventTypeEnum.IMPORT_TASK_EVENT.getValue(), taskName, process, ImportTaskStatusEnum.IN_PROGRESS.getValue(), null);
}
public static ManagerMessage createCompletedMessage(String taskName){
return new ImportTaskMessage(NotifyLevelEnum.SUCCESS.getValue(), ManagerEventTypeEnum.IMPORT_TASK_EVENT.getValue(), taskName, null, ImportTaskStatusEnum.COMPLETED.getValue(), null);
}
public static ManagerMessage createFailedMessage(String taskName, String errMsg){
return new ImportTaskMessage(NotifyLevelEnum.ERROR.getValue(), ManagerEventTypeEnum.IMPORT_TASK_EVENT.getValue(), taskName, null, ImportTaskStatusEnum.FAILED.getValue(), errMsg);
}
}
@@ -1,82 +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;
import java.util.List;
import java.util.Map;
/**
* Metric History Range Query Data
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "Metric Query Data")
public class MetricQueryData {
@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<List<Object>> values;
/**
* Metric Schema
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public static final class MetricSchema {
@Schema(title = "Metrics Field")
private List<MetricField> fields;
@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;
@Schema(title = "Whether is a label")
private Boolean label;
}
}
@@ -61,19 +61,6 @@ public class Job {
* Monitoring Task ID
*/
private long monitorId;
/**
* metadata info bind with this job
* eg: instancename, instancehost
*/
private Map<String, String> metadata;
/**
* bind labels
*/
private Map<String, String> labels;
/**
* bind annotations
*/
private Map<String, String> annotations;
/**
* Is hide this app in main menus layout, only for app type, default true.
*/
@@ -172,36 +159,23 @@ public class Job {
* collector use - construct to initialize metrics execution view
*/
public synchronized void constructPriorMetrics() {
long now = System.currentTimeMillis();
Map<Byte, List<Metrics>> currentCollectMetrics = metrics.stream()
.filter(metrics -> (now >= metrics.getCollectTime() + metrics.getInterval() * 1000))
.peek(metric -> {
metric.setCollectTime(now);
// Determine whether to configure aliasFields If not, configure the default
if ((metric.getAliasFields() == null || metric.getAliasFields().isEmpty()) && metric.getFields() != null) {
metric.setAliasFields(metric.getFields().stream().map(Metrics.Field::getField).collect(Collectors.toList()));
}
// Set the default metrics execution priority, if not filled, the default last priority
if (metric.getPriority() == null) {
metric.setPriority(Byte.MAX_VALUE);
}
})
.collect(Collectors.groupingBy(Metrics::getPriority));
// the current collect metrics can not empty, if empty, add a default availability metrics
// due the metric collect is trigger by the previous metric collect
if (currentCollectMetrics.isEmpty()) {
Optional<Metrics> defaultMetricOption = metrics.stream().filter(metric -> metric.getPriority() == 0).findFirst();
if (defaultMetricOption.isPresent()) {
Metrics defaultMetric = defaultMetricOption.get();
defaultMetric.setCollectTime(now);
currentCollectMetrics.put((byte) 0, Collections.singletonList(defaultMetric));
} else {
log.error("metrics must has one priority 0 metrics at least.");
}
}
Map<Byte, List<Metrics>> map = metrics.stream()
.filter(metrics -> (System.currentTimeMillis() >= metrics.getCollectTime() + metrics.getInterval() * 1000))
.peek(metric -> {
metric.setCollectTime(System.currentTimeMillis());
// Determine whether to configure aliasFields If not, configure the default
if ((metric.getAliasFields() == null || metric.getAliasFields().isEmpty()) && metric.getFields() != null) {
metric.setAliasFields(metric.getFields().stream().map(Metrics.Field::getField).collect(Collectors.toList()));
}
// Set the default metrics execution priority, if not filled, the default last priority
if (metric.getPriority() == null) {
metric.setPriority(Byte.MAX_VALUE);
}
})
.collect(Collectors.groupingBy(Metrics::getPriority));
// Construct a linked list of task execution order of the metrics
priorMetrics = new LinkedList<>();
currentCollectMetrics.values().forEach(metric -> {
map.values().forEach(metric -> {
Set<Metrics> metricsSet = Collections.synchronizedSet(new HashSet<>(metric));
priorMetrics.add(metricsSet);
});
@@ -286,7 +260,7 @@ public class Job {
public void initIntervals() {
List<Long> metricsIntervals = new LinkedList<>();
for (Metrics metrics: getMetrics()) {
metrics.setCollectTime(0L);
metrics.setCollectTime(System.currentTimeMillis());
if (metrics.getInterval() <= 0) {
metrics.setInterval(defaultInterval);
}
@@ -313,10 +287,10 @@ public class Job {
* The least common multiple
*/
public static long lcm(List<Long> array) {
if (array != null && !array.isEmpty()) {
if (array != null) {
long result = array.get(0);
for (int i = 1; i < array.size(); i++) {
result = result / gcd(result, array.get(i)) * array.get(i);
result = (result * array.get(i)) / gcd(result, array.get(i));
}
return result;
}
@@ -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
*/
@@ -377,10 +372,5 @@ public class Metrics {
* Metric unit
*/
private String unit;
/**
* when parse type is xmlParse, use it, like NetPortStatusList/NetPortStatus[id='1']/workSpeed
*/
private String xpath;
}
}
@@ -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
*/
@@ -71,10 +71,7 @@ public class JdbcProtocol implements CommonRequestProtocol, Protocol {
* DATABASE LINK URL eg: jdbc:mysql://localhost:3306/usthe
*/
private String url;
/**
* reuse connection session
*/
private String reuseConnection = "true";
/**
* ssh tunnel
*/
@@ -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.job.protocol;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* Modbus Protocol
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class S7Protocol implements Protocol {
/**
* IP ADDRESS OR DOMAIN NAME OF THE PEER HOST
*/
private String host;
/**
* Port number
*/
private String port;
private String driverName;
private String addressSyntax;
private String rackId;
private String slotId;
private String controllerType;
private String timeout;
private List<String> registerAddresses;
}
@@ -80,34 +80,4 @@ public class SshProtocol implements CommonRequestProtocol, Protocol {
* Response data parsing modeoneRow, multiRow
*/
private String parseType;
/**
* IP ADDRESS OR DOMAIN NAME OF THE PEER PROXY HOST
*/
private String proxyHost;
/**
* Peer proxy host port
*/
private String proxyPort;
/**
* Proxy UserName
*/
private String proxyUsername;
/**
* Proxy Password (optional)
*/
private String proxyPassword;
/**
* flag of use proxy
*/
private String useProxy = "false";
/**
* Proxy private key (optional)
*/
private String proxyPrivateKey;
}
@@ -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)
@@ -43,7 +43,6 @@ import org.apache.arrow.vector.types.pojo.FieldType;
import org.apache.arrow.vector.types.pojo.Schema;
import org.apache.hertzbeat.common.constants.MetricDataConstants;
import org.apache.hertzbeat.common.entity.arrow.RowWrapper;
import org.apache.hertzbeat.common.util.JsonUtil;
@SuppressWarnings("all")
@Slf4j
@@ -139,7 +138,17 @@ public final class CollectRep {
public static Builder newBuilder(MetricsData metricsData) {
Builder builder = new Builder();
builder.addMetadataAll(metricsData.getMetadata());
// get metadata from metricsData
Map<String, String> metadata = metricsData.getMetadata();
builder.setId(Long.parseLong(metadata.getOrDefault(MetricDataConstants.MONITOR_ID, "0")))
.setTenantId(Long.parseLong(metadata.getOrDefault(MetricDataConstants.TENANT_ID, "0")))
.setApp(metadata.getOrDefault(MetricDataConstants.APP, ""))
.setMetrics(metadata.getOrDefault(MetricDataConstants.METRICS, ""))
.setPriority(Integer.parseInt(metadata.getOrDefault(MetricDataConstants.PRIORITY, "0")))
.setTime(Long.parseLong(metadata.getOrDefault(MetricDataConstants.TIME, "0")))
.setCode(Code.forNumber(Integer.parseInt(metadata.getOrDefault(MetricDataConstants.CODE, "0"))))
.setMsg(metadata.getOrDefault(MetricDataConstants.MSG, ""));
metricsData.getFields().forEach(builder::addField);
metricsData.getValues().forEach(builder::addValueRow);
return builder;
@@ -182,70 +191,42 @@ public final class CollectRep {
public long getId() {
Map<String, String> metadata = getMetadata();
return Long.parseLong(metadata.getOrDefault(MetricDataConstants.ID, "0"));
return Long.parseLong(metadata.getOrDefault("id", "0"));
}
public long getTenantId() {
Map<String, String> metadata = getMetadata();
return Long.parseLong(metadata.getOrDefault(MetricDataConstants.TENANT_ID, "0"));
return Long.parseLong(metadata.getOrDefault("tenantId", "0"));
}
public String getApp() {
Map<String, String> metadata = getMetadata();
return metadata.getOrDefault(MetricDataConstants.APP, "");
return metadata.getOrDefault("app", "");
}
public String getMetrics() {
Map<String, String> metadata = getMetadata();
return metadata.getOrDefault(MetricDataConstants.METRICS, "");
return metadata.getOrDefault("metrics", "");
}
public int getPriority() {
Map<String, String> metadata = getMetadata();
return Integer.parseInt(metadata.getOrDefault(MetricDataConstants.PRIORITY, "0"));
return Integer.parseInt(metadata.getOrDefault("priority", "0"));
}
public long getTime() {
Map<String, String> metadata = getMetadata();
return Long.parseLong(metadata.getOrDefault(MetricDataConstants.TIME, "0"));
return Long.parseLong(metadata.getOrDefault("time", "0"));
}
public Code getCode() {
Map<String, String> metadata = getMetadata();
return Code.forNumber(Integer.parseInt(metadata.getOrDefault(MetricDataConstants.CODE, "0")));
return Code.forNumber(Integer.parseInt(metadata.getOrDefault("code", "0")));
}
public String getMsg() {
Map<String, String> metadata = getMetadata();
return metadata.getOrDefault(MetricDataConstants.MSG, "");
}
public String getInstanceName() {
Map<String, String> metadata = getMetadata();
return metadata.getOrDefault(MetricDataConstants.INSTANCE_NAME, null);
}
public String getInstanceHost() {
Map<String, String> metadata = getMetadata();
return metadata.getOrDefault(MetricDataConstants.INSTANCE_HOST, null);
}
public Map<String, String> getLabels() {
Map<String, String> metadata = getMetadata();
String labelStr = metadata.getOrDefault(MetricDataConstants.LABELS, "");
if (labelStr == null || "".equals(labelStr)) {
return null;
}
return JsonUtil.fromJson(labelStr, Map.class);
}
public Map<String, String> getAnnotations() {
Map<String, String> metadata = getMetadata();
String annotationStr = metadata.getOrDefault(MetricDataConstants.ANNOTATIONS, "");
if (annotationStr == null || "".equals(annotationStr)) {
return null;
}
return JsonUtil.fromJson(annotationStr, Map.class);
return metadata.getOrDefault("msg", "");
}
private Map<String, String> getMetadata() {
@@ -317,82 +298,54 @@ public final class CollectRep {
// Builder remains mostly the same, but build() method changes
public static class Builder {
private Map<String, String> metadata = new HashMap<>();
private long id;
private long tenantId;
private String app = "";
private String metrics = "";
private int priority;
private long time;
private Code code = Code.SUCCESS;
private String msg = "";
private List<Field> fields = new ArrayList<>();
private List<ValueRow> values = new ArrayList<>();
public Builder setId(long id) {
metadata.put(MetricDataConstants.ID, String.valueOf(id));
this.id = id;
return this;
}
public Builder setTenantId(long tenantId) {
metadata.put(MetricDataConstants.TENANT_ID, String.valueOf(tenantId));
this.tenantId = tenantId;
return this;
}
public Builder setApp(String app) {
metadata.put(MetricDataConstants.APP, app != null ? app : "");
this.app = app;
return this;
}
public Builder setMetrics(String metrics) {
metadata.put(MetricDataConstants.METRICS, metrics != null ? metrics : "");
return this;
}
public Builder setLabels(Map<String, String> labels) {
if (labels == null || labels.isEmpty()) {
return this;
}
String labelStr = JsonUtil.toJson(labels);
metadata.put(MetricDataConstants.LABELS, labelStr);
return this;
}
public Builder setAnnotations(Map<String, String> annotations) {
if (annotations == null || annotations.isEmpty()) {
return this;
}
String annotationStr = JsonUtil.toJson(annotations);
metadata.put(MetricDataConstants.ANNOTATIONS, annotationStr);
this.metrics = metrics;
return this;
}
public Builder setPriority(int priority) {
metadata.put(MetricDataConstants.PRIORITY, String.valueOf(priority));
this.priority = priority;
return this;
}
public Builder setTime(long time) {
metadata.put(MetricDataConstants.TIME, String.valueOf(time));
this.time = time;
return this;
}
public Builder setCode(Code code) {
metadata.put(MetricDataConstants.CODE, String.valueOf(code != null ? code.value : 0));
this.code = code;
return this;
}
public Builder setMsg(String msg) {
metadata.put(MetricDataConstants.MSG, msg != null ? msg : "");
return this;
}
public Builder addMetadata(String key, String value) {
if (key == null || "".equals(key)) {
return this;
}
metadata.put(key, value);
return this;
}
public Builder addMetadataAll(Map<String, String> meta) {
if (meta == null || meta.isEmpty()) {
return this;
}
this.metadata.putAll(meta);
this.msg = msg;
return this;
}
@@ -407,6 +360,17 @@ public final class CollectRep {
}
public MetricsData build() {
// Create metadata
Map<String, String> metadata = new HashMap<>();
metadata.put(MetricDataConstants.MONITOR_ID, String.valueOf(id));
metadata.put(MetricDataConstants.TENANT_ID, String.valueOf(tenantId));
metadata.put(MetricDataConstants.APP, app != null ? app : "");
metadata.put(MetricDataConstants.METRICS, metrics != null ? metrics : "");
metadata.put(MetricDataConstants.PRIORITY, String.valueOf(priority));
metadata.put(MetricDataConstants.TIME, String.valueOf(time));
metadata.put(MetricDataConstants.CODE, String.valueOf(code != null ? code.value : 0));
metadata.put(MetricDataConstants.MSG, msg != null ? msg : "");
BufferAllocator allocator = new RootAllocator();
try {
// Create Arrow fields with metadata
@@ -443,9 +407,7 @@ public final class CollectRep {
fieldIndex < row.getColumnsList().size()) {
String value = row.getColumns(fieldIndex);
if (value != null) {
// Check byte array size, Arrow buffer size is 32768 bytes
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
vector.setSafe(rowIndex, bytes);
vector.set(rowIndex, value.getBytes(StandardCharsets.UTF_8));
}
}
}
@@ -463,65 +425,38 @@ public final class CollectRep {
throw e;
}
}
// Add getter methods
public long getId() {
return Long.parseLong(metadata.getOrDefault(MetricDataConstants.ID, "0"));
return id;
}
public long getTenantId() {
return Long.parseLong(metadata.getOrDefault(MetricDataConstants.TENANT_ID, "0"));
return tenantId;
}
public String getApp() {
return metadata.getOrDefault(MetricDataConstants.APP, "");
return app;
}
public String getMetrics() {
return metadata.getOrDefault(MetricDataConstants.METRICS, "");
return metrics;
}
public int getPriority() {
return Integer.parseInt(metadata.getOrDefault(MetricDataConstants.PRIORITY, "0"));
return priority;
}
public long getTime() {
return Integer.parseInt(metadata.getOrDefault(MetricDataConstants.TIME, "0"));
return time;
}
public Code getCode() {
return Code.forNumber(Integer.parseInt(metadata.getOrDefault(MetricDataConstants.CODE, "0")));
return code;
}
public String getMsg() {
return metadata.getOrDefault(MetricDataConstants.MSG, "");
}
public String getInstanceName() {
return metadata.getOrDefault(MetricDataConstants.INSTANCE_NAME, null);
}
public String getInstanceHost() {
return metadata.getOrDefault(MetricDataConstants.INSTANCE_HOST, null);
}
public Map<String, String> getLabels() {
String labelStr = metadata.getOrDefault(MetricDataConstants.LABELS, "");
if (labelStr == null || "".equals(labelStr)) {
return null;
}
return JsonUtil.fromJson(labelStr, Map.class);
}
public Map<String, String> getAnnotations() {
String annotationStr = metadata.getOrDefault(MetricDataConstants.ANNOTATIONS, "");
if (annotationStr == null || "".equals(annotationStr)) {
return null;
}
return JsonUtil.fromJson(annotationStr, Map.class);
}
public Map<String, String> getMetadata() {
return metadata;
return msg;
}
public List<Field> getFieldsList() {
@@ -556,6 +491,10 @@ public final class CollectRep {
values = new LinkedList<>();
}
public void clearMetrics() {
metrics = null;
}
public void clearFields() {
fields = new LinkedList<>();
}
@@ -628,13 +567,6 @@ public final class CollectRep {
return new Builder();
}
public boolean equals(Object o) {
if(!(o instanceof Field))
return false;
Field other = (Field)o;
return other.name.equals(this.name);
}
public static class Builder {
private final Field instance;
@@ -1,46 +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.message;
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 CollectRep}
*/
public class CollectRepTest {
@ParameterizedTest
@CsvSource(value = {
"name, name, true",
"name1, name3, false",
})
void testFieldEquals(String name1, String name2, boolean result) {
CollectRep.Field field1 = new CollectRep.Field();
field1.setName(name1);
CollectRep.Field field2 = new CollectRep.Field();
field2.setName(name2);
assertEquals(field1.equals(field2), result);
}
}
@@ -72,7 +72,7 @@ public abstract class AbstractCollectE2eTest {
metricsCollect = new MetricsCollect(mock(Metrics.class), timeout, mock(CollectDataDispatch.class), null, List.of());
// Initialize services and components
appService.afterPropertiesSet();
appService.run();
metrics = new Metrics();
}
@@ -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);
@@ -1,88 +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.manager.config;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.constants.ManagerEventTypeEnum;
import org.apache.hertzbeat.common.entity.dto.ImportTaskMessage;
import org.apache.hertzbeat.common.entity.dto.ManagerMessage;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Manager SSE
*/
@Slf4j
@Component
public class ManagerSseManager {
private final Map<Long, SseEmitter> emitters = new ConcurrentHashMap<>();
public SseEmitter createEmitter(Long clientId) {
SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
emitter.onCompletion(() -> removeEmitter(clientId));
emitter.onTimeout(() -> removeEmitter(clientId));
emitters.put(clientId, emitter);
return emitter;
}
@Async
public void broadcast(String eventName, String data) {
emitters.forEach((clientId, emitter) -> {
try {
emitter.send(SseEmitter.event()
.id(String.valueOf(System.currentTimeMillis()))
.name(eventName)
.data(data));
} catch (IOException | IllegalStateException e) {
emitter.complete();
removeEmitter(clientId);
} catch (Exception exception) {
log.error("Failed to broadcast manager message data to client: {}", exception.getMessage());
emitter.complete();
removeEmitter(clientId);
}
});
}
public void broadcastImportTaskInProgress(String taskName, Integer progress){
ManagerMessage managerMessage = ImportTaskMessage.createInProgressMessage(taskName, progress);
broadcast(ManagerEventTypeEnum.IMPORT_TASK_EVENT.getValue(), JsonUtil.toJson(managerMessage));
}
public void broadcastImportTaskSuccess(String taskName){
ManagerMessage managerMessage = ImportTaskMessage.createCompletedMessage(taskName);
broadcast(ManagerEventTypeEnum.IMPORT_TASK_EVENT.getValue(), JsonUtil.toJson(managerMessage));
}
public void broadcastImportTaskFail(String taskName, String errMsg){
ManagerMessage managerMessage = ImportTaskMessage.createFailedMessage(taskName, errMsg);
broadcast(ManagerEventTypeEnum.IMPORT_TASK_EVENT.getValue(), JsonUtil.toJson(managerMessage));
}
private void removeEmitter(Long clientId) {
emitters.remove(clientId);
}
}
@@ -1,50 +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.manager.controller;
import org.apache.hertzbeat.common.util.SnowFlakeIdGenerator;
import org.apache.hertzbeat.manager.config.ManagerSseManager;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import static org.springframework.http.MediaType.TEXT_EVENT_STREAM_VALUE;
/**
* SSE controller for manager
*/
@RestController
@RequestMapping(path = "/api/manager/sse", produces = {TEXT_EVENT_STREAM_VALUE})
public class ManagerSseController {
private final ManagerSseManager emitterManager;
public ManagerSseController(ManagerSseManager emitterManager) {
this.emitterManager = emitterManager;
}
@GetMapping(path = "/subscribe")
public SseEmitter subscribe() {
Long clientId = SnowFlakeIdGenerator.generateId();
return emitterManager.createEmitter(clientId);
}
}
@@ -15,32 +15,26 @@
* limitations under the License.
*/
package org.apache.hertzbeat.alert.config;
package org.apache.hertzbeat.manager.pojo.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
/**
* AWS Cloud SMS configuration properties
* Alibaba Sms Sender configuration dto
*/
@Data
public class AwsSmsProperties {
/**
* AWS Cloud access key id
*/
public class SmsAlibabaConfig {
@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;
@NotBlank(message = "SignName cannot be empty")
private String signName;
@NotBlank(message = "TemplateCode cannot be null")
private String templateCode;
}
@@ -15,28 +15,29 @@
* limitations under the License.
*/
package org.apache.hertzbeat.common.entity.dto;
package org.apache.hertzbeat.manager.pojo.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Manager Message Entity
* Sms Sender configuration dto
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ManagerMessage {
/**
* Notify Level
* @see org.apache.hertzbeat.common.constants.NotifyLevelEnum
*/
private String notifyLevel;
@AllArgsConstructor
public class SmsNoticeSender {
@NotBlank(message = "Type cannot be empty")
private String type;
private SmsTencentConfig tencent;
private SmsAlibabaConfig alibaba;
/**
* Manager Event Type
* @see org.apache.hertzbeat.common.constants.ManagerEventTypeEnum
*/
private String managerEventType;
}
private SmsUniSmsConfig unisms;
private boolean enable = true;
}
@@ -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,26 @@ import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Twilio SMS configuration properties
* Tencent 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 SmsTencentConfig {
@NotBlank(message = "SecretId cannot be empty")
private String secretId;
/**
* Twilio Auth Token
*/
@NotBlank(message = "Auth Token cannot be empty")
private String authToken;
@NotBlank(message = "SecretKey cannot be empty")
private String secretKey;
/**
* Twilio Issued Phone Number
*/
@NotBlank(message = "Twilio Phone Number cannot be empty")
private String twilioPhoneNumber;
}
@NotBlank(message = "SignName cannot be empty")
private String signName;
@NotBlank(message = "AppId cannot be null")
private String appId;
@NotBlank(message = "templateId cannot be null")
private String templateId;
}
@@ -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";
}
@@ -120,11 +120,7 @@ public class SchedulerInit implements CommandLineRunner {
appDefine.setDefaultInterval(monitor.getIntervals());
appDefine.setCyclic(true);
appDefine.setTimestamp(System.currentTimeMillis());
Map<String, String> metadata = Map.of(CommonConstants.LABEL_INSTANCE_NAME, monitor.getName(),
CommonConstants.LABEL_INSTANCE_HOST, monitor.getHost());
appDefine.setMetadata(metadata);
appDefine.setLabels(monitor.getLabels());
appDefine.setAnnotations(monitor.getAnnotations());
List<Configmap> configmaps = params.stream()
.map(param -> new Configmap(param.getField(), param.getParamValue(),
param.getType())).collect(Collectors.toList());
@@ -29,10 +29,9 @@ public interface ImExportService {
/**
* Import Configuration
* @param taskName task name
* @param is input stream
*/
void importConfig(String taskName, InputStream is);
void importConfig(InputStream is);
/**
* Export Configuration
@@ -22,20 +22,6 @@ import cn.afterturn.easypoi.excel.annotation.ExcelTarget;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import jakarta.annotation.Resource;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.constants.ImExportTaskConstant;
import org.apache.hertzbeat.common.entity.manager.Monitor;
import org.apache.hertzbeat.common.entity.manager.Param;
import org.apache.hertzbeat.manager.config.ManagerSseManager;
import org.apache.hertzbeat.manager.pojo.dto.MonitorDto;
import org.apache.hertzbeat.manager.service.ImExportService;
import org.apache.hertzbeat.manager.service.MonitorService;
import org.apache.hertzbeat.manager.service.TagService;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.util.CollectionUtils;
import java.io.InputStream;
import java.io.OutputStream;
import java.time.LocalDate;
@@ -43,6 +29,17 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.entity.manager.Monitor;
import org.apache.hertzbeat.common.entity.manager.Param;
import org.apache.hertzbeat.manager.pojo.dto.MonitorDto;
import org.apache.hertzbeat.manager.service.ImExportService;
import org.apache.hertzbeat.manager.service.MonitorService;
import org.apache.hertzbeat.manager.service.TagService;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.util.CollectionUtils;
/**
* class AbstractImExportServiceImpl
@@ -57,30 +54,27 @@ public abstract class AbstractImExportServiceImpl implements ImExportService {
@Resource
private TagService tagService;
@Resource
private ManagerSseManager managerSseManager;
@Override
public void importConfig(String taskName, InputStream is) {
var formList = parseImport(is).stream().map(this::convert).toList();
public void importConfig(InputStream is) {
var formList = parseImport(is)
.stream()
.map(this::convert)
.toList();
if (!CollectionUtils.isEmpty(formList)) {
int totalElements = formList.size();
int progressInterval = Math.max(1, totalElements / 10);
for (int i = 0; i < totalElements; i++) {
MonitorDto monitorDto = formList.get(i);
formList.forEach(monitorDto -> {
monitorService.validate(monitorDto, false);
monitorService.addMonitor(monitorDto.getMonitor(), monitorDto.getParams(), monitorDto.getCollector(), monitorDto.getGrafanaDashboard());
if (totalElements >= ImExportTaskConstant.IMPORT_TASK_PROCESS_THRESHOLD && ((i + 1) % progressInterval == 0) && (i + 1 < totalElements)) {
managerSseManager.broadcastImportTaskInProgress(taskName, (int) ((i + 1) * 100.0 / totalElements));
}
}
managerSseManager.broadcastImportTaskSuccess(taskName);
});
}
}
@Override
public void exportConfig(OutputStream os, List<Long> configList) {
var monitorList = configList.stream().map(it -> monitorService.getMonitorDto(it)).filter(Objects::nonNull).map(this::convert).toList();
var monitorList = configList.stream()
.map(it -> monitorService.getMonitorDto(it))
.filter(Objects::nonNull)
.map(this::convert)
.toList();
writeOs(monitorList, os);
}
@@ -105,13 +99,15 @@ public abstract class AbstractImExportServiceImpl implements ImExportService {
var monitor = new MonitorDTO();
BeanUtils.copyProperties(dto.getMonitor(), monitor);
exportMonitor.setMonitor(monitor);
exportMonitor.setParams(dto.getParams().stream().map(it -> {
var param = new ParamDTO();
param.setField(it.getField());
param.setType(it.getType());
param.setValue(it.getParamValue());
return param;
}).toList());
exportMonitor.setParams(dto.getParams().stream()
.map(it -> {
var param = new ParamDTO();
param.setField(it.getField());
param.setType(it.getType());
param.setValue(it.getParamValue());
return param;
})
.toList());
exportMonitor.getMonitor().setCollector(dto.getCollector());
return exportMonitor;
}
@@ -124,7 +120,7 @@ public abstract class AbstractImExportServiceImpl implements ImExportService {
var monitorDto = new MonitorDto();
var monitor = new Monitor();
log.debug("exportMonitor.monitor{}", exportMonitor.monitor);
if (exportMonitor.monitor != null) {
if (exportMonitor.monitor != null) {
// Add one more null check
BeanUtils.copyProperties(exportMonitor.monitor, monitor);
}
@@ -133,13 +129,15 @@ public abstract class AbstractImExportServiceImpl implements ImExportService {
monitorDto.setCollector(exportMonitor.getMonitor().getCollector());
}
if (exportMonitor.params != null) {
monitorDto.setParams(exportMonitor.params.stream().map(it -> {
var param = new Param();
param.setField(it.field);
param.setType(it.type);
param.setParamValue(it.value);
return param;
}).toList());
monitorDto.setParams(exportMonitor.params.stream()
.map(it -> {
var param = new Param();
param.setField(it.field);
param.setType(it.type);
param.setParamValue(it.value);
return param;
})
.toList());
} else {
monitorDto.setParams(Collections.emptyList());
}
@@ -205,4 +203,5 @@ public abstract class AbstractImExportServiceImpl implements ImExportService {
@Excel(name = "Value")
private String value;
}
}
@@ -63,7 +63,7 @@ import org.apache.hertzbeat.manager.service.AppService;
import org.apache.hertzbeat.manager.service.MonitorService;
import org.apache.hertzbeat.manager.service.ObjectStoreService;
import org.apache.hertzbeat.warehouse.service.WarehouseService;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.event.EventListener;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
@@ -75,13 +75,12 @@ 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)
@Slf4j
public class AppServiceImpl implements AppService, InitializingBean {
public class AppServiceImpl implements AppService, CommandLineRunner {
private static final String PUSH_PROTOCOL_METRICS_NAME = "metrics";
@@ -501,7 +500,7 @@ public class AppServiceImpl implements AppService, InitializingBean {
}
@Override
public void afterPropertiesSet() throws Exception {
public void run(String... args) throws Exception {
var objectStoreConfig = objectStoreConfigService.getConfig();
refreshStore(objectStoreConfig);
}
@@ -42,6 +42,7 @@ import org.apache.hertzbeat.common.entity.manager.MonitorBind;
import org.apache.hertzbeat.common.entity.manager.Param;
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
import org.apache.hertzbeat.common.entity.manager.SdMonitorParam;
import org.apache.hertzbeat.common.entity.manager.Tag;
import org.apache.hertzbeat.common.entity.message.CollectRep;
import org.apache.hertzbeat.common.entity.sd.ServiceDiscoveryProtocol;
import org.apache.hertzbeat.common.support.event.MonitorDeletedEvent;
@@ -53,7 +54,6 @@ import org.apache.hertzbeat.common.util.JsonUtil;
import org.apache.hertzbeat.common.util.SdMonitorOperator;
import org.apache.hertzbeat.common.util.SnowFlakeIdGenerator;
import org.apache.hertzbeat.grafana.service.DashboardService;
import org.apache.hertzbeat.manager.config.ManagerSseManager;
import org.apache.hertzbeat.manager.dao.CollectorDao;
import org.apache.hertzbeat.manager.dao.CollectorMonitorBindDao;
import org.apache.hertzbeat.manager.dao.MonitorBindDao;
@@ -90,6 +90,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -137,8 +138,6 @@ public class MonitorServiceImpl implements MonitorService {
private WarehouseService warehouseService;
@Autowired
private DashboardService dashboardService;
@Autowired
private ManagerSseManager managerSseManager;
public MonitorServiceImpl(List<ImExportService> imExportServiceList) {
imExportServiceList.forEach(it -> imExportServiceMap.put(it.type(), it));
@@ -184,21 +183,17 @@ public class MonitorServiceImpl implements MonitorService {
@Override
public void importConfig(MultipartFile file) throws Exception {
var fileName = FileUtil.getFileName(file);
var type = FileUtil.getFileType(file);
try {
if (!imExportServiceMap.containsKey(type)) {
String errMsg = ExportFileConstants.FILE + " " + fileName + " is not supported.";
throw new RuntimeException(errMsg);
}
var imExportService = imExportServiceMap.get(type);
imExportService.importConfig(fileName, file.getInputStream());
} catch (Exception e){
managerSseManager.broadcastImportTaskFail(fileName, e.getMessage());
throw e;
if (!imExportServiceMap.containsKey(type)) {
throw new RuntimeException(ExportFileConstants.FILE + " " + fileName + " is not supported.");
}
var imExportService = imExportServiceMap.get(type);
imExportService.importConfig(file.getInputStream());
}
@Override
@Transactional(readOnly = true)
public void validate(MonitorDto monitorDto, Boolean isModify) throws IllegalArgumentException {
@@ -431,11 +426,6 @@ public class MonitorServiceImpl implements MonitorService {
appDefine.setDefaultInterval(monitor.getIntervals());
appDefine.setCyclic(true);
appDefine.setTimestamp(System.currentTimeMillis());
Map<String, String> metadata = Map.of(CommonConstants.LABEL_INSTANCE_NAME, monitor.getName(),
CommonConstants.LABEL_INSTANCE_HOST, monitor.getHost());
appDefine.setMetadata(metadata);
appDefine.setLabels(monitor.getLabels());
appDefine.setAnnotations(monitor.getAnnotations());
List<Configmap> configmaps = params.stream().map(param ->
new Configmap(param.getField(), param.getParamValue(), param.getType())).collect(Collectors.toList());
appDefine.setConfigmap(configmaps);
@@ -531,7 +521,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);
@@ -683,11 +673,6 @@ public class MonitorServiceImpl implements MonitorService {
appDefine.setDefaultInterval(monitor.getIntervals());
appDefine.setCyclic(true);
appDefine.setTimestamp(System.currentTimeMillis());
Map<String, String> metadata = Map.of(CommonConstants.LABEL_INSTANCE_NAME, monitor.getName(),
CommonConstants.LABEL_INSTANCE_HOST, monitor.getHost());
appDefine.setMetadata(metadata);
appDefine.setLabels(monitor.getLabels());
appDefine.setAnnotations(monitor.getAnnotations());
List<Configmap> configmaps = params.stream().map(param ->
new Configmap(param.getField(), param.getParamValue(), param.getType())).collect(Collectors.toList());
List<ParamDefine> paramDefaultValue = appDefine.getParams().stream()
@@ -798,11 +783,6 @@ public class MonitorServiceImpl implements MonitorService {
appDefine.setDefaultInterval(monitor.getIntervals());
appDefine.setCyclic(true);
appDefine.setTimestamp(System.currentTimeMillis());
Map<String, String> metadata = Map.of(CommonConstants.LABEL_INSTANCE_NAME, monitor.getName(),
CommonConstants.LABEL_INSTANCE_HOST, monitor.getHost());
appDefine.setMetadata(metadata);
appDefine.setLabels(monitor.getLabels());
appDefine.setAnnotations(monitor.getAnnotations());
List<Param> params = paramDao.findParamsByMonitorId(monitor.getId());
List<Configmap> configmaps = params.stream().map(param -> new Configmap(param.getField(),
param.getParamValue(), param.getType())).collect(Collectors.toList());
@@ -854,11 +834,6 @@ public class MonitorServiceImpl implements MonitorService {
appDefine.setDefaultInterval(monitor.getIntervals());
appDefine.setCyclic(true);
appDefine.setTimestamp(System.currentTimeMillis());
Map<String, String> metadata = Map.of(CommonConstants.LABEL_INSTANCE_NAME, monitor.getName(),
CommonConstants.LABEL_INSTANCE_HOST, monitor.getHost());
appDefine.setMetadata(metadata);
appDefine.setLabels(monitor.getLabels());
appDefine.setAnnotations(monitor.getAnnotations());
resetMetricsCommonField(monitor, appDefine, sdMonitorParam);
List<Configmap> configmaps = params.stream().map(param -> {
@@ -969,11 +944,6 @@ public class MonitorServiceImpl implements MonitorService {
appDefine.setMonitorId(monitorId);
appDefine.setCyclic(false);
appDefine.setTimestamp(System.currentTimeMillis());
Map<String, String> metadata = Map.of(CommonConstants.LABEL_INSTANCE_NAME, monitor.getName(),
CommonConstants.LABEL_INSTANCE_HOST, monitor.getHost());
appDefine.setMetadata(metadata);
appDefine.setLabels(monitor.getLabels());
appDefine.setAnnotations(monitor.getAnnotations());
final Job sdJob = SdMonitorOperator.constructSdJob(appDefine, sdParam);
List<CollectRep.MetricsData> collectRep;
if (collector != null) {
@@ -1007,11 +977,6 @@ public class MonitorServiceImpl implements MonitorService {
appDefine.setMonitorId(monitorId);
appDefine.setCyclic(false);
appDefine.setTimestamp(System.currentTimeMillis());
Map<String, String> metadata = Map.of(CommonConstants.LABEL_INSTANCE_NAME, monitor.getName(),
CommonConstants.LABEL_INSTANCE_HOST, monitor.getHost());
appDefine.setMetadata(metadata);
appDefine.setLabels(monitor.getLabels());
appDefine.setAnnotations(monitor.getAnnotations());
List<Configmap> configmaps = params.stream().map(param ->
new Configmap(param.getField(), param.getParamValue(), param.getType())).collect(Collectors.toList());
appDefine.setConfigmap(configmaps);
@@ -1039,6 +1004,15 @@ public class MonitorServiceImpl implements MonitorService {
collectRep.forEach(CollectRep.MetricsData::close);
}
private List<Tag> filterTags(List<Tag> tags) {
if (tags == null || tags.isEmpty()) {
return new LinkedList<>();
}
return tags.stream()
.filter(tag -> !(tag.getName().equals(CommonConstants.TAG_MONITOR_ID) || tag.getName().equals(CommonConstants.TAG_MONITOR_NAME)))
.collect(Collectors.toList());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void copyMonitor(Long id) {
@@ -1096,11 +1070,6 @@ public class MonitorServiceImpl implements MonitorService {
appDefine.setDefaultInterval(newMonitor.getIntervals());
appDefine.setCyclic(true);
appDefine.setTimestamp(System.currentTimeMillis());
Map<String, String> metadata = Map.of(CommonConstants.LABEL_INSTANCE_NAME, newMonitor.getName(),
CommonConstants.LABEL_INSTANCE_HOST, newMonitor.getHost());
appDefine.setMetadata(metadata);
appDefine.setLabels(newMonitor.getLabels());
appDefine.setAnnotations(newMonitor.getAnnotations());
List<Configmap> configmaps = sourceParams.stream()
.map(param -> new Configmap(param.getField(), param.getParamValue(), param.getType()))
.collect(Collectors.toList());
@@ -285,8 +285,11 @@ public class PluginServiceImpl implements PluginService {
String jarPath = new File(this.getClass().getProtectionDomain().getCodeSource().getLocation().getPath()).getAbsolutePath();
Path extLibPath = Paths.get(new File(jarPath).getParent(), "plugin-lib");
File extLibDir = extLibPath.toFile();
String fileName = pluginUpload.getJarFile().getOriginalFilename();
validateFileName(fileName);
if (fileName == null) {
throw new CommonException("Failed to upload plugin");
}
fileName = UUID.randomUUID().toString().replace("-", "") + "_" + fileName;
File destFile = new File(extLibDir, fileName);
FileUtils.createParentDirectories(destFile);
@@ -318,20 +321,6 @@ public class PluginServiceImpl implements PluginService {
syncPluginStatus();
}
/**
* validate file name if file name is invalid, throw exception
*
* @param fileName file name
*/
private void validateFileName(String fileName) {
if (fileName == null) {
throw new CommonException("Failed to upload plugin");
}
if (fileName.matches(".*(\\.\\.|[\n\t\r/\\\\]).*")) {
throw new CommonException("Invalid plugin file name: " + fileName);
}
}
@Override
public boolean pluginIsEnable(Class<?> clazz) {
return Boolean.TRUE.equals(PLUGIN_ENABLE_STATUS.get(clazz.getName()));
@@ -380,7 +369,7 @@ public class PluginServiceImpl implements PluginService {
}
private void syncSinglePluginStatus(PluginMetadata plugin) {
if (plugin == null || CollectionUtils.isEmpty(plugin.getItems())) {
if (plugin == null || CollectionUtils.isEmpty(plugin.getItems())){
return;
}
for (PluginItem item : plugin.getItems()) {
@@ -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:
@@ -225,16 +225,7 @@ alerter:
access-key-secret: YOUR_ACCESS_KEY_SECRET
signature: YOUR_SMS_SIGNATURE
template-id: YOUR_TEMPLATE_ID
smslocal:
api-key: YOUR_API_KEY_HERE
aws:
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;
@@ -81,17 +81,6 @@ params:
required: true
defaultValue: true
# field-param field key
- field: useProxy
# name-param field display i18n name
name:
zh-CN: 使用代理
en-US: Use Proxy Connection
# type-param field type(most mapping the html input type)
type: boolean
# required-true or false
required: true
defaultValue: false
# field-param field key
- field: username
# name-param field display i18n name
name:
@@ -138,72 +127,6 @@ params:
required: false
# hide param-true or false
hide: true
# field-param field key
- field: proxyHost
# name-param field display i18n name
name:
zh-CN: 代理主机
en-US: Proxy Host
# type-param field type(most mapping the html input type)
type: text
# required-true or false
required: false
# hide param-true or false
hide: true
- field: proxyPort
# name-param field display i18n name
name:
zh-CN: 代理端口
en-US: Proxy 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: false
# hide param-true or false
hide: true
# default value
defaultValue: 22
# field-param field key
- field: proxyUsername
# name-param field display i18n name
name:
zh-CN: 代理用户名
en-US: Proxy Username
# type-param field type(most mapping the html input type)
type: text
# when type is text, use limit to limit string length
limit: 50
# required-true or false
required: false
# hide param-true or false
hide: true
# field-param field key
- field: proxyPassword
# name-param field display i18n name
name:
zh-CN: 代理密码
en-US: Proxy Password
# type-param field type(most mapping the html input tag)
type: password
# required-true or false
required: false
# hide param-true or false
hide: true
# field-param field key
- field: proxyPrivateKey
# name-param field display i18n name
name:
zh-CN: 代理主机私钥
en-US: proxyPrivateKey
# type-param field type(most mapping the html input type)
type: textarea
placeholder: -----BEGIN RSA PRIVATE KEY-----
# required-true or false
required: false
# hide param-true or false
hide: true
# collect metrics config list
metrics:
# metrics - basic, inner monitoring metrics (responseTime - response time)
@@ -251,18 +174,6 @@ metrics:
privateKeyPassphrase: ^_^privateKeyPassphrase^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
# whether to use proxy server for ssh connection
useProxy: ^_^useProxy^_^
# ssh proxy host: ipv4 domain
proxyHost: ^_^proxyHost^_^
# ssh proxy port
proxyPort: ^_^proxyPort^_^
# ssh proxy username
proxyUsername: ^_^proxyUsername^_^
# ssh proxy password
proxyPassword: ^_^proxyPassword^_^
# ssh proxy private key
proxyPrivateKey: ^_^proxyPrivateKey^_^
# ssh run collect script
script: (uname -r ; hostname ; uptime | awk -F "," '{print $1}' | sed "s/ //g") | sed ":a;N;s/\n/^/g;ta" | awk -F '^' 'BEGIN{print "version hostname uptime"} {print $1, $2, $3}'
# ssh response data parse type: oneRow, multiRow
@@ -332,18 +243,6 @@ metrics:
privateKeyPassphrase: ^_^privateKeyPassphrase^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
# whether to use proxy server for ssh connection
useProxy: ^_^useProxy^_^
# ssh proxy host: ipv4 domain
proxyHost: ^_^proxyHost^_^
# ssh proxy port
proxyPort: ^_^proxyPort^_^
# ssh proxy username
proxyUsername: ^_^proxyUsername^_^
# ssh proxy password
proxyPassword: ^_^proxyPassword^_^
# ssh proxy private key
proxyPrivateKey: ^_^proxyPrivateKey^_^
script: "LANG=C lscpu | awk -F: '$1==\"Model name\" {print $2}';awk '/processor/{core++} END{print core}' /proc/cpuinfo;uptime | sed 's/,/ /g' | awk '{for(i=NF-2;i<=NF;i++)print $i }' | xargs;vmstat 1 1 | awk 'NR==3{print $11}';vmstat 1 1 | awk 'NR==3{print $12}';vmstat 1 2 | awk 'NR==4{print $15}'"
parseType: oneRow
@@ -412,18 +311,6 @@ metrics:
privateKeyPassphrase: ^_^privateKeyPassphrase^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
# whether to use proxy server for ssh connection
useProxy: ^_^useProxy^_^
# ssh proxy host: ipv4 domain
proxyHost: ^_^proxyHost^_^
# ssh proxy port
proxyPort: ^_^proxyPort^_^
# ssh proxy username
proxyUsername: ^_^proxyUsername^_^
# ssh proxy password
proxyPassword: ^_^proxyPassword^_^
# ssh proxy private key
proxyPrivateKey: ^_^proxyPrivateKey^_^
script: free -m | awk 'BEGIN{print "total used free buff_cache available"} NR==2{print $2,$3,$4,$6,$7}'
parseType: multiRow
@@ -469,18 +356,6 @@ metrics:
privateKeyPassphrase: ^_^privateKeyPassphrase^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
# whether to use proxy server for ssh connection
useProxy: ^_^useProxy^_^
# ssh proxy host: ipv4 domain
proxyHost: ^_^proxyHost^_^
# ssh proxy port
proxyPort: ^_^proxyPort^_^
# ssh proxy username
proxyUsername: ^_^proxyUsername^_^
# ssh proxy password
proxyPassword: ^_^proxyPassword^_^
# ssh proxy private key
proxyPrivateKey: ^_^proxyPrivateKey^_^
script: vmstat -D | awk 'NR==1{print $1}';vmstat -D | awk 'NR==2{print $1}';vmstat 1 1 | awk 'NR==3{print $10}';vmstat 1 1 | awk 'NR==3{print $9}';vmstat 1 1 | awk 'NR==3{print $16}'
parseType: oneRow
@@ -521,18 +396,6 @@ metrics:
privateKeyPassphrase: ^_^privateKeyPassphrase^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
# whether to use proxy server for ssh connection
useProxy: ^_^useProxy^_^
# ssh proxy host: ipv4 domain
proxyHost: ^_^proxyHost^_^
# ssh proxy port
proxyPort: ^_^proxyPort^_^
# ssh proxy username
proxyUsername: ^_^proxyUsername^_^
# ssh proxy password
proxyPassword: ^_^proxyPassword^_^
# ssh proxy private key
proxyPrivateKey: ^_^proxyPrivateKey^_^
script: cat /proc/net/dev | tail -n +3 | awk 'BEGIN{ print "interface_name receive_bytes transmit_bytes"} {print $1,$2,$10}'
parseType: multiRow
@@ -581,18 +444,6 @@ metrics:
privateKeyPassphrase: ^_^privateKeyPassphrase^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
# whether to use proxy server for ssh connection
useProxy: ^_^useProxy^_^
# ssh proxy host: ipv4 domain
proxyHost: ^_^proxyHost^_^
# ssh proxy port
proxyPort: ^_^proxyPort^_^
# ssh proxy username
proxyUsername: ^_^proxyUsername^_^
# ssh proxy password
proxyPassword: ^_^proxyPassword^_^
# ssh proxy private key
proxyPrivateKey: ^_^proxyPrivateKey^_^
script: df -mP | tail -n +2 | awk 'BEGIN{ print "filesystem used available usage mounted"} {print $1,$3,$4,$5,$6}'
parseType: multiRow
@@ -635,18 +486,6 @@ metrics:
privateKeyPassphrase: ^_^privateKeyPassphrase^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
# whether to use proxy server for ssh connection
useProxy: ^_^useProxy^_^
# ssh proxy host: ipv4 domain
proxyHost: ^_^proxyHost^_^
# ssh proxy port
proxyPort: ^_^proxyPort^_^
# ssh proxy username
proxyUsername: ^_^proxyUsername^_^
# ssh proxy password
proxyPassword: ^_^proxyPassword^_^
# ssh proxy private key
proxyPrivateKey: ^_^proxyPrivateKey^_^
script: ps aux | sort -k3nr | awk 'BEGIN{ print "pid cpu_usage mem_usage command" } {printf "%s %s %s ", $2, $3, $4; for (i=11; i<=NF; i++) { printf "%s", $i; if (i < NF) printf ""; } print ""}' | head -n 11
parseType: multiRow
@@ -689,17 +528,5 @@ metrics:
privateKeyPassphrase: ^_^privateKeyPassphrase^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
# whether to use proxy server for ssh connection
useProxy: ^_^useProxy^_^
# ssh proxy host: ipv4 domain
proxyHost: ^_^proxyHost^_^
# ssh proxy port
proxyPort: ^_^proxyPort^_^
# ssh proxy username
proxyUsername: ^_^proxyUsername^_^
# ssh proxy password
proxyPassword: ^_^proxyPassword^_^
# ssh proxy private key
proxyPrivateKey: ^_^proxyPrivateKey^_^
script: ps aux | sort -k4nr | awk 'BEGIN{ print "pid cpu_usage mem_usage command" } {printf "%s %s %s ", $2, $3, $4; for (i=11; i<=NF; i++) { printf "%s", $i; if (i < NF) printf ""; } print ""}' | head -n 11
parseType: multiRow
@@ -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^_^
@@ -82,17 +82,6 @@ params:
required: true
defaultValue: true
# field-param field key
- field: useProxy
# name-param field display i18n name
name:
zh-CN: 使用代理
en-US: Use Proxy Connection
# type-param field type(most mapping the html input type)
type: boolean
# required-true or false
required: true
defaultValue: false
# field-param field key
- field: username
# name-param field display i18n name
name:
@@ -139,72 +128,6 @@ params:
required: false
# hide param-true or false
hide: true
# field-param field key
- field: proxyHost
# name-param field display i18n name
name:
zh-CN: 代理主机
en-US: Proxy Host
# type-param field type(most mapping the html input type)
type: text
# required-true or false
required: false
# hide param-true or false
hide: true
- field: proxyPort
# name-param field display i18n name
name:
zh-CN: 代理端口
en-US: Proxy 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: false
# hide param-true or false
hide: true
# default value
defaultValue: 22
# field-param field key
- field: proxyUsername
# name-param field display i18n name
name:
zh-CN: 代理用户名
en-US: Proxy Username
# type-param field type(most mapping the html input type)
type: text
# when type is text, use limit to limit string length
limit: 50
# required-true or false
required: false
# hide param-true or false
hide: true
# field-param field key
- field: proxyPassword
# name-param field display i18n name
name:
zh-CN: 代理密码
en-US: Proxy Password
# type-param field type(most mapping the html input tag)
type: password
# required-true or false
required: false
# hide param-true or false
hide: true
# field-param field key
- field: proxyPrivateKey
# name-param field display i18n name
name:
zh-CN: 代理主机私钥
en-US: proxyPrivateKey
# type-param field type(most mapping the html input type)
type: textarea
placeholder: -----BEGIN RSA PRIVATE KEY-----
# required-true or false
required: false
# hide param-true or false
hide: true
# collect metrics config list
metrics:
# metrics - basic, inner monitoring metrics (responseTime - response time)
@@ -252,18 +175,6 @@ metrics:
privateKeyPassphrase: ^_^privateKeyPassphrase^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
# whether to use proxy server for ssh connection
useProxy: ^_^useProxy^_^
# ssh proxy host: ipv4 domain
proxyHost: ^_^proxyHost^_^
# ssh proxy port
proxyPort: ^_^proxyPort^_^
# ssh proxy username
proxyUsername: ^_^proxyUsername^_^
# ssh proxy password
proxyPassword: ^_^proxyPassword^_^
# ssh proxy private key
proxyPrivateKey: ^_^proxyPrivateKey^_^
# ssh run collect script
script: (uname -r ; hostname ; uptime | awk -F "," '{print $1}' | sed "s/ //g") | sed ":a;N;s/\n/^/g;ta" | awk -F '^' 'BEGIN{print "version hostname uptime"} {print $1, $2, $3}'
# ssh response data parse type: oneRow, multiRow
@@ -333,18 +244,6 @@ metrics:
privateKeyPassphrase: ^_^privateKeyPassphrase^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
# whether to use proxy server for ssh connection
useProxy: ^_^useProxy^_^
# ssh proxy host: ipv4 domain
proxyHost: ^_^proxyHost^_^
# ssh proxy port
proxyPort: ^_^proxyPort^_^
# ssh proxy username
proxyUsername: ^_^proxyUsername^_^
# ssh proxy password
proxyPassword: ^_^proxyPassword^_^
# ssh proxy private key
proxyPrivateKey: ^_^proxyPrivateKey^_^
script: "LANG=C lscpu | awk -F: '$1==\"Model name\" {print $2}';awk '/processor/{core++} END{print core}' /proc/cpuinfo;uptime | sed 's/,/ /g' | awk '{for(i=NF-2;i<=NF;i++)print $i }' | xargs;vmstat 1 1 | awk 'NR==3{print $11}';vmstat 1 1 | awk 'NR==3{print $12}';vmstat 1 2 | awk 'NR==4{print $15}'"
parseType: oneRow
@@ -413,18 +312,6 @@ metrics:
privateKeyPassphrase: ^_^privateKeyPassphrase^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
# whether to use proxy server for ssh connection
useProxy: ^_^useProxy^_^
# ssh proxy host: ipv4 domain
proxyHost: ^_^proxyHost^_^
# ssh proxy port
proxyPort: ^_^proxyPort^_^
# ssh proxy username
proxyUsername: ^_^proxyUsername^_^
# ssh proxy password
proxyPassword: ^_^proxyPassword^_^
# ssh proxy private key
proxyPrivateKey: ^_^proxyPrivateKey^_^
script: free -m | awk 'BEGIN{print "total used free buff_cache available"} NR==2{print $2,$3,$4,$6,$7}'
parseType: multiRow
@@ -470,18 +357,6 @@ metrics:
privateKeyPassphrase: ^_^privateKeyPassphrase^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
# whether to use proxy server for ssh connection
useProxy: ^_^useProxy^_^
# ssh proxy host: ipv4 domain
proxyHost: ^_^proxyHost^_^
# ssh proxy port
proxyPort: ^_^proxyPort^_^
# ssh proxy username
proxyUsername: ^_^proxyUsername^_^
# ssh proxy password
proxyPassword: ^_^proxyPassword^_^
# ssh proxy private key
proxyPrivateKey: ^_^proxyPrivateKey^_^
script: vmstat -D | awk 'NR==1{print $1}';vmstat -D | awk 'NR==2{print $1}';vmstat 1 1 | awk 'NR==3{print $10}';vmstat 1 1 | awk 'NR==3{print $9}';vmstat 1 1 | awk 'NR==3{print $16}'
parseType: oneRow
@@ -522,18 +397,6 @@ metrics:
privateKeyPassphrase: ^_^privateKeyPassphrase^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
# whether to use proxy server for ssh connection
useProxy: ^_^useProxy^_^
# ssh proxy host: ipv4 domain
proxyHost: ^_^proxyHost^_^
# ssh proxy port
proxyPort: ^_^proxyPort^_^
# ssh proxy username
proxyUsername: ^_^proxyUsername^_^
# ssh proxy password
proxyPassword: ^_^proxyPassword^_^
# ssh proxy private key
proxyPrivateKey: ^_^proxyPrivateKey^_^
script: cat /proc/net/dev | tail -n +3 | awk 'BEGIN{ print "interface_name receive_bytes transmit_bytes"} {gsub(":", "", $1); print $1,$2,$10}'
parseType: multiRow
@@ -582,18 +445,6 @@ metrics:
privateKeyPassphrase: ^_^privateKeyPassphrase^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
# whether to use proxy server for ssh connection
useProxy: ^_^useProxy^_^
# ssh proxy host: ipv4 domain
proxyHost: ^_^proxyHost^_^
# ssh proxy port
proxyPort: ^_^proxyPort^_^
# ssh proxy username
proxyUsername: ^_^proxyUsername^_^
# ssh proxy password
proxyPassword: ^_^proxyPassword^_^
# ssh proxy private key
proxyPrivateKey: ^_^proxyPrivateKey^_^
script: df -mP | tail -n +2 | awk 'BEGIN{ print "filesystem used available usage mounted"} {print $1,$3,$4,$5,$6}'
parseType: multiRow
@@ -636,18 +487,6 @@ metrics:
privateKeyPassphrase: ^_^privateKeyPassphrase^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
# whether to use proxy server for ssh connection
useProxy: ^_^useProxy^_^
# ssh proxy host: ipv4 domain
proxyHost: ^_^proxyHost^_^
# ssh proxy port
proxyPort: ^_^proxyPort^_^
# ssh proxy username
proxyUsername: ^_^proxyUsername^_^
# ssh proxy password
proxyPassword: ^_^proxyPassword^_^
# ssh proxy private key
proxyPrivateKey: ^_^proxyPrivateKey^_^
script: ps aux | sort -k3nr | awk 'BEGIN{ print "pid cpu_usage mem_usage command" } {printf "%s %s %s ", $2, $3, $4; for (i=11; i<=NF; i++) { printf "%s", $i; if (i < NF) printf ""; } print ""}' | head -n 11
parseType: multiRow
@@ -690,18 +529,5 @@ metrics:
privateKeyPassphrase: ^_^privateKeyPassphrase^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
# whether to use proxy server for ssh connection
useProxy: ^_^useProxy^_^
# ssh proxy host: ipv4 domain
proxyHost: ^_^proxyHost^_^
# ssh proxy port
proxyPort: ^_^proxyPort^_^
# ssh proxy username
proxyUsername: ^_^proxyUsername^_^
# ssh proxy password
proxyPassword: ^_^proxyPassword^_^
# ssh proxy private key
proxyPrivateKey: ^_^proxyPrivateKey^_^
script: ps aux | sort -k4nr | awk 'BEGIN{ print "pid cpu_usage mem_usage command" } {printf "%s %s %s ", $2, $3, $4; for (i=11; i<=NF; i++) { printf "%s", $i; if (i < NF) printf ""; } print ""}' | head -n 11
parseType: multiRow
@@ -81,17 +81,6 @@ params:
required: true
defaultValue: true
# field-param field key
- field: useProxy
# name-param field display i18n name
name:
zh-CN: 使用代理
en-US: Use Proxy Connection
# type-param field type(most mapping the html input type)
type: boolean
# required-true or false
required: true
defaultValue: false
# field-param field key
- field: username
# name-param field display i18n name
name:
@@ -138,72 +127,6 @@ params:
required: false
# hide param-true or false
hide: true
# field-param field key
- field: proxyHost
# name-param field display i18n name
name:
zh-CN: 代理主机
en-US: Proxy Host
# type-param field type(most mapping the html input type)
type: text
# required-true or false
required: false
# hide param-true or false
hide: true
- field: proxyPort
# name-param field display i18n name
name:
zh-CN: 代理端口
en-US: Proxy 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: false
# hide param-true or false
hide: true
# default value
defaultValue: 22
# field-param field key
- field: proxyUsername
# name-param field display i18n name
name:
zh-CN: 代理用户名
en-US: Proxy Username
# type-param field type(most mapping the html input type)
type: text
# when type is text, use limit to limit string length
limit: 50
# required-true or false
required: false
# hide param-true or false
hide: true
# field-param field key
- field: proxyPassword
# name-param field display i18n name
name:
zh-CN: 代理密码
en-US: Proxy Password
# type-param field type(most mapping the html input tag)
type: password
# required-true or false
required: false
# hide param-true or false
hide: true
# field-param field key
- field: proxyPrivateKey
# name-param field display i18n name
name:
zh-CN: 代理主机私钥
en-US: proxyPrivateKey
# type-param field type(most mapping the html input type)
type: textarea
placeholder: -----BEGIN RSA PRIVATE KEY-----
# required-true or false
required: false
# hide param-true or false
hide: true
# collect metrics config list
metrics:
# metrics - basic, inner monitoring metrics (responseTime - response time)
@@ -251,18 +174,6 @@ metrics:
privateKeyPassphrase: ^_^privateKeyPassphrase^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
# whether to use proxy server for ssh connection
useProxy: ^_^useProxy^_^
# ssh proxy host: ipv4 domain
proxyHost: ^_^proxyHost^_^
# ssh proxy port
proxyPort: ^_^proxyPort^_^
# ssh proxy username
proxyUsername: ^_^proxyUsername^_^
# ssh proxy password
proxyPassword: ^_^proxyPassword^_^
# ssh proxy private key
proxyPrivateKey: ^_^proxyPrivateKey^_^
# ssh run collect script
script: (uname -r ; hostname ; uptime | awk -F "," '{print $1}' | sed "s/ //g") | sed ":a;N;s/\n/^/g;ta" | awk -F '^' 'BEGIN{print "version hostname uptime"} {print $1, $2, $3}'
# ssh response data parse type: oneRow, multiRow
@@ -332,18 +243,6 @@ metrics:
privateKeyPassphrase: ^_^privateKeyPassphrase^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
# whether to use proxy server for ssh connection
useProxy: ^_^useProxy^_^
# ssh proxy host: ipv4 domain
proxyHost: ^_^proxyHost^_^
# ssh proxy port
proxyPort: ^_^proxyPort^_^
# ssh proxy username
proxyUsername: ^_^proxyUsername^_^
# ssh proxy password
proxyPassword: ^_^proxyPassword^_^
# ssh proxy private key
proxyPrivateKey: ^_^proxyPrivateKey^_^
script: "LANG=C lscpu | awk -F: '$1==\"Model name\" {print $2}';awk '/processor/{core++} END{print core}' /proc/cpuinfo;uptime | sed 's/,/ /g' | awk '{for(i=NF-2;i<=NF;i++)print $i }' | xargs;vmstat 1 1 | awk 'NR==3{print $11}';vmstat 1 1 | awk 'NR==3{print $12}';vmstat 1 2 | awk 'NR==4{print $15}'"
parseType: oneRow
@@ -412,18 +311,6 @@ metrics:
privateKeyPassphrase: ^_^privateKeyPassphrase^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
# whether to use proxy server for ssh connection
useProxy: ^_^useProxy^_^
# ssh proxy host: ipv4 domain
proxyHost: ^_^proxyHost^_^
# ssh proxy port
proxyPort: ^_^proxyPort^_^
# ssh proxy username
proxyUsername: ^_^proxyUsername^_^
# ssh proxy password
proxyPassword: ^_^proxyPassword^_^
# ssh proxy private key
proxyPrivateKey: ^_^proxyPrivateKey^_^
script: free -m | awk 'BEGIN{print "total used free buff_cache available"} NR==2{print $2,$3,$4,$6,$7}'
parseType: multiRow
@@ -469,18 +356,6 @@ metrics:
privateKeyPassphrase: ^_^privateKeyPassphrase^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
# whether to use proxy server for ssh connection
useProxy: ^_^useProxy^_^
# ssh proxy host: ipv4 domain
proxyHost: ^_^proxyHost^_^
# ssh proxy port
proxyPort: ^_^proxyPort^_^
# ssh proxy username
proxyUsername: ^_^proxyUsername^_^
# ssh proxy password
proxyPassword: ^_^proxyPassword^_^
# ssh proxy private key
proxyPrivateKey: ^_^proxyPrivateKey^_^
script: vmstat -D | awk 'NR==1{print $1}';vmstat -D | awk 'NR==2{print $1}';vmstat 1 1 | awk 'NR==3{print $10}';vmstat 1 1 | awk 'NR==3{print $9}';vmstat 1 1 | awk 'NR==3{print $16}'
parseType: oneRow
@@ -521,18 +396,6 @@ metrics:
privateKeyPassphrase: ^_^privateKeyPassphrase^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
# whether to use proxy server for ssh connection
useProxy: ^_^useProxy^_^
# ssh proxy host: ipv4 domain
proxyHost: ^_^proxyHost^_^
# ssh proxy port
proxyPort: ^_^proxyPort^_^
# ssh proxy username
proxyUsername: ^_^proxyUsername^_^
# ssh proxy password
proxyPassword: ^_^proxyPassword^_^
# ssh proxy private key
proxyPrivateKey: ^_^proxyPrivateKey^_^
script: cat /proc/net/dev | tail -n +3 | awk 'BEGIN{ print "interface_name receive_bytes transmit_bytes"} {print $1,$2,$10}'
parseType: multiRow
@@ -581,18 +444,6 @@ metrics:
privateKeyPassphrase: ^_^privateKeyPassphrase^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
# whether to use proxy server for ssh connection
useProxy: ^_^useProxy^_^
# ssh proxy host: ipv4 domain
proxyHost: ^_^proxyHost^_^
# ssh proxy port
proxyPort: ^_^proxyPort^_^
# ssh proxy username
proxyUsername: ^_^proxyUsername^_^
# ssh proxy password
proxyPassword: ^_^proxyPassword^_^
# ssh proxy private key
proxyPrivateKey: ^_^proxyPrivateKey^_^
script: df -mP | tail -n +2 | awk 'BEGIN{ print "filesystem used available usage mounted"} {print $1,$3,$4,$5,$6}'
parseType: multiRow
@@ -635,18 +486,6 @@ metrics:
privateKeyPassphrase: ^_^privateKeyPassphrase^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
# whether to use proxy server for ssh connection
useProxy: ^_^useProxy^_^
# ssh proxy host: ipv4 domain
proxyHost: ^_^proxyHost^_^
# ssh proxy port
proxyPort: ^_^proxyPort^_^
# ssh proxy username
proxyUsername: ^_^proxyUsername^_^
# ssh proxy password
proxyPassword: ^_^proxyPassword^_^
# ssh proxy private key
proxyPrivateKey: ^_^proxyPrivateKey^_^
script: ps aux | sort -k3nr | awk 'BEGIN{ print "pid cpu_usage mem_usage command" } {printf "%s %s %s ", $2, $3, $4; for (i=11; i<=NF; i++) { printf "%s", $i; if (i < NF) printf ""; } print ""}' | head -n 11
parseType: multiRow
@@ -689,17 +528,5 @@ metrics:
privateKeyPassphrase: ^_^privateKeyPassphrase^_^
timeout: ^_^timeout^_^
reuseConnection: ^_^reuseConnection^_^
# whether to use proxy server for ssh connection
useProxy: ^_^useProxy^_^
# ssh proxy host: ipv4 domain
proxyHost: ^_^proxyHost^_^
# ssh proxy port
proxyPort: ^_^proxyPort^_^
# ssh proxy username
proxyUsername: ^_^proxyUsername^_^
# ssh proxy password
proxyPassword: ^_^proxyPassword^_^
# ssh proxy private key
proxyPrivateKey: ^_^proxyPrivateKey^_^
script: ps aux | sort -k4nr | awk 'BEGIN{ print "pid cpu_usage mem_usage command" } {printf "%s %s %s ", $2, $3, $4; for (i=11; i<=NF; i++) { printf "%s", $i; if (i < NF) printf ""; } print ""}' | head -n 11
parseType: multiRow

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