Compare commits

..
Author SHA1 Message Date
tomsun28 1ad532ce75 [webapp] update ui theme
Signed-off-by: tomsun28 <tomsun28@outlook.com>
2025-02-09 17:05:00 +08:00
tomsun28 bf9d1f2b1d [webapp] update ui theme
Signed-off-by: tomsun28 <tomsun28@outlook.com>
2025-02-09 16:26:51 +08:00
214 changed files with 1167 additions and 10322 deletions
-27
View File
@@ -2298,33 +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"
]
}
],
"contributorsPerLine": 7,
-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
+2 -5
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,9 +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>
</tr>
</tbody>
</table>
@@ -537,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 -5
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,9 +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>
</tr>
</tbody>
</table>
@@ -536,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)
+19
View File
@@ -98,6 +98,25 @@
<version>${easy-poi.version}</version>
<scope>compile</scope>
</dependency>
<!-- sms -->
<dependency>
<groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java-sms</artifactId>
<exclusions>
<exclusion>
<groupId>com.squareup.okhttp</groupId>
<artifactId>logging-interceptor</artifactId>
</exclusion>
<exclusion>
<groupId>com.squareup.okhttp</groupId>
<artifactId>okhttp</artifactId>
</exclusion>
<exclusion>
<groupId>com.squareup.okio</groupId>
<artifactId>okio</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.huaweicloud.sdk</groupId>
<artifactId>huaweicloud-sdk-smn</artifactId>
@@ -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);
}
}
}
@@ -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.alert.config;
import lombok.Data;
/**
* Alibaba Cloud SMS configuration properties
*/
@Data
public class AlibabaSmsProperties {
/**
* Alibaba Cloud access key id
*/
private String accessKeyId;
/**
* Alibaba Cloud access key secret
*/
private String accessKeySecret;
/**
* SMS signature
*/
private String signName;
/**
* SMS template Code
*/
private String templateCode;
}
@@ -1,42 +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.config;
import lombok.Data;
/**
* AWS Cloud SMS configuration properties
*/
@Data
public class AwsSmsProperties {
/**
* AWS Cloud access key id
*/
private String accessKeyId;
/**
* AWS Cloud access key secret
*/
private String accessKeySecret;
/**
* AWS Cloud region
*/
private String region;
}
@@ -1,65 +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.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* SMS configuration
*/
@Data
@Component
@ConfigurationProperties(prefix = "alerter.sms")
public class SmsConfig {
/**
* whether to enable SMS, default is false
*/
private boolean enable = false;
/**
* sms service provider
*/
private String type;
/**
* Tencent cloud SMS configuration
*/
private TencentSmsProperties tencent;
/**
* Aliyun SMS configuration
*/
private AlibabaSmsProperties alibaba;
/**
* UniSMS configuration
*/
private UniSmsProperties unisms;
/**
* Aws configuration
*/
private AwsSmsProperties aws;
/**
* Smslocal SMS configuration
*/
private SmslocalSmsProperties smslocal;
}
@@ -1,33 +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.config;
import lombok.Data;
/**
* Smslocal SMS Properties
*/
@Data
public class SmslocalSmsProperties {
/**
* SmsLocal account api key
*/
private String apiKey;
}
@@ -1,51 +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.config;
import lombok.Data;
/**
* Tencent Cloud SMS properties
*/
@Data
public class TencentSmsProperties {
/**
* Tencent cloud account secret id
*/
private String secretId;
/**
* Tencent cloud account secret key
*/
private String secretKey;
/**
* SMS app id
*/
private String appId;
/**
* SMS signature
*/
private String signName;
/**
* SMS template ID
*/
private String templateId;
}
@@ -1,51 +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.config;
import lombok.Data;
/**
* UniSMS properties
*/
@Data
public class UniSmsProperties {
/**
* UniSMS access key id
*/
private String accessKeyId;
/**
* UniSMS access key secret, required for HMAC mode
*/
private String accessKeySecret;
/**
* SMS signature
*/
private String signature;
/**
* SMS template ID
*/
private String templateId;
/**
* Authentication mode: simple or hmac, default is simple
*/
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;
}
}
@@ -80,7 +80,7 @@ public class AlertNoticeDispatch {
if (noticeTemplate == null) {
noticeTemplate = noticeConfigService.getDefaultNoticeTemplateByType(alertNotifyHandler.type());
}
if (noticeTemplate == null && alertNotifyHandler.type() != 0) {
if (noticeTemplate == null) {
log.error("alert does not have mapping default notice template. type: {}.", alertNotifyHandler.type());
throw new NullPointerException(alertNotifyHandler.type() + " does not have mapping default notice template");
}
@@ -0,0 +1,56 @@
/*
* 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.notice.impl;
import java.util.ResourceBundle;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.notice.AlertNoticeException;
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.util.ResourceBundleUtil;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
/**
* Send alarm information through Alibaba Cloud SMS
*/
@Component
@RequiredArgsConstructor
@Slf4j
@ConditionalOnProperty("common.sms.aliyun.app-id")
final class AliYunAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl {
private final ResourceBundle bundle = ResourceBundleUtil.getBundle("alerter");
@Override
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
// SMS notification
try {
// todo send aliyun sms
} catch (Exception e) {
throw new AlertNoticeException("[Sms Notify Error] " + e.getMessage());
}
}
@Override
public byte type() {
return 0;
}
}
@@ -21,12 +21,12 @@ import java.util.ResourceBundle;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.notice.AlertNoticeException;
import org.apache.hertzbeat.alert.service.SmsClient;
import org.apache.hertzbeat.alert.service.SmsClientFactory;
import org.apache.hertzbeat.alert.service.TencentSmsClient;
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.util.ResourceBundleUtil;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
/**
@@ -35,22 +35,35 @@ import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
@Slf4j
@ConditionalOnProperty("common.sms.tencent.app-id")
@Deprecated
final class SmsAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl {
private final SmsClientFactory smsFactory;
private final TencentSmsClient tencentSmsClient;
private final ResourceBundle bundle = ResourceBundleUtil.getBundle("alerter");
@Override
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
// SMS notification todo use the rest api not sdk
try {
SmsClient smsClient = smsFactory.getSmsClient();
if (smsClient == null) {
throw new AlertNoticeException("No SMS Service available, please check the configuration");
String instance = null;
String priority = null;
String content = null;
if (alert.getCommonLabels() != null) {
instance = alert.getCommonLabels().get("instance");
priority = 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);
}
}
if (!smsClient.checkConfig()) {
throw new AlertNoticeException(smsClient.getType() + " SMS Service configuration is invalid, please check the configuration");
}
smsClient.sendMessage(receiver, noticeTemplate, alert);
String[] params = new String[3];
params[0] = instance == null ? alert.getGroupKey() : instance;
params[1] = priority == null ? "unknown" : priority;
params[2] = content;
tencentSmsClient.sendMessage(params, new String[]{receiver.getPhone()});
} catch (Exception e) {
throw new AlertNoticeException("[Sms Notify Error] " + e.getMessage());
}
@@ -80,6 +80,7 @@ final class WeChatAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl
JsonObject textContent = new JsonObject();
// Here you can construct the message content based on the NoticeTemplate and Alert information
// String alertMessage = String.format("警告:%s\n详情:%s", alert.getAlertDefineId(), alert.getContent());
String alertMessage = "Alert message content";
textContent.addProperty("content", alertMessage);
messageContent.add("text", textContent);
@@ -1,42 +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.common.entity.alerter.GroupAlert;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
/**
* SMS client interface
*/
public interface SmsClient {
/**
* send SMS
*/
void sendMessage(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert);
/**
* get SMS provider type
*/
String getType();
/**
* check SMS configuration, return true if the configuration is correct
*/
boolean checkConfig();
}
@@ -1,149 +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 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.UniSmsClientImpl;
import org.apache.hertzbeat.alert.service.impl.AlibabaSmsClientImpl;
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
import org.apache.hertzbeat.common.constants.GeneralConfigTypeEnum;
import org.apache.hertzbeat.common.entity.manager.GeneralConfig;
import org.apache.hertzbeat.common.support.event.SmsConfigChangeEvent;
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.UNISMS;
import static org.apache.hertzbeat.common.constants.SmsConstants.SMSLOCAL;
/**
* SMS client factory
*/
@Slf4j
@Component
public class SmsClientFactory {
private static final String TYPE = GeneralConfigTypeEnum.sms.name();
private final GeneralConfigDao generalConfigDao;
private final ObjectMapper objectMapper;
private final SmsConfig yamlSmsConfig;
private volatile SmsClient currentSmsClient;
public SmsClientFactory(GeneralConfigDao generalConfigDao, ObjectMapper objectMapper, SmsConfig yamlSmsConfig) {
this.generalConfigDao = generalConfigDao;
this.objectMapper = objectMapper;
this.yamlSmsConfig = yamlSmsConfig;
}
/**
* SMS configuration change event listener
*/
@EventListener(SmsConfigChangeEvent.class)
public void onSmsConfigChange(SmsConfigChangeEvent event) {
log.info("[SmsClientFactory] SMS configuration change event received");
synchronized (this) {
currentSmsClient = null;
}
}
public SmsClient getSmsClient() {
if (currentSmsClient != null) {
return currentSmsClient;
}
synchronized (this) {
if (currentSmsClient != null) {
return currentSmsClient;
}
loadConfig();
return currentSmsClient;
}
}
private void loadConfig() {
try {
// 1. try to load database configuration
SmsConfig dbConfig = loadDatabaseConfig();
if (dbConfig != null && !dbConfig.getType().isBlank() && dbConfig.isEnable()) {
createSmsClient(dbConfig);
if (currentSmsClient != null) {
log.info("[SmsClientFactory] Using database SMS configuration, provider: {}", dbConfig.getType());
return;
}
}
// 2. try to load YAML configuration
if (yamlSmsConfig != null && !yamlSmsConfig.getType().isBlank() && yamlSmsConfig.isEnable()) {
createSmsClient(yamlSmsConfig);
if (currentSmsClient != null) {
log.info("[SmsClientFactory] Using YAML SMS configuration, provider: {}", yamlSmsConfig.getType());
return;
}
}
log.warn("[SmsClientFactory] No valid SMS configuration found");
} catch (Exception e) {
log.error("[SmsClientFactory] Failed to load SMS configuration", e);
currentSmsClient = null;
}
}
private SmsConfig loadDatabaseConfig() {
try {
GeneralConfig config = generalConfigDao.findByType(TYPE);
if (config != null && config.getContent() != null) {
return objectMapper.readValue(config.getContent(), SmsConfig.class);
}
} catch (Exception e) {
log.error("[SmsClientFactory] Failed to load database configuration", e);
}
return null;
}
private void createSmsClient(SmsConfig smsConfig) {
switch (smsConfig.getType()) {
case TENCENT:
currentSmsClient = new TencentSmsClientImpl(smsConfig.getTencent());
break;
case UNISMS:
currentSmsClient = new UniSmsClientImpl(smsConfig.getUnisms());
break;
case ALIBABA:
currentSmsClient = new AlibabaSmsClientImpl(smsConfig.getAlibaba());
break;
case SMSLOCAL:
currentSmsClient = new SmsLocalSmsClientImpl(smsConfig.getSmslocal());
break;
case AWS:
currentSmsClient = new AwsSmsClientImpl(smsConfig.getAws());
break;
default:
log.warn("[SmsClientFactory] Unsupported SMS provider type: {}", smsConfig.getType());
break;
}
}
}
@@ -0,0 +1,101 @@
/*
* 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 com.tencentcloudapi.common.Credential;
import com.tencentcloudapi.sms.v20210111.SmsClient;
import com.tencentcloudapi.sms.v20210111.models.SendSmsRequest;
import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse;
import com.tencentcloudapi.sms.v20210111.models.SendStatus;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.config.CommonProperties;
import org.apache.hertzbeat.common.support.exception.SendMessageException;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
/**
* sms service client for tencent cloud
*/
@Component
@ConditionalOnProperty("common.sms.tencent.app-id")
@Slf4j
public class TencentSmsClient {
private static final String RESPONSE_OK = "Ok";
private static final String REGION = "ap-guangzhou";
private SmsClient smsClient;
private String appId;
private String signName;
private String templateId;
public TencentSmsClient(CommonProperties properties) {
if (properties == null || properties.getSms() == null || properties.getSms().getTencent() == null) {
log.error("init error, please config TencentSmsClient props in application.yml");
throw new IllegalArgumentException("please config TencentSmsClient props");
}
initSmsClient(properties.getSms().getTencent());
}
private void initSmsClient(CommonProperties.TencentSmsProperties tencent) {
this.appId = tencent.getAppId();
this.signName = tencent.getSignName();
this.templateId = tencent.getTemplateId();
Credential cred = new Credential(tencent.getSecretId(), tencent.getSecretKey());
smsClient = new SmsClient(cred, REGION);
}
/**
* send text message
* @param appId appId
* @param signName sign name
* @param templateId template id
* @param templateValues template values
* @param phones phones num
*/
public void sendMessage(String appId, String signName, String templateId,
String[] templateValues, String[] phones) {
SendSmsRequest req = new SendSmsRequest();
req.setSmsSdkAppId(appId);
req.setSignName(signName);
req.setTemplateId(templateId);
req.setTemplateParamSet(templateValues);
req.setPhoneNumberSet(phones);
try {
SendSmsResponse smsResponse = this.smsClient.SendSms(req);
SendStatus sendStatus = smsResponse.getSendStatusSet()[0];
if (!RESPONSE_OK.equals(sendStatus.getCode())) {
throw new SendMessageException(sendStatus.getCode() + ":" + sendStatus.getMessage());
}
} catch (Exception e) {
log.warn(e.getMessage());
throw new SendMessageException(e.getMessage());
}
}
/**
* send text message
* @param templateValues template values
* @param phones phones num
*/
public void sendMessage(String[] templateValues, String[] phones) {
sendMessage(this.appId, this.signName, this.templateId, templateValues, phones);
}
}
@@ -1,234 +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.AlibabaSmsProperties;
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.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.SimpleTimeZone;
import java.util.TreeMap;
import java.util.UUID;
import static org.apache.hertzbeat.common.constants.SmsConstants.ALIBABA;
/**
* Alibaba Cloud SMS Client Implementation<br>
* API doc: <a href="https://next.api.aliyun.com/document/Dysmsapi/2017-05-25/SendSms">https://next.api.aliyun.com/document/Dysmsapi/2017-05-25/SendSms</a><br>
* Singnature doc: <a href="https://help.aliyun.com/zh/sdk/product-overview/v3-request-structure-and-signature">https://help.aliyun.com/zh/sdk/product-overview/v3-request-structure-and-signature</a>
*/
@Slf4j
public class AlibabaSmsClientImpl implements SmsClient {
private static final String API_VERSION = "2017-05-25";
private static final String ACTION = "SendSms";
private static final String HOST = "dysmsapi.aliyuncs.com";
private static final String ALGORITHM = "ACS3-HMAC-SHA256";
private final String accessKeyId;
private final String accessKeySecret;
private final String signName;
private final String templateCode;
public AlibabaSmsClientImpl(AlibabaSmsProperties config) {
if (config != null) {
this.accessKeyId = config.getAccessKeyId();
this.accessKeySecret = config.getAccessKeySecret();
this.signName = config.getSignName();
this.templateCode = config.getTemplateCode();
} else {
this.accessKeyId = "";
this.accessKeySecret = "";
this.signName = "";
this.templateCode = "";
}
}
@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");
priority = 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);
}
}
// Build template parameters
Map<String, String> templateParam = new HashMap<>();
templateParam.put("instance", instance == null ? alert.getGroupKey() : instance);
templateParam.put("priority", priority == null ? "unknown" : priority);
templateParam.put("content", content);
sendSms(receiver.getPhone(), JsonUtil.toJson(templateParam));
}
private void sendSms(String phoneNumber, String templateParam) {
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
// Build query parameters
Map<String, String> queryParams = new TreeMap<>();
queryParams.put("PhoneNumbers", phoneNumber);
queryParams.put("SignName", signName);
queryParams.put("TemplateCode", templateCode);
queryParams.put("TemplateParam", templateParam);
// Build canonical query string
StringBuilder canonicalQueryString = new StringBuilder();
queryParams.forEach((key, value) -> {
if (canonicalQueryString.length() > 0) {
canonicalQueryString.append("&");
}
canonicalQueryString.append(percentEncode(key))
.append("=")
.append(percentEncode(value));
});
// Generate timestamp and nonce
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
sdf.setTimeZone(new SimpleTimeZone(0, "GMT"));
String timestamp = sdf.format(new Date());
String nonce = UUID.randomUUID().toString();
// Calculate signature
String authorization = calculateAuthorization(
canonicalQueryString.toString(),
timestamp,
nonce
);
// Build URL
String url = "https://" + HOST + "/?" + canonicalQueryString;
// Build HTTP request
HttpPost httpPost = new HttpPost(url);
httpPost.setHeader("Content-Type", "application/json");
httpPost.setHeader("Host", HOST);
httpPost.setHeader("Authorization", authorization);
httpPost.setHeader("x-acs-action", ACTION);
httpPost.setHeader("x-acs-version", API_VERSION);
httpPost.setHeader("x-acs-date", timestamp);
httpPost.setHeader("x-acs-signature-nonce", nonce);
httpPost.setHeader("x-acs-content-sha256",
CryptoUtils.sha256Hex(""));
log.info("Sending Alibaba SMS request to {}", url + ", params: " + templateParam + "headers: " + Arrays.toString(httpPost.getAllHeaders()));
// Send request and handle response
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);
String code = jsonResponse.get("Code").asText();
if (!"OK".equals(code)) {
String message = jsonResponse.get("Message").asText();
throw new SendMessageException(code + ":" + message);
}
log.info("Successfully sent SMS to phone: {}", phoneNumber);
}
} catch (Exception e) {
log.warn("Failed to send SMS: {}", e.getMessage());
throw new SendMessageException(e.getMessage());
}
}
private String calculateAuthorization(String canonicalQueryString, String timestamp, String nonce) {
try {
// Step 1: Build canonical request
String canonicalRequest = buildCanonicalRequest(canonicalQueryString, timestamp, nonce);
// Step 2: Build string to sign
String stringToSign = ALGORITHM + "\n" + CryptoUtils.sha256Hex(canonicalRequest);
// Step 3: Calculate signature
String signature = CryptoUtils.hmacSha256Hex(accessKeySecret, stringToSign);
// Step 4: Build authorization header
return ALGORITHM + " Credential=" + accessKeyId + ",SignedHeaders=host;x-acs-action;x-acs-content-sha256;x-acs-date;" + "x-acs-signature-nonce;x-acs-version,Signature=" + signature;
} catch (Exception e) {
throw new RuntimeException("Failed to calculate authorization", e);
}
}
private String buildCanonicalRequest(String canonicalQueryString, String timestamp, String nonce) {
return "POST\n"
+ "/\n"
+ canonicalQueryString + "\n"
+ "host:" + HOST + "\n"
+ "x-acs-action:" + ACTION + "\n"
+ "x-acs-content-sha256:" + CryptoUtils.sha256Hex("") + "\n"
+ "x-acs-date:" + timestamp + "\n"
+ "x-acs-signature-nonce:" + nonce + "\n"
+ "x-acs-version:" + API_VERSION + "\n\n"
+ "host;x-acs-action;x-acs-content-sha256;x-acs-date;"
+ "x-acs-signature-nonce;x-acs-version\n"
+ CryptoUtils.sha256Hex("");
}
private String percentEncode(String value) {
try {
return java.net.URLEncoder.encode(value, StandardCharsets.UTF_8)
.replace("+", "%20")
.replace("*", "%2A")
.replace("%7E", "~");
} catch (Exception e) {
throw new RuntimeException("Failed to encode value", e);
}
}
@Override
public String getType() {
return ALIBABA;
}
@Override
public boolean checkConfig() {
return !(accessKeyId.isBlank() || accessKeySecret.isBlank() || signName.isBlank() || templateCode.isBlank());
}
}
@@ -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;
}
}
}
@@ -1,223 +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 lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.config.TencentSmsProperties;
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.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 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;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;
import static org.apache.hertzbeat.common.constants.SmsConstants.TENCENT;
/**
* sms service client for tencent cloud <br>
* doc: <a href="https://cloud.tencent.com/document/api/382/55981">https://cloud.tencent.com/document/api/382/55981</a>
*/
@Slf4j
public class TencentSmsClientImpl implements SmsClient {
private static final String RESPONSE_OK = "Ok";
private static final String REGION = "ap-guangzhou";
private static final String API_VERSION = "2021-01-11";
private static final String ACTION = "SendSms";
private static final String HOST = "sms.tencentcloudapi.com";
private static final Charset UTF8 = StandardCharsets.UTF_8;
private String appId;
private String signName;
private String templateId;
private String secretId;
private String secretKey;
public TencentSmsClientImpl(TencentSmsProperties config) {
if (config != null) {
this.appId = config.getAppId();
this.signName = config.getSignName();
this.templateId = config.getTemplateId();
this.secretId = config.getSecretId();
this.secretKey = config.getSecretKey();
}
}
@Override
public void sendMessage(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
// todo limit the number of words
String instance = null;
String priority = null;
String content = null;
if (alert.getCommonLabels() != null) {
instance = alert.getCommonLabels().get("instance");
priority = 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);
}
}
String[] templateValues = new String[3];
templateValues[0] = instance == null ? alert.getGroupKey() : instance;
templateValues[1] = priority == null ? "unknown" : priority;
templateValues[2] = content;
String[] phones = new String[1];
phones[0] = receiver.getPhone();
sendSms(this.appId, this.signName, this.templateId, templateValues, phones);
}
public void sendSms(String appId, String signName, String templateId,
String[] templateValues, String[] phones) {
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
// build request payload
Map<String, Object> params = new HashMap<>();
params.put("SmsSdkAppId", appId);
params.put("SignName", signName);
params.put("TemplateId", templateId);
params.put("TemplateParamSet", templateValues);
params.put("PhoneNumberSet", phones);
String payload = JsonUtil.toJson(params);
// calculate request signature
String authorization = calculateAuthorization(
secretId, secretKey, "sms", HOST, REGION,
ACTION, API_VERSION, payload);
// build http request
HttpPost httpPost = new HttpPost("https://" + HOST);
httpPost.setHeader("Content-Type", "application/json; charset=utf-8");
httpPost.setHeader("Host", HOST);
httpPost.setHeader("X-TC-Action", ACTION);
httpPost.setHeader("X-TC-Timestamp", timestamp);
httpPost.setHeader("X-TC-Version", API_VERSION);
httpPost.setHeader("X-TC-Region", REGION);
httpPost.setHeader("Authorization", authorization);
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 responseNode = jsonResponse.get("Response");
JsonNode error = responseNode.get("Error");
if (error != null) {
String code = error.get("Code").asText();
String message = error.get("Message").asText();
throw new SendMessageException(code + ":" + message);
}
JsonNode sendStatusSet = responseNode.get("SendStatusSet");
if (sendStatusSet != null && sendStatusSet.isArray() && sendStatusSet.size() > 0) {
JsonNode firstStatus = sendStatusSet.get(0);
String code = firstStatus.get("Code").asText();
String message = firstStatus.get("Message").asText();
if (!RESPONSE_OK.equals(code)) {
throw new SendMessageException(code + ":" + message);
}
}
log.info("Successfully sent SMS to phones: {}", String.join(",", phones));
}
} catch (Exception e) {
log.warn("Failed to send SMS: {}", e.getMessage());
throw new SendMessageException(e.getMessage());
}
}
@Override
public String getType() {
return TENCENT;
}
@Override
public boolean checkConfig() {
if (appId.isBlank() || templateId.isBlank() || secretId.isBlank() || secretKey.isBlank()) {
return false;
}
return true;
}
public static String calculateAuthorization(String secretId, String secretKey,
String service, String host, String region,
String action, String version, String payload) throws Exception {
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
String date = sdf.format(new Date(Long.valueOf(timestamp + "000")));
// Step 1: Construct the canonical request string
String httpRequestMethod = "POST";
String canonicalUri = "/";
String canonicalQueryString = "";
String canonicalHeaders = "content-type:application/json; charset=utf-8\n" + "host:" + host + "\n";
String signedHeaders = "content-type;host";
String hashedRequestPayload = CryptoUtils.sha256Hex(payload);
String canonicalRequest = httpRequestMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n"
+ canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestPayload;
// Step 2: Construct the string to sign
String algorithm = "TC3-HMAC-SHA256";
String credentialScope = date + "/" + service + "/" + "tc3_request";
String hashedCanonicalRequest = CryptoUtils.sha256Hex(canonicalRequest);
String stringToSign = algorithm + "\n" + timestamp + "\n" + credentialScope + "\n" + hashedCanonicalRequest;
// Step 3: Calculate the signature
byte[] secretDate = CryptoUtils.hmac256(("TC3" + secretKey).getBytes(UTF8), date);
byte[] secretService = CryptoUtils.hmac256(secretDate, service);
byte[] secretSigning = CryptoUtils.hmac256(secretService, "tc3_request");
String signature = DatatypeConverter.printHexBinary(
CryptoUtils.hmac256(secretSigning, stringToSign)).toLowerCase();
// Step 4: Construct the Authorization header
return algorithm + " " + "Credential=" + secretId + "/" + credentialScope + ", "
+ "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature;
}
}
@@ -1,193 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.config.UniSmsProperties;
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.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import com.fasterxml.jackson.databind.JsonNode;
import org.apache.hertzbeat.alert.util.CryptoUtils;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;
import java.util.stream.Collectors;
import static org.apache.hertzbeat.common.constants.SmsConstants.UNISMS;
/**
* UniSMS client implementation <br/>
* doc:<a href="https://unisms.apistd.com/docs/api/send">https://unisms.apistd.com/docs/api/send</a>
*/
@Slf4j
public class UniSmsClientImpl implements SmsClient {
private static final String API_URL = "https://uni.apistd.com";
private static final String ACTION = "sms.message.send";
private static final String SUCCESS_CODE = "0";
private static final String HMAC_ALGORITHM = "hmac-sha256";
private final UniSmsProperties config;
public UniSmsClientImpl(UniSmsProperties config) {
this.config = config;
}
@Override
public void sendMessage(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
// build request parameters
Map<String, Object> params = new HashMap<>();
params.put("to", receiver.getPhone());
params.put("signature", config.getSignature());
params.put("templateId", config.getTemplateId());
// build template data
Map<String, String> templateData = new HashMap<>();
String instance = alert.getCommonLabels().getOrDefault("instance", alert.getGroupKey());
String priority = alert.getCommonLabels().getOrDefault("priority", "unknown");
String content = alert.getCommonAnnotations().get("summary");
content = content == null ? alert.getCommonAnnotations().get("description") : content;
if (content == null) {
content = alert.getCommonAnnotations().values().stream().findFirst().orElse(null);
}
templateData.put("instance", instance);
templateData.put("priority", priority);
templateData.put("content", content);
params.put("templateData", templateData);
// build URL and request headers
String url;
if ("hmac".equalsIgnoreCase(config.getAuthMode())) {
url = buildHmacUrl();
} else {
url = buildSimpleUrl();
}
// send HTTP request
HttpPost httpPost = new HttpPost(url);
httpPost.setHeader("Content-Type", "application/json");
httpPost.setHeader("Accept", "application/json");
String payload = JsonUtil.toJson(params);
httpPost.setEntity(new StringEntity(payload, StandardCharsets.UTF_8));
log.info("Sending SMS request to UniSMS, payload: {}, url: {}", payload, url);
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
handleResponse(response, receiver.getPhone());
}
} catch (Exception e) {
log.error("Failed to send SMS via UniSMS: {}", e.getMessage());
throw new SendMessageException(e.getMessage());
}
}
private String buildSimpleUrl() {
return String.format("%s/?action=%s&accessKeyId=%s",
API_URL, ACTION, config.getAccessKeyId());
}
private String buildHmacUrl() {
long timestamp = System.currentTimeMillis();
String nonce = generateNonce();
// build query parameters
Map<String, String> params = new TreeMap<>();
params.put("accessKeyId", config.getAccessKeyId());
params.put("action", ACTION);
params.put("algorithm", HMAC_ALGORITHM);
params.put("nonce", nonce);
params.put("timestamp", String.valueOf(timestamp));
// build sign text
String signText = params.entrySet().stream()
.map(entry -> entry.getKey() + "=" + entry.getValue())
.collect(Collectors.joining("&"));
// calculate signature
String signature = CryptoUtils.hmacSha256Base64(config.getAccessKeySecret(), signText);
return String.format("%s/?action=%s&accessKeyId=%s&algorithm=%s&timestamp=%d&nonce=%s&signature=%s",
API_URL, ACTION, config.getAccessKeyId(), HMAC_ALGORITHM, timestamp, nonce, signature);
}
private String generateNonce() {
return UUID.randomUUID().toString().replace("-", "").substring(0, 16);
}
private void handleResponse(CloseableHttpResponse response, String phone) throws IOException {
int statusCode = response.getStatusLine().getStatusCode();
String responseBody = EntityUtils.toString(response.getEntity());
log.info("UniSMS 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);
String code = jsonResponse.get("code").asText();
if (!SUCCESS_CODE.equals(code)) {
String message = jsonResponse.get("message").asText();
throw new SendMessageException(code + ":" + message);
}
log.info("Successfully sent SMS to phone: {}", phone);
}
@Override
public String getType() {
return UNISMS;
}
@Override
public boolean checkConfig() {
if (config == null
|| config.getAccessKeyId() == null
|| config.getAccessKeyId().isBlank()
|| config.getSignature() == null
|| config.getSignature().isBlank()
|| config.getTemplateId() == null
|| config.getTemplateId().isBlank()) {
return false;
}
// HMAC mode requires additional check for accessKeySecret
if ("hmac".equalsIgnoreCase(config.getAuthMode())
&& (config.getAccessKeySecret() == null || config.getAccessKeySecret().isBlank())) {
return false;
}
return true;
}
}
@@ -1,106 +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.util;
import lombok.extern.slf4j.Slf4j;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Base64;
/**
* General encryption utility class
*/
@Slf4j
public class CryptoUtils {
private static final Charset UTF8 = StandardCharsets.UTF_8;
private CryptoUtils() {}
/**
* Calculate HMAC-SHA256 signature
* @param key secret key
* @param msg message to be signed
* @return signed byte array
*/
public static byte[] hmac256(byte[] key, String msg) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, mac.getAlgorithm());
mac.init(secretKeySpec);
return mac.doFinal(msg.getBytes(UTF8));
} catch (Exception e) {
log.error("Failed to calculate HMAC-SHA256: {}", e.getMessage());
throw new RuntimeException("Failed to calculate HMAC-SHA256", e);
}
}
/**
* Calculate SHA256 hash and convert to lowercase hexadecimal string
* @param data data to be hashed
* @return lowercase hexadecimal string
*/
public static String sha256Hex(String data) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] digest = md.digest(data.getBytes(UTF8));
return DatatypeConverter.printHexBinary(digest).toLowerCase();
} catch (Exception e) {
log.error("Failed to calculate SHA256: {}", e.getMessage());
throw new RuntimeException("Failed to calculate SHA256", e);
}
}
/**
* Calculate HMAC-SHA256 signature and convert to Base64 string
* @param key secret key
* @param data data to be signed
* @return Base64 encoded signature string
*/
public static String hmacSha256Base64(String key, String data) {
byte[] hmacResult = hmac256(key.getBytes(UTF8), data);
return Base64.getEncoder().encodeToString(hmacResult);
}
/**
* Calculate HMAC-SHA256 signature and convert to lowercase hexadecimal string
* @param key secret key
* @param data data to be signed
* @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);
return DatatypeConverter.printHexBinary(hmacResult).toLowerCase();
}
}
@@ -99,7 +99,7 @@ class EmailAlertNotifyHandlerImplTest {
template.setName("test-template");
template.setContent("test content");
// Set up email server configuration
// 设置邮件服务器配置
MailServerConfig mailServerConfig = new MailServerConfig();
mailServerConfig.setEmailHost("smtp.example.com");
mailServerConfig.setEmailPort(587);
@@ -83,8 +83,8 @@ class AlarmInhibitReduceTest {
MockitoAnnotations.openMocks(this);
when(alertInhibitDao.findAlertInhibitsByEnableIsTrue())
.thenReturn(Collections.emptyList());
// Correctly set up AlerterProperties mock
// 正确设置 AlerterProperties mock
AlerterProperties.InhibitProperties inhibitProperties = new AlerterProperties.InhibitProperties();
inhibitProperties.setTtl(60000);
when(alerterProperties.getInhibit()).thenReturn(inhibitProperties);
@@ -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());
}
}
@@ -26,25 +26,20 @@ import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.collector.collect.AbstractCollect;
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.JdbcConnect;
import org.apache.hertzbeat.collector.collect.common.ssh.SshTunnelHelper;
import org.apache.hertzbeat.collector.constants.CollectorConstants;
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
import org.apache.hertzbeat.collector.util.CollectUtil;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.job.Metrics;
import org.apache.hertzbeat.common.entity.job.SshTunnel;
import org.apache.hertzbeat.common.entity.job.protocol.JdbcProtocol;
import org.apache.hertzbeat.common.entity.message.CollectRep;
import org.apache.hertzbeat.common.util.CommonUtil;
import org.apache.sshd.common.SshException;
import org.apache.sshd.common.channel.exception.SshChannelOpenException;
import org.postgresql.util.PSQLException;
import org.springframework.core.io.FileSystemResource;
import org.springframework.jdbc.datasource.init.ScriptUtils;
@@ -60,7 +55,7 @@ public class JdbcCommonCollect extends AbstractCollect {
private static final String QUERY_TYPE_MULTI_ROW = "multiRow";
private static final String QUERY_TYPE_COLUMNS = "columns";
private static final String RUN_SCRIPT = "runScript";
private static final String[] VULNERABLE_KEYWORDS = {"allowLoadLocalInfile", "allowLoadLocalInfileInPath", "useLocalInfile"};
private final GlobalConnectionCache connectionCommonCache = GlobalConnectionCache.getInstance();
@@ -78,29 +73,18 @@ public class JdbcCommonCollect extends AbstractCollect {
}
}
}
SshTunnelHelper.checkTunnelParam(metrics.getJdbc().getSshTunnel());
}
@Override
public void collect(CollectRep.MetricsData.Builder builder, Metrics metrics) {
long startTime = System.currentTimeMillis();
JdbcProtocol jdbcProtocol = metrics.getJdbc();
SshTunnel sshTunnel = jdbcProtocol.getSshTunnel();
String databaseUrl = constructDatabaseUrl(jdbcProtocol);
int timeout = CollectUtil.getTimeout(jdbcProtocol.getTimeout());
boolean reuseConnection = Boolean.parseBoolean(jdbcProtocol.getReuseConnection());
Statement statement = null;
String databaseUrl;
try {
if (sshTunnel != null && Boolean.parseBoolean(sshTunnel.getEnable())) {
int localPort = SshTunnelHelper.localPortForward(sshTunnel, jdbcProtocol.getHost(), jdbcProtocol.getPort());
databaseUrl = constructDatabaseUrl(jdbcProtocol, "localhost", String.valueOf(localPort));
} else {
databaseUrl = constructDatabaseUrl(jdbcProtocol, jdbcProtocol.getHost(), jdbcProtocol.getPort());
}
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);
@@ -128,14 +112,6 @@ public class JdbcCommonCollect extends AbstractCollect {
log.warn("Jdbc sql error: {}, code: {}.", sqlException.getMessage(), sqlException.getErrorCode());
builder.setCode(CollectRep.Code.FAIL);
builder.setMsg("Query Error: " + sqlException.getMessage() + " Code: " + sqlException.getErrorCode());
} catch (SshException sshException) {
Throwable throwable = sshException.getCause();
if (throwable instanceof SshChannelOpenException) {
log.warn("[Jdbc collect] Remote ssh server no more session channel, please increase sshd_config MaxSessions.");
}
String errorMsg = CommonUtil.getMessageFromThrowable(sshException);
builder.setCode(CollectRep.Code.UN_CONNECTABLE);
builder.setMsg("Peer ssh connection failed: " + errorMsg);
} catch (Exception e) {
String errorMessage = CommonUtil.getMessageFromThrowable(e);
log.error("Jdbc error: {}.", errorMessage, e);
@@ -143,20 +119,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 +134,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 +175,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;
}
@@ -222,14 +185,13 @@ public class JdbcCommonCollect extends AbstractCollect {
* eg:
* query metricsone tow three four
* query sqlselect one, tow, three, four from book limit 1;
*
* @param statement statement
* @param sql sql
* @param columns query metrics field list
* @param sql sql
* @param columns query metrics field list
* @throws Exception when error happen
*/
private void queryOneRow(Statement statement, String sql, List<String> columns,
CollectRep.MetricsData.Builder builder, long startTime) throws Exception {
CollectRep.MetricsData.Builder builder, long startTime) throws Exception {
statement.setMaxRows(1);
try (ResultSet resultSet = statement.executeQuery(sql)) {
if (resultSet.next()) {
@@ -254,15 +216,14 @@ public class JdbcCommonCollect extends AbstractCollect {
* eg:
* query metricsone two three four
* query sqlselect key, value from book; the key is the query metrics fields
* select key, value from book;
* select key, value from book;
* one - value1
* two - value2
* three - value3
* four - value4
*
* @param statement statement
* @param sql sql
* @param columns query metrics field list
* @param sql sql
* @param columns query metrics field list
* @throws Exception when error happen
*/
private void queryOneRowByMatchTwoColumns(Statement statement, String sql, List<String> columns,
@@ -295,10 +256,9 @@ public class JdbcCommonCollect extends AbstractCollect {
* query metricsone tow three four
* query sqlselect one, tow, three, four from book;
* and return multi row record mapping with the metrics
*
* @param statement statement
* @param sql sql
* @param columns query metrics field list
* @param sql sql
* @param columns query metrics field list
* @throws Exception when error happen
*/
private void queryMultiRow(Statement statement, String sql, List<String> columns,
@@ -323,16 +283,14 @@ public class JdbcCommonCollect extends AbstractCollect {
/**
* construct jdbc url due the jdbc protocol
*
* @param jdbcProtocol jdbc
* @return URL
*/
private String constructDatabaseUrl(JdbcProtocol jdbcProtocol, String host, String port) {
private String constructDatabaseUrl(JdbcProtocol jdbcProtocol) {
if (Objects.nonNull(jdbcProtocol.getUrl())
&& !Objects.equals("", jdbcProtocol.getUrl())
&& jdbcProtocol.getUrl().startsWith("jdbc")) {
// convert the URL to lowercase for case-insensitive checking
String url = jdbcProtocol.getUrl().toLowerCase();
String url = jdbcProtocol.getUrl().toLowerCase(); // convert the URL to lowercase for case-insensitive checking
// check whether the parameter is valid
if (url.contains("create trigger") || url.contains("create alias") || url.contains("runscript from")
|| url.contains("allowloadlocalinfile") || url.contains("allowloadlocalinfileinpath")
@@ -344,19 +302,25 @@ public class JdbcCommonCollect extends AbstractCollect {
return jdbcProtocol.getUrl();
}
return switch (jdbcProtocol.getPlatform()) {
case "mysql", "mariadb" -> "jdbc:mysql://" + host + ":" + port
case "mysql", "mariadb" ->
"jdbc:mysql://" + jdbcProtocol.getHost() + ":" + jdbcProtocol.getPort()
+ "/" + (jdbcProtocol.getDatabase() == null ? "" : jdbcProtocol.getDatabase())
+ "?useUnicode=true&characterEncoding=utf-8&useSSL=false";
case "postgresql" -> "jdbc:postgresql://" + host + ":" + port
case "postgresql" ->
"jdbc:postgresql://" + jdbcProtocol.getHost() + ":" + jdbcProtocol.getPort()
+ "/" + (jdbcProtocol.getDatabase() == null ? "" : jdbcProtocol.getDatabase());
case "clickhouse" -> "jdbc:clickhouse://" + host + ":" + port
case "clickhouse" ->
"jdbc:clickhouse://" + jdbcProtocol.getHost() + ":" + jdbcProtocol.getPort()
+ "/" + (jdbcProtocol.getDatabase() == null ? "" : jdbcProtocol.getDatabase());
case "sqlserver" -> "jdbc:sqlserver://" + host + ":" + port
case "sqlserver" ->
"jdbc:sqlserver://" + jdbcProtocol.getHost() + ":" + jdbcProtocol.getPort()
+ ";" + (jdbcProtocol.getDatabase() == null ? "" : "DatabaseName=" + jdbcProtocol.getDatabase())
+ ";trustServerCertificate=true;";
case "oracle" -> "jdbc:oracle:thin:@" + host + ":" + port
case "oracle" ->
"jdbc:oracle:thin:@" + jdbcProtocol.getHost() + ":" + jdbcProtocol.getPort()
+ "/" + (jdbcProtocol.getDatabase() == null ? "" : jdbcProtocol.getDatabase());
case "dm" -> "jdbc:dm://" + host + ":" + port;
case "dm" ->
"jdbc:dm://" + jdbcProtocol.getHost() + ":" + jdbcProtocol.getPort();
default -> throw new IllegalArgumentException("Not support database platform: " + jdbcProtocol.getPlatform());
};
}
@@ -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();
@@ -28,8 +28,6 @@ import io.lettuce.core.cluster.models.partitions.Partitions;
import io.lettuce.core.cluster.models.partitions.RedisClusterNode;
import io.lettuce.core.resource.ClientResources;
import io.lettuce.core.resource.DefaultClientResources;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
@@ -44,7 +42,6 @@ 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.RedisConnect;
import org.apache.hertzbeat.collector.collect.common.ssh.SshTunnelHelper;
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
import org.apache.hertzbeat.collector.util.CollectUtil;
import org.apache.hertzbeat.common.constants.CommonConstants;
@@ -54,8 +51,6 @@ import org.apache.hertzbeat.common.entity.job.protocol.RedisProtocol;
import org.apache.hertzbeat.common.entity.message.CollectRep;
import org.apache.hertzbeat.common.util.CommonUtil;
import org.apache.hertzbeat.common.util.MapCapUtil;
import org.apache.sshd.common.SshException;
import org.apache.sshd.common.channel.exception.SshChannelOpenException;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -84,7 +79,6 @@ public class RedisCommonCollectImpl extends AbstractCollect {
RedisProtocol redisProtocol = metrics.getRedis();
Assert.hasText(redisProtocol.getHost(), "Redis Protocol host is required.");
Assert.hasText(redisProtocol.getPort(), "Redis Protocol port is required.");
SshTunnelHelper.checkTunnelParam(metrics.getRedis().getSshTunnel());
}
@Override
@@ -102,14 +96,6 @@ public class RedisCommonCollectImpl extends AbstractCollect {
log.info("[redis connection] error: {}", errorMsg);
builder.setCode(CollectRep.Code.UN_CONNECTABLE);
builder.setMsg(errorMsg);
} catch (SshException sshException) {
Throwable throwable = sshException.getCause();
if (throwable instanceof SshChannelOpenException) {
log.warn("[redis collect] Remote ssh server no more session channel, please increase sshd_config MaxSessions.");
}
String errorMsg = CommonUtil.getMessageFromThrowable(sshException);
builder.setCode(CollectRep.Code.UN_CONNECTABLE);
builder.setMsg("Peer ssh connection failed: " + errorMsg);
} catch (Exception e) {
String errorMsg = CommonUtil.getMessageFromThrowable(e);
log.warn("[redis collect] error: {}", e.getMessage(), e);
@@ -123,7 +109,7 @@ public class RedisCommonCollectImpl extends AbstractCollect {
* @param metrics metrics config
* @return data
*/
private Map<String, String> getSingleRedisInfo(Metrics metrics) throws GeneralSecurityException, IOException {
private Map<String, String> getSingleRedisInfo(Metrics metrics) {
StatefulRedisConnection<String, String> connection = getSingleConnection(metrics.getRedis());
String info = connection.sync().info(metrics.getName());
Map<String, String> valueMap = parseInfo(info, metrics);
@@ -139,7 +125,7 @@ public class RedisCommonCollectImpl extends AbstractCollect {
* @param metrics metrics config
* @return data
*/
private List<Map<String, String>> getClusterRedisInfo(Metrics metrics) throws GeneralSecurityException, IOException {
private List<Map<String, String>> getClusterRedisInfo(Metrics metrics) {
Map<String, StatefulRedisClusterConnection<String, String>> connectionMap = getConnectionList(metrics.getRedis());
List<Map<String, String>> list = new ArrayList<>(connectionMap.size());
connectionMap.forEach((identity, connection) ->{
@@ -193,16 +179,12 @@ public class RedisCommonCollectImpl extends AbstractCollect {
* @param redisProtocol protocol
* @return connection
*/
private StatefulRedisConnection<String, String> getSingleConnection(RedisProtocol redisProtocol) throws GeneralSecurityException, IOException {
String[] resolvedArr = resolveHostAndPort(redisProtocol);
String host = resolvedArr[0];
String port = resolvedArr[1];
CacheIdentifier identifier = doIdentifier(redisProtocol, host, port);
private StatefulRedisConnection<String, String> getSingleConnection(RedisProtocol redisProtocol) {
CacheIdentifier identifier = doIdentifier(redisProtocol);
StatefulRedisConnection<String, String> connection = (StatefulRedisConnection<String, String>) getStatefulConnection(identifier);
if (Objects.isNull(connection)) {
// reuse connection failed, new one
RedisClient redisClient = buildSingleClient(redisProtocol, host, port);
RedisClient redisClient = buildSingleClient(redisProtocol);
connection = redisClient.connect();
connectionCache.addCache(identifier, new RedisConnect(connection));
}
@@ -214,7 +196,7 @@ public class RedisCommonCollectImpl extends AbstractCollect {
* @param redisProtocol protocol
* @return connection map
*/
private Map<String, StatefulRedisClusterConnection<String, String>> getConnectionList(RedisProtocol redisProtocol) throws GeneralSecurityException, IOException {
private Map<String, StatefulRedisClusterConnection<String, String>> getConnectionList(RedisProtocol redisProtocol) {
// first connection
StatefulRedisClusterConnection<String, String> connection = getClusterConnection(redisProtocol);
Partitions partitions = connection.getPartitions();
@@ -235,16 +217,12 @@ public class RedisCommonCollectImpl extends AbstractCollect {
* @param redisProtocol redis protocol
* @return cluster connection
*/
private StatefulRedisClusterConnection<String, String> getClusterConnection(RedisProtocol redisProtocol) throws GeneralSecurityException, IOException {
String[] resolvedArr = resolveHostAndPort(redisProtocol);
String host = resolvedArr[0];
String port = resolvedArr[1];
CacheIdentifier identifier = doIdentifier(redisProtocol, host, port);
private StatefulRedisClusterConnection<String, String> getClusterConnection(RedisProtocol redisProtocol) {
CacheIdentifier identifier = doIdentifier(redisProtocol);
StatefulRedisClusterConnection<String, String> connection = (StatefulRedisClusterConnection<String, String>) getStatefulConnection(identifier);
if (connection == null) {
// reuse connection failed, new one
RedisClusterClient redisClusterClient = buildClusterClient(redisProtocol, host, port);
RedisClusterClient redisClusterClient = buildClusterClient(redisProtocol);
connection = redisClusterClient.connect();
connectionCache.addCache(identifier, new RedisConnect(connection));
}
@@ -282,8 +260,8 @@ public class RedisCommonCollectImpl extends AbstractCollect {
* @param redisProtocol redis protocol config
* @return redis cluster client
*/
private RedisClusterClient buildClusterClient(RedisProtocol redisProtocol, String host, String port) {
return RedisClusterClient.create(defaultClientResources, redisUri(redisProtocol, host, port));
private RedisClusterClient buildClusterClient(RedisProtocol redisProtocol) {
return RedisClusterClient.create(defaultClientResources, redisUri(redisProtocol));
}
/**
@@ -292,12 +270,12 @@ public class RedisCommonCollectImpl extends AbstractCollect {
* @param redisProtocol redis protocol config
* @return redis single client
*/
private RedisClient buildSingleClient(RedisProtocol redisProtocol, String host, String port) {
return RedisClient.create(defaultClientResources, redisUri(redisProtocol, host, port));
private RedisClient buildSingleClient(RedisProtocol redisProtocol) {
return RedisClient.create(defaultClientResources, redisUri(redisProtocol));
}
private RedisURI redisUri(RedisProtocol redisProtocol, String host, String port) {
RedisURI.Builder redisUriBuilder = RedisURI.builder().withHost(host).withPort(Integer.parseInt(port));
private RedisURI redisUri(RedisProtocol redisProtocol) {
RedisURI.Builder redisUriBuilder = RedisURI.builder().withHost(redisProtocol.getHost()).withPort(Integer.parseInt(redisProtocol.getPort()));
if (StringUtils.hasText(redisProtocol.getUsername())) {
redisUriBuilder.withClientName(redisProtocol.getUsername());
}
@@ -317,10 +295,10 @@ public class RedisCommonCollectImpl extends AbstractCollect {
return ip + SignConstants.DOUBLE_MARK + port;
}
private CacheIdentifier doIdentifier(RedisProtocol redisProtocol, String host, String port) {
private CacheIdentifier doIdentifier(RedisProtocol redisProtocol) {
return CacheIdentifier.builder()
.ip(host)
.port(port)
.ip(redisProtocol.getHost())
.port(redisProtocol.getPort())
.username(redisProtocol.getUsername())
.password(redisProtocol.getPassword())
.customArg(redisProtocol.getPattern())
@@ -350,22 +328,6 @@ public class RedisCommonCollectImpl extends AbstractCollect {
return result;
}
private String[] resolveHostAndPort(RedisProtocol redisProtocol) throws GeneralSecurityException, IOException {
boolean enableSshTunnel = Optional.ofNullable(redisProtocol.getSshTunnel())
.map(ssh -> Boolean.parseBoolean(ssh.getEnable()))
.orElse(false);
String host;
String port;
if (enableSshTunnel){
host = "localhost";
port = String.valueOf(SshTunnelHelper.localPortForward(redisProtocol.getSshTunnel(), redisProtocol.getHost(), redisProtocol.getPort()));
} else {
host = redisProtocol.getHost();
port = redisProtocol.getPort();
}
return new String[]{host, port};
}
@Override
public String supportProtocol() {
return DispatchConstants.PROTOCOL_REDIS;
@@ -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);
@@ -18,6 +18,7 @@
package org.apache.hertzbeat.collector.collect.ssh;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
@@ -29,27 +30,35 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.collector.collect.AbstractCollect;
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.collect.common.ssh.CommonSshBlacklist;
import org.apache.hertzbeat.collector.collect.common.ssh.SshHelper;
import org.apache.hertzbeat.collector.collect.common.ssh.CommonSshClient;
import org.apache.hertzbeat.collector.constants.CollectorConstants;
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
import org.apache.hertzbeat.collector.util.CollectUtil;
import org.apache.hertzbeat.collector.util.PrivateKeyUtils;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.job.Metrics;
import org.apache.hertzbeat.common.entity.job.protocol.SshProtocol;
import org.apache.hertzbeat.common.entity.message.CollectRep;
import org.apache.hertzbeat.common.util.CommonUtil;
import org.apache.sshd.client.SshClient;
import org.apache.sshd.client.channel.ClientChannel;
import org.apache.sshd.client.channel.ClientChannelEvent;
import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.common.SshException;
import org.apache.sshd.common.channel.exception.SshChannelOpenException;
import org.apache.sshd.common.config.keys.FilePasswordProvider;
import org.apache.sshd.common.util.io.output.NoCloseOutputStream;
import org.apache.sshd.common.util.security.SecurityUtils;
import org.springframework.util.StringUtils;
/**
@@ -79,12 +88,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 +165,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 +289,59 @@ 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);
CacheIdentifier identifier = CacheIdentifier.builder()
.ip(sshProtocol.getHost()).port(sshProtocol.getPort())
.username(sshProtocol.getUsername()).password(sshProtocol.getPassword())
.build();
ClientSession clientSession = null;
if (reuseConnection) {
Optional<AbstractConnection<?>> cacheOption = connectionCommonCache.getCache(identifier, true);
if (cacheOption.isPresent()) {
SshConnect sshConnect = (SshConnect) cacheOption.get();
clientSession = sshConnect.getConnection();
try {
if (clientSession == null || clientSession.isClosed() || clientSession.isClosing()) {
clientSession = null;
connectionCommonCache.removeCache(identifier);
}
} catch (Exception e) {
log.warn(e.getMessage());
clientSession = null;
connectionCommonCache.removeCache(identifier);
}
}
if (clientSession != null) {
return clientSession;
}
}
SshClient sshClient = CommonSshClient.getSshClient();
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());
FilePasswordProvider passwordProvider = (session, resource, index) -> {
if (StringUtils.hasText(sshProtocol.getPrivateKeyPassphrase())) {
return sshProtocol.getPrivateKeyPassphrase();
}
return null;
};
SecurityUtils.loadKeyPairIdentities(null, () -> resourceKey, new FileInputStream(resourceKey), passwordProvider)
.forEach(clientSession::addPublicKeyIdentity);
} // 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) {
SshConnect sshConnect = new SshConnect(clientSession);
connectionCommonCache.addCache(identifier, sshConnect);
}
return clientSession;
}
}
@@ -20,7 +20,6 @@ package org.apache.hertzbeat.collector.collect.database;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
import org.apache.hertzbeat.common.entity.job.Metrics;
@@ -100,27 +99,28 @@ class JdbcCommonCollectTest {
"dm"
};
for (String platform : platforms) {
assertDoesNotThrow(() -> {
JdbcProtocol jdbc = new JdbcProtocol();
jdbc.setPlatform(platform);
Metrics metrics = new Metrics();
metrics.setJdbc(jdbc);
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
jdbcCommonCollect.collect(builder, metrics);
});
}
// invalid platform
assertThrows(IllegalArgumentException.class, () -> {
JdbcProtocol jdbc = new JdbcProtocol();
jdbc.setPlatform(platform);
jdbc.setPlatform("invalid");
Metrics metrics = new Metrics();
metrics.setJdbc(jdbc);
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
jdbcCommonCollect.collect(builder, metrics);
assertNotEquals(builder.getMsg(), "Query Error: Not support database platform: " + platform);
}
// invalid platform
JdbcProtocol jdbc = new JdbcProtocol();
jdbc.setPlatform("invalid");
Metrics metrics = new Metrics();
metrics.setJdbc(jdbc);
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
jdbcCommonCollect.collect(builder, metrics);
assertEquals(builder.getCode(), CollectRep.Code.FAIL);
assertEquals(builder.getMsg(), "Query Error: Not support database platform: invalid");
});
}
@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
@@ -25,7 +25,6 @@ import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.PropertyResolverUtils;
import org.apache.sshd.common.kex.BuiltinDHFactories;
import org.apache.sshd.core.CoreModuleProperties;
import org.apache.sshd.server.forward.AcceptAllForwardingFilter;
/**
* common ssh pool client
@@ -53,7 +52,6 @@ public class CommonSshClient {
BuiltinDHFactories.VALUES,
ClientBuilder.DH2KEX
));
SSH_CLIENT.setForwardingFilter(new AcceptAllForwardingFilter());
// todo when connect AlibabaCloud ubuntu server, custom signature factories will cause error, why?
// SSH_CLIENT.setSignatureFactories(new ArrayList<>(BuiltinSignatures.VALUES));
SSH_CLIENT.start();
@@ -1,206 +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.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;
import org.springframework.util.StringUtils;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
/**
* ssh helper
*/
@Slf4j
public class SshHelper {
private static final GlobalConnectionCache CONNECTION_COMMON_CACHE = GlobalConnectionCache.getInstance();
public static ClientSession getConnectSession(String host, String port, String username, String password, String privateKey,
String privateKeyPassphrase, int timeout, boolean reuseConnection)
throws IOException, GeneralSecurityException {
CacheIdentifier identifier = CacheIdentifier.builder()
.ip(host).port(port)
.username(username).password(password)
.build();
ClientSession clientSession = null;
if (reuseConnection) {
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();
clientSession = sshClient.connect(username, host, Integer.parseInt(port))
.verify(timeout, TimeUnit.MILLISECONDS).getSession();
if (StringUtils.hasText(password)) {
clientSession.addPasswordIdentity(password);
} else if (StringUtils.hasText(privateKey)) {
var resourceKey = PrivateKeyUtils.writePrivateKey(host, privateKey);
FilePasswordProvider passwordProvider = (session, resource, index) -> {
if (StringUtils.hasText(privateKeyPassphrase)) {
return privateKeyPassphrase;
}
return null;
};
SecurityUtils.loadKeyPairIdentities(null, () -> resourceKey, new FileInputStream(resourceKey), passwordProvider)
.forEach(clientSession::addPublicKeyIdentity);
} // 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) {
SshConnect sshConnect = new SshConnect(clientSession);
CONNECTION_COMMON_CACHE.addCache(identifier, sshConnect);
}
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;
}
}
@@ -1,313 +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.common.ssh;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.RemovalCause;
import com.github.benmanes.caffeine.cache.Scheduler;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.entity.job.SshTunnel;
import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.client.session.forward.ExplicitPortForwardingTracker;
import org.apache.sshd.common.util.net.SshdSocketAddress;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.net.ServerSocket;
import java.security.GeneralSecurityException;
import java.time.Duration;
import java.util.Comparator;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
/**
* Ssh Tunnel Helper
*/
@Slf4j
public class SshTunnelHelper {
private static final long DEFAULT_CACHE_TIMEOUT = 500 * 1000;
private static final Cache<SshClientSessionWrapper, LocalPortForwardingWrapper> TRACKER_CACHE =
Caffeine.newBuilder()
.initialCapacity(1)
.maximumSize(1000)
.expireAfterAccess(Duration.ofMillis(DEFAULT_CACHE_TIMEOUT))
.scheduler(Scheduler.systemScheduler())
.removalListener((key, value, cause) -> {
if (cause == RemovalCause.REPLACED) {
return;
}
if (key != null && value != null) {
// 1. try close tunnel
SshClientSessionWrapper clientSessionWrapper = (SshClientSessionWrapper) key;
LocalPortForwardingWrapper wrapper = (LocalPortForwardingWrapper) value;
wrapper.remove(clientSessionWrapper.getClientSession());
// 2. try close session
if (!clientSessionWrapper.isShareConnection()) {
try {
clientSessionWrapper.close();
log.info("[SSH Tunnel] close unshared ssh connection, {}", clientSessionWrapper);
} catch (IOException e) {
log.error("[SSH Tunnel] close unshared ssh connection error", e);
}
}
}
})
.build();
/**
* check ssh tunnel param
*
* @param sshTunnel ssh tunnel param
*/
public static void checkTunnelParam(SshTunnel sshTunnel) {
if (sshTunnel == null || !Boolean.parseBoolean(sshTunnel.getEnable())) {
return;
}
if (!StringUtils.hasText(sshTunnel.getHost())) {
throw new IllegalArgumentException("ssh tunnel must has ssh host param");
}
if (!StringUtils.hasText(sshTunnel.getPort())) {
throw new IllegalArgumentException("ssh tunnel must has ssh port param");
}
if (!StringUtils.hasText(sshTunnel.getUsername())) {
throw new IllegalArgumentException("ssh tunnel must has ssh username param");
}
}
/**
* create ssh tunnel
*
* @param sshTunnel ssh tunnel param
* @param remoteHost remote host
* @param remotePort remote port
* @return local port
*/
public static int localPortForward(SshTunnel sshTunnel, String remoteHost, String remotePort) throws GeneralSecurityException, IOException {
boolean shareConnection = Boolean.parseBoolean(sshTunnel.getShareConnection());
// 1. get ssh session
ClientSession session = SshHelper.getConnectSession(sshTunnel.getHost(), sshTunnel.getPort(),
sshTunnel.getUsername(), sshTunnel.getPassword(), sshTunnel.getPrivateKey(), sshTunnel.getPrivateKeyPassphrase(),
Integer.parseInt(sshTunnel.getTimeout()), shareConnection);
SshClientSessionWrapper sessionWrapper = new SshClientSessionWrapper(session, shareConnection);
// 2. get tunnel
LocalPortForwardingWrapper forwardingWrapper = selectWrapper(
TRACKER_CACHE.getIfPresent(sessionWrapper), sessionWrapper, remoteHost, remotePort);
int localPort;
if (forwardingWrapper == null) {
localPort = getRandomPort();
LocalPortForwardingWrapper newForwardingWrapper = sessionWrapper
.createLocalPortForwardingTracker(localPort, remoteHost, Integer.parseInt(remotePort));
if (TRACKER_CACHE.getIfPresent(sessionWrapper) == null) {
TRACKER_CACHE.put(sessionWrapper, newForwardingWrapper);
}
log.info("[SSH Tunnel] created ssh forwarding tracker ssh:{}, remote:{}, localPort:{}",
sshTunnel.getHost() + ":" + sshTunnel.getPort(), remoteHost + ":" + remotePort, localPort);
} else {
localPort = forwardingWrapper.getTracker().getLocalAddress().getPort();
}
return localPort;
}
/**
* get tunnel
*
* @param wrapper LocalPortForwardingWrapper
* @param sessionWrapper SshClientSessionWrapper
* @param remoteHost remote host
* @param remotePort remote port
* @return LocalPortForwardingWrapper
*/
private static LocalPortForwardingWrapper selectWrapper(LocalPortForwardingWrapper wrapper, SshClientSessionWrapper sessionWrapper,
String remoteHost, String remotePort) {
if (wrapper == null) {
return null;
}
List<LocalPortForwardingWrapper> selectList = wrapper.select(sessionWrapper.getClientSession(), localPortForwardWrapper -> {
if (!localPortForwardWrapper.isOpen()) {
return false;
}
ExplicitPortForwardingTracker tracker = localPortForwardWrapper.getTracker();
SshdSocketAddress remoteAddress = tracker.getRemoteAddress();
return Objects.equals(remoteAddress.getHostName(), remoteHost)
&& Objects.equals(remoteAddress.getPort(), Integer.parseInt(remotePort));
});
if (selectList.isEmpty()) {
return null;
}
LocalPortForwardingWrapper selected;
if (selectList.size() == 1) {
selected = selectList.get(0);
} else {
selected = selectList.stream().min(Comparator.comparing(LocalPortForwardingWrapper::getLastAccessTime)).get();
}
selected.setLastAccessTime(System.currentTimeMillis());
return selected;
}
private static int getRandomPort() throws IOException {
try (ServerSocket serverSocket = new ServerSocket(0)) {
return serverSocket.getLocalPort();
}
}
@Getter
@Setter
@EqualsAndHashCode
private static class SshClientSessionWrapper {
private ClientSession clientSession;
private boolean shareConnection;
public SshClientSessionWrapper(ClientSession clientSession, boolean shareConnection) {
this.clientSession = clientSession;
this.shareConnection = shareConnection;
}
/**
* Starts a local port forwarding
* @param localPort local port
* @param remoteHost remove host
* @param remotePort remote port
* @return LocalPortForwardingWrapper
*/
public LocalPortForwardingWrapper createLocalPortForwardingTracker(Integer localPort, String remoteHost, Integer remotePort) throws IOException {
SshdSocketAddress remoteAddress = new SshdSocketAddress(remoteHost, remotePort);
SshdSocketAddress localAddress = new SshdSocketAddress("localhost", localPort);
ExplicitPortForwardingTracker tracker = clientSession.createLocalPortForwardingTracker(localAddress, remoteAddress);
return new LocalPortForwardingWrapper(tracker);
}
/**
* close client session
*/
public void close() throws IOException {
clientSession.close();
}
@Override
public String toString() {
return "{ ssh:%s, shareConnection:%b }".formatted(clientSession, shareConnection);
}
}
@Getter
@Setter
@EqualsAndHashCode
private static class LocalPortForwardingWrapper {
private static Map<ClientSession, List<LocalPortForwardingWrapper>> map = new ConcurrentHashMap<>();
private ExplicitPortForwardingTracker tracker;
private Long lastAccessTime;
public LocalPortForwardingWrapper(ExplicitPortForwardingTracker tracker) {
this.tracker = tracker;
this.lastAccessTime = System.currentTimeMillis();
map.computeIfAbsent(tracker.getClientSession(), (key) -> new ArrayList<>()).add(this);
}
/**
* select ClientSession LocalPortForwardingWrapper List
* @param session ssh client session
* @param predicate condition
* @return LocalPortForwardWrapper
*/
public List<LocalPortForwardingWrapper> select(ClientSession session, Predicate<LocalPortForwardingWrapper> predicate) {
List<LocalPortForwardingWrapper> trackerList = map.get(session);
if (CollectionUtils.isEmpty(trackerList)) {
return trackerList;
}
List<LocalPortForwardingWrapper> list = new ArrayList<>();
long currentTimeMillis = System.currentTimeMillis();
Iterator<LocalPortForwardingWrapper> iterator = trackerList.iterator();
while (iterator.hasNext()) {
LocalPortForwardingWrapper wrapper = iterator.next();
// lazy remove
if (currentTimeMillis - wrapper.getLastAccessTime() > DEFAULT_CACHE_TIMEOUT) {
try {
wrapper.getTracker().close();
iterator.remove();
log.info("[SSH Tunnel] Lazy Remove ssh local port forwarding {}", wrapper);
} catch (IOException e) {
log.warn("[SSH Tunnel] Lazy Remove ssh local port forwarding Error", e);
}
} else if (predicate == null || predicate.test(wrapper)) {
list.add(wrapper);
}
}
return list;
}
/**
* remove session local port forwarding
* @param session ssh client session
*/
public void remove(ClientSession session) {
List<LocalPortForwardingWrapper> trackerList = map.get(session);
if (CollectionUtils.isEmpty(trackerList)) {
return;
}
Iterator<LocalPortForwardingWrapper> iterator = trackerList.iterator();
while (iterator.hasNext()){
try {
LocalPortForwardingWrapper next = iterator.next();
next.close();
iterator.remove();
log.info("[SSH Tunnel] Remove ssh local port forwarding, {}", next);
} catch (IOException e) {
log.error("[SSH Tunnel] Remove ssh session local port forwarding error", e);
}
}
}
public void close() throws IOException {
tracker.close();
}
public boolean isOpen() {
return tracker.isOpen();
}
@Override
public String toString() {
return "{ ssh:%s, remote:%s, localPort:%d }".formatted(
tracker.getSession().getConnectAddress(),
tracker.getRemoteAddress(),
tracker.getLocalAddress().getPort()
);
}
}
}
@@ -154,11 +154,13 @@ public class DispatchProperties {
/**
* Schedule Data Export Configuration Properties
* 调度数据出口配置属性
*/
public static class ExportProperties {
/**
* kafka configuration information
* kafka配置信息
*/
private KafkaProperties kafka;
@@ -176,15 +178,18 @@ public class DispatchProperties {
public static class KafkaProperties {
/**
* Whether the kafka data export is started
* kafka数据出口是否启动
*/
private boolean enabled = true;
/**
* kafka's connection server url
* kafka的连接服务器url
*/
private String servers = "http://127.0.0.1:2379";
/**
* Topic name to send data to
* 发送数据的topic名称
*/
private String topic;
@@ -223,9 +223,7 @@ public class KafkaCollectImpl extends AbstractCollect {
@Override
public void preCheck(Metrics metrics) throws IllegalArgumentException {
Assert.isTrue(metrics != null, "Metrics cannot be null");
KafkaProtocol kafkaProtocol = metrics.getKclient();
// Ensure that metrics and kafkaProtocol are not null
Assert.isTrue(metrics != null && kafkaProtocol != null, "Kafka collect must have kafkaProtocol params");
// Ensure that host and port are not empty
@@ -267,8 +265,6 @@ 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);
}
}
@@ -42,7 +42,7 @@ public class KafkaCollectTest {
@Test
void preCheck() {
// metrics is null
assertThrows(IllegalArgumentException.class, () -> {
assertThrows(NullPointerException.class, () -> {
collect.preCheck(null);
});
@@ -51,21 +51,22 @@ public class KafkaCollectTest {
collect.preCheck(Metrics.builder().build());
});
KafkaProtocol kafka = new KafkaProtocol();
Metrics metric = Metrics.builder().kclient(kafka).build();
// kafka srv host is null
assertThrows(IllegalArgumentException.class, () -> {
collect.preCheck(metric);
KafkaProtocol kafka = new KafkaProtocol();
collect.preCheck(Metrics.builder().kclient(kafka).build());
});
// kafka port is null
assertThrows(IllegalArgumentException.class, () -> {
kafka.setHost("127.0.0.1");
collect.preCheck(metric);
KafkaProtocol kafka = KafkaProtocol.builder().host("127.0.0.1").build();
collect.preCheck(Metrics.builder().kclient(kafka).build());
});
// no exception throw
assertDoesNotThrow(() -> {
kafka.setPort("9092");
collect.preCheck(metric);
KafkaProtocol kafka = KafkaProtocol.builder().host("127.0.0.1").port("9092").build();
collect.preCheck(Metrics.builder().kclient(kafka).build());
});
}
-6
View File
@@ -173,12 +173,6 @@
<artifactId>snappy-java</artifactId>
<version>${snappy-java.version}</version>
</dependency>
<dependency>
<groupId>com.github.javaparser</groupId>
<artifactId>javaparser-core</artifactId>
<version>${javaparser.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -42,6 +42,11 @@ public class CommonProperties {
*/
private DataQueueProperties queue;
/**
* sms impl properties
*/
private SmsProperties sms;
/**
* data queue properties
*/
@@ -141,4 +146,83 @@ public class CommonProperties {
*/
private String alertsDataTopic;
}
/**
* sms properties
*/
@Getter
@Setter
public static class SmsProperties {
//Tencent cloud SMS configuration
private TencentSmsProperties tencent;
//Ali cloud SMS configuration
private AliYunSmsProperties aliYun;
}
/**
* tencent sms properties
*/
@Getter
@Setter
public static class TencentSmsProperties {
/**
* Tencent cloud account secret id
*/
private String secretId;
/**
* Tencent cloud account secret key
*/
private String secretKey;
/**
* SMS app id
*/
private String appId;
/**
* SMS signature
*/
private String signName;
/**
* SMS template ID
*/
private String templateId;
}
/**
* aliYun sms properties
*/
@Getter
@Setter
public static class AliYunSmsProperties {
/**
* Aliyun account access key id
*/
private String secretId;
/**
* Ali Cloud account access key
*/
private String secretKey;
/**
* SMS app id
*/
private String appId;
/**
* SMS signature
*/
private String signName;
/**
* ID of the SMS template
*/
private String templateId;
}
}
@@ -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
@@ -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";
}
@@ -1,40 +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;
/**
* Notify Level Enum
*/
@Getter
@AllArgsConstructor
@ToString
public enum NotifyLevelEnum {
SUCCESS("SUCCESS"),
ERROR("ERROR"),
INFO("INFO"),
WARNING("WARNING"),
BLANK("BLANK");
private final String value;
}
@@ -1,38 +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;
/**
* SMS provider 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";
}
@@ -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")
@@ -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,42 +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 lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Manager Message Entity
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ManagerMessage {
/**
* Notify Level
* @see org.apache.hertzbeat.common.constants.NotifyLevelEnum
*/
private String notifyLevel;
/**
* Manager Event Type
* @see org.apache.hertzbeat.common.constants.ManagerEventTypeEnum
*/
private String managerEventType;
}
@@ -41,7 +41,7 @@ public class Configmap implements Serializable {
private String key;
/**
* parameter value
* parameter value 参数value
*/
private Object value;
@@ -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;
}
@@ -1,80 +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;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.hertzbeat.common.entity.job.protocol.CommonRequestProtocol;
import org.apache.hertzbeat.common.entity.job.protocol.Protocol;
/**
* ssh tunnel
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class SshTunnel implements CommonRequestProtocol, Protocol {
/**
* enable ssh tunnel
*/
private String enable = "false";
/**
* IP ADDRESS OR DOMAIN NAME OF THE PEER HOST
*/
private String host;
/**
* Peer host port
*/
private String port = "22";
/**
* TIME OUT PERIOD
*/
private String timeout = "6000";
/**
* UserName
*/
private String username;
/**
* Password (optional)
*/
private String password;
/**
* Private key (optional)
*/
private String privateKey;
/**
* private key passphrase (optional)
*/
private String privateKeyPassphrase;
/**
* share connection session
*/
private String shareConnection = "true";
}
@@ -21,7 +21,6 @@ import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.hertzbeat.common.entity.job.SshTunnel;
/**
* Database configuration information implemented by the common jdbc specification
@@ -71,12 +70,4 @@ 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
*/
private SshTunnel sshTunnel;
}
@@ -21,7 +21,6 @@ import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.hertzbeat.common.entity.job.SshTunnel;
/**
* Redis Protocol
@@ -62,9 +61,4 @@ public class RedisProtocol implements CommonRequestProtocol, Protocol {
*/
private String timeout;
/**
* SSH TUNNEL
*/
private SshTunnel sshTunnel;
}
@@ -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;
}
@@ -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,17 +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);
if (bytes.length > 32700) {
log.warn("Value too large for Arrow buffer ({}), truncating to 32700 bytes. Meta: {}",
bytes.length, JsonUtil.toJson(metadata));
byte[] truncatedBytes = new byte[32700];
System.arraycopy(bytes, 0, truncatedBytes, 0, 32700);
vector.set(rowIndex, truncatedBytes);
} else {
vector.set(rowIndex, bytes);
}
vector.set(rowIndex, value.getBytes(StandardCharsets.UTF_8));
}
}
}
@@ -471,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() {
@@ -564,6 +491,10 @@ public final class CollectRep {
values = new LinkedList<>();
}
public void clearMetrics() {
metrics = null;
}
public void clearFields() {
fields = new LinkedList<>();
}
@@ -636,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,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.support.event;
import org.springframework.context.ApplicationEvent;
/**
* the event for sms config change
*/
public class SmsConfigChangeEvent extends ApplicationEvent {
public SmsConfigChangeEvent(Object source) {
super(source);
}
}
@@ -142,7 +142,7 @@ public final class AesUtil {
/**
* Determine whether it is encrypted
* @param text text
* @return true false
* @return true- false-
*/
public static boolean isCiphertext(String text, String decryptKey) {
// First use whether it is base64 to determine whether it has been encrypted
@@ -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);
}
}
@@ -68,19 +68,19 @@ class KafkaCommonDataQueueTest {
when(commonProperties.getQueue()).thenReturn(dataQueueProperties);
when(dataQueueProperties.getKafka()).thenReturn(kafkaProperties);
// Set all required topics
// 设置所有必需的 topic
when(kafkaProperties.getMetricsDataTopic()).thenReturn("metricsDataTopic");
when(kafkaProperties.getAlertsDataTopic()).thenReturn("alertsDataTopic");
when(kafkaProperties.getMetricsDataToStorageTopic()).thenReturn("metricsDataToStorageTopic");
when(kafkaProperties.getServiceDiscoveryDataTopic()).thenReturn("serviceDiscoveryDataTopic");
when(kafkaProperties.getServers()).thenReturn("localhost:9092");
// Simulate the subscribe method for consumers
// 模拟 consumer subscribe 方法
doNothing().when(metricsDataToAlertConsumer).subscribe(anyCollection());
kafkaCommonDataQueue = new KafkaCommonDataQueue(commonProperties);
// Use reflection to set private fields
// 使用反射设置私有字段
setPrivateField(kafkaCommonDataQueue, "metricsDataProducer", metricsDataProducer);
setPrivateField(kafkaCommonDataQueue, "metricsDataToAlertConsumer", metricsDataToAlertConsumer);
}
@@ -98,16 +98,16 @@ class KafkaCommonDataQueueTest {
@Test
void testPollMetricsDataToAlerter() throws InterruptedException {
// Create a test data
// 创建一个测试数据
CollectRep.MetricsData expectedData = CollectRep.MetricsData.newBuilder()
.setMetrics("test metrics")
.build();
// Create a ConsumerRecord containing test data
// 创建一个包含测试数据的 ConsumerRecord
ConsumerRecord<Long, CollectRep.MetricsData> record =
new ConsumerRecord<>("metricsDataTopic", 0, 0L, 1L, expectedData);
// Create a ConsumerRecords containing a single record.
// 创建一个包含单个记录的 ConsumerRecords
Map<TopicPartition, List<ConsumerRecord<Long, CollectRep.MetricsData>>> recordsMap =
Collections.singletonMap(
new TopicPartition("metricsDataTopic", 0),
@@ -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.common.util;
import com.github.javaparser.JavaParser;
import com.github.javaparser.ParseResult;
import com.github.javaparser.ast.CompilationUnit;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Stream;
/**
* Test case for checking Chinese characters in Java files
*/
@Slf4j
public class ChineseCharacterCheckTest {
private static final Pattern CHINESE_CHAR_PATTERN = Pattern.compile("[\u4e00-\u9fa5]");
private static final Set<String> EXCLUDED_FILES = new HashSet<>(Collections.singletonList("Metrics"));
private static final String MAIN_SOURCE_DIR = "src/main/java";
private static final String TEST_SOURCE_DIR = "src/test/java";
private final JavaParser javaParser = new JavaParser();
private final String sourceDir;
private final String testDir;
public ChineseCharacterCheckTest() {
boolean isWindowsOs = System.getProperty("os.name").toLowerCase().startsWith("win");
String separator = isWindowsOs ? "\\" : "/";
this.sourceDir = MAIN_SOURCE_DIR.replace("/", separator);
this.testDir = TEST_SOURCE_DIR.replace("/", separator);
}
@Test
void shouldNotContainChineseInComments() {
List<String> violations = scanForChineseCharacters(ScanTarget.COMMENTS);
assertNoChineseCharacters(violations);
}
private List<String> scanForChineseCharacters(ScanTarget target) {
List<String> violations = new ArrayList<>();
try (Stream<Path> paths = Files.walk(Paths.get(".."), FileVisitOption.FOLLOW_LINKS)) {
paths.filter(this::isValidJavaFile)
.forEach(path -> processFile(path, target, violations));
} catch (IOException e) {
throw new RuntimeException("Failed to scan Java files", e);
}
return violations;
}
private boolean isValidJavaFile(Path path) {
String pathStr = path.toString();
return pathStr.endsWith(".java")
&& (pathStr.contains(sourceDir) || pathStr.contains(testDir))
&& EXCLUDED_FILES.stream().noneMatch(pathStr::contains);
}
private void processFile(Path path, ScanTarget target, List<String> violations) {
try {
ParseResult<CompilationUnit> parseResult = javaParser.parse(Files.newInputStream(path));
parseResult.getResult().ifPresent(cu -> {
if (target.includeComments()) {
checkComments(cu, path, violations);
}
if (target.includeCode()) {
checkCode(cu, path, violations);
}
});
} catch (Exception e) {
log.error("Error processing file: {}", path, e);
}
}
private void checkComments(CompilationUnit cu, Path path, List<String> violations) {
cu.getAllContainedComments().stream()
.filter(comment -> CHINESE_CHAR_PATTERN.matcher(comment.getContent()).find())
.forEach(comment -> violations.add(formatViolation(path, "comment", comment.getContent().trim())));
}
private void checkCode(CompilationUnit cu, Path path, List<String> violations) {
cu.findAll(com.github.javaparser.ast.expr.StringLiteralExpr.class).stream()
.filter(str -> CHINESE_CHAR_PATTERN.matcher(str.getValue()).find())
.forEach(str -> violations.add(formatViolation(path, "code", str.getValue())));
}
private String formatViolation(Path path, String location, String content) {
return String.format("Chinese characters found in %s at %s: %s",
location, path.toAbsolutePath(), content);
}
private void assertNoChineseCharacters(List<String> violations) {
Assertions.assertEquals(0, violations.size(),
() -> String.format("Found Chinese characters in files:%n%s",
String.join(System.lineSeparator(), violations)));
}
private enum ScanTarget {
COMMENTS(true, false),
CODE(false, true),
ALL(true, true);
private final boolean checkComments;
private final boolean checkCode;
ScanTarget(boolean checkComments, boolean checkCode) {
this.checkComments = checkComments;
this.checkCode = checkCode;
}
public boolean includeComments() {
return checkComments;
}
public boolean includeCode() {
return checkCode;
}
}
}
@@ -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();
}
@@ -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);
}
}
@@ -46,6 +46,7 @@ public class AppCount {
private String app;
/**
* 任务状态
* task status
*/
private transient byte status;
@@ -26,15 +26,10 @@ import lombok.Data;
@Data
public class SmsAlibabaConfig {
@NotBlank(message = "AccessKeyId cannot be empty")
private String accessKeyId;
@NotBlank(message = "SecretId cannot be empty")
private String secretId;
@NotBlank(message = "AccessKeySecret cannot be empty")
private String accessKeySecret;
@NotBlank(message = "SecretKey cannot be empty")
private String secretKey;
@NotBlank(message = "SignName cannot be empty")
private String signName;
@NotBlank(message = "TemplateCode cannot be null")
private String templateCode;
}
@@ -1,38 +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.pojo.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
/**
* AWS Sms Sender configuration dto
*/
@Data
public class SmsAwsConfig {
@NotBlank(message = "AccessKeyId cannot be empty")
private String accessKeyId;
@NotBlank(message = "AccessKeySecret cannot be empty")
private String accessKeySecret;
@NotBlank(message = "region cannot be empty")
private String region;
}
@@ -37,11 +37,5 @@ public class SmsNoticeSender {
private SmsAlibabaConfig alibaba;
private SmsUniSmsConfig unisms;
private SmslocalConfig smslocal;
private SmsAwsConfig aws;
private boolean enable = true;
}
@@ -1,62 +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.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";
}
@@ -1,38 +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.pojo.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Smslocal SMS configuration
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SmslocalConfig {
/**
* Smslocal api key
*/
private String apiKey;
}
@@ -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;
@@ -80,7 +80,7 @@ import org.yaml.snakeyaml.Yaml;
@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";
@@ -500,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);
@@ -446,12 +436,11 @@ public class MonitorServiceImpl implements MonitorService {
newJobId = collectJobScheduling.updateAsyncCollectJob(appDefine, collector);
}
monitor.setJobId(newJobId);
}
// execute only in non paused status
try {
detectMonitor(monitor, params, collector);
} catch (Exception ignored) {
}
try {
detectMonitor(monitor, params, collector);
} catch (Exception ignored) {
}
// After the update is successfully released, refresh the database
@@ -683,11 +672,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 +782,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 +833,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 +943,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 +976,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 +1003,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 +1069,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()) {
@@ -20,13 +20,9 @@ package org.apache.hertzbeat.manager.service.impl;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.lang.reflect.Type;
import jakarta.annotation.Resource;
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;
/**
@@ -36,8 +32,6 @@ import org.springframework.stereotype.Service;
@Service
public class SmsGeneralConfigServiceImpl extends AbstractGeneralConfigServiceImpl<SmsNoticeSender> {
@Resource
private ApplicationContext applicationContext;
/**
* SmsGeneralConfigServiceImpl's constructor creates an instance of this class
@@ -50,14 +44,6 @@ public class SmsGeneralConfigServiceImpl extends AbstractGeneralConfigServiceImp
public SmsGeneralConfigServiceImpl(GeneralConfigDao generalConfigDao, ObjectMapper objectMapper) {
super(generalConfigDao, objectMapper);
}
/**
* This method is used to handle the sms configuration change event.
*/
@Override
public void handler(SmsNoticeSender smsNoticeSender) {
applicationContext.publishEvent(new SmsConfigChangeEvent(applicationContext));
}
@Override
public String type() {
@@ -203,34 +203,7 @@ alerter:
# alert inhibit ttl unit ms, default 14400000(4 hours)
inhibit:
ttl: 14400000
sms:
enable: false
type: tencent
tencent:
secret-id:
secret-key:
app-id:
sign-name:
template-id:
alibaba:
access-key-id:
access-key-secret:
sign-name:
template-code:
unisms:
# auth-mode: simple or hmac
auth-mode: simple
access-key-id: YOUR_ACCESS_KEY_ID
# hmac mode need to fill in access-key-secret
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
scheduler:
server:
enabled: true
@@ -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
@@ -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
@@ -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:
@@ -137,72 +126,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)
@@ -250,18 +173,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
@@ -331,18 +242,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
@@ -411,18 +310,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
@@ -468,18 +355,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
@@ -520,18 +395,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
@@ -580,18 +443,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
@@ -634,18 +485,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 --no-headers -eo pid,%cpu,%mem,cmd --sort=-%cpu | head -n 10 | awk 'BEGIN {print "pid cpu_usage mem_usage command"} {print $1, $2, $3, $4}'
parseType: multiRow
@@ -688,17 +527,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 --no-headers -eo pid,%cpu,%mem,cmd --sort=-%mem | head -n 10 | awk 'BEGIN {print "pid cpu_usage mem_usage command"} {print $1, $2, $3, $4}'
parseType: multiRow
@@ -1,100 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# The monitoring type categoryservice-application service monitoring db-database monitoring custom-custom monitoring os-operating system monitoring
category: llm
# The monitoring type eg: linux windows tomcat mysql aws...
app: deepseek
# The monitoring i18n name
name:
zh-CN: Deepseek
en-US: Deepseek
# The description and help of this monitoring type
help:
zh-CN: Hertzbeat 对 Deepseek Api进行测量监控。<br>您可以点击 “<i>新建 Deepseek</i>” 并进行配置,或者选择“<i>更多操作</i>”,导入已有配置。
en-US: Hertzbeat monitors Deepseek Api. You could click the "<i>New Deepseek</i>" button and proceed with the configuration or import an existing setup through the "<i>More Actions</i>" menu.
zh-TW: Hertzbeat對Deepseek Api進行量測監控。<br>您可以點擊“<i>Deepseek</i>”並進行配寘,或者選擇“<i>更多操作</i>”,導入已有配寘。
helpLink:
zh-CN: https://hertzbeat.apache.org/zh-cn/docs/help/deepseek
en-US: https://hertzbeat.apache.org/docs/help/deepseek
# Input params define for monitoring(render web ui by the definition)
params:
# field-param field key
- field: token
# name-param field display i18n name
name:
zh-CN: 会话密钥
en-US: Session Key
# type-param field type(most mapping the html input type)
type: text
# required-true or false
required: true
# collect metrics config list
metrics:
# metrics - auth
- name: billing
i18n:
zh-CN: 计费
en-US: Billing
# metrics scheduling priority(0->127)->(high->low), metrics with the same priority will be scheduled in parallel
# priority 0's metrics is availability metrics, it will be scheduled first, only availability metrics collect success will the scheduling continue
priority: 0
# collect metrics content
fields:
# field-metric name, type-metric type(0-number,1-string), unit-metric unit('%','ms','MB'), label-whether it is a metrics label field
- field: currency
type: 1
i18n:
zh-CN: 货币
en-US: Currency
- field: total_balance
type: 0
i18n:
zh-CN: 可用余额
en-US: Available Balance
- field: granted_balance
type: 0
i18n:
zh-CN: 未过期的赠金余额
en-US: Unexpired Gift Balance
- field: topped_up_balance
type: 0
i18n:
zh-CN: 充值的余额
en-US: Topped Up Balance
# the protocol used for monitoring, eg: sql, ssh, http, telnet, wmi, snmp, sdk
protocol: http
# the config content when protocol is http
http:
# http host: ipv4 ipv6 domain
host: api.deepseek.com
# http port
port: 443
# http url
url: /user/balance
# http method: GET POST PUT DELETE PATCH
method: GET
# if enabled https
ssl: true
# http auth
authorization:
# http auth type: Basic Auth, Digest Auth, Bearer Token
type: Bearer Token
bearerTokenToken: ^_^token^_^
# http response data parse type: default-hertzbeat rule, jsonpath-jsonpath script, website-for website monitoring, prometheus-prometheus exporter rule
parseType: jsonPath
parseScript: '$.balance_infos.*'
@@ -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
@@ -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"
# 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: "dmesg | grep -i cpu | awk 'NR==1';sysctl hw.ncpu | awk '{print $2}';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
@@ -410,18 +309,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: sysctl hw | egrep 'hw.(phys|user|real)' | awk '{print $2}';dmesg | grep memory | grep avail | awk '{print $4}';
parseType: oneRow
@@ -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: df -m | tail -n +2 | awk 'BEGIN{ print "filesystem used available usage mounted"} {print $1,$3,$4,$5,$6}'
parseType: multiRow
@@ -524,18 +399,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
@@ -578,17 +441,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:
@@ -137,72 +126,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)
@@ -250,18 +173,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
@@ -331,18 +242,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
@@ -411,18 +310,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
@@ -468,18 +355,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
@@ -520,18 +395,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
@@ -580,18 +443,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
@@ -634,18 +485,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
@@ -688,17 +527,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
@@ -115,78 +115,6 @@ params:
required: false
# hide param-true or false
hide: true
- field: enableSshTunnel
name:
zh-CN: 是否启用SSH隧道
en-US: Enable SSH Tunnel
type: boolean
required: true
hide: true
- field: sshHost
name:
zh-CN: SSH Host
en-US: SSH Host
type: text
required: false
placeholder: 'When Enable SSH Tunnel'
hide: true
- field: sshPort
name:
zh-CN: SSH端口
en-US: SSH Port
type: number
range: '[0,65535]'
required: false
defaultValue: 22
placeholder: 'When Enable SSH tunnel'
hide: true
- field: sshTimeout
name:
zh-CN: SSH超时时间(ms)
en-US: SSH Timeout(ms)
type: number
required: false
range: '[400,200000]'
defaultValue: 6000
hide: true
- field: sshUsername
name:
zh-CN: SSH用户名
en-US: SSH Username
type: text
required: false
placeholder: 'When Enable SSH tunnel'
hide: true
- field: sshPassword
name:
zh-CN: SSH密码
en-US: SSH Password
type: password
required: false
hide: true
- field: sshShareConnection
name:
zh-CN: 是否共享SSH连接
en-US: Share SSH Connection
type: boolean
required: true
defaultValue: true
hide: true
- field: sshPrivateKey
name:
zh-CN: SSH私钥
en-US: SSH PrivateKey
type: textarea
placeholder: -----BEGIN RSA PRIVATE KEY-----
required: false
hide: true
- field: sshPrivateKeyPassphrase
name:
zh-CN: SSH密钥短语
en-US: SSH PrivateKey PassPhrase
type: password
required: false
hide: true
# collect metrics config list
metrics:
@@ -277,16 +205,6 @@ metrics:
sql: show global variables where Variable_name like 'version%' or Variable_name = 'max_connections' or Variable_name = 'datadir' or Variable_name = 'port' or Variable_name = 'thread_cache_size' or Variable_name = 'table_open_cache' or Variable_name = 'innodb_buffer_pool_size';
# JDBC url
url: ^_^url^_^
sshTunnel:
enable: ^_^enableSshTunnel^_^
host: ^_^sshHost^_^
port: ^_^sshPort^_^
timeout: ^_^sshTimeout^_^
username: ^_^sshUsername^_^
password: ^_^sshPassword^_^
privateKey: ^_^sshPrivateKey^_^
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
shareConnection: ^_^sshShareConnection^_^
- name: cache
# metrics scheduling priority(0->127)->(high->low), metrics with the same priority will be scheduled in parallel
@@ -366,16 +284,6 @@ metrics:
# sql
sql: show global status like 'QCache%';
url: ^_^url^_^
sshTunnel:
enable: ^_^enableSshTunnel^_^
host: ^_^sshHost^_^
port: ^_^sshPort^_^
timeout: ^_^sshTimeout^_^
username: ^_^sshUsername^_^
password: ^_^sshPassword^_^
privateKey: ^_^sshPrivateKey^_^
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
shareConnection: ^_^sshShareConnection^_^
- name: performance
priority: 2
@@ -410,16 +318,6 @@ metrics:
queryType: columns
sql: show global status where Variable_name = 'questions' or Variable_name = 'uptime';
url: ^_^url^_^
sshTunnel:
enable: ^_^enableSshTunnel^_^
host: ^_^sshHost^_^
port: ^_^sshPort^_^
timeout: ^_^sshTimeout^_^
username: ^_^sshUsername^_^
password: ^_^sshPassword^_^
privateKey: ^_^sshPrivateKey^_^
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
shareConnection: ^_^sshShareConnection^_^
- name: innodb
priority: 3
@@ -486,16 +384,6 @@ metrics:
queryType: columns
sql: show global status where Variable_name like 'innodb%';
url: ^_^url^_^
sshTunnel:
enable: ^_^enableSshTunnel^_^
host: ^_^sshHost^_^
port: ^_^sshPort^_^
timeout: ^_^sshTimeout^_^
username: ^_^sshUsername^_^
password: ^_^sshPassword^_^
privateKey: ^_^sshPrivateKey^_^
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
shareConnection: ^_^sshShareConnection^_^
- name: status
priority: 4
@@ -591,16 +479,6 @@ metrics:
queryType: columns
sql: show global status where Variable_name like 'thread%' or Variable_name = 'com_select' or Variable_name = 'com_insert' or Variable_name = 'com_update' or Variable_name = 'com_delete' or Variable_name = 'com_commit' or Variable_name = 'com_rollback' or Variable_name = 'questions' or Variable_name = 'uptime';
url: ^_^url^_^
sshTunnel:
enable: ^_^enableSshTunnel^_^
host: ^_^sshHost^_^
port: ^_^sshPort^_^
timeout: ^_^sshTimeout^_^
username: ^_^sshUsername^_^
password: ^_^sshPassword^_^
privateKey: ^_^sshPrivateKey^_^
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
shareConnection: ^_^sshShareConnection^_^
- name: handler
priority: 5
@@ -680,16 +558,6 @@ metrics:
queryType: columns
sql: show global status like 'Handler%';
url: ^_^url^_^
sshTunnel:
enable: ^_^enableSshTunnel^_^
host: ^_^sshHost^_^
port: ^_^sshPort^_^
timeout: ^_^sshTimeout^_^
username: ^_^sshUsername^_^
password: ^_^sshPassword^_^
privateKey: ^_^sshPrivateKey^_^
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
shareConnection: ^_^sshShareConnection^_^
- name: connection
priority: 6
@@ -729,16 +597,6 @@ metrics:
queryType: columns
sql: show global status;
url: ^_^url^_^
sshTunnel:
enable: ^_^enableSshTunnel^_^
host: ^_^sshHost^_^
port: ^_^sshPort^_^
timeout: ^_^sshTimeout^_^
username: ^_^sshUsername^_^
password: ^_^sshPassword^_^
privateKey: ^_^sshPrivateKey^_^
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
shareConnection: ^_^sshShareConnection^_^
- name: thread
priority: 7
@@ -778,16 +636,6 @@ metrics:
queryType: columns
sql: show global status like 'thread%';
url: ^_^url^_^
sshTunnel:
enable: ^_^enableSshTunnel^_^
host: ^_^sshHost^_^
port: ^_^sshPort^_^
timeout: ^_^sshTimeout^_^
username: ^_^sshUsername^_^
password: ^_^sshPassword^_^
privateKey: ^_^sshPrivateKey^_^
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
shareConnection: ^_^sshShareConnection^_^
- name: tmp
priority: 8
@@ -822,16 +670,6 @@ metrics:
queryType: columns
sql: show global status where Variable_name like '%tmp%';
url: ^_^url^_^
sshTunnel:
enable: ^_^enableSshTunnel^_^
host: ^_^sshHost^_^
port: ^_^sshPort^_^
timeout: ^_^sshTimeout^_^
username: ^_^sshUsername^_^
password: ^_^sshPassword^_^
privateKey: ^_^sshPrivateKey^_^
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
shareConnection: ^_^sshShareConnection^_^
- name: select_type
priority: 9
@@ -876,16 +714,6 @@ metrics:
queryType: columns
sql: show global status where Variable_name like 'select%';
url: ^_^url^_^
sshTunnel:
enable: ^_^enableSshTunnel^_^
host: ^_^sshHost^_^
port: ^_^sshPort^_^
timeout: ^_^sshTimeout^_^
username: ^_^sshUsername^_^
password: ^_^sshPassword^_^
privateKey: ^_^sshPrivateKey^_^
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
shareConnection: ^_^sshShareConnection^_^
- name: sort
priority: 10
@@ -925,16 +753,6 @@ metrics:
queryType: columns
sql: show global status where Variable_name like 'sort%';
url: ^_^url^_^
sshTunnel:
enable: ^_^enableSshTunnel^_^
host: ^_^sshHost^_^
port: ^_^sshPort^_^
timeout: ^_^sshTimeout^_^
username: ^_^sshUsername^_^
password: ^_^sshPassword^_^
privateKey: ^_^sshPrivateKey^_^
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
shareConnection: ^_^sshShareConnection^_^
- name: table_lock
priority: 11
@@ -964,16 +782,6 @@ metrics:
queryType: columns
sql: show global status where Variable_name like 'table_lock%';
url: ^_^url^_^
sshTunnel:
enable: ^_^enableSshTunnel^_^
host: ^_^sshHost^_^
port: ^_^sshPort^_^
timeout: ^_^sshTimeout^_^
username: ^_^sshUsername^_^
password: ^_^sshPassword^_^
privateKey: ^_^sshPrivateKey^_^
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
shareConnection: ^_^sshShareConnection^_^
- name: process_state
priority: 12
@@ -1004,16 +812,6 @@ metrics:
queryType: multiRow
sql: select state, count(*) as num from information_schema.PROCESSLIST where state != '' group by state;
url: ^_^url^_^
sshTunnel:
enable: ^_^enableSshTunnel^_^
host: ^_^sshHost^_^
port: ^_^sshPort^_^
timeout: ^_^sshTimeout^_^
username: ^_^sshUsername^_^
password: ^_^sshPassword^_^
privateKey: ^_^sshPrivateKey^_^
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
shareConnection: ^_^sshShareConnection^_^
- name: slow_sql
priority: 13
@@ -1066,13 +864,3 @@ metrics:
queryType: multiRow
sql: select sql_text, start_time, db, query_time from mysql.slow_log;
url: ^_^url^_^
sshTunnel:
enable: ^_^enableSshTunnel^_^
host: ^_^sshHost^_^
port: ^_^sshPort^_^
timeout: ^_^sshTimeout^_^
username: ^_^sshUsername^_^
password: ^_^sshPassword^_^
privateKey: ^_^sshPrivateKey^_^
privateKeyPassphrase: ^_^sshPrivateKeyPassphrase^_^
shareConnection: ^_^sshShareConnection^_^
@@ -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:
@@ -127,139 +116,6 @@ params:
required: false
# hide param-true or false
hide: true
# collect metrics config list
# 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
# 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)
@@ -336,18 +192,6 @@ metrics:
privateKey: ^_^privateKey^_^
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: nvidia-smi --query-gpu=index,name,utilization.gpu,utilization.memory,memory.total,memory.used,memory.free,temperature.gpu --format=csv,nounits | sed 's/ *, */,/g' | sed 's/ //g' | sed 's/,/ /g'
# ssh response data parse type: oneRow, 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:
@@ -137,72 +126,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)
@@ -250,18 +173,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
@@ -331,18 +242,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
@@ -411,18 +310,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
@@ -468,18 +355,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
@@ -520,18 +395,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
@@ -580,18 +443,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 -m | tail -n +2 | awk 'BEGIN{ print "filesystem used available usage mounted"} {print $1,$3,$4,$5,$6}'
parseType: multiRow
@@ -634,18 +485,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
@@ -688,17 +527,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