mirror of
https://github.com/dromara/hertzbeat.git
synced 2026-09-17 18:19:02 +00:00
Compare commits
72
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7f688ef9c | ||
|
|
4b5a98ea26 | ||
|
|
066d74629a | ||
|
|
95852ca2bc | ||
|
|
9619638cff | ||
|
|
b91f849594 | ||
|
|
e1806f856f | ||
|
|
0da2835b24 | ||
|
|
73ce238cad | ||
|
|
95654e49c5 | ||
|
|
2763c264ad | ||
|
|
148cca0e6e | ||
|
|
9ceb6e6400 | ||
|
|
64cc15fabb | ||
|
|
27da850ca4 | ||
|
|
1ad52321ea | ||
|
|
ff037a6a3a | ||
|
|
acda0a9097 | ||
|
|
433f09a4e3 | ||
|
|
706baffd5e | ||
|
|
0b6fb353f1 | ||
|
|
1c64e8022f | ||
|
|
e5d2aedfe0 | ||
|
|
e70633c256 | ||
|
|
5e69374456 | ||
|
|
6acf76c910 | ||
|
|
6d8e6f949a | ||
|
|
e6f91cae42 | ||
|
|
73e7d0ade1 | ||
|
|
79673834f1 | ||
|
|
e3a458786c | ||
|
|
0385892674 | ||
|
|
04e2321175 | ||
|
|
8373f0a247 | ||
|
|
5ad03687b2 | ||
|
|
7127159063 | ||
|
|
51715797ef | ||
|
|
47a1b13781 | ||
|
|
d5fc492b98 | ||
|
|
bbc30cd3db | ||
|
|
6e50d1f3a7 | ||
|
|
b18412254e | ||
|
|
fc0ef7773c | ||
|
|
adf71441f8 | ||
|
|
e36842420f | ||
|
|
a2c1123b09 | ||
|
|
b4151a4e73 | ||
|
|
1c1683d9c6 | ||
|
|
46d4fc2dd9 | ||
|
|
ffa2487587 | ||
|
|
30c7b22c4c | ||
|
|
7a3e23ec59 | ||
|
|
da6556641a | ||
|
|
b008f16b67 | ||
|
|
b77222765c | ||
|
|
aeb4188f83 | ||
|
|
311cb131fd | ||
|
|
21558e9b4f | ||
|
|
8d1f306433 | ||
|
|
e98af9527e | ||
|
|
811a07553b | ||
|
|
9fd58ced4a | ||
|
|
a331c045ae | ||
|
|
63d3d18428 | ||
|
|
cec8d8b396 | ||
|
|
9d68d16a78 | ||
|
|
290474a715 | ||
|
|
0dbc7347b4 | ||
|
|
e2a2d5eaec | ||
|
|
356cca295d | ||
|
|
5e09c6d9ae | ||
|
|
54caa84fc1 |
@@ -2325,6 +2325,42 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "JuJinPark",
|
||||
"name": "jujin",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/44892459?v=4",
|
||||
"profile": "https://github.com/JuJinPark",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "LL-LIN",
|
||||
"name": "LL-LIN",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/43002118?v=4",
|
||||
"profile": "https://github.com/LL-LIN",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "bigcyy",
|
||||
"name": "Yang Chen",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/73413979?v=4",
|
||||
"profile": "https://bigcyy.github.io/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "sarthakeash",
|
||||
"name": "Sarthak Arora",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/74091160?v=4",
|
||||
"profile": "https://github.com/sarthakeash",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
|
||||
/home export-ignore
|
||||
/hip export-ignore
|
||||
/template-marketplace export-ignore
|
||||
/.github export-ignore
|
||||
/.idea export-ignore
|
||||
/.devcontainer export-ignore
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
|
||||
@@ -31,6 +31,8 @@ name: "CodeQL"
|
||||
on:
|
||||
schedule:
|
||||
- cron: '21 13 * * 4'
|
||||
push:
|
||||
branches: [ action* ]
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
|
||||
@@ -19,7 +19,7 @@ name: DOC CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, dev ]
|
||||
branches: [ master, dev, action* ]
|
||||
paths:
|
||||
- 'home/**'
|
||||
pull_request:
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
|
||||
name: 'HertzBeat Doc Pdf Builder'
|
||||
on:
|
||||
page_build
|
||||
push:
|
||||
branches: [ page_build* ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
@@ -22,7 +22,7 @@ name: Frontend CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, dev ]
|
||||
branches: [ master, dev, action* ]
|
||||
paths:
|
||||
- 'web-app/**'
|
||||
pull_request:
|
||||
|
||||
@@ -22,7 +22,7 @@ name: License Checker
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, dev ]
|
||||
branches: [ master, dev, action* ]
|
||||
pull_request:
|
||||
branches: [ master, dev ]
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
run: |
|
||||
mvn clean install
|
||||
mvn clean package -Prelease -DskipTests
|
||||
cd collector
|
||||
cd hertzbeat-collector
|
||||
mvn clean package -Pcluster -DskipTests
|
||||
|
||||
- uses: docker/setup-qemu-action@v3
|
||||
|
||||
@@ -13,6 +13,7 @@ mvnw.bat
|
||||
package-lock.json
|
||||
*.zip
|
||||
jdk/
|
||||
data/
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
@@ -54,3 +55,5 @@ node_modules
|
||||
application-dev.yml
|
||||
application-mysql.yml
|
||||
application-pg.yml
|
||||
/package.json
|
||||
/yarn.lock
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Apache HertzBeat (incubating)
|
||||
Copyright 2024 The Apache Software Foundation
|
||||
Copyright 2024-2025 The Apache Software Foundation
|
||||
|
||||
This product includes software developed at
|
||||
The Apache Software Foundation (http://www.apache.org/).
|
||||
|
||||
@@ -510,6 +510,12 @@ Thanks to these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yasminvo"><img src="https://avatars.githubusercontent.com/u/107528848?v=4?s=100" width="100px;" alt="yasminvo"/><br /><sub><b>yasminvo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yasminvo" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/notbugggg"><img src="https://avatars.githubusercontent.com/u/147966331?v=4?s=100" width="100px;" alt="不关银渐层的事哦"/><br /><sub><b>不关银渐层的事哦</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=notbugggg" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yyahang"><img src="https://avatars.githubusercontent.com/u/90464876?v=4?s=100" width="100px;" alt="yyahang"/><br /><sub><b>yyahang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yyahang" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/JuJinPark"><img src="https://avatars.githubusercontent.com/u/44892459?v=4?s=100" width="100px;" alt="jujin"/><br /><sub><b>jujin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=JuJinPark" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LL-LIN"><img src="https://avatars.githubusercontent.com/u/43002118?v=4?s=100" width="100px;" alt="LL-LIN"/><br /><sub><b>LL-LIN</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LL-LIN" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://bigcyy.github.io/"><img src="https://avatars.githubusercontent.com/u/73413979?v=4?s=100" width="100px;" alt="Yang Chen"/><br /><sub><b>Yang Chen</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=bigcyy" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sarthakeash"><img src="https://avatars.githubusercontent.com/u/74091160?v=4?s=100" width="100px;" alt="Sarthak Arora"/><br /><sub><b>Sarthak Arora</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=sarthakeash" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -509,6 +509,12 @@ Thanks these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yasminvo"><img src="https://avatars.githubusercontent.com/u/107528848?v=4?s=100" width="100px;" alt="yasminvo"/><br /><sub><b>yasminvo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yasminvo" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/notbugggg"><img src="https://avatars.githubusercontent.com/u/147966331?v=4?s=100" width="100px;" alt="不关银渐层的事哦"/><br /><sub><b>不关银渐层的事哦</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=notbugggg" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yyahang"><img src="https://avatars.githubusercontent.com/u/90464876?v=4?s=100" width="100px;" alt="yyahang"/><br /><sub><b>yyahang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yyahang" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/JuJinPark"><img src="https://avatars.githubusercontent.com/u/44892459?v=4?s=100" width="100px;" alt="jujin"/><br /><sub><b>jujin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=JuJinPark" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LL-LIN"><img src="https://avatars.githubusercontent.com/u/43002118?v=4?s=100" width="100px;" alt="LL-LIN"/><br /><sub><b>LL-LIN</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LL-LIN" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://bigcyy.github.io/"><img src="https://avatars.githubusercontent.com/u/73413979?v=4?s=100" width="100px;" alt="Yang Chen"/><br /><sub><b>Yang Chen</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=bigcyy" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sarthakeash"><img src="https://avatars.githubusercontent.com/u/74091160?v=4?s=100" width="100px;" alt="Sarthak Arora"/><br /><sub><b>Sarthak Arora</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=sarthakeash" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -73,6 +73,11 @@
|
||||
"field": "successCode",
|
||||
"type": 4,
|
||||
"paramValue": "200, 201"
|
||||
},
|
||||
{
|
||||
"field": "enableUrlEncoding",
|
||||
"type": 1,
|
||||
"paramValue": true
|
||||
}
|
||||
]
|
||||
}
|
||||
+9
@@ -17,30 +17,39 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.config;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Alibaba Cloud SMS configuration properties
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AlibabaSmsProperties {
|
||||
/**
|
||||
* Alibaba Cloud access key id
|
||||
*/
|
||||
@NotBlank(message = "AccessKeyId cannot be empty")
|
||||
private String accessKeyId;
|
||||
|
||||
/**
|
||||
* Alibaba Cloud access key secret
|
||||
*/
|
||||
@NotBlank(message = "AccessKeySecret cannot be empty")
|
||||
private String accessKeySecret;
|
||||
|
||||
/**
|
||||
* SMS signature
|
||||
*/
|
||||
@NotBlank(message = "SignName cannot be empty")
|
||||
private String signName;
|
||||
|
||||
/**
|
||||
* SMS template Code
|
||||
*/
|
||||
@NotBlank(message = "TemplateCode cannot be null")
|
||||
private String templateCode;
|
||||
}
|
||||
|
||||
+4
@@ -17,6 +17,7 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.config;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
@@ -27,16 +28,19 @@ public class AwsSmsProperties {
|
||||
/**
|
||||
* AWS Cloud access key id
|
||||
*/
|
||||
@NotBlank(message = "AccessKeyId cannot be empty")
|
||||
private String accessKeyId;
|
||||
|
||||
/**
|
||||
* AWS Cloud access key secret
|
||||
*/
|
||||
@NotBlank(message = "AccessKeySecret cannot be empty")
|
||||
private String accessKeySecret;
|
||||
|
||||
/**
|
||||
* AWS Cloud region
|
||||
*/
|
||||
@NotBlank(message = "region cannot be empty")
|
||||
private String region;
|
||||
|
||||
}
|
||||
|
||||
@@ -17,7 +17,10 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.config;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -25,6 +28,8 @@ import org.springframework.stereotype.Component;
|
||||
* SMS configuration
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "alerter.sms")
|
||||
public class SmsConfig {
|
||||
@@ -37,6 +42,7 @@ public class SmsConfig {
|
||||
/**
|
||||
* sms service provider
|
||||
*/
|
||||
@NotBlank(message = "Type cannot be empty")
|
||||
private String type;
|
||||
|
||||
/**
|
||||
@@ -58,6 +64,12 @@ public class SmsConfig {
|
||||
* Aws configuration
|
||||
*/
|
||||
private AwsSmsProperties aws;
|
||||
|
||||
/**
|
||||
* Twilio SMS configuration
|
||||
*/
|
||||
private TwilioSmsProperties twilio;
|
||||
|
||||
/**
|
||||
* Smslocal SMS configuration
|
||||
*/
|
||||
|
||||
+4
@@ -18,12 +18,16 @@
|
||||
package org.apache.hertzbeat.alert.config;
|
||||
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Smslocal SMS Properties
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SmslocalSmsProperties {
|
||||
/**
|
||||
* SmsLocal account api key
|
||||
|
||||
+10
@@ -17,35 +17,45 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.config;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Tencent Cloud SMS properties
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class TencentSmsProperties {
|
||||
/**
|
||||
* Tencent cloud account secret id
|
||||
*/
|
||||
@NotBlank(message = "SecretId cannot be empty")
|
||||
private String secretId;
|
||||
|
||||
/**
|
||||
* Tencent cloud account secret key
|
||||
*/
|
||||
@NotBlank(message = "SecretKey cannot be empty")
|
||||
private String secretKey;
|
||||
|
||||
/**
|
||||
* SMS app id
|
||||
*/
|
||||
@NotBlank(message = "AppId cannot be null")
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* SMS signature
|
||||
*/
|
||||
@NotBlank(message = "SignName cannot be empty")
|
||||
private String signName;
|
||||
|
||||
/**
|
||||
* SMS template ID
|
||||
*/
|
||||
@NotBlank(message = "templateId cannot be null")
|
||||
private String templateId;
|
||||
}
|
||||
+20
-18
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.pojo.dto;
|
||||
package org.apache.hertzbeat.alert.config;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -23,25 +23,27 @@ import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Sms Sender configuration dto
|
||||
* Twilio SMS configuration properties
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SmsNoticeSender {
|
||||
|
||||
@NotBlank(message = "Type cannot be empty")
|
||||
private String type;
|
||||
|
||||
private SmsTencentConfig tencent;
|
||||
|
||||
private SmsAlibabaConfig alibaba;
|
||||
@NoArgsConstructor
|
||||
public class TwilioSmsProperties {
|
||||
/**
|
||||
* Twilio Account SID
|
||||
*/
|
||||
@NotBlank(message = "Account SID cannot be empty")
|
||||
private String accountSid;
|
||||
|
||||
private SmsUniSmsConfig unisms;
|
||||
/**
|
||||
* Twilio Auth Token
|
||||
*/
|
||||
@NotBlank(message = "Auth Token cannot be empty")
|
||||
private String authToken;
|
||||
|
||||
private SmslocalConfig smslocal;
|
||||
|
||||
private SmsAwsConfig aws;
|
||||
|
||||
private boolean enable = true;
|
||||
}
|
||||
/**
|
||||
* Twilio Issued Phone Number
|
||||
*/
|
||||
@NotBlank(message = "Twilio Phone Number cannot be empty")
|
||||
private String twilioPhoneNumber;
|
||||
}
|
||||
+9
@@ -17,16 +17,22 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.config;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* UniSMS properties
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class UniSmsProperties {
|
||||
/**
|
||||
* UniSMS access key id
|
||||
*/
|
||||
@NotBlank(message = "accessKeyId cannot be empty")
|
||||
private String accessKeyId;
|
||||
|
||||
/**
|
||||
@@ -37,15 +43,18 @@ public class UniSmsProperties {
|
||||
/**
|
||||
* SMS signature
|
||||
*/
|
||||
@NotBlank(message = "signature cannot be null")
|
||||
private String signature;
|
||||
|
||||
/**
|
||||
* SMS template ID
|
||||
*/
|
||||
@NotBlank(message = "templateId cannot be null")
|
||||
private String templateId;
|
||||
|
||||
/**
|
||||
* Authentication mode: simple or hmac, default is simple
|
||||
*/
|
||||
@NotBlank(message = "authMode cannot be null")
|
||||
private String authMode = "simple";
|
||||
}
|
||||
+2
-2
@@ -118,11 +118,11 @@ public class TencentCloudExternAlert {
|
||||
/**
|
||||
* Transaction alarm
|
||||
*/
|
||||
private static final String EVENT = "event";
|
||||
public static final String EVENT = "event";
|
||||
|
||||
/**
|
||||
* Indicator alarm
|
||||
*/
|
||||
private static final String METRIC = "metric";
|
||||
public static final String METRIC = "metric";
|
||||
|
||||
}
|
||||
|
||||
+3
@@ -48,6 +48,7 @@ final class TelegramBotAlertNotifyHandlerImpl extends AbstractAlertNotifyHandler
|
||||
TelegramBotNotifyDTO notifyBody = TelegramBotNotifyDTO.builder()
|
||||
.chatId(receiver.getTgUserId())
|
||||
.text(renderContent(noticeTemplate, alert))
|
||||
.messageThreadId(receiver.getTgMessageThreadId())
|
||||
.disableWebPagePreview(true)
|
||||
.build();
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
@@ -84,6 +85,8 @@ final class TelegramBotAlertNotifyHandlerImpl extends AbstractAlertNotifyHandler
|
||||
private String text;
|
||||
@JsonProperty("disable_web_page_preview")
|
||||
private Boolean disableWebPagePreview;
|
||||
@JsonProperty("message_thread_id")
|
||||
private String messageThreadId;
|
||||
}
|
||||
|
||||
@NoArgsConstructor
|
||||
|
||||
+6
-1
@@ -23,6 +23,7 @@ import org.apache.hertzbeat.alert.config.SmsConfig;
|
||||
import org.apache.hertzbeat.alert.service.impl.SmsLocalSmsClientImpl;
|
||||
import org.apache.hertzbeat.alert.service.impl.AwsSmsClientImpl;
|
||||
import org.apache.hertzbeat.alert.service.impl.TencentSmsClientImpl;
|
||||
import org.apache.hertzbeat.alert.service.impl.TwilioSmsClientImpl;
|
||||
import org.apache.hertzbeat.alert.service.impl.UniSmsClientImpl;
|
||||
import org.apache.hertzbeat.alert.service.impl.AlibabaSmsClientImpl;
|
||||
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
|
||||
@@ -35,6 +36,7 @@ import org.springframework.stereotype.Component;
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.ALIBABA;
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.AWS;
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.TENCENT;
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.TWILIO;
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.UNISMS;
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.SMSLOCAL;
|
||||
|
||||
@@ -141,9 +143,12 @@ public class SmsClientFactory {
|
||||
case AWS:
|
||||
currentSmsClient = new AwsSmsClientImpl(smsConfig.getAws());
|
||||
break;
|
||||
case TWILIO:
|
||||
currentSmsClient = new TwilioSmsClientImpl(smsConfig.getTwilio());
|
||||
break;
|
||||
default:
|
||||
log.warn("[SmsClientFactory] Unsupported SMS provider type: {}", smsConfig.getType());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+174
-2
@@ -18,14 +18,22 @@
|
||||
package org.apache.hertzbeat.alert.service.impl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.hertzbeat.alert.dto.TencentCloudExternAlert;
|
||||
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
|
||||
import org.apache.hertzbeat.alert.service.ExternAlertService;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Default external alarm service impl
|
||||
*/
|
||||
@@ -43,8 +51,7 @@ public class TencentExternAlertService implements ExternAlertService {
|
||||
log.warn("parse extern alert content failed! content: {}", content);
|
||||
return;
|
||||
}
|
||||
// todo convert TenCloudAlertReport to SingleAlert
|
||||
SingleAlert alert = new SingleAlert();
|
||||
SingleAlert alert = new TencentCloudAlertConverter().convert(report);
|
||||
alarmCommonReduce.reduceAndSendAlarm(alert);
|
||||
}
|
||||
|
||||
@@ -52,4 +59,169 @@ public class TencentExternAlertService implements ExternAlertService {
|
||||
public String supportSource() {
|
||||
return "tencent";
|
||||
}
|
||||
|
||||
/**
|
||||
* Converter: TencentCloud alert to SingleAlert
|
||||
*/
|
||||
public static class TencentCloudAlertConverter {
|
||||
|
||||
/**
|
||||
* Metric alert content template
|
||||
*/
|
||||
private final String metricTemplate = "Tencent Cloud Metric Alert: {metricShowName} {calcType} {calcValue}{calcUnit}, Current: {currentValue}{unit}";
|
||||
|
||||
/**
|
||||
* Event alert content template
|
||||
*/
|
||||
private final String eventTemplate = "Tencent Cloud Event Alert: {productShowName} - {eventShowName}";
|
||||
|
||||
|
||||
/**
|
||||
* Convert TencentCloud alert to SingleAlert
|
||||
*/
|
||||
public SingleAlert convert(TencentCloudExternAlert tencentAlert) {
|
||||
// Building basic information
|
||||
SingleAlert.SingleAlertBuilder builder = SingleAlert.builder()
|
||||
.status(convertStatus(tencentAlert.getAlarmStatus()))
|
||||
.startAt(parseTime(tencentAlert.getFirstOccurTime()))
|
||||
.activeAt(parseTime(tencentAlert.getFirstOccurTime()))
|
||||
.triggerTimes(1);
|
||||
// If it is a recovery state, set the end time.
|
||||
if ("0".equals(tencentAlert.getAlarmStatus())) {
|
||||
builder.endAt(parseTime(tencentAlert.getRecoverTime()));
|
||||
}
|
||||
|
||||
// Build labels
|
||||
Map<String, String> labels = new HashMap<>();
|
||||
buildLabels(labels, tencentAlert);
|
||||
|
||||
// Build annotations
|
||||
Map<String, String> annotations = new HashMap<>();
|
||||
buildAnnotations(annotations, tencentAlert);
|
||||
|
||||
// Build content
|
||||
String content = generateContent(tencentAlert);
|
||||
|
||||
return builder
|
||||
.labels(labels)
|
||||
.annotations(annotations)
|
||||
.content(content)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void buildLabels(Map<String, String> labels, TencentCloudExternAlert alert) {
|
||||
labels.put("__source__", "tencent_cloud");
|
||||
labels.put("alert_type", alert.getAlarmType());
|
||||
if (TencentCloudExternAlert.METRIC.equals(alert.getAlarmType())) {
|
||||
labels.put("metric_name", alert.getAlarmPolicyInfo().getConditions().getMetricName());
|
||||
labels.put("namespace", alert.getAlarmObjInfo().getNamespace());
|
||||
} else {
|
||||
labels.put("event_name", alert.getAlarmPolicyInfo().getConditions().getEventName());
|
||||
labels.put("product_name", alert.getAlarmPolicyInfo().getConditions().getProductName());
|
||||
}
|
||||
}
|
||||
|
||||
private void buildAnnotations(Map<String, String> annotations, TencentCloudExternAlert alert) {
|
||||
TencentCloudExternAlert.AlarmPolicyInfo alarmPolicyInfo = alert.getAlarmPolicyInfo();
|
||||
TencentCloudExternAlert.AlarmObjInfo alarmObjInfo = alert.getAlarmObjInfo();
|
||||
TencentCloudExternAlert.Dimensions dimensions = alert.getAlarmObjInfo().getDimensions();
|
||||
|
||||
putIfNotNull(annotations, "policy_id", alarmPolicyInfo.getPolicyId());
|
||||
putIfNotNull(annotations, "policy_type", alarmPolicyInfo.getPolicyType());
|
||||
putIfNotNull(annotations, "policy_name", alarmPolicyInfo.getPolicyName());
|
||||
putIfNotNull(annotations, "policy_type_cname", alarmPolicyInfo.getPolicyTypeCname());
|
||||
|
||||
putIfNotNull(annotations, "namespace", alarmObjInfo.getNamespace());
|
||||
putIfNotNull(annotations, "region", alarmObjInfo.getRegion());
|
||||
putIfNotNull(annotations, "app_id", alarmObjInfo.getAppId());
|
||||
putIfNotNull(annotations, "uin", alarmObjInfo.getUin());
|
||||
|
||||
putIfNotNull(annotations, "instance_id", dimensions.getUnInstanceId());
|
||||
putIfNotNull(annotations, "obj_id", dimensions.getObjId());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate alert content with template
|
||||
*/
|
||||
private String generateContent(TencentCloudExternAlert alert) {
|
||||
if (TencentCloudExternAlert.METRIC.equals(alert.getAlarmType())) {
|
||||
return generateMetricContent(alert);
|
||||
} else {
|
||||
return generateEventContent(alert);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate metric alert content
|
||||
*/
|
||||
private String generateMetricContent(TencentCloudExternAlert alert) {
|
||||
TencentCloudExternAlert.Conditions conditions = alert.getAlarmPolicyInfo().getConditions();
|
||||
Map<String, String> params = new HashMap<>();
|
||||
|
||||
params.put("metricShowName", conditions.getMetricShowName());
|
||||
|
||||
if (StringUtils.isNotEmpty(conditions.getCalcType())
|
||||
&& StringUtils.isNotEmpty(conditions.getCalcValue())) {
|
||||
params.put("calcType", conditions.getCalcType());
|
||||
params.put("calcValue", conditions.getCalcValue());
|
||||
params.put("calcUnit", Objects.toString(conditions.getCalcUnit(), ""));
|
||||
} else {
|
||||
params.put("calcType", "");
|
||||
params.put("calcValue", "N/A");
|
||||
params.put("calcUnit", "");
|
||||
}
|
||||
|
||||
if (StringUtils.isNotEmpty(conditions.getCurrentValue())) {
|
||||
params.put("currentValue", conditions.getCurrentValue());
|
||||
params.put("unit", Objects.toString(conditions.getUnit(), ""));
|
||||
} else {
|
||||
params.put("currentValue", "N/A");
|
||||
params.put("unit", "");
|
||||
}
|
||||
return replacePlaceholders(metricTemplate, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate event alert content
|
||||
*/
|
||||
private String generateEventContent(TencentCloudExternAlert alert) {
|
||||
TencentCloudExternAlert.Conditions conditions = alert.getAlarmPolicyInfo().getConditions();
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("productShowName", conditions.getProductShowName());
|
||||
params.put("eventShowName", conditions.getEventShowName());
|
||||
return replacePlaceholders(eventTemplate, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace placeholders in template with actual values
|
||||
*/
|
||||
private String replacePlaceholders(String template, Map<String, String> params) {
|
||||
String result = template;
|
||||
for (Map.Entry<String, String> entry : params.entrySet()) {
|
||||
result = result.replace("{" + entry.getKey() + "}", Objects.toString(entry.getValue(), "NULL"));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String convertStatus(String alarmStatus) {
|
||||
return "1".equals(alarmStatus) ? CommonConstants.ALERT_STATUS_FIRING : CommonConstants.ALERT_STATUS_RESOLVED;
|
||||
}
|
||||
|
||||
private Long parseTime(String timeStr) {
|
||||
try {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
return sdf.parse(timeStr).getTime();
|
||||
} catch (ParseException e) {
|
||||
log.error("Failed to parse time: {}", timeStr);
|
||||
throw new IllegalArgumentException("Failed to parse time: " + timeStr, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void putIfNotNull(Map<String, String> map, String key, String value){
|
||||
if (StringUtils.isNotEmpty(value)){
|
||||
map.put(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -17,6 +17,7 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.service.impl;
|
||||
|
||||
import jakarta.xml.bind.DatatypeConverter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.config.TencentSmsProperties;
|
||||
import org.apache.hertzbeat.alert.service.SmsClient;
|
||||
@@ -34,7 +35,6 @@ 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;
|
||||
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.service.impl;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.config.TwilioSmsProperties;
|
||||
import org.apache.hertzbeat.alert.service.SmsClient;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
|
||||
import org.apache.hertzbeat.common.support.exception.SendMessageException;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
|
||||
import static org.apache.hertzbeat.common.constants.SmsConstants.TWILIO;
|
||||
|
||||
/**
|
||||
* Twilio SMS Client Implementation<br>
|
||||
* API doc: <a href=
|
||||
* "https://www.twilio.com/docs/sms/api">https://www.twilio.com/docs/sms/api</a>
|
||||
*/
|
||||
@Slf4j
|
||||
public class TwilioSmsClientImpl implements SmsClient {
|
||||
|
||||
private static final String API_URL_FORMAT = "https://api.twilio.com/2010-04-01/Accounts/%s/Messages.json";
|
||||
private static final String MESSAGE_TEMPLATE = "Instance: %s, Priority: %s, Content: %s";
|
||||
|
||||
private final String accountSid;
|
||||
private final String authToken;
|
||||
private final String twilioPhoneNumber;
|
||||
|
||||
public TwilioSmsClientImpl(TwilioSmsProperties config) {
|
||||
if (config != null) {
|
||||
this.accountSid = config.getAccountSid();
|
||||
this.authToken = config.getAuthToken();
|
||||
this.twilioPhoneNumber = config.getTwilioPhoneNumber();
|
||||
} else {
|
||||
this.accountSid = "";
|
||||
this.authToken = "";
|
||||
this.twilioPhoneNumber = "";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) {
|
||||
String instance = null;
|
||||
String priority = null;
|
||||
String content = null;
|
||||
if (alert.getCommonLabels() != null) {
|
||||
instance = alert.getCommonLabels().get("instance") == null ? alert.getGroupKey()
|
||||
: alert.getCommonLabels().get("instance");
|
||||
priority = alert.getCommonLabels().get("priority") == null ? "unknown"
|
||||
: alert.getCommonLabels().get("priority");
|
||||
content = alert.getCommonAnnotations().get("summary");
|
||||
content = content == null ? alert.getCommonAnnotations().get("description") : content;
|
||||
if (content == null) {
|
||||
content = alert.getCommonAnnotations().values().stream().findFirst().orElse(null);
|
||||
}
|
||||
}
|
||||
this.send(receiver.getPhone(), createMessage(instance, priority, content));
|
||||
}
|
||||
|
||||
private String createMessage(String instance, String priority, String content) {
|
||||
return String.format(MESSAGE_TEMPLATE, instance, priority, content);
|
||||
}
|
||||
|
||||
private void send(String phoneNumber, String message) {
|
||||
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
|
||||
String endpoint = String.format(API_URL_FORMAT, accountSid);
|
||||
URI requestUri = new URI(endpoint);
|
||||
|
||||
HttpPost httpPost = createHttpPost(requestUri, phoneNumber, message);
|
||||
log.info("Sending Twilio SMS request to {}", requestUri);
|
||||
executeRequest(httpClient, httpPost, phoneNumber);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to send SMS: {}", e.getMessage());
|
||||
throw new SendMessageException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private HttpPost createHttpPost(URI requestUri, String toNumber, String message) {
|
||||
HttpPost httpPost = new HttpPost(requestUri);
|
||||
|
||||
String auth = accountSid + ":" + authToken;
|
||||
String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes());
|
||||
httpPost.setHeader("Authorization", "Basic " + encodedAuth);
|
||||
|
||||
List<BasicNameValuePair> parameters = new ArrayList<>();
|
||||
parameters.add(new BasicNameValuePair("To", toNumber));
|
||||
parameters.add(new BasicNameValuePair("From", twilioPhoneNumber));
|
||||
parameters.add(new BasicNameValuePair("Body", message));
|
||||
|
||||
try {
|
||||
httpPost.setEntity(new UrlEncodedFormEntity(parameters));
|
||||
return httpPost;
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to create HTTP request: {}", e.getMessage());
|
||||
throw new SendMessageException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void executeRequest(CloseableHttpClient httpClient, HttpPost httpPost, String phoneNumber)
|
||||
throws Exception {
|
||||
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
String responseBody = EntityUtils.toString(response.getEntity());
|
||||
log.info("SMS response status: {}, body: {}", statusCode, responseBody);
|
||||
|
||||
if (statusCode < 200 || statusCode >= 300) {
|
||||
|
||||
if (responseBody.contains("21608")) {
|
||||
throw new SendMessageException(
|
||||
"The Twilio trial account can only send SMS to verified phone numbers");
|
||||
} else {
|
||||
throw new SendMessageException(
|
||||
"HTTP request failed with status code: " + statusCode + ", response: " + responseBody);
|
||||
}
|
||||
}
|
||||
|
||||
JsonNode jsonResponse = JsonUtil.fromJson(responseBody);
|
||||
if (jsonResponse == null) {
|
||||
throw new SendMessageException(statusCode + ":" + responseBody);
|
||||
}
|
||||
|
||||
JsonNode sidNode = jsonResponse.get("sid");
|
||||
if (sidNode == null) {
|
||||
throw new SendMessageException(statusCode + ":" + responseBody);
|
||||
}
|
||||
|
||||
String sid = sidNode.asText();
|
||||
log.info("Successfully sent SMS to phone: {}, sid: {}", phoneNumber, sid);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return TWILIO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkConfig() {
|
||||
return !(accountSid.isBlank() || authToken.isBlank() || twilioPhoneNumber.isBlank());
|
||||
}
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.controller;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
|
||||
|
||||
import org.apache.hertzbeat.alert.service.AlertInhibitService;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertInhibit;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* test case for {@link AlertInhibitControllerTest}
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class AlertInhibitControllerTest {
|
||||
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Mock
|
||||
private AlertInhibitService alertInhibitService;
|
||||
|
||||
@InjectMocks
|
||||
private AlertInhibitController alertInhibitController;
|
||||
|
||||
private AlertInhibit alertInhibit;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
this.mockMvc = standaloneSetup(alertInhibitController).build();
|
||||
|
||||
HashMap<String, String> sourceLabels = new HashMap<>();
|
||||
HashMap<String, String> targetLabels = new HashMap<>();
|
||||
|
||||
alertInhibit = AlertInhibit.builder()
|
||||
.id(1L)
|
||||
.name("test")
|
||||
.sourceLabels(sourceLabels)
|
||||
.targetLabels(targetLabels)
|
||||
.creator("test")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddNewAlertInhibit() throws Exception {
|
||||
|
||||
doNothing().when(alertInhibitService).validate(any(AlertInhibit.class), eq(false));
|
||||
doNothing().when(alertInhibitService).addAlertInhibit(any(AlertInhibit.class));
|
||||
|
||||
mockMvc.perform(post("/api/alert/inhibit")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(JsonUtil.toJson(alertInhibit)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
|
||||
.andExpect(jsonPath("$.msg").value("Add success"));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void testModifyAlertInhibit() throws Exception {
|
||||
|
||||
doNothing().when(alertInhibitService).validate(any(AlertInhibit.class), eq(true));
|
||||
doNothing().when(alertInhibitService).modifyAlertInhibit(any(AlertInhibit.class));
|
||||
|
||||
mockMvc.perform(put("/api/alert/inhibit")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(JsonUtil.toJson(alertInhibit)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
|
||||
.andExpect(jsonPath("$.msg").value("Modify success"));
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAlertInhibitExists() throws Exception {
|
||||
|
||||
when(alertInhibitService.getAlertInhibit(1L)).thenReturn(alertInhibit);
|
||||
|
||||
mockMvc.perform(get("/api/alert/inhibit/{id}", 1L)
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.id").value(alertInhibit.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAlertInhibitNotExists() throws Exception {
|
||||
|
||||
when(alertInhibitService.getAlertInhibit(1L)).thenReturn(null);
|
||||
|
||||
mockMvc.perform(get("/api/alert/inhibit/{id}", 1L)
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value((int) CommonConstants.MONITOR_NOT_EXIST_CODE))
|
||||
.andExpect(jsonPath("$.msg").value("AlertInhibit not exist."));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+1
-1
@@ -41,12 +41,12 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import java.util.HashMap;
|
||||
import org.apache.hertzbeat.alert.service.impl.DataSourceServiceImpl;
|
||||
import org.apache.hertzbeat.warehouse.db.QueryExecutor;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.mockito.Mockito;
|
||||
import org.apache.hertzbeat.warehouse.db.QueryExecutor;
|
||||
|
||||
/**
|
||||
* test case for {@link DataSourceService}
|
||||
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.service;
|
||||
|
||||
import org.apache.hertzbeat.alert.config.TwilioSmsProperties;
|
||||
import org.apache.hertzbeat.alert.service.impl.TwilioSmsClientImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* Test case for {@link TwilioSmsClientImpl}
|
||||
*/
|
||||
class TwilioSmsClientImplTest {
|
||||
|
||||
@Test
|
||||
void getType() {
|
||||
TwilioSmsProperties twilioSmsProperties = new TwilioSmsProperties();
|
||||
twilioSmsProperties.setAccountSid("accountSid");
|
||||
twilioSmsProperties.setAuthToken("authToken");
|
||||
twilioSmsProperties.setTwilioPhoneNumber("twilioPhoneNumber");
|
||||
TwilioSmsClientImpl twilioSmsClient = new TwilioSmsClientImpl(twilioSmsProperties);
|
||||
|
||||
assertEquals("twilio", twilioSmsClient.getType());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({
|
||||
"accountSid, authToken, twilioPhoneNumber, true",
|
||||
"accountSid, authToken, '', false",
|
||||
"accountSid, '', twilioPhoneNumber, false",
|
||||
"accountSid, '', '', false",
|
||||
"'', authToken, twilioPhoneNumber, false",
|
||||
"'', authToken, '', false",
|
||||
"'', '', twilioPhoneNumber, false",
|
||||
"'', '', '', false",
|
||||
})
|
||||
void checkConfig(String accountSid, String authToken, String twilioPhoneNumber, boolean expected) {
|
||||
TwilioSmsProperties twilioSmsProperties = new TwilioSmsProperties();
|
||||
twilioSmsProperties.setAccountSid(accountSid);
|
||||
twilioSmsProperties.setAuthToken(authToken);
|
||||
twilioSmsProperties.setTwilioPhoneNumber(twilioPhoneNumber);
|
||||
TwilioSmsClientImpl twilioSmsClient = new TwilioSmsClientImpl(twilioSmsProperties);
|
||||
|
||||
assertEquals(expected, twilioSmsClient.checkConfig());
|
||||
}
|
||||
}
|
||||
@@ -155,6 +155,11 @@
|
||||
<artifactId>plc4j-driver-modbus</artifactId>
|
||||
<version>0.12.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.plc4x</groupId>
|
||||
<artifactId>plc4j-driver-s7</artifactId>
|
||||
<version>0.12.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.sshd</groupId>
|
||||
<artifactId>sshd-sftp</artifactId>
|
||||
|
||||
+302
-55
@@ -18,11 +18,14 @@
|
||||
package org.apache.hertzbeat.collector.collect.http;
|
||||
|
||||
import static org.apache.hertzbeat.common.constants.SignConstants.RIGHT_DASH;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.StringReader;
|
||||
import java.net.ConnectException;
|
||||
@@ -34,19 +37,22 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath.XPathExpressionException;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.collector.collect.AbstractCollect;
|
||||
import org.apache.hertzbeat.collector.collect.common.http.CommonHttpClient;
|
||||
import org.apache.hertzbeat.collector.collect.http.promethus.AbstractPrometheusParse;
|
||||
import org.apache.hertzbeat.collector.collect.http.promethus.PrometheusParseCreator;
|
||||
import org.apache.hertzbeat.collector.collect.http.promethus.exporter.ExporterParser;
|
||||
import org.apache.hertzbeat.collector.collect.http.promethus.exporter.MetricFamily;
|
||||
import org.apache.hertzbeat.collector.collect.prometheus.parser.MetricFamily;
|
||||
import org.apache.hertzbeat.collector.collect.prometheus.parser.OnlineParser;
|
||||
import org.apache.hertzbeat.collector.constants.CollectorConstants;
|
||||
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
|
||||
import org.apache.hertzbeat.collector.util.CollectUtil;
|
||||
@@ -63,11 +69,9 @@ import org.apache.hertzbeat.common.util.CommonUtil;
|
||||
import org.apache.hertzbeat.common.util.IpDomainUtil;
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpHeaders;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.auth.AuthScope;
|
||||
import org.apache.http.auth.UsernamePasswordCredentials;
|
||||
import org.apache.http.client.AuthCache;
|
||||
import org.apache.http.client.ClientProtocolException;
|
||||
import org.apache.http.client.CredentialsProvider;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
@@ -77,8 +81,6 @@ import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.client.methods.RequestBuilder;
|
||||
import org.apache.http.client.protocol.HttpClientContext;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.auth.DigestScheme;
|
||||
import org.apache.http.impl.client.BasicAuthCache;
|
||||
import org.apache.http.impl.client.BasicCredentialsProvider;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
@@ -86,20 +88,30 @@ import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import java.util.Properties;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.HashSet;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* http https collect
|
||||
*/
|
||||
@Slf4j
|
||||
public class HttpCollectImpl extends AbstractCollect {
|
||||
private static final Map<Long, ExporterParser> EXPORTER_PARSER_TABLE = new ConcurrentHashMap<>();
|
||||
private final Set<Integer> defaultSuccessStatusCodes = Stream.of(HttpStatus.SC_OK, HttpStatus.SC_CREATED,
|
||||
HttpStatus.SC_ACCEPTED, HttpStatus.SC_MULTIPLE_CHOICES, HttpStatus.SC_MOVED_PERMANENTLY,
|
||||
HttpStatus.SC_MOVED_TEMPORARILY).collect(Collectors.toSet());
|
||||
private final Set<Integer> defaultSuccessStatusCodes = Set.of(
|
||||
HttpStatus.SC_OK,
|
||||
HttpStatus.SC_CREATED,
|
||||
HttpStatus.SC_ACCEPTED,
|
||||
HttpStatus.SC_MULTIPLE_CHOICES,
|
||||
HttpStatus.SC_MOVED_PERMANENTLY,
|
||||
HttpStatus.SC_MOVED_TEMPORARILY);
|
||||
|
||||
@Override
|
||||
public void preCheck(Metrics metrics) throws IllegalArgumentException {
|
||||
@@ -132,10 +144,11 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
builder.setMsg(NetworkConstants.STATUS_CODE + SignConstants.BLANK + statusCode);
|
||||
return;
|
||||
}
|
||||
// todo This code converts an InputStream directly to a String. For large data in Prometheus exporters,
|
||||
// this could create large objects, potentially impacting JVM memory space significantly.
|
||||
// Option 1: Parse using InputStream, but this requires significant code changes;
|
||||
// Option 2: Manually trigger garbage collection, similar to how it's done in Dubbo for large inputs.
|
||||
/*
|
||||
this could create large objects, potentially impacting JVM memory space significantly.
|
||||
Option 1: Parse using InputStream, but this requires significant code changes;
|
||||
Option 2: Manually trigger garbage collection, similar to how it's done in Dubbo for large inputs.
|
||||
*/
|
||||
String resp = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
|
||||
if (!StringUtils.hasText(resp)) {
|
||||
log.info("http response entity is empty, status: {}.", statusCode);
|
||||
@@ -149,15 +162,17 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
case DispatchConstants.PARSE_PROM_QL ->
|
||||
parseResponseByPromQl(resp, metrics.getAliasFields(), metrics.getHttp(), builder);
|
||||
case DispatchConstants.PARSE_PROMETHEUS ->
|
||||
parseResponseByPrometheusExporter(resp, metrics.getAliasFields(), builder);
|
||||
parseResponseByPrometheusExporter(response.getEntity().getContent(), metrics.getAliasFields(), builder);
|
||||
case DispatchConstants.PARSE_XML_PATH ->
|
||||
parseResponseByXmlPath(resp, metrics.getAliasFields(), metrics.getHttp(), builder);
|
||||
parseResponseByXmlPath(resp, metrics, builder, responseTime);
|
||||
case DispatchConstants.PARSE_WEBSITE ->
|
||||
parseResponseByWebsite(resp, metrics, metrics.getHttp(), builder, responseTime);
|
||||
case DispatchConstants.PARSE_SITE_MAP ->
|
||||
parseResponseBySiteMap(resp, metrics.getAliasFields(), builder);
|
||||
case DispatchConstants.PARSE_HEADER ->
|
||||
parseResponseByHeader(builder, metrics.getAliasFields(), response);
|
||||
case DispatchConstants.PARSE_CONFIG ->
|
||||
parseResponseByConfig(resp, metrics.getAliasFields(), metrics.getHttp(), builder, responseTime);
|
||||
default ->
|
||||
parseResponseByDefault(resp, metrics.getAliasFields(), metrics.getHttp(), builder, responseTime);
|
||||
}
|
||||
@@ -328,8 +343,207 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
}
|
||||
}
|
||||
|
||||
private void parseResponseByXmlPath(String resp, List<String> aliasFields, HttpProtocol http,
|
||||
CollectRep.MetricsData.Builder builder) {
|
||||
private void parseResponseByXmlPath(String resp, Metrics metrics,
|
||||
CollectRep.MetricsData.Builder builder, Long responseTime) {
|
||||
HttpProtocol http = metrics.getHttp();
|
||||
List<String> aliasFields = metrics.getAliasFields();
|
||||
String xpathExpression = http.getParseScript();
|
||||
if (!StringUtils.hasText(xpathExpression)) {
|
||||
log.warn("Http collect parse type is xmlPath, but the xpath expression is empty.");
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg("XPath expression is empty");
|
||||
return;
|
||||
}
|
||||
int keywordNum = CollectUtil.countMatchKeyword(resp, http.getKeyword());
|
||||
|
||||
try {
|
||||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
||||
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
|
||||
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
|
||||
dbf.setXIncludeAware(false);
|
||||
dbf.setExpandEntityReferences(false);
|
||||
|
||||
DocumentBuilder db = dbf.newDocumentBuilder();
|
||||
Document document = db.parse(new InputSource(new StringReader(resp)));
|
||||
|
||||
XPathFactory xpathFactory = XPathFactory.newInstance();
|
||||
XPath xpath = xpathFactory.newXPath();
|
||||
|
||||
NodeList nodeList = (NodeList) xpath.evaluate(xpathExpression, document, XPathConstants.NODESET);
|
||||
|
||||
if (nodeList == null || nodeList.getLength() == 0) {
|
||||
log.debug("XPath expression '{}' returned no nodes.", xpathExpression);
|
||||
boolean requestedSummaryFields = aliasFields.stream()
|
||||
.anyMatch(alias -> NetworkConstants.RESPONSE_TIME.equalsIgnoreCase(alias)
|
||||
|| CollectorConstants.KEYWORD.equalsIgnoreCase(alias));
|
||||
|
||||
if (requestedSummaryFields) {
|
||||
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
|
||||
for (String alias : aliasFields) {
|
||||
if (NetworkConstants.RESPONSE_TIME.equalsIgnoreCase(alias)) {
|
||||
valueRowBuilder.addColumn(responseTime.toString());
|
||||
} else if (CollectorConstants.KEYWORD.equalsIgnoreCase(alias)) {
|
||||
valueRowBuilder.addColumn(Integer.toString(keywordNum));
|
||||
} else {
|
||||
valueRowBuilder.addColumn(CommonConstants.NULL_VALUE);
|
||||
}
|
||||
}
|
||||
builder.addValueRow(valueRowBuilder.build());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < nodeList.getLength(); i++) {
|
||||
Node node = nodeList.item(i);
|
||||
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
|
||||
|
||||
for (String alias : aliasFields) {
|
||||
if (NetworkConstants.RESPONSE_TIME.equalsIgnoreCase(alias)) {
|
||||
valueRowBuilder.addColumn(responseTime.toString());
|
||||
} else if (CollectorConstants.KEYWORD.equalsIgnoreCase(alias)) {
|
||||
valueRowBuilder.addColumn(Integer.toString(keywordNum));
|
||||
} else {
|
||||
try {
|
||||
String value = (String) xpath.evaluate(alias, node, XPathConstants.STRING);
|
||||
valueRowBuilder.addColumn(StringUtils.hasText(value) ? value : CommonConstants.NULL_VALUE);
|
||||
} catch (XPathExpressionException e) {
|
||||
log.warn("Failed to evaluate XPath '{}' for node [{}]: {}", alias, node.getNodeName(), e.getMessage());
|
||||
valueRowBuilder.addColumn(CommonConstants.NULL_VALUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
builder.addValueRow(valueRowBuilder.build());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to parse XML response with XPath '{}': {}", xpathExpression, e.getMessage(), e);
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg("Failed to parse XML response: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Parses the response body in Properties/Config format.
|
||||
* Two modes are supported:
|
||||
* 1. single-object mode: if http.parseScript is null, aliasFields are treated as indicator names.
|
||||
* - If there is a locator in the indicator definition, use the locator as the key of the Properties.
|
||||
* - Otherwise, use aliasField (metric name) as the key for Properties.
|
||||
* Generate a single row of data.
|
||||
* 2. array mode: if http.parseScript is not empty (e.g. “users”), treat it as an array base path.
|
||||
* Treat aliasFields as the attribute name of an array element, and generate a single row of data for each array index. locator is invalid in this mode.
|
||||
*
|
||||
* @param resp Response body string
|
||||
* @param aliasFields List of metrics aliases (i.e., the list of fields in metrics.fields).
|
||||
* @param http http protocol configuration
|
||||
* @param builder The metrics data builder.
|
||||
* @param responseTime response time
|
||||
*/
|
||||
private void parseResponseByConfig(String resp, List<String> aliasFields, HttpProtocol http,
|
||||
CollectRep.MetricsData.Builder builder, Long responseTime) {
|
||||
if (!StringUtils.hasText(resp)) {
|
||||
log.warn("Http collect parse type is config, but response body is empty.");
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg("Response body is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
Properties properties = new Properties();
|
||||
try (StringReader reader = new StringReader(resp)) {
|
||||
properties.load(reader);
|
||||
} catch (IOException e) {
|
||||
log.warn("Failed to parse config response: {}", e.getMessage(), e);
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg("Failed to parse config response: " + e.getMessage());
|
||||
return;
|
||||
}
|
||||
String arrayBasePath = http.getParseScript();
|
||||
int keywordNum = CollectUtil.countMatchKeyword(resp, http.getKeyword());
|
||||
|
||||
if (!StringUtils.hasText(arrayBasePath)) {
|
||||
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
|
||||
for (String alias : aliasFields) {
|
||||
if (NetworkConstants.RESPONSE_TIME.equalsIgnoreCase(alias)) {
|
||||
valueRowBuilder.addColumn(responseTime.toString());
|
||||
} else if (CollectorConstants.KEYWORD.equalsIgnoreCase(alias)) {
|
||||
valueRowBuilder.addColumn(Integer.toString(keywordNum));
|
||||
} else {
|
||||
String value = properties.getProperty(alias);
|
||||
valueRowBuilder.addColumn(value != null ? value : CommonConstants.NULL_VALUE);
|
||||
}
|
||||
}
|
||||
CollectRep.ValueRow valueRow = valueRowBuilder.build();
|
||||
if (hasMeaningfulDataInRow(valueRow, aliasFields)) {
|
||||
builder.addValueRow(valueRow);
|
||||
} else {
|
||||
log.warn("No meaningful data found in single config object response for aliasFields: {}", aliasFields);
|
||||
}
|
||||
} else {
|
||||
Pattern pattern = Pattern.compile("^" + Pattern.quote(arrayBasePath) + "\\[(\\d+)]\\.");
|
||||
Set<Integer> existingIndices = new HashSet<>();
|
||||
for (String key : properties.stringPropertyNames()) {
|
||||
Matcher matcher = pattern.matcher(key);
|
||||
if (matcher.find()) {
|
||||
try {
|
||||
int index = Integer.parseInt(matcher.group(1));
|
||||
existingIndices.add(index);
|
||||
} catch (NumberFormatException e) {
|
||||
log.error("Could not parse index from key: {}", key);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (existingIndices.isEmpty()) {
|
||||
log.warn("Could not find any array elements for base path '{}' in config response.", arrayBasePath);
|
||||
return;
|
||||
}
|
||||
List<Integer> sortedIndices = new ArrayList<>(existingIndices);
|
||||
Collections.sort(sortedIndices);
|
||||
for (int i : sortedIndices) {
|
||||
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
|
||||
for (String alias : aliasFields) {
|
||||
if (NetworkConstants.RESPONSE_TIME.equalsIgnoreCase(alias)) {
|
||||
valueRowBuilder.addColumn(responseTime.toString());
|
||||
} else if (CollectorConstants.KEYWORD.equalsIgnoreCase(alias)) {
|
||||
valueRowBuilder.addColumn(Integer.toString(keywordNum));
|
||||
} else {
|
||||
String currentKey = arrayBasePath + "[" + i + "]." + alias;
|
||||
String value = properties.getProperty(currentKey);
|
||||
valueRowBuilder.addColumn(value != null ? value : CommonConstants.NULL_VALUE);
|
||||
}
|
||||
}
|
||||
CollectRep.ValueRow valueRow = valueRowBuilder.build();
|
||||
if (hasMeaningfulDataInRow(valueRow, aliasFields)) {
|
||||
builder.addValueRow(valueRowBuilder.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasMeaningfulDataInRow(CollectRep.ValueRow valueRow, List<String> aliasFields) {
|
||||
if (valueRow.getColumnsCount() == 0) {
|
||||
return false;
|
||||
}
|
||||
if (valueRow.getColumnsCount() != aliasFields.size()) {
|
||||
log.error("Column count ({}) mismatch with aliasFields size ({}) when checking meaningful data.",
|
||||
valueRow.getColumnsCount(), aliasFields.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean hasMeaningfulData = false;
|
||||
for (int i = 0; i < valueRow.getColumnsCount(); i++) {
|
||||
String columnValue = valueRow.getColumns(i);
|
||||
String alias = aliasFields.get(i);
|
||||
if (!CommonConstants.NULL_VALUE.equals(columnValue) && (!NetworkConstants.RESPONSE_TIME.equalsIgnoreCase(alias) && !CollectorConstants.KEYWORD.equalsIgnoreCase(alias))) {
|
||||
hasMeaningfulData = true;
|
||||
break;
|
||||
}
|
||||
if ((NetworkConstants.RESPONSE_TIME.equalsIgnoreCase(alias) || CollectorConstants.KEYWORD.equalsIgnoreCase(alias)) && !CommonConstants.NULL_VALUE.equals(columnValue)) {
|
||||
hasMeaningfulData = true;
|
||||
}
|
||||
}
|
||||
return hasMeaningfulData;
|
||||
}
|
||||
|
||||
private void parseResponseByJsonPath(String resp, List<String> aliasFields, HttpProtocol http,
|
||||
@@ -386,36 +600,22 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
prometheusParser.handle(resp, aliasFields, http, builder);
|
||||
}
|
||||
|
||||
private void parseResponseByPrometheusExporter(String resp, List<String> aliasFields,
|
||||
CollectRep.MetricsData.Builder builder) {
|
||||
if (!EXPORTER_PARSER_TABLE.containsKey(builder.getId())) {
|
||||
EXPORTER_PARSER_TABLE.put(builder.getId(), new ExporterParser());
|
||||
private void parseResponseByPrometheusExporter(InputStream content, List<String> aliasFields, CollectRep.MetricsData.Builder builder) throws IOException {
|
||||
Map<String, MetricFamily> metricFamilyMap = OnlineParser.parseMetrics(content);
|
||||
if (metricFamilyMap == null || metricFamilyMap.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
ExporterParser parser = EXPORTER_PARSER_TABLE.get(builder.getId());
|
||||
Map<String, MetricFamily> metricFamilyMap = parser.textToMetric(resp);
|
||||
String metrics = builder.getMetrics();
|
||||
if (metricFamilyMap.containsKey(metrics)) {
|
||||
MetricFamily metricFamily = metricFamilyMap.get(metrics);
|
||||
for (MetricFamily.Metric metric : metricFamily.getMetricList()) {
|
||||
Map<String, String> labelMap = metric.getLabelPair()
|
||||
Map<String, String> labelMap = metric.getLabels()
|
||||
.stream()
|
||||
.collect(Collectors.toMap(MetricFamily.Label::getName, MetricFamily.Label::getValue));
|
||||
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
|
||||
for (String aliasField : aliasFields) {
|
||||
if ("value".equals(aliasField)) {
|
||||
if (metric.getCounter() != null) {
|
||||
valueRowBuilder.addColumn(String.valueOf(metric.getCounter().getValue()));
|
||||
} else if (metric.getGauge() != null) {
|
||||
valueRowBuilder.addColumn(String.valueOf(metric.getGauge().getValue()));
|
||||
} else if (metric.getUntyped() != null) {
|
||||
valueRowBuilder.addColumn(String.valueOf(metric.getUntyped().getValue()));
|
||||
} else if (metric.getInfo() != null) {
|
||||
valueRowBuilder.addColumn(String.valueOf(metric.getInfo().getValue()));
|
||||
} else if (metric.getSummary() != null) {
|
||||
valueRowBuilder.addColumn(String.valueOf(metric.getSummary().getValue()));
|
||||
} else if (metric.getHistogram() != null) {
|
||||
valueRowBuilder.addColumn(String.valueOf(metric.getHistogram().getValue()));
|
||||
}
|
||||
valueRowBuilder.addColumn(String.valueOf(metric.getValue()));
|
||||
} else {
|
||||
String columnValue = labelMap.get(aliasField);
|
||||
valueRowBuilder.addColumn(columnValue == null ? CommonConstants.NULL_VALUE : columnValue);
|
||||
@@ -472,11 +672,10 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
CredentialsProvider provider = new BasicCredentialsProvider();
|
||||
UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(auth.getDigestAuthUsername(),
|
||||
auth.getDigestAuthPassword());
|
||||
provider.setCredentials(AuthScope.ANY, credentials);
|
||||
AuthCache authCache = new BasicAuthCache();
|
||||
authCache.put(new HttpHost(httpProtocol.getHost(), Integer.parseInt(httpProtocol.getPort())), new DigestScheme());
|
||||
AuthScope authScope = new AuthScope(httpProtocol.getHost(), Integer.parseInt(httpProtocol.getPort()));
|
||||
provider.setCredentials(authScope, credentials);
|
||||
|
||||
clientContext.setCredentialsProvider(provider);
|
||||
clientContext.setAuthCache(authCache);
|
||||
return clientContext;
|
||||
}
|
||||
}
|
||||
@@ -509,13 +708,37 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
}
|
||||
// params
|
||||
Map<String, String> params = httpProtocol.getParams();
|
||||
boolean enableUrlEncoding = Boolean.parseBoolean(httpProtocol.getEnableUrlEncoding());
|
||||
StringBuilder queryParams = new StringBuilder();
|
||||
|
||||
if (params != null && !params.isEmpty()) {
|
||||
for (Map.Entry<String, String> param : params.entrySet()) {
|
||||
if (StringUtils.hasText(param.getValue())) {
|
||||
requestBuilder.addParameter(param.getKey(), TimeExpressionUtil.calculate(param.getValue()));
|
||||
String key = param.getKey();
|
||||
String value = param.getValue();
|
||||
|
||||
if (!StringUtils.hasText(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!queryParams.isEmpty()) {
|
||||
queryParams.append("&");
|
||||
}
|
||||
|
||||
if (enableUrlEncoding) {
|
||||
key = UriUtils.encodeQueryParam(key, "UTF-8");
|
||||
}
|
||||
queryParams.append(key);
|
||||
|
||||
if (StringUtils.hasText(value)) {
|
||||
String calculatedValue = TimeExpressionUtil.calculate(value);
|
||||
if (enableUrlEncoding) {
|
||||
calculatedValue = UriUtils.encodeQueryParam(calculatedValue, "UTF-8");
|
||||
}
|
||||
queryParams.append("=").append(calculatedValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The default request header can be overridden if customized
|
||||
// keep-alive
|
||||
requestBuilder.addHeader(HttpHeaders.CONNECTION, NetworkConstants.KEEP_ALIVE);
|
||||
@@ -525,8 +748,7 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
if (headers != null && !headers.isEmpty()) {
|
||||
for (Map.Entry<String, String> header : headers.entrySet()) {
|
||||
if (StringUtils.hasText(header.getValue())) {
|
||||
requestBuilder.addHeader(CollectUtil.replaceUriSpecialChar(header.getKey()),
|
||||
CollectUtil.replaceUriSpecialChar(header.getValue()));
|
||||
requestBuilder.addHeader(header.getKey(), header.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -557,14 +779,32 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
|
||||
// if it has payload, would override post params
|
||||
if (StringUtils.hasLength(httpProtocol.getPayload()) && (HttpMethod.POST.matches(httpMethod) || HttpMethod.PUT.matches(httpMethod))) {
|
||||
requestBuilder.setEntity(new StringEntity(httpProtocol.getPayload(), StandardCharsets.UTF_8));
|
||||
requestBuilder.setEntity(new StringEntity(TimeExpressionUtil.calculate(httpProtocol.getPayload()), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
// uri
|
||||
String uri = CollectUtil.replaceUriSpecialChar(httpProtocol.getUrl());
|
||||
if (IpDomainUtil.isHasSchema(httpProtocol.getHost())) {
|
||||
// uri encode
|
||||
String uri;
|
||||
if (enableUrlEncoding) {
|
||||
// if the url contains parameters directly
|
||||
if (httpProtocol.getUrl().contains("?")) {
|
||||
String path = httpProtocol.getUrl().substring(0, httpProtocol.getUrl().indexOf("?"));
|
||||
String query = httpProtocol.getUrl().substring(httpProtocol.getUrl().indexOf("?") + 1);
|
||||
uri = UriUtils.encodePath(path, "UTF-8") + "?" + UriUtils.encodeQuery(query, "UTF-8");
|
||||
} else {
|
||||
uri = UriUtils.encodePath(httpProtocol.getUrl(), "UTF-8");
|
||||
}
|
||||
} else {
|
||||
uri = httpProtocol.getUrl();
|
||||
}
|
||||
|
||||
requestBuilder.setUri(httpProtocol.getHost() + ":" + httpProtocol.getPort() + uri);
|
||||
// append query params
|
||||
if (!queryParams.isEmpty()) {
|
||||
uri += (uri.contains("?") ? "&" : "?") + queryParams;
|
||||
}
|
||||
|
||||
String finalUri;
|
||||
if (IpDomainUtil.isHasSchema(httpProtocol.getHost())) {
|
||||
finalUri = httpProtocol.getHost() + ":" + httpProtocol.getPort() + uri;
|
||||
} else {
|
||||
String ipAddressType = IpDomainUtil.checkIpAddressType(httpProtocol.getHost());
|
||||
String baseUri = NetworkConstants.IPV6.equals(ipAddressType)
|
||||
@@ -572,12 +812,19 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
: String.format("%s:%s%s", httpProtocol.getHost(), httpProtocol.getPort(), uri);
|
||||
boolean ssl = Boolean.parseBoolean(httpProtocol.getSsl());
|
||||
if (ssl) {
|
||||
requestBuilder.setUri(NetworkConstants.HTTPS_HEADER + baseUri);
|
||||
finalUri = NetworkConstants.HTTPS_HEADER + baseUri;
|
||||
} else {
|
||||
requestBuilder.setUri(NetworkConstants.HTTP_HEADER + baseUri);
|
||||
finalUri = NetworkConstants.HTTP_HEADER + baseUri;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
requestBuilder.setUri(finalUri);
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("Invalid URI with illegal characters: {}. User has disabled URL encoding, not applying any encoding.", finalUri);
|
||||
throw e;
|
||||
}
|
||||
|
||||
// custom timeout
|
||||
int timeout = CollectUtil.getTimeout(httpProtocol.getTimeout(), 0);
|
||||
if (timeout > 0) {
|
||||
|
||||
-432
@@ -1,432 +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.http.promethus.exporter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.collector.collect.http.promethus.ParseException;
|
||||
import org.apache.hertzbeat.common.util.StrBuffer;
|
||||
|
||||
/**
|
||||
* Resolves the data passed by prometheus's exporter interface http:xxx/metrics
|
||||
* Reference: prometheus text_parse.go code, entry: TextToMetricFamilies
|
||||
*/
|
||||
@Slf4j
|
||||
public class ExporterParser {
|
||||
private static final String HELP = "HELP";
|
||||
private static final String TYPE = "TYPE";
|
||||
private static final String EOF = "EOF";
|
||||
private static final String METRIC_NAME_LABEL = ".name";
|
||||
private static final String QUANTILE_LABEL = "quantile";
|
||||
private static final String BUCKET_LABEL = "le";
|
||||
private static final String NAME_LABEL = "__name__";
|
||||
private static final String SUM_SUFFIX = "_sum";
|
||||
private static final String COUNT_SUFFIX = "_count";
|
||||
|
||||
private static final char LEFT_CURLY_BRACKET = '{';
|
||||
private static final char RIGHT_CURLY_BRACKET = '}';
|
||||
private static final char EQUALS = '=';
|
||||
private static final char QUOTES = '"';
|
||||
private static final char ENTER = '\n';
|
||||
private static final char SPACE = ' ';
|
||||
private static final char COMMA = ',';
|
||||
private final Lock lock = new ReentrantLock();
|
||||
private MetricFamily currentMetricFamily;
|
||||
private String currentQuantile;
|
||||
private String currentBucket;
|
||||
|
||||
public Map<String, MetricFamily> textToMetric(String resp) {
|
||||
// key: metric name, value: metric family
|
||||
Map<String, MetricFamily> metricMap = new ConcurrentHashMap<>(10);
|
||||
lock.lock();
|
||||
try {
|
||||
String[] lines = resp.split("\n");
|
||||
for (String line : lines) {
|
||||
this.parseLine(metricMap, new StrBuffer(line));
|
||||
}
|
||||
return metricMap;
|
||||
} catch (Exception e) {
|
||||
log.error("parse prometheus exporter data error, msg: {}", e.getMessage());
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
return metricMap;
|
||||
}
|
||||
|
||||
private void parseLine(Map<String, MetricFamily> metricMap, StrBuffer buffer) {
|
||||
buffer.skipBlankTabs();
|
||||
if (buffer.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
switch (buffer.charAt(0)) {
|
||||
case '#' -> {
|
||||
buffer.read();
|
||||
this.currentMetricFamily = null;
|
||||
this.parseComment(metricMap, buffer);
|
||||
}
|
||||
case ENTER -> {
|
||||
}
|
||||
default -> {
|
||||
this.currentBucket = null;
|
||||
this.currentQuantile = null;
|
||||
this.parseMetric(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseComment(Map<String, MetricFamily> metricMap, StrBuffer buffer) {
|
||||
buffer.skipBlankTabs();
|
||||
if (buffer.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
String token = this.readTokenUnitWhitespace(buffer);
|
||||
if (EOF.equals(token)) {
|
||||
return;
|
||||
}
|
||||
if (!HELP.equals(token) && !TYPE.equals(token)) {
|
||||
log.error("parse comment error {}, start without {} or {}", buffer.toStr(), HELP, TYPE);
|
||||
return;
|
||||
}
|
||||
String metricName = this.readTokenAsMetricName(buffer);
|
||||
this.currentMetricFamily = metricMap.computeIfAbsent(metricName, key -> new MetricFamily());
|
||||
this.currentMetricFamily.setName(metricName);
|
||||
switch (token) {
|
||||
case HELP -> this.parseHelp(buffer);
|
||||
case TYPE -> this.parseType(buffer);
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseHelp(StrBuffer line) {
|
||||
line.skipBlankTabs();
|
||||
this.currentMetricFamily.setHelp(line.toStr());
|
||||
}
|
||||
|
||||
private void parseType(StrBuffer line) {
|
||||
line.skipBlankTabs();
|
||||
String type = line.toStr().toLowerCase();
|
||||
MetricType metricType = MetricType.getType(type);
|
||||
if (metricType == null) {
|
||||
throw new ParseException("pare type error");
|
||||
}
|
||||
this.currentMetricFamily.setMetricType(metricType);
|
||||
}
|
||||
|
||||
private void parseMetric(StrBuffer buffer) {
|
||||
String metricName = this.readTokenAsMetricName(buffer);
|
||||
MetricFamily.Label label = new MetricFamily.Label();
|
||||
label.setName(METRIC_NAME_LABEL);
|
||||
label.setValue(metricName);
|
||||
|
||||
if (metricName.isEmpty()) {
|
||||
log.error("error parse metric, metric name is null, line: {}", buffer.toStr());
|
||||
return;
|
||||
}
|
||||
|
||||
List<MetricFamily.Metric> metricList = this.currentMetricFamily.getMetricList();
|
||||
if (metricList == null) {
|
||||
metricList = new ArrayList<>();
|
||||
this.currentMetricFamily.setMetricList(metricList);
|
||||
}
|
||||
|
||||
// TODO For the time being, the data is displayed in the form of labels. If there is a better chart display method in the future, we will optimize it.
|
||||
MetricFamily.Metric metric = new MetricFamily.Metric();
|
||||
metricList.add(metric);
|
||||
|
||||
metric.setLabelPair(new ArrayList<>());
|
||||
metric.getLabelPair().add(label);
|
||||
this.readLabels(metric, buffer);
|
||||
}
|
||||
|
||||
private void readLabels(MetricFamily.Metric metric, StrBuffer buffer) {
|
||||
buffer.skipBlankTabs();
|
||||
if (buffer.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (buffer.charAt(0) == LEFT_CURLY_BRACKET) {
|
||||
buffer.read();
|
||||
this.startReadLabelName(metric, buffer);
|
||||
} else {
|
||||
this.readLabelValue(metric, null, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
private void startReadLabelName(MetricFamily.Metric metric, StrBuffer buffer) {
|
||||
buffer.skipBlankTabs();
|
||||
if (buffer.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (buffer.charAt(0) == RIGHT_CURLY_BRACKET) {
|
||||
buffer.read();
|
||||
buffer.skipBlankTabs();
|
||||
if (buffer.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
this.readLabelValue(metric, new MetricFamily.Label(), buffer);
|
||||
return;
|
||||
}
|
||||
String labelName = this.readTokenAsLabelName(buffer);
|
||||
if (labelName.isEmpty() || labelName.equals(NAME_LABEL)) {
|
||||
throw new ParseException("invalid label name" + labelName + ", label name size = 0 or label name equals " + NAME_LABEL);
|
||||
}
|
||||
MetricFamily.Label label = new MetricFamily.Label();
|
||||
label.setName(labelName);
|
||||
if (buffer.read() != EQUALS) {
|
||||
throw new ParseException("parse error, not match the format of labelName=labelValue");
|
||||
}
|
||||
this.startReadLabelValue(metric, label, buffer);
|
||||
}
|
||||
|
||||
private void startReadLabelValue(MetricFamily.Metric metric, MetricFamily.Label label, StrBuffer buffer) {
|
||||
buffer.skipBlankTabs();
|
||||
if (buffer.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
char c = buffer.read();
|
||||
if (c != QUOTES) {
|
||||
throw new ParseException("expected '\"' at start of label value, line: " + buffer.toStr());
|
||||
}
|
||||
String labelValue = this.readTokenAsLabelValue(buffer);
|
||||
label.setValue(labelValue);
|
||||
if (!this.isValidLabelValue(labelValue)) {
|
||||
throw new ParseException("no valid label value: " + labelValue);
|
||||
}
|
||||
if (this.currentMetricFamily.getMetricType().equals(MetricType.SUMMARY) && label.getName().equals(QUANTILE_LABEL)) {
|
||||
this.currentQuantile = labelValue;
|
||||
} else if (this.currentMetricFamily.getMetricType().equals(MetricType.HISTOGRAM) && label.getName().equals(BUCKET_LABEL)) {
|
||||
this.currentBucket = labelValue;
|
||||
}
|
||||
metric.getLabelPair().add(label);
|
||||
|
||||
if (buffer.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
c = buffer.read();
|
||||
switch (c) {
|
||||
case COMMA -> this.startReadLabelName(metric, buffer);
|
||||
case RIGHT_CURLY_BRACKET -> this.readLabelValue(metric, label, buffer);
|
||||
default -> throw new ParseException("expected '}' or ',' at end of label value, line: " + buffer.toStr());
|
||||
}
|
||||
}
|
||||
|
||||
private void readLabelValue(MetricFamily.Metric metric, MetricFamily.Label label, StrBuffer buffer) {
|
||||
buffer.skipBlankTabs();
|
||||
if (buffer.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
switch (this.currentMetricFamily.getMetricType()) {
|
||||
case INFO -> {
|
||||
MetricFamily.Info info = new MetricFamily.Info();
|
||||
info.setValue(buffer.toDouble());
|
||||
metric.setInfo(info);
|
||||
}
|
||||
case COUNTER -> {
|
||||
MetricFamily.Counter counter = new MetricFamily.Counter();
|
||||
counter.setValue(buffer.toDouble());
|
||||
metric.setCounter(counter);
|
||||
}
|
||||
case GAUGE -> {
|
||||
MetricFamily.Gauge gauge = new MetricFamily.Gauge();
|
||||
gauge.setValue(buffer.toDouble());
|
||||
metric.setGauge(gauge);
|
||||
}
|
||||
case UNTYPED -> {
|
||||
MetricFamily.Untyped untyped = new MetricFamily.Untyped();
|
||||
untyped.setValue(buffer.toDouble());
|
||||
metric.setUntyped(untyped);
|
||||
}
|
||||
case SUMMARY -> {
|
||||
// For the time being, the data is displayed in the form of labels. If there is a better chart display method in the future, we will optimize it.
|
||||
MetricFamily.Summary summary = new MetricFamily.Summary();
|
||||
summary.setValue(buffer.toDouble());
|
||||
metric.setSummary(summary);
|
||||
}
|
||||
case HISTOGRAM -> {
|
||||
// For the time being, the data is displayed in the form of labels. If there is a better chart display method in the future, we will optimize it.
|
||||
MetricFamily.Histogram histogram = new MetricFamily.Histogram();
|
||||
histogram.setValue(buffer.toDouble());
|
||||
metric.setHistogram(histogram);
|
||||
}
|
||||
default -> throw new ParseException("no such type in metricFamily");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the token before the first whitespace
|
||||
*
|
||||
* @param buffer A line data object
|
||||
* @return token unit
|
||||
*/
|
||||
private String readTokenUnitWhitespace(StrBuffer buffer) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
while (!buffer.isEmpty()) {
|
||||
char c = buffer.read();
|
||||
if (c == SPACE) {
|
||||
break;
|
||||
}
|
||||
builder.append(c);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the metric
|
||||
*
|
||||
* @param buffer A line data object
|
||||
* @return token name
|
||||
*/
|
||||
private String readTokenAsMetricName(StrBuffer buffer) {
|
||||
buffer.skipBlankTabs();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (this.isValidMetricNameStart(buffer.charAt(0))) {
|
||||
while (!buffer.isEmpty()) {
|
||||
char c = buffer.read();
|
||||
if (!this.isValidMetricNameContinuation(c)) {
|
||||
buffer.rollback();
|
||||
break;
|
||||
}
|
||||
builder.append(c);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
throw new ParseException("parse metric name error");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the label
|
||||
*
|
||||
* @param buffer A line data object
|
||||
* @return label name
|
||||
*/
|
||||
private String readTokenAsLabelName(StrBuffer buffer) {
|
||||
buffer.skipBlankTabs();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
char c = buffer.read();
|
||||
if (this.isValidLabelNameStart(c)) {
|
||||
builder.append(c);
|
||||
while (!buffer.isEmpty()) {
|
||||
c = buffer.read();
|
||||
if (!this.isValidLabelNameContinuation(c)) {
|
||||
buffer.rollback();
|
||||
break;
|
||||
}
|
||||
builder.append(c);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
throw new ParseException("parse label name error");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the label
|
||||
*
|
||||
* @param buffer A line data object
|
||||
* @return label value
|
||||
*/
|
||||
private String readTokenAsLabelValue(StrBuffer buffer) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
boolean escaped = false;
|
||||
while (!buffer.isEmpty()) {
|
||||
char c = buffer.read();
|
||||
// Handle '\\' escape sequences
|
||||
if (escaped) {
|
||||
switch (c) {
|
||||
case QUOTES, '\\' -> builder.append(c);
|
||||
case 'n' -> builder.append('\n');
|
||||
default -> throw new ParseException("parse label value error");
|
||||
}
|
||||
escaped = false;
|
||||
} else {
|
||||
switch (c) {
|
||||
case QUOTES -> {
|
||||
return builder.toString();
|
||||
}
|
||||
case ENTER -> throw new ParseException("parse label value error, next line");
|
||||
case '\\' -> escaped = true;
|
||||
default -> builder.append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a character conforms to the first character rule for metric names
|
||||
*
|
||||
* @param c metric character
|
||||
* @return true/false
|
||||
*/
|
||||
private boolean isValidMetricNameStart(char c) {
|
||||
return isValidLabelNameStart(c) || c == ':';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a character conforms to rules for metric name characters other than the first
|
||||
*
|
||||
* @param c metric character
|
||||
* @return true/false
|
||||
*/
|
||||
private boolean isValidMetricNameContinuation(char c) {
|
||||
return isValidLabelNameContinuation(c) || c == ':';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a character conforms to the first character rule for label names
|
||||
*
|
||||
* @param c metric character
|
||||
* @return true/false
|
||||
*/
|
||||
private boolean isValidLabelNameStart(char c) {
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a character conforms to rules for label name characters other than the first
|
||||
*
|
||||
* @param c metric character
|
||||
* @return true/false
|
||||
*/
|
||||
private boolean isValidLabelNameContinuation(char c) {
|
||||
return isValidLabelNameStart(c) || (c >= '0' && c <= '9');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a string is a valid UTF-8 encoded string
|
||||
*
|
||||
* @param s label value
|
||||
* @return true/false
|
||||
*/
|
||||
private boolean isValidLabelValue(String s) {
|
||||
return s != null;
|
||||
}
|
||||
|
||||
private boolean isSum(String s) {
|
||||
return s != null && s.endsWith(SUM_SUFFIX);
|
||||
}
|
||||
|
||||
private boolean isCount(String s) {
|
||||
return s != null && s.endsWith(COUNT_SUFFIX);
|
||||
}
|
||||
|
||||
}
|
||||
-252
@@ -1,252 +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.http.promethus.exporter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* MetricFamily.
|
||||
*/
|
||||
@Data
|
||||
@ToString
|
||||
public class MetricFamily {
|
||||
/**
|
||||
* metric name
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* metric help
|
||||
*/
|
||||
private String help;
|
||||
|
||||
/**
|
||||
* metric type
|
||||
*/
|
||||
private MetricType metricType;
|
||||
|
||||
/**
|
||||
* Specific metric
|
||||
*/
|
||||
private List<Metric> metricList;
|
||||
|
||||
/**
|
||||
* Metric
|
||||
*/
|
||||
@Data
|
||||
public static class Metric {
|
||||
|
||||
/**
|
||||
* Label data, mainly corresponding to the content within {}
|
||||
*/
|
||||
private List<Label> labelPair;
|
||||
|
||||
/**
|
||||
* info
|
||||
*/
|
||||
private Info info;
|
||||
|
||||
/**
|
||||
* gauge
|
||||
*/
|
||||
private Gauge gauge;
|
||||
|
||||
/**
|
||||
* counter
|
||||
*/
|
||||
private Counter counter;
|
||||
|
||||
/**
|
||||
* summary
|
||||
*/
|
||||
private Summary summary;
|
||||
|
||||
/**
|
||||
* untyped
|
||||
*/
|
||||
private Untyped untyped;
|
||||
|
||||
/**
|
||||
* histogram
|
||||
*/
|
||||
private Histogram histogram;
|
||||
|
||||
/**
|
||||
* timestampMs
|
||||
*/
|
||||
private Long timestampMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Label
|
||||
*/
|
||||
@Data
|
||||
public static class Label {
|
||||
|
||||
/**
|
||||
* name
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* value
|
||||
*/
|
||||
private String value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Info
|
||||
*/
|
||||
@Data
|
||||
public static class Info {
|
||||
|
||||
/**
|
||||
* value
|
||||
*/
|
||||
private double value;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Counter
|
||||
*/
|
||||
@Data
|
||||
public static class Counter {
|
||||
|
||||
/**
|
||||
* value
|
||||
*/
|
||||
private double value;
|
||||
|
||||
// Exemplar
|
||||
}
|
||||
|
||||
/**
|
||||
* Gauge
|
||||
*/
|
||||
@Data
|
||||
public static class Gauge {
|
||||
|
||||
/**
|
||||
* value
|
||||
*/
|
||||
private double value;
|
||||
}
|
||||
|
||||
/**
|
||||
* untyped
|
||||
*/
|
||||
@Data
|
||||
public static class Untyped {
|
||||
|
||||
/**
|
||||
* value
|
||||
*/
|
||||
private double value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Summary
|
||||
*/
|
||||
@Data
|
||||
public static class Summary {
|
||||
|
||||
/**
|
||||
* value
|
||||
*/
|
||||
private double value;
|
||||
|
||||
/**
|
||||
* count
|
||||
*/
|
||||
private long count;
|
||||
|
||||
/**
|
||||
* sum
|
||||
*/
|
||||
private double sum;
|
||||
|
||||
/**
|
||||
* quantileList
|
||||
*/
|
||||
private List<Quantile> quantileList = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Quantile
|
||||
*/
|
||||
@Data
|
||||
public static class Quantile {
|
||||
/**
|
||||
* Corresponding to the quantile field in Prometheus
|
||||
*/
|
||||
private double xLabel;
|
||||
|
||||
/**
|
||||
* value
|
||||
*/
|
||||
private double value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Histogram
|
||||
*/
|
||||
@Data
|
||||
public static class Histogram {
|
||||
|
||||
/**
|
||||
* value
|
||||
*/
|
||||
private double value;
|
||||
|
||||
/**
|
||||
* count
|
||||
*/
|
||||
private long count;
|
||||
|
||||
/**
|
||||
* sum
|
||||
*/
|
||||
private double sum;
|
||||
|
||||
/**
|
||||
* bucketList
|
||||
*/
|
||||
private List<Bucket> bucketList = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Bucket
|
||||
*/
|
||||
@Data
|
||||
public static class Bucket {
|
||||
|
||||
/**
|
||||
* cumulativeCount
|
||||
*/
|
||||
private long cumulativeCount;
|
||||
|
||||
/**
|
||||
* upperBound
|
||||
*/
|
||||
private double upperBound;
|
||||
}
|
||||
}
|
||||
-52
@@ -1,52 +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.http.promethus.exporter;
|
||||
|
||||
/**
|
||||
* prometheus metrics type
|
||||
*/
|
||||
public enum MetricType {
|
||||
// for string metric info
|
||||
INFO("info"),
|
||||
// Represents a monotonically increasing counter, e.g., counting occurrences
|
||||
COUNTER("counter"),
|
||||
// A metric type that can fluctuate up and down, e.g., CPU usage rate
|
||||
GAUGE("gauge"),
|
||||
SUMMARY("summary"),
|
||||
UNTYPED("untyped"),
|
||||
HISTOGRAM("histogram");
|
||||
|
||||
private final String value;
|
||||
|
||||
MetricType(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static MetricType getType(String value) {
|
||||
for (MetricType metricType : values()) {
|
||||
if (metricType.getValue().equals(value)) {
|
||||
return metricType;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+12
-23
@@ -19,6 +19,7 @@ package org.apache.hertzbeat.collector.collect.prometheus;
|
||||
|
||||
import static org.apache.hertzbeat.common.constants.SignConstants.RIGHT_DASH;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.UnknownHostException;
|
||||
@@ -34,7 +35,6 @@ import javax.net.ssl.SSLException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.collector.collect.common.http.CommonHttpClient;
|
||||
import org.apache.hertzbeat.collector.collect.prometheus.parser.MetricFamily;
|
||||
import org.apache.hertzbeat.collector.collect.prometheus.parser.TextParser;
|
||||
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
|
||||
import org.apache.hertzbeat.collector.util.CollectUtil;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
@@ -46,6 +46,7 @@ import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.util.Base64Util;
|
||||
import org.apache.hertzbeat.common.util.CommonUtil;
|
||||
import org.apache.hertzbeat.common.util.IpDomainUtil;
|
||||
import org.apache.hertzbeat.collector.collect.prometheus.parser.OnlineParser;
|
||||
import org.apache.http.HttpHeaders;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpStatus;
|
||||
@@ -64,7 +65,6 @@ import org.apache.http.impl.auth.DigestScheme;
|
||||
import org.apache.http.impl.client.BasicAuthCache;
|
||||
import org.apache.http.impl.client.BasicCredentialsProvider;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -99,25 +99,12 @@ public class PrometheusAutoCollectImpl {
|
||||
builder.setMsg(NetworkConstants.STATUS_CODE + SignConstants.BLANK + statusCode);
|
||||
return null;
|
||||
}
|
||||
// todo: The InputStream is directly converted to a String here
|
||||
// For large data in the Prometheus exporter, this can generate large objects, which could severely impact JVM memory space
|
||||
// todo: Option one: Use InputStream for parsing, but this requires significant code changes
|
||||
// Option two: Manually trigger garbage collection, which can be referenced from Dubbo for long i
|
||||
String resp = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
|
||||
long collectTime = System.currentTimeMillis();
|
||||
builder.setTime(collectTime);
|
||||
if (resp == null || !StringUtils.hasText(resp)) {
|
||||
log.error("http response content is empty, status: {}.", statusCode);
|
||||
try {
|
||||
return parseResponseByPrometheusExporter(response.getEntity().getContent(), builder);
|
||||
} catch (Exception e) {
|
||||
log.info("parse error: {}.", e.getMessage(), e);
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg("http response content is empty");
|
||||
} else {
|
||||
try {
|
||||
return parseResponseByPrometheusExporter(resp, metrics.getAliasFields(), builder);
|
||||
} catch (Exception e) {
|
||||
log.info("parse error: {}.", e.getMessage(), e);
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
builder.setMsg("parse response data error:" + e.getMessage());
|
||||
}
|
||||
builder.setMsg("parse response data error:" + e.getMessage());
|
||||
}
|
||||
} catch (ClientProtocolException e1) {
|
||||
String errorMsg = CommonUtil.getMessageFromThrowable(e1);
|
||||
@@ -168,10 +155,12 @@ public class PrometheusAutoCollectImpl {
|
||||
}
|
||||
}
|
||||
|
||||
private List<CollectRep.MetricsData> parseResponseByPrometheusExporter(String resp, List<String> aliasFields,
|
||||
CollectRep.MetricsData.Builder builder) {
|
||||
Map<String, MetricFamily> metricFamilyMap = TextParser.textToMetricFamilies(resp);
|
||||
private List<CollectRep.MetricsData> parseResponseByPrometheusExporter(InputStream inputStream, CollectRep.MetricsData.Builder builder) throws IOException {
|
||||
Map<String, MetricFamily> metricFamilyMap = OnlineParser.parseMetrics(inputStream);
|
||||
List<CollectRep.MetricsData> metricsDataList = new LinkedList<>();
|
||||
if (metricFamilyMap == null) {
|
||||
return metricsDataList;
|
||||
}
|
||||
for (Map.Entry<String, MetricFamily> entry : metricFamilyMap.entrySet()) {
|
||||
builder.clearFields();
|
||||
builder.clearValues();
|
||||
|
||||
+88
-70
@@ -17,16 +17,17 @@
|
||||
|
||||
package org.apache.hertzbeat.collector.collect.prometheus.parser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
|
||||
@@ -36,9 +37,47 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
@Slf4j
|
||||
public class OnlineParser {
|
||||
|
||||
private static final Map<Integer, Integer> escapeMap = new HashMap<>(8);
|
||||
|
||||
static {
|
||||
escapeMap.put((int) 'n', (int) '\n');
|
||||
escapeMap.put((int) 'b', (int) '\b');
|
||||
escapeMap.put((int) 't', (int) '\t');
|
||||
escapeMap.put((int) 'r', (int) '\r');
|
||||
escapeMap.put((int) 'f', (int) '\f');
|
||||
escapeMap.put((int) '\'', (int) '\'');
|
||||
escapeMap.put((int) '\"', (int) '\"');
|
||||
escapeMap.put((int) '\\', (int) '\\');
|
||||
}
|
||||
|
||||
private OnlineParser() {
|
||||
}
|
||||
|
||||
public static Map<String, MetricFamily> parseMetrics(InputStream inputStream) throws IOException {
|
||||
Map<String, MetricFamily> metricFamilyMap = new ConcurrentHashMap<>(10);
|
||||
try {
|
||||
int i = getChar(inputStream);
|
||||
while (i != -1) {
|
||||
if (i == '#' || i == '\n') {
|
||||
skipToLineEnd(inputStream).maybeEol().maybeEof().noElse();
|
||||
} else {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
stringBuilder.append((char) i);
|
||||
parseMetric(inputStream, metricFamilyMap, stringBuilder);
|
||||
}
|
||||
i = getChar(inputStream);
|
||||
}
|
||||
} catch (FormatException e) {
|
||||
log.error("prometheus parser failed because of wrong input format. {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
return metricFamilyMap;
|
||||
}
|
||||
|
||||
private static class FormatException extends Exception {
|
||||
|
||||
public FormatException() {}
|
||||
public FormatException() {
|
||||
}
|
||||
|
||||
public FormatException(String message) {
|
||||
super(message);
|
||||
@@ -117,91 +156,92 @@ public class OnlineParser {
|
||||
return this.i;
|
||||
}
|
||||
|
||||
private int getInt() throws FormatException {
|
||||
private int getInt() {
|
||||
return this.i;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static CharChecker parseOneChar(InputStream inputStream) throws IOException {
|
||||
private static int getChar(InputStream inputStream) throws IOException, FormatException {
|
||||
int i = inputStream.read();
|
||||
return new CharChecker(i);
|
||||
if (i == '\\') {
|
||||
i = inputStream.read();
|
||||
if (escapeMap.containsKey(i)) {
|
||||
return escapeMap.get(i);
|
||||
} else {
|
||||
throw new FormatException("Escape character failed.");
|
||||
}
|
||||
} else {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
private static CharChecker parseOneDouble(InputStream inputStream, StringBuilder stringBuilder) throws IOException {
|
||||
int i = inputStream.read();
|
||||
while ((i >= '0' && i <= '9') || (i >= 'a' && i <= 'z') || (i >= 'A' && i <= 'Z') || i == '-' || i == '+' || i == 'e' || i == '.') {
|
||||
private static CharChecker parseOneDouble(InputStream inputStream, StringBuilder stringBuilder) throws IOException, FormatException {
|
||||
int i = getChar(inputStream);
|
||||
while (i >= '0' && i <= '9' || i >= 'a' && i <= 'z' || i >= 'A' && i <= 'Z' || i == '-' || i == '+' || i == '.') {
|
||||
stringBuilder.append((char) i);
|
||||
i = inputStream.read();
|
||||
i = getChar(inputStream);
|
||||
}
|
||||
return new CharChecker(i);
|
||||
}
|
||||
|
||||
private static CharChecker skipOneLong(InputStream inputStream) throws IOException {
|
||||
int i = inputStream.read();
|
||||
private static CharChecker skipOneLong(InputStream inputStream) throws IOException, FormatException {
|
||||
int i = getChar(inputStream);
|
||||
while (i >= '0' && i <= '9') {
|
||||
i = inputStream.read();
|
||||
i = getChar(inputStream);
|
||||
}
|
||||
return new CharChecker(i);
|
||||
}
|
||||
|
||||
private static CharChecker parseMetricName(InputStream inputStream, StringBuilder stringBuilder) throws IOException {
|
||||
int i = inputStream.read();
|
||||
private static CharChecker parseMetricName(InputStream inputStream, StringBuilder stringBuilder) throws IOException, FormatException {
|
||||
int i = getChar(inputStream);
|
||||
while ((i >= 'a' && i <= 'z') || (i >= 'A' && i <= 'Z') || (i >= '0' && i <= '9') || i == '_' || i == ':') {
|
||||
stringBuilder.append((char) i);
|
||||
i = inputStream.read();
|
||||
i = getChar(inputStream);
|
||||
}
|
||||
return new CharChecker(i);
|
||||
}
|
||||
|
||||
private static CharChecker parseLabelName(InputStream inputStream, StringBuilder stringBuilder) throws IOException {
|
||||
int i = inputStream.read();
|
||||
private static CharChecker parseLabelName(InputStream inputStream, StringBuilder stringBuilder) throws IOException, FormatException {
|
||||
int i = getChar(inputStream);
|
||||
while ((i >= 'a' && i <= 'z') || (i >= 'A' && i <= 'Z') || (i >= '0' && i <= '9') || i == '_') {
|
||||
stringBuilder.append((char) i);
|
||||
i = inputStream.read();
|
||||
i = getChar(inputStream);
|
||||
}
|
||||
return new CharChecker(i);
|
||||
}
|
||||
|
||||
private static CharChecker parseLabelValue(InputStream inputStream, StringBuilder stringBuilder) throws IOException, FormatException {
|
||||
int i = inputStream.read();
|
||||
int i = getChar(inputStream);
|
||||
while (i != '"' && i != -1) {
|
||||
if (i == '\\') {
|
||||
i = inputStream.read();
|
||||
i = getChar(inputStream);
|
||||
switch (i) {
|
||||
case 'n':
|
||||
stringBuilder.append('\n');
|
||||
break;
|
||||
case '\\':
|
||||
stringBuilder.append('\\');
|
||||
break;
|
||||
case '\"':
|
||||
stringBuilder.append('\"');
|
||||
break;
|
||||
default:
|
||||
throw new FormatException();
|
||||
case 'n' -> stringBuilder.append('\n');
|
||||
case '\\' -> stringBuilder.append('\\');
|
||||
case '\"' -> stringBuilder.append('\"');
|
||||
default -> throw new FormatException();
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
stringBuilder.append((char) i);
|
||||
}
|
||||
i = inputStream.read();
|
||||
i = getChar(inputStream);
|
||||
}
|
||||
return new CharChecker(i);
|
||||
}
|
||||
|
||||
private static CharChecker skipSpaces(InputStream inputStream) throws IOException {
|
||||
int i = inputStream.read();
|
||||
private static CharChecker skipSpaces(InputStream inputStream) throws IOException, FormatException {
|
||||
int i = getChar(inputStream);
|
||||
while (i == ' ') {
|
||||
i = inputStream.read();
|
||||
i = getChar(inputStream);
|
||||
}
|
||||
return new CharChecker(i);
|
||||
}
|
||||
|
||||
private static CharChecker skipToLineEnd(InputStream inputStream) throws IOException {
|
||||
int i = inputStream.read();
|
||||
private static CharChecker skipToLineEnd(InputStream inputStream) throws IOException, FormatException {
|
||||
int i = getChar(inputStream);
|
||||
while (i != '\n' && i != -1) {
|
||||
i = inputStream.read();
|
||||
i = getChar(inputStream);
|
||||
}
|
||||
return new CharChecker(i);
|
||||
}
|
||||
@@ -256,7 +296,7 @@ public class OnlineParser {
|
||||
}
|
||||
|
||||
private static CharChecker parseMetric(InputStream inputStream, Map<String, MetricFamily> metricFamilyMap, StringBuilder stringBuilder) throws IOException, FormatException {
|
||||
MetricFamily metricFamily = null;
|
||||
MetricFamily metricFamily;
|
||||
MetricFamily.Metric metric = new MetricFamily.Metric();
|
||||
int i = parseMetricName(inputStream, stringBuilder).maybeSpace().maybeLeftBracket().noElse();
|
||||
String metricName = stringBuilder.toString();
|
||||
@@ -267,18 +307,18 @@ public class OnlineParser {
|
||||
metricFamily.setMetricList(new ArrayList<>());
|
||||
metricFamily.setName(metricName);
|
||||
metricFamilyMap.put(metricName, metricFamily);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
metricFamily = metricFamilyMap.get(metricName);
|
||||
}
|
||||
|
||||
if (i == ' ') {
|
||||
i = skipSpaces(inputStream).getInt();
|
||||
}
|
||||
|
||||
List<MetricFamily.Label> labelList = new LinkedList<>();
|
||||
metric.setLabels(labelList);
|
||||
if (i == '{') {
|
||||
List<MetricFamily.Label> labelList = new LinkedList<>();
|
||||
parseLabels(inputStream, stringBuilder, labelList);
|
||||
metric.setLabels(labelList);
|
||||
i = skipSpaces(inputStream).getInt();
|
||||
}
|
||||
|
||||
@@ -304,26 +344,4 @@ public class OnlineParser {
|
||||
metricFamily.getMetricList().add(metric);
|
||||
return new CharChecker(i);
|
||||
}
|
||||
|
||||
public static Map<String, MetricFamily> parseMetrics(InputStream inputStream) throws IOException {
|
||||
Map<String, MetricFamily> metricFamilyMap = new ConcurrentHashMap<>(10);
|
||||
int i = inputStream.read();
|
||||
try {
|
||||
while (i != -1) {
|
||||
if (i == '#' || i == '\n') {
|
||||
skipToLineEnd(inputStream).maybeEol().maybeEof().noElse();
|
||||
} else {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
stringBuilder.append((char) i);
|
||||
parseMetric(inputStream, metricFamilyMap, stringBuilder);
|
||||
}
|
||||
i = inputStream.read();
|
||||
}
|
||||
} catch (FormatException e) {
|
||||
log.error("prometheus parser failed because of wrong input format. {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
return metricFamilyMap;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-1
@@ -29,8 +29,10 @@ import org.apache.hertzbeat.common.util.StrBuffer;
|
||||
/**
|
||||
* Resolves the data passed by prometheus's exporter interface http:xxx/metrics
|
||||
* Reference: prometheus text_parse.go code, entry: TextToMetricFamilies
|
||||
* recommend use OnlineParser
|
||||
*/
|
||||
@Slf4j
|
||||
@Deprecated(since = "1.7.0")
|
||||
public class TextParser {
|
||||
private static final String NAME_LABEL = "__name__";
|
||||
private static final char LEFT_CURLY_BRACKET = '{';
|
||||
@@ -44,7 +46,6 @@ public class TextParser {
|
||||
|
||||
/**
|
||||
* parser prometheus exporter text metrics data
|
||||
* todo use inputStream bytebuffer instead of resp string
|
||||
* @param resp txt data
|
||||
* @return metrics family
|
||||
*/
|
||||
|
||||
+6
-6
@@ -58,13 +58,13 @@ public class PushCollectImpl extends AbstractCollect {
|
||||
private static final Map<Long, Long> timeMap = new ConcurrentHashMap<>();
|
||||
|
||||
// ms
|
||||
private static final Integer timeout = 3000;
|
||||
private static final Integer DEFAULT_TIMEOUT = 3000;
|
||||
|
||||
private static final Integer SUCCESS_CODE = 200;
|
||||
|
||||
// It's hard to determine how long ago the first data collection was, because there's no way to know when the last collection occurred.
|
||||
// This makes it difficult to avoid re-collecting data after a restart. The default is 30 seconds
|
||||
private static final Integer firstCollectInterval = 30000;
|
||||
private static final Integer FIRST_COLLECT_INTERVAL = 30000;
|
||||
|
||||
@Override
|
||||
public void preCheck(Metrics metrics) throws IllegalArgumentException {
|
||||
@@ -80,7 +80,7 @@ public class PushCollectImpl extends AbstractCollect {
|
||||
long monitorId = builder.getId();
|
||||
PushProtocol pushProtocol = metrics.getPush();
|
||||
|
||||
Long time = timeMap.getOrDefault(monitorId, curTime - firstCollectInterval);
|
||||
Long time = timeMap.getOrDefault(monitorId, curTime - FIRST_COLLECT_INTERVAL);
|
||||
timeMap.put(monitorId, curTime);
|
||||
|
||||
HttpContext httpContext = createHttpContext(pushProtocol);
|
||||
@@ -145,10 +145,10 @@ public class PushCollectImpl extends AbstractCollect {
|
||||
|
||||
//requestBuilder.setUri(pushProtocol.getUri());
|
||||
|
||||
if (timeout > 0) {
|
||||
if (DEFAULT_TIMEOUT > 0) {
|
||||
RequestConfig requestConfig = RequestConfig.custom()
|
||||
.setConnectTimeout(timeout)
|
||||
.setSocketTimeout(timeout)
|
||||
.setConnectTimeout(DEFAULT_TIMEOUT)
|
||||
.setSocketTimeout(DEFAULT_TIMEOUT)
|
||||
.setRedirectsEnabled(true)
|
||||
.build();
|
||||
requestBuilder.setConfig(requestConfig);
|
||||
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.collector.collect.s7;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.collector.collect.plc.AbstractPlcCollectImpl;
|
||||
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.PlcProtocol;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.S7Protocol;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.plc4x.java.api.PlcConnection;
|
||||
import org.apache.plc4x.java.api.messages.PlcReadRequest;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* plc collect
|
||||
*/
|
||||
@Slf4j
|
||||
public class S7CollectImpl extends AbstractPlcCollectImpl {
|
||||
|
||||
@Override
|
||||
public void preCheck(Metrics metrics) throws IllegalArgumentException {
|
||||
S7Protocol s7 = metrics.getS7();
|
||||
List<String> registerAddressList = s7.getRegisterAddresses();
|
||||
// check RackId
|
||||
if (!StringUtils.hasText(s7.getRackId())) {
|
||||
s7.setRackId("0");
|
||||
}
|
||||
// check SlotId
|
||||
if (!StringUtils.hasText(s7.getSlotId())) {
|
||||
s7.setSlotId("0");
|
||||
}
|
||||
// check controllerType
|
||||
if (!StringUtils.hasText(s7.getControllerType())) {
|
||||
s7.setControllerType("S7_1500");
|
||||
}
|
||||
if (!StringUtils.hasText(s7.getTimeout())) {
|
||||
s7.setTimeout("5000");
|
||||
}
|
||||
PlcProtocol plc = metrics.getPlc() == null ? new PlcProtocol() : metrics.getPlc();
|
||||
plc.setRegisterAddresses(registerAddressList);
|
||||
BeanUtils.copyProperties(s7, plc);
|
||||
metrics.setPlc(plc);
|
||||
super.preCheck(metrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void collect(CollectRep.MetricsData.Builder builder, Metrics metrics) {
|
||||
super.collect(builder, metrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String supportProtocol() {
|
||||
return DispatchConstants.PROTOCOL_S7;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getConnectionString(Metrics metrics) {
|
||||
S7Protocol s7Protocol = metrics.getS7();
|
||||
return "s7://" + s7Protocol.getHost() + ":" + s7Protocol.getPort() + "?remote-rack:" + s7Protocol.getRackId()
|
||||
+ "&remote-slot:" + s7Protocol.getSlotId() + "&controller-type=" + s7Protocol.getControllerType();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PlcReadRequest buildRequest(Metrics metrics, PlcConnection connection) {
|
||||
S7Protocol s7Protocol = metrics.getS7();
|
||||
List<String> registerAddressList = s7Protocol.getRegisterAddresses();
|
||||
// Create a new read request:
|
||||
PlcReadRequest.Builder requestBuilder = connection.readRequestBuilder();
|
||||
for (int i = 0; i < registerAddressList.size(); i++) {
|
||||
String s1 = registerAddressList.get(i);
|
||||
requestBuilder.addTagAddress(metrics.getS7().getAddressSyntax() + ":" + i, s1);
|
||||
}
|
||||
return requestBuilder.build();
|
||||
}
|
||||
}
|
||||
+23
-31
@@ -47,31 +47,28 @@ public class DnsCollectImplTest {
|
||||
.address("www.google.com")
|
||||
.timeout("3000")
|
||||
.port("53")
|
||||
.tcp("tcp")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPreCheck() {
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
Metrics metrics = new Metrics();
|
||||
metrics.setName("question");
|
||||
metrics.setDns(dnsProtocol);
|
||||
dnsCollect.preCheck(metrics);
|
||||
});
|
||||
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
|
||||
//metrics is null
|
||||
dnsCollect.collect(builder, null);
|
||||
assertEquals(CollectRep.Code.FAIL, builder.getCode());
|
||||
|
||||
//query class is blank
|
||||
//invalid DnsProtocol
|
||||
Metrics metrics = new Metrics();
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
DnsProtocol dns = DnsProtocol.builder().build();
|
||||
|
||||
Metrics metrics = new Metrics();
|
||||
metrics.setDns(dns);
|
||||
dnsCollect.preCheck(metrics);
|
||||
});
|
||||
|
||||
// no exception throws
|
||||
//validated DnsProtocol
|
||||
assertDoesNotThrow(() -> {
|
||||
dnsProtocol.setTcp("tcp");
|
||||
Metrics metrics = new Metrics();
|
||||
metrics.setName("question");
|
||||
metrics.setDns(dnsProtocol);
|
||||
dnsCollect.preCheck(metrics);
|
||||
});
|
||||
@@ -80,28 +77,23 @@ public class DnsCollectImplTest {
|
||||
@Test
|
||||
public void testCollect() {
|
||||
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
|
||||
long monitorId = 666;
|
||||
String app = "testDNS";
|
||||
Metrics metrics = new Metrics();
|
||||
metrics.setName("question");
|
||||
metrics.setDns(dnsProtocol);
|
||||
metrics.setAliasFields(Collections.singletonList("section"));
|
||||
dnsCollect.collect(builder, metrics);
|
||||
Metrics metrics0 = Metrics.builder()
|
||||
.name("question")
|
||||
.dns(dnsProtocol)
|
||||
.aliasFields(Collections.singletonList("section"))
|
||||
.build();
|
||||
dnsCollect.collect(builder, metrics0);
|
||||
assertEquals(CollectRep.Code.SUCCESS, builder.getCode());
|
||||
assertNotNull(builder.getValues(0).getColumns(0));
|
||||
|
||||
// dns is null, no exception throws
|
||||
assertDoesNotThrow(() -> {
|
||||
dnsCollect.collect(builder, null);
|
||||
});
|
||||
|
||||
// metric name is header
|
||||
assertDoesNotThrow(() -> {
|
||||
Metrics metrics1 = new Metrics();
|
||||
metrics1.setName("header");
|
||||
metrics1.setDns(dnsProtocol);
|
||||
metrics1.setAliasFields(Collections.singletonList("section"));
|
||||
dnsCollect.collect(builder, metrics1);
|
||||
});
|
||||
Metrics metrics1 = Metrics.builder()
|
||||
.name("header")
|
||||
.dns(dnsProtocol)
|
||||
.aliasFields(Collections.singletonList("section"))
|
||||
.build();
|
||||
dnsCollect.collect(builder, metrics1);
|
||||
assertEquals(CollectRep.Code.SUCCESS, builder.getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+96
-1
@@ -18,7 +18,14 @@
|
||||
package org.apache.hertzbeat.collector.collect.http;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.HttpProtocol;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
@@ -41,7 +48,7 @@ class HttpCollectImplTest {
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
httpCollectImpl.preCheck(null);
|
||||
});
|
||||
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
Metrics metrics = Metrics.builder().build();
|
||||
httpCollectImpl.preCheck(metrics);
|
||||
@@ -65,4 +72,92 @@ class HttpCollectImplTest {
|
||||
String protocol = httpCollectImpl.supportProtocol();
|
||||
assert "http".equals(protocol);
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseResponseByXmlPath() throws Exception {
|
||||
// Create a sample XML response
|
||||
String xmlResponse = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root>
|
||||
<server>
|
||||
<name>Server1</name>
|
||||
<status>Running</status>
|
||||
<metrics>
|
||||
<cpu>75.5</cpu>
|
||||
<memory>1024</memory>
|
||||
<disk>500</disk>
|
||||
</metrics>
|
||||
</server>
|
||||
<server>
|
||||
<name>Server2</name>
|
||||
<status>Stopped</status>
|
||||
<metrics>
|
||||
<cpu>0.0</cpu>
|
||||
<memory>0</memory>
|
||||
<disk>500</disk>
|
||||
</metrics>
|
||||
</server>
|
||||
</root>
|
||||
""";
|
||||
|
||||
// Set up HttpProtocol with XML path parsing
|
||||
HttpProtocol http = HttpProtocol.builder()
|
||||
.parseType(DispatchConstants.PARSE_XML_PATH)
|
||||
.parseScript("//server") // XPath to select all server nodes
|
||||
.build();
|
||||
|
||||
// Set up Metrics with fields that have XPath expressions
|
||||
List<Metrics.Field> fields = new ArrayList<>();
|
||||
fields.add(Metrics.Field.builder().field("name").build());
|
||||
fields.add(Metrics.Field.builder().field("status").build());
|
||||
fields.add(Metrics.Field.builder().field("metrics/cpu").build());
|
||||
fields.add(Metrics.Field.builder().field("metrics/memory").build());
|
||||
|
||||
Metrics metrics = Metrics.builder()
|
||||
.http(http)
|
||||
.fields(fields)
|
||||
.aliasFields(Arrays.asList("name", "status", "metrics/cpu", "metrics/memory"))
|
||||
.build();
|
||||
|
||||
// Create a custom builder that captures added rows
|
||||
List<CollectRep.ValueRow> capturedRows = new ArrayList<>();
|
||||
CollectRep.MetricsData.Builder builder = new CollectRep.MetricsData.Builder() {
|
||||
@Override
|
||||
public CollectRep.MetricsData.Builder addValueRow(CollectRep.ValueRow valueRow) {
|
||||
capturedRows.add(valueRow);
|
||||
return super.addValueRow(valueRow);
|
||||
}
|
||||
};
|
||||
|
||||
// Use reflection to access the private parseResponseByXmlPath method
|
||||
Method parseMethod = HttpCollectImpl.class.getDeclaredMethod(
|
||||
"parseResponseByXmlPath",
|
||||
String.class,
|
||||
Metrics.class,
|
||||
CollectRep.MetricsData.Builder.class,
|
||||
Long.class);
|
||||
parseMethod.setAccessible(true);
|
||||
|
||||
// Call the method
|
||||
parseMethod.invoke(httpCollectImpl, xmlResponse, metrics, builder, 100L);
|
||||
|
||||
// Verify the results
|
||||
assertEquals(2, capturedRows.size(), "Should have parsed 2 server nodes");
|
||||
|
||||
// Check first server
|
||||
CollectRep.ValueRow firstRow = capturedRows.get(0);
|
||||
assertEquals(4, firstRow.getColumnsCount(), "First row should have 4 columns");
|
||||
assertEquals("Server1", firstRow.getColumns(0), "First server name should be Server1");
|
||||
assertEquals("Running", firstRow.getColumns(1), "First server status should be Running");
|
||||
assertEquals("75.5", firstRow.getColumns(2), "First server CPU should be 75.5");
|
||||
assertEquals("1024", firstRow.getColumns(3), "First server memory should be 1024");
|
||||
|
||||
// Check second server
|
||||
CollectRep.ValueRow secondRow = capturedRows.get(1);
|
||||
assertEquals(4, secondRow.getColumnsCount(), "Second row should have 4 columns");
|
||||
assertEquals("Server2", secondRow.getColumns(0), "Second server name should be Server2");
|
||||
assertEquals("Stopped", secondRow.getColumns(1), "Second server status should be Stopped");
|
||||
assertEquals("0.0", secondRow.getColumns(2), "Second server CPU should be 0.0");
|
||||
assertEquals("0", secondRow.getColumns(3), "Second server memory should be 0");
|
||||
}
|
||||
}
|
||||
|
||||
-79
@@ -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.collector.collect.http.promethus.exporter;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import java.util.Map;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Test case for {@link ExporterParser}
|
||||
*/
|
||||
class ExporterParserTest {
|
||||
|
||||
@Test
|
||||
void textToMetric() {
|
||||
String resp = """
|
||||
# HELP disk_total_bytes Total space for path
|
||||
# TYPE disk_total_bytes gauge
|
||||
disk_total_bytes{path="C:\\\\hertzbeat\\\\repo\\\\testpath",} 4.29496725504E11
|
||||
# HELP go_gc_cycles_automatic_gc_cycles_total Count of completed GC cycles generated by the Go runtime.
|
||||
# TYPE go_gc_cycles_automatic_gc_cycles_total counter
|
||||
go_gc_cycles_automatic_gc_cycles_total 0
|
||||
# HELP go_gc_cycles_forced_gc_cycles_total Count of completed GC cycles forced by the application.
|
||||
# TYPE go_gc_cycles_forced_gc_cycles_total counter
|
||||
go_gc_cycles_forced_gc_cycles_total 0
|
||||
# HELP go_gc_cycles_total_gc_cycles_total Count of all completed GC cycles.
|
||||
# TYPE go_gc_cycles_total_gc_cycles_total counter
|
||||
go_gc_cycles_total_gc_cycles_total 0
|
||||
# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.
|
||||
# TYPE go_gc_duration_seconds summary
|
||||
go_gc_duration_seconds{quantile="0"} 0
|
||||
go_gc_duration_seconds{quantile="0.25"} 0
|
||||
go_gc_duration_seconds{quantile="0.5"} 0
|
||||
go_gc_duration_seconds{quantile="0.75"} 0
|
||||
go_gc_duration_seconds{quantile="1"} 0
|
||||
# TYPE jvm info
|
||||
# HELP jvm VM version info
|
||||
jvm_info{runtime="OpenJDK Runtime Environment",vendor="Azul Systems, Inc.",version="11.0.13+8-LTS"} 1.0
|
||||
# TYPE jvm_gc_collection_seconds summary
|
||||
# HELP jvm_gc_collection_seconds Time spent in a given JVM garbage collector in seconds.
|
||||
jvm_gc_collection_seconds_count{gc="G1 Young Generation"} 10.0
|
||||
jvm_gc_collection_seconds_sum{gc="G1 Young Generation"} 0.051
|
||||
jvm_gc_collection_seconds_count{gc="G1 Old Generation"} 0.0
|
||||
jvm_gc_collection_seconds_sum{gc="G1 Old Generation"} 0.0
|
||||
# TYPE resource_group_aggregate_usage_secs summary
|
||||
resource_group_aggregate_usage_secs{cluster="standalone",quantile="0.5"} 2.69245E-4
|
||||
resource_group_aggregate_usage_secs{cluster="standalone",quantile="0.9"} 3.49601E-4
|
||||
resource_group_aggregate_usage_secs_count{cluster="standalone"} 13.0
|
||||
resource_group_aggregate_usage_secs_sum{cluster="standalone"} 0.004832498
|
||||
resource_group_aggregate_usage_secs_created{cluster="standalone"} 1.715842140749E9
|
||||
# TYPE metadata_store_ops_latency_ms histogram
|
||||
metadata_store_ops_latency_ms_bucket{cluster="standalone",name="metadata-store",type="get",status="success",le="1.0"} 59.0
|
||||
metadata_store_ops_latency_ms_bucket{cluster="standalone",name="metadata-store",type="get",status="success",le="3.0"} 61.0
|
||||
metadata_store_ops_latency_ms_bucket{cluster="standalone",name="metadata-store",type="get",status="success",le="5.0"} 61.0
|
||||
# EOF""";
|
||||
|
||||
ExporterParser parser = new ExporterParser();
|
||||
Map<String, MetricFamily> metricFamilyMap = parser.textToMetric(resp);
|
||||
assertEquals(5, metricFamilyMap.get("resource_group_aggregate_usage_secs").getMetricList().size());
|
||||
assertEquals(3, metricFamilyMap.get("metadata_store_ops_latency_ms").getMetricList().size());
|
||||
assertEquals(5, metricFamilyMap.get("go_gc_duration_seconds").getMetricList().size());
|
||||
assertEquals(9, metricFamilyMap.size());
|
||||
}
|
||||
}
|
||||
+30
-7
@@ -25,8 +25,12 @@ import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
class OnlineParserTest {
|
||||
|
||||
@@ -44,13 +48,13 @@ class OnlineParserTest {
|
||||
String str = """
|
||||
# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.
|
||||
# TYPE go_gc_duration_seconds summary
|
||||
go_gc_duration_seconds { quantile="0"} 2.0209e-05 321312
|
||||
go_gc_duration_seconds{ quantile = "0.25" } 6.6917e-05
|
||||
go_gc_duration_seconds{quantile="0"} 2.0209e-05
|
||||
go_gc_duration_seconds{quantile="0.25"} 6.6917e-05
|
||||
go_gc_duration_seconds{quantile="0.5"} -Inf
|
||||
go_gc_duration_seconds{ quantile = "0.75"} +Inf
|
||||
go_gc_duration_seconds{quantile="0.75"} +Inf
|
||||
go_gc_duration_seconds{quantile="1"} NaN
|
||||
go_gc_duration_seconds_sum 0.001134793 321314
|
||||
go_gc_duration_seconds_count 5 43
|
||||
go_gc_duration_seconds_sum 0.001134793
|
||||
go_gc_duration_seconds_count 5
|
||||
# HELP go_goroutines Number of goroutines that currently exist.
|
||||
# TYPE go_goroutines gauge
|
||||
go_goroutines 32
|
||||
@@ -73,7 +77,26 @@ class OnlineParserTest {
|
||||
# TYPE go_memstats_gc_sys_bytes gauge
|
||||
go_memstats_gc_sys_bytes 4.614808e+06""";
|
||||
InputStream inputStream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
|
||||
Map<String, MetricFamily> metricFamilyMap = OnlineParser.parseMetrics(inputStream);
|
||||
assertNotNull(metricFamilyMap);
|
||||
Map<String, MetricFamily> metricFamilyMap1 = OnlineParser.parseMetrics(inputStream);
|
||||
Map<String, MetricFamily> metricFamilyMap2 = TextParser.textToMetricFamilies(str);
|
||||
assertNotNull(metricFamilyMap1);
|
||||
assertNotNull(metricFamilyMap2);
|
||||
assertEquals(metricFamilyMap1.size(), metricFamilyMap2.size());
|
||||
metricFamilyMap2.forEach((metricFamilyName, metricFamily2) -> {
|
||||
if (!metricFamilyMap1.containsKey(metricFamilyName)) {
|
||||
fail("parse failed, different result from two parser.");
|
||||
}
|
||||
MetricFamily metricFamily1 = metricFamilyMap1.get(metricFamilyName);
|
||||
assertEquals(metricFamily1.getName(), metricFamily1.getName());
|
||||
Set<Double> metricValueSet = metricFamily2.getMetricList().stream().map(MetricFamily.Metric::getValue).collect(Collectors.toSet());
|
||||
metricFamily1.getMetricList().forEach(metric -> {
|
||||
// this is for something different between two algorithms above, and both of them is current on this parsing behavior.
|
||||
if (!(metric.getValue() == Double.POSITIVE_INFINITY || metric.getValue() == Double.NEGATIVE_INFINITY)) {
|
||||
if (!metricValueSet.contains(metric.getValue())) {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -302,7 +302,7 @@ public class MetricsCollect implements Runnable, Comparable<MetricsCollect> {
|
||||
value = String.valueOf(objValue);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.info("[calculates execute warning] {}.", e.getMessage());
|
||||
log.warn("[calculates execute warning, use original value.] {}", e.getMessage());
|
||||
value = Optional.ofNullable(fieldValueMap.get(expression.getSourceText()))
|
||||
.map(String::valueOf)
|
||||
.orElse(null);
|
||||
|
||||
+2
-1
@@ -29,4 +29,5 @@ org.apache.hertzbeat.collector.collect.mqtt.MqttCollectImpl
|
||||
org.apache.hertzbeat.collector.collect.ipmi2.IpmiCollectImpl
|
||||
org.apache.hertzbeat.collector.collect.kafka.KafkaCollectImpl
|
||||
org.apache.hertzbeat.collector.collect.sd.HttpSdCollectImpl
|
||||
org.apache.hertzbeat.collector.collect.modbus.ModbusCollectImpl
|
||||
org.apache.hertzbeat.collector.collect.modbus.ModbusCollectImpl
|
||||
org.apache.hertzbeat.collector.collect.s7.S7CollectImpl
|
||||
+1
-1
@@ -39,7 +39,7 @@ spring:
|
||||
|
||||
collector:
|
||||
info:
|
||||
version: ${COLLECTOR_VERSION:1.6.1}
|
||||
version: ${COLLECTOR_VERSION:1.7.0}
|
||||
ip: ${COLLECTOR_IP:}
|
||||
dispatch:
|
||||
entrance:
|
||||
|
||||
+9
@@ -202,6 +202,10 @@ public interface DispatchConstants {
|
||||
* Parsing method prometheus exporter data
|
||||
*/
|
||||
String PARSE_PROMETHEUS = "prometheus";
|
||||
/**
|
||||
* Parse response body as config/properties format
|
||||
*/
|
||||
String PARSE_CONFIG = "config";
|
||||
/**
|
||||
* prometheus accept header
|
||||
*/
|
||||
@@ -227,4 +231,9 @@ public interface DispatchConstants {
|
||||
* protocol modbus
|
||||
*/
|
||||
String PROTOCOL_MODBUS = "modbus";
|
||||
|
||||
/**
|
||||
* protocol modbus
|
||||
*/
|
||||
String PROTOCOL_S7 = "s7";
|
||||
}
|
||||
|
||||
+3
-7
@@ -613,9 +613,8 @@ public class HashedWheelTimer implements Timer {
|
||||
HashedWheelBucket bucket = this.bucket;
|
||||
if (bucket != null) {
|
||||
bucket.remove(this);
|
||||
} else {
|
||||
timer.pendingTimeouts.decrementAndGet();
|
||||
}
|
||||
timer.pendingTimeouts.decrementAndGet();
|
||||
}
|
||||
|
||||
public boolean compareAndSetState(int expected, int state) {
|
||||
@@ -642,6 +641,7 @@ public class HashedWheelTimer implements Timer {
|
||||
}
|
||||
|
||||
try {
|
||||
remove();
|
||||
task.run(this);
|
||||
} catch (Throwable t) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
@@ -718,7 +718,6 @@ public class HashedWheelTimer implements Timer {
|
||||
while (timeout != null) {
|
||||
HashedWheelTimeout next = timeout.next;
|
||||
if (timeout.remainingRounds <= 0) {
|
||||
next = remove(timeout);
|
||||
if (timeout.deadline <= deadline) {
|
||||
timeout.expire();
|
||||
} else {
|
||||
@@ -726,9 +725,7 @@ public class HashedWheelTimer implements Timer {
|
||||
throw new IllegalStateException(String.format(
|
||||
"timeout.deadline (%d) > deadline (%d)", timeout.deadline, deadline));
|
||||
}
|
||||
} else if (timeout.isCancelled()) {
|
||||
next = remove(timeout);
|
||||
} else {
|
||||
} else if (!timeout.isCancelled()) {
|
||||
timeout.remainingRounds--;
|
||||
}
|
||||
timeout = next;
|
||||
@@ -761,7 +758,6 @@ public class HashedWheelTimer implements Timer {
|
||||
timeout.prev = null;
|
||||
timeout.next = null;
|
||||
timeout.bucket = null;
|
||||
timeout.timer.pendingTimeouts.decrementAndGet();
|
||||
return next;
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -242,6 +242,7 @@ public class KafkaCollectImpl extends AbstractCollect {
|
||||
boolean isKafkaCommand = SupportedCommand.isKafkaCommand(command);
|
||||
if (!isKafkaCommand) {
|
||||
log.error("Unsupported command: {}", command);
|
||||
builder.setCode(CollectRep.Code.FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -273,7 +274,7 @@ public class KafkaCollectImpl extends AbstractCollect {
|
||||
}
|
||||
}
|
||||
|
||||
private AdminClient getAdminClient(KafkaProtocol kafkaProtocol) {
|
||||
protected AdminClient getAdminClient(KafkaProtocol kafkaProtocol) {
|
||||
CacheIdentifier kafkaAdminClientIdentifier = CacheIdentifier.builder()
|
||||
.ip(kafkaProtocol.getHost()).port(kafkaProtocol.getPort())
|
||||
.build();
|
||||
|
||||
+59
-21
@@ -17,12 +17,24 @@
|
||||
|
||||
package org.apache.hertzbeat.collector.collect.kafka;
|
||||
|
||||
import org.apache.hertzbeat.collector.collect.kafka.constants.SupportedCommand;
|
||||
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.KafkaProtocol;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.kafka.clients.admin.AdminClient;
|
||||
import org.apache.kafka.clients.admin.ListTopicsOptions;
|
||||
import org.apache.kafka.clients.admin.ListTopicsResult;
|
||||
import org.apache.kafka.common.KafkaFuture;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
@@ -31,12 +43,25 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
/**
|
||||
* Test case for {@link KafkaCollectImpl}
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class KafkaCollectTest {
|
||||
|
||||
private static final String HOST = "127.0.0.1";
|
||||
private static final String PORT = "9092";
|
||||
|
||||
private KafkaCollectImpl collect;
|
||||
|
||||
@Mock
|
||||
private AdminClient adminClient;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws Exception {
|
||||
collect = new KafkaCollectImpl();
|
||||
collect = new KafkaCollectImpl(){
|
||||
@Override
|
||||
protected AdminClient getAdminClient(KafkaProtocol protocol) {
|
||||
return adminClient;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -59,38 +84,51 @@ public class KafkaCollectTest {
|
||||
});
|
||||
// kafka port is null
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
kafka.setHost("127.0.0.1");
|
||||
kafka.setHost(HOST);
|
||||
collect.preCheck(metric);
|
||||
});
|
||||
// no exception throw
|
||||
assertDoesNotThrow(() -> {
|
||||
kafka.setPort("9092");
|
||||
kafka.setPort(PORT);
|
||||
collect.preCheck(metric);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void collect() {
|
||||
// metrics is null
|
||||
assertThrows(NullPointerException.class, () -> {
|
||||
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
|
||||
collect.collect(builder, null);
|
||||
});
|
||||
void testCollect() throws Exception {
|
||||
Set<String> topicSet = new HashSet<>();
|
||||
topicSet.add("test-topic");
|
||||
|
||||
ListTopicsResult listTopicsResult = Mockito.mock(ListTopicsResult.class);
|
||||
KafkaFuture<Set<String>> future = Mockito.mock(KafkaFuture.class);
|
||||
|
||||
Mockito.when(adminClient.listTopics(Mockito.any(ListTopicsOptions.class))).thenReturn(listTopicsResult);
|
||||
Mockito.when(listTopicsResult.names()).thenReturn(future);
|
||||
Mockito.when(future.get()).thenReturn(topicSet);
|
||||
|
||||
KafkaProtocol kafka = KafkaProtocol.builder()
|
||||
.host(HOST)
|
||||
.port(PORT)
|
||||
.build();
|
||||
|
||||
Metrics metrics = Metrics.builder()
|
||||
.kclient(kafka)
|
||||
.build();
|
||||
|
||||
// test if not kafka command
|
||||
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
|
||||
collect.collect(builder, metrics);
|
||||
assertEquals(CollectRep.Code.FAIL, builder.getCode());
|
||||
|
||||
KafkaProtocol kafka = KafkaProtocol.builder().host("127.0.0.1").port("9092").build();
|
||||
Metrics metrics = Metrics.builder().kclient(kafka).build();
|
||||
//test if not kafka command
|
||||
assertDoesNotThrow(() -> {
|
||||
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
|
||||
collect.collect(builder, metrics);
|
||||
});
|
||||
//test kafka command
|
||||
assertDoesNotThrow(() -> {
|
||||
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
|
||||
metrics.getKclient().setCommand("topic-list");
|
||||
collect.collect(builder, metrics);
|
||||
});
|
||||
kafka.setCommand(SupportedCommand.TOPIC_LIST.getCommand());
|
||||
builder.setCode(CollectRep.Code.forNumber(6));
|
||||
collect.collect(builder, metrics);
|
||||
|
||||
assertEquals(CollectRep.Code.SUCCESS, builder.getCode());
|
||||
assertEquals(1, builder.getValuesList().size());
|
||||
assertEquals("test-topic", builder.getValues(0).getColumns(0));
|
||||
Mockito.verify(adminClient).listTopics(Mockito.any(ListTopicsOptions.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+15
@@ -455,4 +455,19 @@ public interface CommonConstants {
|
||||
* status page incident state resolved
|
||||
*/
|
||||
byte STATUS_PAGE_INCIDENT_STATE_RESOLVED = 3;
|
||||
|
||||
/**
|
||||
* status page incident state resolved
|
||||
*/
|
||||
byte MONITOR_TYPE_NORMAL = 0;
|
||||
|
||||
/**
|
||||
* status page incident state resolved
|
||||
*/
|
||||
byte MONITOR_TYPE_PUSH_AUTO_CREATE = 1;
|
||||
|
||||
/**
|
||||
* status page incident state resolved
|
||||
*/
|
||||
byte MONITOR_TYPE_DISCOVERY_AUTO_CREATE = 2;
|
||||
}
|
||||
|
||||
+2
@@ -65,6 +65,8 @@ public interface ConfigConstants {
|
||||
String INFO = "info";
|
||||
|
||||
String GRAFANA = "grafana";
|
||||
|
||||
String LOG = "log";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -35,4 +35,7 @@ public interface SmsConstants {
|
||||
|
||||
// Aws cloud SMS
|
||||
String AWS = "aws";
|
||||
|
||||
// Twilio SMS
|
||||
String TWILIO = "twilio";
|
||||
}
|
||||
|
||||
+5
@@ -122,6 +122,11 @@ public class NoticeReceiver {
|
||||
example = "779294123", accessMode = READ_WRITE)
|
||||
private String tgUserId;
|
||||
|
||||
@Schema(title = "Telegram message thread id: The notification method is valid for Telegram Bot",
|
||||
description = "TTelegram message thread id: The notification method is valid for Telegram Bot",
|
||||
example = "779294123", accessMode = READ_WRITE)
|
||||
private String tgMessageThreadId;
|
||||
|
||||
@Schema(title = "DingTalk,FeiShu,WeWork user id: The notification method is valid for DingTalk,FeiShu,WeWork Bot",
|
||||
description = "DingTalk,FeiShu,WeWork user id: The notification method is valid for DingTalk,FeiShu,WeWork Bot",
|
||||
example = "779294123", accessMode = READ_WRITE)
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.common.entity.dto.query;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Metric History Range Query Data
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(description = "Query Request Data")
|
||||
public class DatasourceQuery {
|
||||
|
||||
@Schema(title = "Ref Id, unique id for the query")
|
||||
private String refId;
|
||||
|
||||
@Schema(title = "datasource name")
|
||||
private String datasource;
|
||||
|
||||
@Schema(title = "query expr, like prometheus query")
|
||||
private String expr;
|
||||
|
||||
@Schema(title = "query expr type, like promql or sql or influxql")
|
||||
private String exprType;
|
||||
|
||||
@Schema(title = "query range type, like range or instant")
|
||||
private String timeType;
|
||||
|
||||
@Schema(title = "query range start time")
|
||||
private Long start;
|
||||
|
||||
@Schema(title = "query range end time")
|
||||
private Long end;
|
||||
|
||||
@Schema(title = "query time step, like 5m or 1h")
|
||||
private String step;
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.common.entity.dto.query;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Metric History Range Query Data
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(description = "Metric Query Data")
|
||||
public class DatasourceQueryData {
|
||||
|
||||
@Schema(title = "Ref Id, unique id for the query")
|
||||
private String refId;
|
||||
|
||||
@Schema(title = "query status code, 200 for success, other for error")
|
||||
private Integer status;
|
||||
|
||||
@Schema(title = "query error message")
|
||||
private String msg;
|
||||
|
||||
@Schema(title = "query result data frames")
|
||||
private List<SchemaData> frames;
|
||||
|
||||
/**
|
||||
* Schema Data
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
public static final class SchemaData {
|
||||
|
||||
@Schema(title = "Metric Schema")
|
||||
private MetricSchema schema;
|
||||
|
||||
@Schema(title = "metrics row values, first is the timestamp-ts", example = "[[29,32,44],[32,34,true]]")
|
||||
private List<Object[]> data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Metric Schema
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
public static final class MetricSchema {
|
||||
|
||||
@Schema(title = "Metrics Field")
|
||||
private List<MetricField> fields;
|
||||
|
||||
@Schema(title = "This frame labels")
|
||||
private Map<String, String> labels;
|
||||
|
||||
@Schema(title = "Meta Information")
|
||||
private Map<String, String> meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Metric Field
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
public static final class MetricField {
|
||||
|
||||
@Schema(title = "Metric Field Name")
|
||||
private String name;
|
||||
|
||||
@Schema(title = "Field Type: number, string, time, bool")
|
||||
private String type;
|
||||
|
||||
@Schema(title = "Field Unit: %, Mb, Kbps etc.")
|
||||
private String unit;
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,7 @@ import org.apache.hertzbeat.common.entity.job.protocol.PushProtocol;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.RedfishProtocol;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.RedisProtocol;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.RocketmqProtocol;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.S7Protocol;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.ScriptProtocol;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.SmtpProtocol;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.SnmpProtocol;
|
||||
@@ -270,6 +271,10 @@ public class Metrics {
|
||||
* Monitoring configuration information using the public modBus protocol
|
||||
*/
|
||||
private ModbusProtocol modbus;
|
||||
/**
|
||||
* Monitoring configuration information using the public s7 protocol
|
||||
*/
|
||||
private S7Protocol s7;
|
||||
/**
|
||||
* collector use - Temporarily store subTask metrics response data
|
||||
*/
|
||||
|
||||
+6
@@ -95,6 +95,12 @@ public class HttpProtocol implements CommonRequestProtocol, Protocol {
|
||||
*/
|
||||
private List<String> successCodes;
|
||||
|
||||
/**
|
||||
* Whether to enable URL encoding for the path. Default is true.
|
||||
* When true, the URL path will be encoded. When false, the URL path will not be encoded.
|
||||
*/
|
||||
private String enableUrlEncoding = "true";
|
||||
|
||||
/**
|
||||
* authentication information
|
||||
*/
|
||||
|
||||
+27
-16
@@ -15,34 +15,45 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.pojo.dto;
|
||||
package org.apache.hertzbeat.common.entity.job.protocol;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* Tencent Sms Sender configuration dto
|
||||
* Modbus Protocol
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
public class SmsTencentConfig {
|
||||
|
||||
@NotBlank(message = "SecretId cannot be empty")
|
||||
private String secretId;
|
||||
@NoArgsConstructor
|
||||
public class S7Protocol implements Protocol {
|
||||
|
||||
@NotBlank(message = "SecretKey cannot be empty")
|
||||
private String secretKey;
|
||||
/**
|
||||
* IP ADDRESS OR DOMAIN NAME OF THE PEER HOST
|
||||
*/
|
||||
private String host;
|
||||
/**
|
||||
* Port number
|
||||
*/
|
||||
private String port;
|
||||
|
||||
@NotBlank(message = "SignName cannot be empty")
|
||||
private String signName;
|
||||
private String driverName;
|
||||
|
||||
@NotBlank(message = "AppId cannot be null")
|
||||
private String appId;
|
||||
private String addressSyntax;
|
||||
|
||||
@NotBlank(message = "templateId cannot be null")
|
||||
private String templateId;
|
||||
private String rackId;
|
||||
|
||||
private String slotId;
|
||||
|
||||
private String controllerType;
|
||||
|
||||
private String timeout;
|
||||
|
||||
private List<String> registerAddresses;
|
||||
}
|
||||
@@ -91,6 +91,9 @@ public class Monitor {
|
||||
@Max(4)
|
||||
private byte status;
|
||||
|
||||
@Schema(title = "Task type 0: Normal, 1: push auto create, 2: discovery auto create")
|
||||
private byte type;
|
||||
|
||||
@Schema(title = "task label", example = "{env:test}", accessMode = READ_WRITE)
|
||||
@Convert(converter = JsonMapAttributeConverter.class)
|
||||
@Column(length = 4096)
|
||||
|
||||
+1
-9
@@ -445,15 +445,7 @@ public final class CollectRep {
|
||||
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.setSafe(rowIndex, bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,4 +49,46 @@ public final class TimePeriodUtil {
|
||||
return Duration.parse("PT" + tokenTime);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* transform any timestamp to milliseconds
|
||||
* @param timestamp timestamp
|
||||
* @return milliseconds
|
||||
*/
|
||||
public static long normalizeToMilliseconds(Object timestamp) {
|
||||
if (timestamp instanceof String timestampStr) {
|
||||
// string type, may be second, millisecond or decimal second
|
||||
// eg: "1672531199000", "1672531199", "1672531199.123"
|
||||
if (timestampStr.contains(".")) {
|
||||
// contains decimal point, parse as second timestamp
|
||||
double seconds = Double.parseDouble(timestampStr);
|
||||
return (long) (seconds * 1000);
|
||||
} else {
|
||||
// integer form, determine second or millisecond
|
||||
long numericTimestamp = Long.parseLong(timestampStr);
|
||||
return convertNumericTimestamp(numericTimestamp);
|
||||
}
|
||||
} else if (timestamp instanceof Number) {
|
||||
// number eg Integer、Long、Double
|
||||
if (timestamp instanceof Double || timestamp instanceof Float) {
|
||||
// float type, treat as second timestamp
|
||||
double seconds = ((Number) timestamp).doubleValue();
|
||||
return (long) (seconds * 1000);
|
||||
} else {
|
||||
// integer type, directly determine second or millisecond
|
||||
long numericTimestamp = ((Number) timestamp).longValue();
|
||||
return convertNumericTimestamp(numericTimestamp);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Not support this timestamp type: " + timestamp.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
private static long convertNumericTimestamp(long numericTimestamp) {
|
||||
if (String.valueOf(numericTimestamp).length() <= 10) {
|
||||
return numericTimestamp * 1000;
|
||||
} else {
|
||||
return numericTimestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
@@ -40,6 +40,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.client.HttpClientErrorException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@@ -71,6 +72,11 @@ public class DashboardService {
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ResponseEntity<?> createOrUpdateDashboard(String dashboardJson, Long monitorId) {
|
||||
if (!grafanaProperties.enabled()) {
|
||||
log.info("HertzBeat Grafana config not enabled");
|
||||
throw new RuntimeException("HertzBeat Grafana config not enabled");
|
||||
}
|
||||
|
||||
String token = serviceAccountService.getToken();
|
||||
String url = grafanaProperties.getPrefix() + grafanaProperties.getUrl() + CREATE_DASHBOARD_API;
|
||||
|
||||
@@ -103,6 +109,12 @@ public class DashboardService {
|
||||
log.error("create dashboard error: {}", response.getStatusCode());
|
||||
throw new RuntimeException("create dashboard error");
|
||||
}
|
||||
} catch (HttpClientErrorException.Forbidden ex) {
|
||||
log.error("Grafana Access denied to save dashboard", ex);
|
||||
throw new RuntimeException("Grafana Access denied to save dashboard", ex);
|
||||
} catch (HttpClientErrorException.NotFound ex){
|
||||
log.error("Grafana Dashboard not found", ex);
|
||||
throw new RuntimeException("Grafana Dashboard not found", ex);
|
||||
} catch (Exception ex) {
|
||||
log.error("create dashboard error", ex);
|
||||
throw new RuntimeException("create dashboard error", ex);
|
||||
|
||||
+5
@@ -61,6 +61,11 @@ public class DatasourceService {
|
||||
* Create a new datasource in Grafana.
|
||||
*/
|
||||
public void existOrCreateDatasource(String token) {
|
||||
if (!warehouseProperties.enabled()) {
|
||||
log.info("HertzBeat VictoriaMetrics config not enabled");
|
||||
return;
|
||||
}
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.setBearerAuth(token);
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
~ contributor license agreements. See the NOTICE file distributed with
|
||||
~ this work for additional information regarding copyright ownership.
|
||||
~ The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
~ (the "License"); you may not use this file except in compliance with
|
||||
~ the License. You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
<artifactId>hertzbeat</artifactId>
|
||||
<version>2.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hertzbeat-log</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<properties>
|
||||
<maven-jar-plugin.version>3.2.0</maven-jar-plugin.version>
|
||||
<maven-assembly-plugin.version>3.3.0</maven-assembly-plugin.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- hertzbeat common -->
|
||||
<dependency>
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
<artifactId>hertzbeat-common</artifactId>
|
||||
</dependency>
|
||||
<!-- hertzbeat warehouse -->
|
||||
<dependency>
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
<artifactId>hertzbeat-warehouse</artifactId>
|
||||
</dependency>
|
||||
<!-- OpenTelemetry -->
|
||||
<dependency>
|
||||
<groupId>io.opentelemetry</groupId>
|
||||
<artifactId>opentelemetry-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.opentelemetry</groupId>
|
||||
<artifactId>opentelemetry-sdk</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.opentelemetry</groupId>
|
||||
<artifactId>opentelemetry-sdk-logs</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.opentelemetry</groupId>
|
||||
<artifactId>opentelemetry-exporter-otlp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.opentelemetry.instrumentation</groupId>
|
||||
<artifactId>opentelemetry-logback-appender-1.0</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
+13
-18
@@ -15,26 +15,21 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.pojo.dto;
|
||||
package org.apache.hertzbeat.log.config;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import org.apache.hertzbeat.common.constants.ConfigConstants;
|
||||
import org.apache.hertzbeat.common.constants.SignConstants;
|
||||
import org.apache.hertzbeat.warehouse.store.history.greptime.GreptimeProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
|
||||
/**
|
||||
* Alibaba Sms Sender configuration dto
|
||||
* Log auto configuration.
|
||||
*/
|
||||
@Data
|
||||
public class SmsAlibabaConfig {
|
||||
|
||||
@NotBlank(message = "AccessKeyId cannot be empty")
|
||||
private String accessKeyId;
|
||||
|
||||
@NotBlank(message = "AccessKeySecret cannot be empty")
|
||||
private String accessKeySecret;
|
||||
|
||||
@NotBlank(message = "SignName cannot be empty")
|
||||
private String signName;
|
||||
|
||||
@NotBlank(message = "TemplateCode cannot be null")
|
||||
private String templateCode;
|
||||
@ComponentScan(basePackages = ConfigConstants.PkgConstant.PKG
|
||||
+ SignConstants.DOT
|
||||
+ ConfigConstants.FunctionModuleConstants.LOG
|
||||
)
|
||||
@EnableConfigurationProperties(GreptimeProperties.class)
|
||||
public class LogAutoConfiguration {
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.log.config;
|
||||
|
||||
import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_NAME;
|
||||
import io.opentelemetry.api.OpenTelemetry;
|
||||
import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter;
|
||||
import io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender;
|
||||
import io.opentelemetry.sdk.OpenTelemetrySdk;
|
||||
import io.opentelemetry.sdk.logs.SdkLoggerProvider;
|
||||
import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor;
|
||||
import io.opentelemetry.sdk.resources.Resource;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.hertzbeat.warehouse.store.history.greptime.GreptimeProperties;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* OpenTelemetryConfig is responsible for initializing OpenTelemetry with the specified service name and GrepTimeDB endpoint.
|
||||
* It ensures that the initialization is done in a thread-safe manner and includes authentication for GrepTimeDB.
|
||||
*/
|
||||
@Configuration
|
||||
@Slf4j
|
||||
public class OpenTelemetryConfig {
|
||||
|
||||
@Autowired
|
||||
private GreptimeProperties greptimeProperties;
|
||||
|
||||
/**
|
||||
* Initializes OpenTelemetry with the given service name and GrepTimeDB endpoint.
|
||||
* Includes authentication if configured in GreptimeProperties.
|
||||
*/
|
||||
@PostConstruct
|
||||
public void initializeOpenTelemetry() {
|
||||
if (greptimeProperties == null || !greptimeProperties.enabled()) {
|
||||
log.info("GrepTimeDB logging is disabled, skipping OpenTelemetry configuration.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Resource resource = Resource.getDefault()
|
||||
.merge(Resource.builder()
|
||||
.put(SERVICE_NAME, "HertzBeat")
|
||||
.build());
|
||||
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
|
||||
headers.put("X-Greptime-DB-Name", "public");
|
||||
headers.put("X-Greptime-Log-Table-Name", "hzb_log");
|
||||
|
||||
addAuthenticationHeaders(headers);
|
||||
|
||||
OtlpHttpLogRecordExporter logExporter = OtlpHttpLogRecordExporter.builder()
|
||||
.setEndpoint(greptimeProperties.httpEndpoint() + "/v1/otlp/v1/logs")
|
||||
.setHeaders(()-> headers)
|
||||
.setTimeout(10, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
SdkLoggerProvider loggerProvider = SdkLoggerProvider.builder()
|
||||
.setResource(resource)
|
||||
.addLogRecordProcessor(
|
||||
BatchLogRecordProcessor.builder(logExporter)
|
||||
.setScheduleDelay(1000, TimeUnit.MILLISECONDS)
|
||||
.setMaxExportBatchSize(512)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
OpenTelemetry openTelemetry = OpenTelemetrySdk.builder()
|
||||
.setLoggerProvider(loggerProvider)
|
||||
.build();
|
||||
|
||||
OpenTelemetryAppender.install(openTelemetry);
|
||||
log.info("OpenTelemetry successfully configured with GrepTimeDB exporter.");
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to initialize OpenTelemetry with GrepTimeDB", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds authentication headers to the provided map if username and password are configured.
|
||||
*
|
||||
* @param headers the map to which authentication headers will be added
|
||||
*/
|
||||
private void addAuthenticationHeaders(Map<String, String> headers) {
|
||||
if (StringUtils.isNotBlank(greptimeProperties.username())
|
||||
&& StringUtils.isNotBlank(greptimeProperties.password())) {
|
||||
String credentials = greptimeProperties.username() + ":" + greptimeProperties.password();
|
||||
String encodedCredentials = Base64.getEncoder().encodeToString(credentials.getBytes());
|
||||
headers.put("Authorization", "Basic " + encodedCredentials);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.apache.hertzbeat.log.config.LogAutoConfiguration
|
||||
@@ -89,6 +89,11 @@
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
<artifactId>hertzbeat-grafana</artifactId>
|
||||
</dependency>
|
||||
<!-- log -->
|
||||
<dependency>
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
<artifactId>hertzbeat-log</artifactId>
|
||||
</dependency>
|
||||
<!-- spring -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.springframework.context.annotation.ImportRuntimeHints;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
/**
|
||||
* start up class.
|
||||
@@ -41,6 +42,7 @@ import org.springframework.scheduling.annotation.EnableAsync;
|
||||
@ConfigurationPropertiesScan(basePackages = {"org.apache.hertzbeat"})
|
||||
@ImportRuntimeHints(HertzbeatRuntimeHintsRegistrar.class)
|
||||
@EnableAsync
|
||||
@EnableScheduling
|
||||
public class Manager {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Manager.class, args);
|
||||
|
||||
-62
@@ -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
@@ -427,6 +427,7 @@ public class CollectorJobScheduler implements CollectorScheduling, CollectJobSch
|
||||
if (jobId == null) {
|
||||
return;
|
||||
}
|
||||
jobContentCache.remove(jobId);
|
||||
for (ConsistentHash.Node node : consistentHash.getAllNodes().values()) {
|
||||
AssignJobs assignJobs = node.getAssignJobs();
|
||||
if (assignJobs.getPinnedJobs().remove(jobId)
|
||||
|
||||
+2
-1
@@ -75,7 +75,8 @@ import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
/**
|
||||
* Monitoring Type Management Implementation
|
||||
* temporarily stores the monitoring configuration and parameter configuration in memory and then stores it in the
|
||||
* Temporarily stores the monitoring configuration and parameter configuration in memory,
|
||||
* and then persists it to a storage system.
|
||||
*/
|
||||
@Service
|
||||
@Order(value = Ordered.HIGHEST_PRECEDENCE)
|
||||
|
||||
+1
-1
@@ -531,7 +531,7 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
MonitorDto monitorDto = new MonitorDto();
|
||||
List<Param> params = paramDao.findParamsByMonitorId(id);
|
||||
monitorDto.setParams(params);
|
||||
if (DispatchConstants.PROTOCOL_PROMETHEUS.equalsIgnoreCase(monitor.getApp())) {
|
||||
if (DispatchConstants.PROTOCOL_PROMETHEUS.equalsIgnoreCase(monitor.getApp()) || monitor.getType() == CommonConstants.MONITOR_TYPE_PUSH_AUTO_CREATE) {
|
||||
List<CollectRep.MetricsData> metricsDataList = warehouseService.queryMonitorMetricsData(id);
|
||||
List<String> metrics = metricsDataList.stream().map(CollectRep.MetricsData::getMetrics).collect(Collectors.toList());
|
||||
monitorDto.setMetrics(metrics);
|
||||
|
||||
+5
-5
@@ -22,10 +22,10 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.hertzbeat.alert.config.SmsConfig;
|
||||
import org.apache.hertzbeat.common.constants.GeneralConfigTypeEnum;
|
||||
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
|
||||
import org.apache.hertzbeat.common.support.event.SmsConfigChangeEvent;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.SmsNoticeSender;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -35,7 +35,7 @@ import org.springframework.stereotype.Service;
|
||||
*/
|
||||
|
||||
@Service
|
||||
public class SmsGeneralConfigServiceImpl extends AbstractGeneralConfigServiceImpl<SmsNoticeSender> {
|
||||
public class SmsGeneralConfigServiceImpl extends AbstractGeneralConfigServiceImpl<SmsConfig> {
|
||||
@Resource
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@@ -55,7 +55,7 @@ public class SmsGeneralConfigServiceImpl extends AbstractGeneralConfigServiceImp
|
||||
* This method is used to handle the sms configuration change event.
|
||||
*/
|
||||
@Override
|
||||
public void handler(SmsNoticeSender smsNoticeSender) {
|
||||
public void handler(SmsConfig smsConfig) {
|
||||
applicationContext.publishEvent(new SmsConfigChangeEvent(applicationContext));
|
||||
}
|
||||
|
||||
@@ -69,11 +69,11 @@ public class SmsGeneralConfigServiceImpl extends AbstractGeneralConfigServiceImp
|
||||
* a TypeReference of NoticeSender type
|
||||
*/
|
||||
@Override
|
||||
public TypeReference<SmsNoticeSender> getTypeReference() {
|
||||
public TypeReference<SmsConfig> getTypeReference() {
|
||||
return new TypeReference<>() {
|
||||
@Override
|
||||
public Type getType() {
|
||||
return SmsNoticeSender.class;
|
||||
return SmsConfig.class;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -72,14 +72,22 @@ warehouse:
|
||||
rpc-port: 6667
|
||||
username: root
|
||||
password: root
|
||||
redis:
|
||||
enabled: false
|
||||
host: 127.0.0.1
|
||||
port: 6379
|
||||
password: 123456
|
||||
# store real-time metrics data, enable only one below
|
||||
real-time:
|
||||
memory:
|
||||
enabled: true
|
||||
init-size: 16
|
||||
redis:
|
||||
enabled: false
|
||||
# redis mode: single, sentinel, cluster. Default is single
|
||||
mode: single
|
||||
# separate each address with comma when using cluster mode, eg: 127.0.0.1:6379,127.0.0.1:6380
|
||||
address: 127.0.0.1:6379
|
||||
# enter master name when using sentinel mode
|
||||
masterName: mymaster
|
||||
password: 123456
|
||||
# redis db index, default: DB0
|
||||
db: 0
|
||||
|
||||
scheduler:
|
||||
server:
|
||||
@@ -102,4 +110,4 @@ ai:
|
||||
# api key
|
||||
api-key:
|
||||
#At present, only IFLYTEK large model needs to be filled in
|
||||
api-secret:
|
||||
api-secret:
|
||||
|
||||
@@ -231,6 +231,10 @@ alerter:
|
||||
access-key-id: YOUR_ACCESS_KEY_ID
|
||||
access-key-secret: YOUR_ACCESS_KEY_SECRET
|
||||
region: AWS_REGION_FOR_END_USER_MESSAGING
|
||||
twilio:
|
||||
account-sid: YOUR_ACCOUNT_SID
|
||||
auth-token: YOUR_AUTH_TOKEN
|
||||
twilio-phone-number: YOUR_TWILIO_PHONE_NUMBER
|
||||
scheduler:
|
||||
server:
|
||||
enabled: true
|
||||
|
||||
@@ -17,71 +17,44 @@
|
||||
|
||||
-- ensure every sql can rerun without error
|
||||
|
||||
-- add repair table sql
|
||||
REPAIR TABLE HZB_ALERT_DEFINE;
|
||||
|
||||
DELIMITER //
|
||||
CREATE PROCEDURE AddOrDropColumns()
|
||||
BEGIN
|
||||
DECLARE col_exists INT;
|
||||
|
||||
-- Check and add 'app' column if it does not exist
|
||||
SELECT COUNT(*) INTO col_exists FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='HZB_ALERT_DEFINE' AND COLUMN_NAME='app';
|
||||
IF col_exists = 0 THEN
|
||||
ALTER TABLE HZB_ALERT_DEFINE ADD COLUMN app VARCHAR(255);
|
||||
END IF;
|
||||
-- Drop 'app' column if it exists
|
||||
SELECT COUNT(*) INTO col_exists FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='HZB_ALERT_DEFINE' AND COLUMN_NAME='app';
|
||||
IF col_exists = 1 THEN
|
||||
ALTER TABLE HZB_ALERT_DEFINE DROP COLUMN app;
|
||||
END IF;
|
||||
|
||||
-- Check and add 'metric' column if it does not exist
|
||||
SELECT COUNT(*) INTO col_exists FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='HZB_ALERT_DEFINE' AND COLUMN_NAME='metric';
|
||||
IF col_exists = 0 THEN
|
||||
ALTER TABLE HZB_ALERT_DEFINE ADD COLUMN metric VARCHAR(255);
|
||||
END IF;
|
||||
-- Drop 'metric' column if it exists
|
||||
SELECT COUNT(*) INTO col_exists FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='HZB_ALERT_DEFINE' AND COLUMN_NAME='metric';
|
||||
IF col_exists = 1 THEN
|
||||
ALTER TABLE HZB_ALERT_DEFINE DROP COLUMN metric;
|
||||
END IF;
|
||||
|
||||
-- Check and add 'field' column if it does not exist
|
||||
SELECT COUNT(*) INTO col_exists FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='HZB_ALERT_DEFINE' AND COLUMN_NAME='field';
|
||||
IF col_exists = 0 THEN
|
||||
ALTER TABLE HZB_ALERT_DEFINE ADD COLUMN field VARCHAR(255);
|
||||
END IF;
|
||||
-- Drop 'field' column if it exists
|
||||
SELECT COUNT(*) INTO col_exists FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='HZB_ALERT_DEFINE' AND COLUMN_NAME='field';
|
||||
IF col_exists = 1 THEN
|
||||
ALTER TABLE HZB_ALERT_DEFINE DROP COLUMN field;
|
||||
END IF;
|
||||
|
||||
-- Check and add 'preset' column if it does not exist
|
||||
SELECT COUNT(*) INTO col_exists FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='HZB_ALERT_DEFINE' AND COLUMN_NAME='preset';
|
||||
IF col_exists = 0 THEN
|
||||
ALTER TABLE HZB_ALERT_DEFINE ADD COLUMN preset BOOLEAN;
|
||||
END IF;
|
||||
-- Drop 'preset' column if it exists
|
||||
SELECT COUNT(*) INTO col_exists FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='HZB_ALERT_DEFINE' AND COLUMN_NAME='preset';
|
||||
IF col_exists = 1 THEN
|
||||
ALTER TABLE HZB_ALERT_DEFINE DROP COLUMN preset;
|
||||
END IF;
|
||||
|
||||
-- Check and add 'priority' column if it does not exist
|
||||
SELECT COUNT(*) INTO col_exists FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='HZB_ALERT_DEFINE' AND COLUMN_NAME='priority';
|
||||
IF col_exists = 0 THEN
|
||||
ALTER TABLE HZB_ALERT_DEFINE ADD COLUMN priority INTEGER;
|
||||
END IF;
|
||||
-- Drop 'priority' column if it exists
|
||||
SELECT COUNT(*) INTO col_exists FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='HZB_ALERT_DEFINE' AND COLUMN_NAME='priority';
|
||||
IF col_exists = 1 THEN
|
||||
ALTER TABLE HZB_ALERT_DEFINE DROP COLUMN priority;
|
||||
END IF;
|
||||
|
||||
-- Check and add 'tags' column if it does not exist
|
||||
SELECT COUNT(*) INTO col_exists FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='HZB_ALERT_DEFINE' AND COLUMN_NAME='tags';
|
||||
IF col_exists = 0 THEN
|
||||
ALTER TABLE HZB_ALERT_DEFINE ADD COLUMN tags VARCHAR(255);
|
||||
END IF;
|
||||
-- Drop 'tags' column if it exists
|
||||
SELECT COUNT(*) INTO col_exists FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='HZB_ALERT_DEFINE' AND COLUMN_NAME='tags';
|
||||
IF col_exists = 1 THEN
|
||||
@@ -93,4 +66,5 @@ DELIMITER ;
|
||||
|
||||
|
||||
CALL AddOrDropColumns();
|
||||
DROP PROCEDURE IF EXISTS AddOrDropColumns;
|
||||
DROP PROCEDURE IF EXISTS AddOrDropColumns;
|
||||
commit;
|
||||
|
||||
@@ -246,6 +246,20 @@ params:
|
||||
defaultValue: '200, 201'
|
||||
# hide param-true or false
|
||||
hide: true
|
||||
# field-param field key
|
||||
- field: enableUrlEncoding
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 启用URL编码(UTF-8)
|
||||
en-US: Enable URL Encoding(UTF-8)
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: boolean
|
||||
# required-true or false
|
||||
required: true
|
||||
# default value
|
||||
defaultValue: true
|
||||
# hide param-true or false
|
||||
hide: true
|
||||
# collect metrics config list
|
||||
metrics:
|
||||
# metrics - summary
|
||||
@@ -286,6 +300,8 @@ metrics:
|
||||
method: ^_^httpMethod^_^
|
||||
# if enabled https
|
||||
ssl: ^_^ssl^_^
|
||||
# enable url encoding
|
||||
enableUrlEncoding: ^_^enableUrlEncoding^_^
|
||||
# http request payload
|
||||
payload: ^_^payload^_^
|
||||
# http request header content
|
||||
@@ -344,6 +360,8 @@ metrics:
|
||||
method: GET
|
||||
# if enabled https
|
||||
ssl: ^_^ssl^_^
|
||||
# enable url encoding
|
||||
enableUrlEncoding: ^_^enableUrlEncoding^_^
|
||||
authorization:
|
||||
# http auth type: Basic Auth, Digest Auth, Bearer Token
|
||||
type: ^_^authType^_^
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
# 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 category:service-application service monitoring db-database monitoring custom-custom monitoring os-operating system monitoring
|
||||
category: server
|
||||
# The monitoring type eg: linux windows tomcat mysql aws...
|
||||
app: dahua
|
||||
# The monitoring i18n name
|
||||
name:
|
||||
zh-CN: 大华
|
||||
en-US: Dahua
|
||||
# The description and help of this monitoring type
|
||||
help:
|
||||
zh-CN: 通过http接口监控大华设备状态,获取设备健康数据。
|
||||
en-US: Monitor Dahua devices through http interface to collect health data.
|
||||
|
||||
# Input params define for monitoring(render web ui by the definition)
|
||||
params:
|
||||
- field: host
|
||||
name:
|
||||
zh-CN: 主机Host
|
||||
en-US: Host
|
||||
type: host
|
||||
required: true
|
||||
- field: port
|
||||
name:
|
||||
zh-CN: 端口
|
||||
en-US: Port
|
||||
type: number
|
||||
range: '[0,65535]'
|
||||
required: true
|
||||
defaultValue: 80
|
||||
- field: timeout
|
||||
name:
|
||||
zh-CN: 超时时间(ms)
|
||||
en-US: Timeout(ms)
|
||||
type: number
|
||||
range: '[1000,60000]'
|
||||
required: true
|
||||
defaultValue: 5000
|
||||
- field: username
|
||||
name:
|
||||
zh-CN: 用户名
|
||||
en-US: Username
|
||||
type: text
|
||||
required: true
|
||||
- field: password
|
||||
name:
|
||||
zh-CN: 密码
|
||||
en-US: Password
|
||||
type: password
|
||||
required: true
|
||||
- field: ssl
|
||||
name:
|
||||
zh-CN: 启用HTTPS
|
||||
en-US: SSL
|
||||
type: boolean
|
||||
required: false
|
||||
defaultValue: false
|
||||
|
||||
# collect metrics config list
|
||||
metrics:
|
||||
- name: network_info # 指标集合名称
|
||||
i18n:
|
||||
zh-CN: 网络信息
|
||||
en-US: Network Info
|
||||
priority: 0
|
||||
protocol: http
|
||||
http:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
ssl: ^_^ssl^_^
|
||||
url: /cgi-bin/configManager.cgi?action=getConfig&name=Network
|
||||
method: GET
|
||||
timeout: ^_^timeout^_^
|
||||
authorization:
|
||||
type: Digest Auth
|
||||
digestAuthUsername: ^_^username^_^
|
||||
digestAuthPassword: ^_^password^_^
|
||||
parseType: config
|
||||
fields:
|
||||
- field: default_interface
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 默认网卡
|
||||
en-US: Default Interface
|
||||
- field: domain_name
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 域名
|
||||
en-US: Domain Name
|
||||
- field: hostname
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 主机名
|
||||
en-US: Hostname
|
||||
- field: eth0_ip_address
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 网卡 eth0 IP地址
|
||||
en-US: Interface eth0 IP Address
|
||||
- field: eth0_gateway
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 网卡 eth0 默认网关
|
||||
en-US: Interface eth0 Default Gateway
|
||||
- field: eth0_mac_address
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 网卡 eth0 物理地址
|
||||
en-US: Interface eth0 Physical Address
|
||||
- field: eth0_subnet_mask
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 网卡 eth0 子网掩码
|
||||
en-US: Interface eth0 Subnet Mask
|
||||
- field: eth0_mtu
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: 网卡 eth0 MTU
|
||||
en-US: Interface eth0 MTU
|
||||
- field: eth0_dns1
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 网卡 eth0 DNS服务器1
|
||||
en-US: Interface eth0 DNS Server 1
|
||||
- field: eth0_dns2
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 网卡 eth0 DNS服务器2
|
||||
en-US: Interface eth0 DNS Server 2
|
||||
aliasFields:
|
||||
- table.Network.DefaultInterface
|
||||
- table.Network.Domain
|
||||
- table.Network.Hostname
|
||||
- table.Network.eth0.IPAddress
|
||||
- table.Network.eth0.DefaultGateway
|
||||
- table.Network.eth0.PhysicalAddress
|
||||
- table.Network.eth0.SubnetMask
|
||||
- table.Network.eth0.MTU
|
||||
- table.Network.eth0.DnsServers[0]
|
||||
- table.Network.eth0.DnsServers[1]
|
||||
calculates:
|
||||
- default_interface=table.Network.DefaultInterface
|
||||
- domain_name=table.Network.Domain
|
||||
- hostname=table.Network.Hostname
|
||||
- eth0_ip_address=table.Network.eth0.IPAddress
|
||||
- eth0_gateway=table.Network.eth0.DefaultGateway
|
||||
- eth0_mac_address=table.Network.eth0.PhysicalAddress
|
||||
- eth0_subnet_mask=table.Network.eth0.SubnetMask
|
||||
- eth0_mtu=table.Network.eth0.MTU
|
||||
- eth0_dns1=table.Network.eth0.DnsServers[0]
|
||||
- eth0_dns2=table.Network.eth0.DnsServers[1]
|
||||
- name: user_info
|
||||
i18n:
|
||||
zh-CN: 用户信息
|
||||
en-US: User Info
|
||||
priority: 0
|
||||
protocol: http
|
||||
http:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
ssl: ^_^ssl^_^
|
||||
url: /cgi-bin/userManager.cgi?action=getActiveUserInfoAll
|
||||
method: GET
|
||||
timeout: ^_^timeout^_^
|
||||
authorization:
|
||||
type: Digest Auth
|
||||
digestAuthUsername: ^_^username^_^
|
||||
digestAuthPassword: ^_^password^_^
|
||||
parseType: config
|
||||
parseScript: users
|
||||
fields:
|
||||
- field: ClientAddress
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 客户端地址
|
||||
en-US: ClientAddress
|
||||
- field: Name
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 客户端用户
|
||||
en-US: Name
|
||||
- field: ClientType
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 客户端登录类型
|
||||
en-US: ClientType
|
||||
- field: LoginTime
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 客户端登录时间
|
||||
en-US: LoginTime
|
||||
- name: ntp_info
|
||||
i18n:
|
||||
zh-CN: 校时信息
|
||||
en-US: Ntp Info
|
||||
priority: 0
|
||||
protocol: http
|
||||
http:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
ssl: ^_^ssl^_^
|
||||
url: /cgi-bin/configManager.cgi?action=getConfig&name=NTP
|
||||
method: GET
|
||||
timeout: ^_^timeout^_^
|
||||
authorization:
|
||||
type: Digest Auth
|
||||
digestAuthUsername: ^_^username^_^
|
||||
digestAuthPassword: ^_^password^_^
|
||||
parseType: config
|
||||
fields:
|
||||
- field: ntp_address
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 校时服务器
|
||||
en-US: Ntp Address
|
||||
- field: ntp_port
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 校时端口
|
||||
en-US: Ntp Port
|
||||
- field: ntp_update_period
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 校时间隔
|
||||
en-US: Ntp Update Period
|
||||
aliasFields:
|
||||
- table.NTP.Address
|
||||
- table.NTP.Port
|
||||
- table.NTP.UpdatePeriod
|
||||
calculates:
|
||||
- ntp_address=table.NTP.Address
|
||||
- ntp_port=table.NTP.Port
|
||||
- ntp_update_period=table.NTP.UpdatePeriod
|
||||
@@ -0,0 +1,268 @@
|
||||
# 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 category:service-application service monitoring db-database monitoring custom-custom monitoring os-operating system monitoring
|
||||
category: server
|
||||
# The monitoring type eg: linux windows tomcat mysql aws...
|
||||
app: hikvision_isapi
|
||||
# The monitoring i18n name
|
||||
name:
|
||||
zh-CN: 海康威视 ISAPI
|
||||
en-US: Hikvision ISAPI
|
||||
# The description and help of this monitoring type
|
||||
help:
|
||||
zh-CN: 通过ISAPI接口监控海康威视设备状态,获取设备健康数据。
|
||||
en-US: Monitor Hikvision devices through ISAPI interface to collect health data.
|
||||
|
||||
# Input params define for monitoring(render web ui by the definition)
|
||||
params:
|
||||
- field: host
|
||||
name:
|
||||
zh-CN: 主机Host
|
||||
en-US: Host
|
||||
type: host
|
||||
required: true
|
||||
- field: port
|
||||
name:
|
||||
zh-CN: 端口
|
||||
en-US: Port
|
||||
type: number
|
||||
range: '[0,65535]'
|
||||
required: true
|
||||
defaultValue: 80
|
||||
- field: timeout
|
||||
name:
|
||||
zh-CN: 超时时间(ms)
|
||||
en-US: Timeout(ms)
|
||||
type: number
|
||||
range: '[1000,60000]'
|
||||
required: true
|
||||
defaultValue: 5000
|
||||
- field: username
|
||||
name:
|
||||
zh-CN: 用户名
|
||||
en-US: Username
|
||||
type: text
|
||||
required: true
|
||||
- field: password
|
||||
name:
|
||||
zh-CN: 密码
|
||||
en-US: Password
|
||||
type: password
|
||||
required: true
|
||||
- field: ssl
|
||||
name:
|
||||
zh-CN: 启用HTTPS
|
||||
en-US: SSL
|
||||
type: boolean
|
||||
required: false
|
||||
defaultValue: false
|
||||
|
||||
# collect metrics config list
|
||||
metrics:
|
||||
- name: system_info
|
||||
i18n:
|
||||
zh-CN: 系统信息
|
||||
en-US: System Info
|
||||
priority: 0
|
||||
protocol: http
|
||||
http:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
ssl: ^_^ssl^_^
|
||||
url: /ISAPI/System/deviceInfo
|
||||
method: GET
|
||||
timeout: ^_^timeout^_^
|
||||
authorization:
|
||||
type: Digest Auth
|
||||
digestAuthUsername: ^_^username^_^
|
||||
digestAuthPassword: ^_^password^_^
|
||||
parseType: xmlPath
|
||||
parseScript: //DeviceInfo
|
||||
fields:
|
||||
- field: deviceName
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 设备名称
|
||||
en-US: Device Name
|
||||
- field: deviceID
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 设备ID
|
||||
en-US: Device ID
|
||||
- field: firmwareVersion
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 固件版本
|
||||
en-US: Firmware Version
|
||||
- field: model
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 设备型号
|
||||
en-US: Device Model
|
||||
- field: macAddress
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: mac地址
|
||||
en-US: Mac Address
|
||||
- name: status
|
||||
i18n:
|
||||
zh-CN: 设备状态
|
||||
en-US: Status
|
||||
priority: 0
|
||||
protocol: http
|
||||
http:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
ssl: ^_^ssl^_^
|
||||
url: /ISAPI/System/status
|
||||
method: GET
|
||||
timeout: ^_^timeout^_^
|
||||
authorization:
|
||||
type: Digest Auth
|
||||
digestAuthUsername: ^_^username^_^
|
||||
digestAuthPassword: ^_^password^_^
|
||||
parseType: xmlPath
|
||||
parseScript: //DeviceStatus
|
||||
fields:
|
||||
- field: CPU_utilization
|
||||
i18n:
|
||||
zh-CN: CPU 利用率
|
||||
en-US: CPU Utilization
|
||||
type: 0
|
||||
unit: '%'
|
||||
- field: memory_usage
|
||||
i18n:
|
||||
zh-CN: 内存使用量
|
||||
en-US: Memory Usage
|
||||
type: 0
|
||||
unit: MB
|
||||
- field: memory_available
|
||||
i18n:
|
||||
zh-CN: 可用内存
|
||||
en-US: Memory Available
|
||||
type: 0
|
||||
unit: MB
|
||||
- field: cache_size
|
||||
i18n:
|
||||
zh-CN: 缓存大小
|
||||
en-US: Cache Size
|
||||
type: 0
|
||||
unit: MB
|
||||
- field: net_port_1_speed
|
||||
i18n:
|
||||
zh-CN: 网口1速度
|
||||
en-US: Net Port 1 Speed
|
||||
type: 0
|
||||
unit: Mbps
|
||||
- field: net_port_2_speed
|
||||
i18n:
|
||||
zh-CN: 网口2速度
|
||||
en-US: Net Port 2 Speed
|
||||
type: 0
|
||||
unit: Mbps
|
||||
- field: boot_time
|
||||
i18n:
|
||||
zh-CN: 启动时间
|
||||
en-US: Boot Time
|
||||
type: 1
|
||||
- field: device_uptime
|
||||
i18n:
|
||||
zh-CN: 运行时长
|
||||
en-US: Device Uptime
|
||||
type: 1
|
||||
- field: last_calibration_time
|
||||
i18n:
|
||||
zh-CN: 上次校时时间
|
||||
en-US: Last Calibration Time
|
||||
type: 1
|
||||
- field: last_calibration_time_diff
|
||||
i18n:
|
||||
zh-CN: 上次校时时间差
|
||||
en-US: Last Calibration Time Diff
|
||||
type: 0
|
||||
unit: s
|
||||
- field: avg_upload_time
|
||||
i18n:
|
||||
zh-CN: 平均上传耗时
|
||||
en-US: Avg Upload Time
|
||||
type: 0
|
||||
unit: ms
|
||||
- field: max_upload_time
|
||||
i18n:
|
||||
zh-CN: 最大上传耗时
|
||||
en-US: Max Upload Time
|
||||
type: 0
|
||||
unit: ms
|
||||
- field: min_upload_time
|
||||
i18n:
|
||||
zh-CN: 最小上传耗时
|
||||
en-US: Min Upload Time
|
||||
type: 0
|
||||
unit: ms
|
||||
- field: last_calibration_mode
|
||||
i18n:
|
||||
zh-CN: 上次校时模式
|
||||
en-US: Last Calibration Mode
|
||||
type: 1
|
||||
- field: last_calibration_address
|
||||
i18n:
|
||||
zh-CN: 上次校时地址
|
||||
en-US: Last Calibration Address
|
||||
type: 1
|
||||
- field: response_time
|
||||
i18n:
|
||||
zh-CN: 响应时间
|
||||
en-US: Response Time
|
||||
type: 0
|
||||
unit: ms
|
||||
aliasFields:
|
||||
- CPUList/CPU/cpuUtilization
|
||||
- MemoryList/Memory/memoryUsage
|
||||
- MemoryList/Memory/memoryAvailable
|
||||
- MemoryList/Memory/cacheSize
|
||||
- NetPortStatusList/NetPortStatus[id='1']/workSpeed
|
||||
- NetPortStatusList/NetPortStatus[id='2']/workSpeed
|
||||
- bootTime
|
||||
- deviceUpTime
|
||||
- lastCalibrationTime
|
||||
- lastCalibrationTimeDiff
|
||||
- uploadTimeConsumingList/avgTime
|
||||
- uploadTimeConsumingList/maxTime
|
||||
- uploadTimeConsumingList/minTime
|
||||
- lastCalibrationTimeMode
|
||||
- lastCalibrationTimeAddress
|
||||
- responseTime
|
||||
calculates:
|
||||
- CPU_utilization=CPUList/CPU/cpuUtilization
|
||||
- memory_usage=MemoryList/Memory/memoryUsage
|
||||
- memory_available=MemoryList/Memory/memoryAvailable
|
||||
- cache_size=MemoryList/Memory/cacheSize
|
||||
- net_port_1_speed=NetPortStatusList/NetPortStatus[id='1']/workSpeed
|
||||
- net_port_2_speed=NetPortStatusList/NetPortStatus[id='2']/workSpeed
|
||||
- boot_time=bootTime
|
||||
- device_uptime=deviceUpTime
|
||||
- last_calibration_time=lastCalibrationTime
|
||||
- last_calibration_time_diff=lastCalibrationTimeDiff
|
||||
- avg_upload_time=uploadTimeConsumingList/avgTime
|
||||
- max_upload_time=uploadTimeConsumingList/maxTime
|
||||
- min_upload_time=uploadTimeConsumingList/minTime
|
||||
- last_calibration_mode=lastCalibrationTimeMode
|
||||
- last_calibration_address=lastCalibrationTimeAddress
|
||||
- response_time=responseTime
|
||||
units:
|
||||
- memory_usage=KB->MB
|
||||
- memory_available=KB->MB
|
||||
- cache_size=KB->MB
|
||||
@@ -0,0 +1,263 @@
|
||||
# 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 category:service-application service monitoring db-database monitoring mid-middleware custom-custom monitoring os-operating system monitoring
|
||||
category: service
|
||||
# The monitoring type eg: linux windows tomcat mysql aws...
|
||||
app: s7
|
||||
# The app api i18n name
|
||||
name:
|
||||
zh-CN: s7服务器
|
||||
en-US: s7 Server
|
||||
# The description and help of this monitoring type
|
||||
help:
|
||||
zh-CN: HertzBeat对支持s7协议的服务进行(保持寄存器和线圈)相关指标进行采集
|
||||
en-US: HertzBeat collects metrics related to maintaining registers and coils for services that support s7 protocol
|
||||
zh-TW: HertzBeat對支持s7協定的服務進行(保持寄存器和線圈)相關名額進行採集
|
||||
# Input params define for monitoring(render web ui by the definition)
|
||||
params:
|
||||
# field-param field key
|
||||
- field: host
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: s7服务Host
|
||||
en-US: s7 Server Host
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: host
|
||||
# required-true or false
|
||||
required: true
|
||||
# field-param field key
|
||||
- field: port
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 端口
|
||||
en-US: Port
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: number
|
||||
# when type is number, range is required
|
||||
range: '[0,65535]'
|
||||
# required-true or false
|
||||
required: true
|
||||
# default value
|
||||
defaultValue: 102
|
||||
- field: rackId
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: rackId
|
||||
en-US: rackId
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: text
|
||||
# required-true or false
|
||||
required: false
|
||||
defaultValue: 0
|
||||
- field: slotId
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: slotId
|
||||
en-US: slotId
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: text
|
||||
# required-true or false
|
||||
required: false
|
||||
defaultValue: 0
|
||||
- field: controllerType
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: controllerType
|
||||
en-US: controllerType
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: text
|
||||
# required-true or false
|
||||
required: false
|
||||
defaultValue: S7_1500
|
||||
- field: timeout
|
||||
name:
|
||||
zh-CN: 请求超时时间(ms)
|
||||
en-US: Request Timeout(ms)
|
||||
type: number
|
||||
# when type is number, range is required
|
||||
range: '[400,200000]'
|
||||
required: false
|
||||
defaultValue: 6000
|
||||
hide: true
|
||||
- field: holdingRegisterAddresses
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 保持寄存器地址
|
||||
en-US: Holding Registers address
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: array
|
||||
# param field input placeholder
|
||||
placeholder: 'Input RegisterAddress'
|
||||
# required-true or false
|
||||
required: true
|
||||
# hide param-true or false
|
||||
# hide: true
|
||||
- field: coilRegisterAddresses
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 线圈寄存器地址
|
||||
en-US: Coil Register address
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: array
|
||||
# param field input placeholder
|
||||
placeholder: 'Input RegisterAddress'
|
||||
# required-true or false
|
||||
required: true
|
||||
# hide param-true or false
|
||||
# hide: true
|
||||
|
||||
# collect metrics config list
|
||||
metrics:
|
||||
# metrics - summary
|
||||
- name: holding-register
|
||||
i18n:
|
||||
zh-CN: 保持寄存器 统计信息
|
||||
en-US: holding-register stats
|
||||
# metrics scheduling priority(0->127)->(high->low), metrics with the same priority will be scheduled in parallel
|
||||
# priority 0's metrics is availability metrics, it will be scheduled first, only availability metrics collect success will the scheduling continue
|
||||
priority: 0
|
||||
# collect metrics content
|
||||
fields:
|
||||
- field: responseTime
|
||||
type: 0
|
||||
unit: ms
|
||||
i18n:
|
||||
zh-CN: 响应时间
|
||||
en-US: Response Time
|
||||
# field-metric name, type-metric type(0-number,1-string), unit-metric unit('%','ms','MB'), label-whether it is a metrics label field
|
||||
- field: address-0
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: address-0
|
||||
en-US: address-0
|
||||
# field-metric name, type-metric type(0-number,1-string), unit-metric unit('%','ms','MB'), label-whether it is a metrics label field
|
||||
- field: address-1
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: address-1
|
||||
en-US: address-1
|
||||
- field: address-2
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: address-2
|
||||
en-US: address-2
|
||||
- field: address-3
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: address-3
|
||||
en-US: address-3
|
||||
# 指标别名列表,按照寄存器地址来进行命名的
|
||||
# 例如 地址为下标为0 ,值为m holding-register:0
|
||||
# 地址为下标为1,值为m[2] holding-register:1-0 holding-register:1-1
|
||||
aliasFields:
|
||||
- responseTime
|
||||
- holding-register:0
|
||||
- holding-register:1
|
||||
- holding-register:2
|
||||
- holding-register:3
|
||||
# mapping and conversion expressions, use thesand aliasField above to calculate metrics value# (可选)指标映射转换计算表达式,与上面的别名一起作用,计算出最终需要的指标值# eg: cores=core1+core2, usage=usage, waitTimeallTime-runningTime
|
||||
calculates:
|
||||
- responseTime=responseTime
|
||||
- address-0=holding-register:0
|
||||
- address-1=holding-register:1
|
||||
- address-2=holding-register:2
|
||||
- address-3=holding-register:3
|
||||
protocol: s7
|
||||
# the config content when protocol is http
|
||||
s7:
|
||||
# host
|
||||
host: ^_^host^_^
|
||||
# port
|
||||
port: ^_^port^_^
|
||||
driverName: s7
|
||||
addressSyntax: holding-register
|
||||
rackId: ^_^rackId^_^
|
||||
slotId: ^_^slotId^_^
|
||||
controllerType: ^_^controllerType^_^
|
||||
timeout: ^_^timeout^_^
|
||||
registerAddresses: [ ^_^holdingRegisterAddresses^_^ ]
|
||||
|
||||
# metrics - summary
|
||||
- name: coil
|
||||
i18n:
|
||||
zh-CN: 线圈 统计信息
|
||||
en-US: coil stats
|
||||
# metrics scheduling priority(0->127)->(high->low), metrics with the same priority will be scheduled in parallel
|
||||
# priority 0's metrics is availability metrics, it will be scheduled first, only availability metrics collect success will the scheduling continue
|
||||
priority: 1
|
||||
# collect metrics content
|
||||
fields:
|
||||
- field: responseTime
|
||||
type: 0
|
||||
unit: ms
|
||||
i18n:
|
||||
zh-CN: 响应时间
|
||||
en-US: Response Time
|
||||
# field-metric name, type-metric type(0-number,1-string), unit-metric unit('%','ms','MB'), label-whether it is a metrics label field
|
||||
- field: address-0
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: address-0
|
||||
en-US: address-0
|
||||
# field-metric name, type-metric type(0-number,1-string), unit-metric unit('%','ms','MB'), label-whether it is a metrics label field
|
||||
- field: address-1
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: address-1
|
||||
en-US: address-1
|
||||
- field: address-2
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: address-2
|
||||
en-US: address-2
|
||||
- field: address-3
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: address-3
|
||||
en-US: address-3
|
||||
# 指标别名列表,按照寄存器地址来进行命名的
|
||||
# 例如 地址为下标为0 ,值为m coil:0
|
||||
# 地址为下标为1,值为m[2] coil:1-0 coil:1-1
|
||||
aliasFields:
|
||||
- responseTime
|
||||
- coil:0
|
||||
- coil:1
|
||||
- coil:2
|
||||
- coil:3
|
||||
# mapping and conversion expressions, use thesand aliasField above to calculate metrics value# (可选)指标映射转换计算表达式,与上面的别名一起作用,计算出最终需要的指标值# eg: cores=core1+core2, usage=usage, waitTimeallTime-runningTime
|
||||
calculates:
|
||||
- responseTime=responseTime
|
||||
- address-0=coil:0
|
||||
- address-1=coil:1
|
||||
- address-2=coil:2
|
||||
- address-3=coil:3
|
||||
protocol: s7
|
||||
# the config content when protocol is http
|
||||
s7:
|
||||
# host
|
||||
host: ^_^host^_^
|
||||
# port
|
||||
port: ^_^port^_^
|
||||
driverName: s7
|
||||
addressSyntax: coil
|
||||
rackId: ^_^rackId^_^
|
||||
slotId: ^_^slotId^_^
|
||||
controllerType: ^_^controllerType^_^
|
||||
timeout: ^_^timeout^_^
|
||||
registerAddresses: [ ^_^coilRegisterAddresses^_^ ]
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
# 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 category:service-application service monitoring db-database monitoring custom-custom monitoring os-operating system monitoring
|
||||
category: server
|
||||
# The monitoring type eg: linux windows tomcat mysql aws...
|
||||
app: uniview
|
||||
# The monitoring i18n name
|
||||
name:
|
||||
zh-CN: 宇视
|
||||
en-US: Uniview
|
||||
# The description and help of this monitoring type
|
||||
help:
|
||||
zh-CN: 通过HTTP接口监控宇视设备状态,获取设备健康数据。
|
||||
en-US: Monitor Uniview devices through HTTP interface to collect health data.
|
||||
|
||||
# Input params define for monitoring(render web ui by the definition)
|
||||
params:
|
||||
- field: host
|
||||
name:
|
||||
zh-CN: 主机Host
|
||||
en-US: Host
|
||||
type: host
|
||||
required: true
|
||||
- field: port
|
||||
name:
|
||||
zh-CN: 端口
|
||||
en-US: Port
|
||||
type: number
|
||||
range: '[0,65535]'
|
||||
required: true
|
||||
defaultValue: 80
|
||||
- field: timeout
|
||||
name:
|
||||
zh-CN: 超时时间(ms)
|
||||
en-US: Timeout(ms)
|
||||
type: number
|
||||
range: '[1000,60000]'
|
||||
required: true
|
||||
defaultValue: 5000
|
||||
- field: username
|
||||
name:
|
||||
zh-CN: 用户名
|
||||
en-US: Username
|
||||
type: text
|
||||
required: true
|
||||
- field: password
|
||||
name:
|
||||
zh-CN: 密码
|
||||
en-US: Password
|
||||
type: password
|
||||
required: true
|
||||
- field: ssl
|
||||
name:
|
||||
zh-CN: 启用HTTPS
|
||||
en-US: SSL
|
||||
type: boolean
|
||||
required: false
|
||||
defaultValue: false
|
||||
|
||||
# collect metrics config list
|
||||
metrics:
|
||||
- name: system_info
|
||||
i18n:
|
||||
zh-CN: 系统信息
|
||||
en-US: System Info
|
||||
priority: 0
|
||||
protocol: http
|
||||
http:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
ssl: ^_^ssl^_^
|
||||
url: /LAPI/V1.0/System/DeviceInfo
|
||||
method: GET
|
||||
timeout: ^_^timeout^_^
|
||||
authorization:
|
||||
type: Digest Auth
|
||||
digestAuthUsername: ^_^username^_^
|
||||
digestAuthPassword: ^_^password^_^
|
||||
parseType: jsonPath
|
||||
parseScript: '$.Response.Data'
|
||||
fields:
|
||||
- field: DeviceName
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 设备名称
|
||||
en-US: Device Name
|
||||
- field: SerialNumber
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 序列号
|
||||
en-US: SerialNumber
|
||||
- field: FirmwareVersion
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 固件版本
|
||||
en-US: Firmware Version
|
||||
- field: DeviceModel
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 设备型号
|
||||
en-US: Device Model
|
||||
- name: ntp_info
|
||||
i18n:
|
||||
zh-CN: 校时信息
|
||||
en-US: NTP Info
|
||||
priority: 0
|
||||
protocol: http
|
||||
http:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
ssl: ^_^ssl^_^
|
||||
url: /LAPI/V1.0/System/Time/NTP
|
||||
method: GET
|
||||
timeout: ^_^timeout^_^
|
||||
authorization:
|
||||
type: Digest Auth
|
||||
digestAuthUsername: ^_^username^_^
|
||||
digestAuthPassword: ^_^password^_^
|
||||
parseType: jsonPath
|
||||
parseScript: '$.Response.Data.NTPServerInfos[0]'
|
||||
fields:
|
||||
- field: IPAddress
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: IP地址
|
||||
en-US: IPAddress
|
||||
- field: Port
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 端口号
|
||||
en-US: Port
|
||||
- field: SynchronizeInterval
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 同步间隔
|
||||
en-US: SynchronizeInterval
|
||||
- field: Enabled
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 是否启用
|
||||
en-US: Enabled
|
||||
@@ -76,6 +76,26 @@
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<!-- OpenTelemetry Appender for shipping logs to GrepTimeDB -->
|
||||
<appender name="OpenTelemetryAppender" class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender">
|
||||
<!-- Capture source code information (class name, method name, line number) -->
|
||||
<captureCodeAttributes>true</captureCodeAttributes>
|
||||
|
||||
<!-- Capture MDC context values -->
|
||||
<captureMdcAttributes>
|
||||
<pattern>.*</pattern>
|
||||
</captureMdcAttributes>
|
||||
|
||||
<!-- Capture experimental attributes like exception details -->
|
||||
<captureExperimentalAttributes>true</captureExperimentalAttributes>
|
||||
|
||||
<!-- Capture marker attributes -->
|
||||
<captureMarkerAttribute>true</captureMarkerAttribute>
|
||||
|
||||
<!-- Capture logger context attributes -->
|
||||
<captureLoggerContext>true</captureLoggerContext>
|
||||
</appender>
|
||||
|
||||
<!-- Settings for this logger: for example, all output logs under the org.springframework package must be at level info or above to be output! -->
|
||||
<!-- This can avoid outputting many common debug information of the spring framework! -->
|
||||
<logger name="org.springframework" level="info"/>
|
||||
@@ -90,12 +110,14 @@
|
||||
<logger name="org.mongodb" level="warn"/>
|
||||
<logger name="io.greptime" level="warn"/>
|
||||
<logger name="org.apache.kafka" level="warn"/>
|
||||
<logger name="io.opentelemetry" level="info"/>
|
||||
|
||||
<!-- Production environment configuration -->
|
||||
<springProfile name="prod">
|
||||
<root level="INFO">
|
||||
<appender-ref ref="SystemOutFileAppender"/>
|
||||
<appender-ref ref="ErrOutFileAppender"/>
|
||||
<appender-ref ref="OpenTelemetryAppender"/>
|
||||
</root>
|
||||
<!-- North log -->
|
||||
<Logger name="com.obs.services.AbstractClient" level="OFF"
|
||||
@@ -118,6 +140,7 @@
|
||||
<appender-ref ref="ConsoleAppender"/>
|
||||
<appender-ref ref="SystemOutFileAppender"/>
|
||||
<appender-ref ref="ErrOutFileAppender"/>
|
||||
<appender-ref ref="OpenTelemetryAppender"/>
|
||||
</root>
|
||||
</springProfile>
|
||||
|
||||
@@ -127,6 +150,7 @@
|
||||
<appender-ref ref="ConsoleAppender"/>
|
||||
<appender-ref ref="SystemOutFileAppender"/>
|
||||
<appender-ref ref="ErrOutFileAppender"/>
|
||||
<appender-ref ref="OpenTelemetryAppender"/>
|
||||
</root>
|
||||
</springProfile>
|
||||
|
||||
@@ -136,7 +160,8 @@
|
||||
<appender-ref ref="ConsoleAppender"/>
|
||||
<appender-ref ref="SystemOutFileAppender"/>
|
||||
<appender-ref ref="ErrOutFileAppender"/>
|
||||
<appender-ref ref="OpenTelemetryAppender"/>
|
||||
</root>
|
||||
</springProfile>
|
||||
|
||||
</configuration>
|
||||
</configuration>
|
||||
+3
-3
@@ -21,9 +21,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.hertzbeat.alert.config.SmsConfig;
|
||||
import org.apache.hertzbeat.common.constants.GeneralConfigTypeEnum;
|
||||
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.SmsNoticeSender;
|
||||
import org.apache.hertzbeat.manager.service.impl.SmsGeneralConfigServiceImpl;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -66,9 +66,9 @@ class SmsGeneralConfigServiceTest {
|
||||
@Test
|
||||
void testGetTypeReference() {
|
||||
|
||||
TypeReference<SmsNoticeSender> typeReference = service.getTypeReference();
|
||||
TypeReference<SmsConfig> typeReference = service.getTypeReference();
|
||||
assertNotNull(typeReference);
|
||||
assertEquals(SmsNoticeSender.class, typeReference.getType());
|
||||
assertEquals(SmsConfig.class, typeReference.getType());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -29,10 +29,11 @@
|
||||
<name>${project.artifactId}</name>
|
||||
|
||||
<dependencies>
|
||||
<!-- common -->
|
||||
<!-- collector basic -->
|
||||
<dependency>
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
<artifactId>hertzbeat-common</artifactId>
|
||||
<artifactId>hertzbeat-collector-basic</artifactId>
|
||||
<version>2.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<!-- spring -->
|
||||
<dependency>
|
||||
|
||||
+18
-17
@@ -17,25 +17,26 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
package org.apache.hertzbeat.push.config;
|
||||
|
||||
import { LabelsInputComponent } from './labels-input.component';
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||
import lombok.Getter;
|
||||
|
||||
describe('LabelsInputComponent', () => {
|
||||
let component: LabelsInputComponent;
|
||||
let fixture: ComponentFixture<LabelsInputComponent>;
|
||||
/**
|
||||
* push error request wrapper
|
||||
*/
|
||||
@Getter
|
||||
public class PushErrorRequestWrapper extends HttpServletRequestWrapper {
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [LabelsInputComponent]
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(LabelsInputComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
private final String job;
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
private final String instance;
|
||||
|
||||
public PushErrorRequestWrapper(HttpServletRequest request, String job, String instance) {
|
||||
super(request);
|
||||
this.job = job;
|
||||
this.instance = instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.push.config;
|
||||
|
||||
import org.apache.hertzbeat.push.service.PushGatewayService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Configuration
|
||||
public class PushFilterConfig {
|
||||
|
||||
@Autowired
|
||||
private PushGatewayService pushGatewayService;
|
||||
|
||||
private static final String URI_PREFIX = "/api/push/prometheus/*";
|
||||
|
||||
@Bean
|
||||
public FilterRegistrationBean<PushPrometheusStreamReadingFilter> contentTypeFilter() {
|
||||
FilterRegistrationBean<PushPrometheusStreamReadingFilter> registrationBean = new FilterRegistrationBean<>();
|
||||
registrationBean.setFilter(new PushPrometheusStreamReadingFilter(pushGatewayService));
|
||||
registrationBean.addUrlPatterns(URI_PREFIX);
|
||||
registrationBean.setOrder(Integer.MIN_VALUE);
|
||||
return registrationBean;
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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.push.config;
|
||||
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.FilterConfig;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
import org.apache.hertzbeat.push.service.PushGatewayService;
|
||||
|
||||
|
||||
/**
|
||||
* todo
|
||||
*/
|
||||
public class PushPrometheusStreamReadingFilter implements Filter {
|
||||
|
||||
private final PushGatewayService pushGatewayService;
|
||||
|
||||
private final Pattern pathPattern = Pattern.compile("^/api/push/prometheus/job/([a-zA-Z0-9_]*)(?:/instance/([a-zA-Z0-9_]*))?$");
|
||||
|
||||
public PushPrometheusStreamReadingFilter(PushGatewayService pushGatewayService) {
|
||||
this.pushGatewayService = pushGatewayService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
if (request instanceof HttpServletRequest httpRequest) {
|
||||
String uri = httpRequest.getRequestURI();
|
||||
Matcher matcher = pathPattern.matcher(uri);
|
||||
String job = null;
|
||||
String instance = null;
|
||||
if (matcher.matches()) {
|
||||
job = matcher.group(1);
|
||||
instance = matcher.group(2);
|
||||
boolean flag = pushGatewayService.pushPrometheusMetrics(request.getInputStream(), job, instance);
|
||||
if (flag) {
|
||||
PushSuccessRequestWrapper successRequestWrapper = new PushSuccessRequestWrapper(httpRequest, job, instance);
|
||||
chain.doFilter(successRequestWrapper, response);
|
||||
} else {
|
||||
PushErrorRequestWrapper errorRequestWrapper = new PushErrorRequestWrapper(httpRequest, job, instance);
|
||||
chain.doFilter(errorRequestWrapper, response);
|
||||
}
|
||||
} else {
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
} else {
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {}
|
||||
}
|
||||
+18
-20
@@ -17,27 +17,25 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
package org.apache.hertzbeat.push.config;
|
||||
|
||||
import { KeyValueInputComponent } from './key-value-input.component';
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||
import lombok.Getter;
|
||||
|
||||
describe('KeyValueInputComponent', () => {
|
||||
let component: KeyValueInputComponent;
|
||||
let fixture: ComponentFixture<KeyValueInputComponent>;
|
||||
/**
|
||||
* push success request wrapper
|
||||
*/
|
||||
@Getter
|
||||
public class PushSuccessRequestWrapper extends HttpServletRequestWrapper {
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [KeyValueInputComponent]
|
||||
}).compileComponents();
|
||||
});
|
||||
private final String job;
|
||||
|
||||
private final String instance;
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(KeyValueInputComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
public PushSuccessRequestWrapper(HttpServletRequest request, String job, String instance) {
|
||||
super(request);
|
||||
this.job = job;
|
||||
this.instance = instance;
|
||||
}
|
||||
}
|
||||
+15
-16
@@ -22,11 +22,9 @@ package org.apache.hertzbeat.push.controller;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import org.apache.hertzbeat.common.entity.dto.Message;
|
||||
import org.apache.hertzbeat.push.service.PushGatewayService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.apache.hertzbeat.push.config.PushErrorRequestWrapper;
|
||||
import org.apache.hertzbeat.push.config.PushSuccessRequestWrapper;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -37,22 +35,23 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
*/
|
||||
@Tag(name = "Metrics Push Gateway API")
|
||||
@RestController
|
||||
@RequestMapping(value = "/api/push/pushgateway")
|
||||
public class PushGatewayController {
|
||||
|
||||
@Autowired
|
||||
private PushGatewayService pushGatewayService;
|
||||
@RequestMapping(value = "/api/push/prometheus/**")
|
||||
public class PushPrometheusController {
|
||||
|
||||
@PostMapping()
|
||||
@Operation(summary = "Push metric data to hertzbeat pushgateway", description = "Push metric data to hertzbeat pushgateway")
|
||||
public ResponseEntity<Message<Void>> pushMetrics(HttpServletRequest request) throws IOException {
|
||||
InputStream inputStream = request.getInputStream();
|
||||
boolean result = pushGatewayService.pushMetricsData(inputStream);
|
||||
if (result) {
|
||||
return ResponseEntity.ok(Message.success("Push success"));
|
||||
@Operation(summary = "Prometheus push gateway", description = "Push prometheus metric data to hertzbeat")
|
||||
public ResponseEntity<Message<Void>> pushMetrics(HttpServletRequest request) {
|
||||
if (request instanceof PushErrorRequestWrapper error) {
|
||||
return ResponseEntity.badRequest().body(Message.success(String.format("Push failed, job: %s, instance: %s",
|
||||
error.getJob(), error.getInstance())));
|
||||
}
|
||||
else if (request instanceof PushSuccessRequestWrapper success) {
|
||||
return ResponseEntity.ok(Message.success(String.format("Push success, job: %s, instance: %s",
|
||||
success.getJob(), success.getInstance())));
|
||||
}
|
||||
else {
|
||||
return ResponseEntity.ok(Message.success("Push failed"));
|
||||
return ResponseEntity.badRequest()
|
||||
.body(Message.success(String.format("Request %s not matched.", request.getRequestURI())));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.push.dao;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.push.PushMetrics;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* push metrics dao
|
||||
*/
|
||||
public interface PushMetricsDao extends JpaRepository<PushMetrics, Long> {
|
||||
|
||||
PushMetrics findFirstByMonitorIdOrderByTimeDesc(Long monitorId);
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
void deleteAllByTimeBefore(Long time);
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
package org.apache.hertzbeat.push.dao;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.hertzbeat.common.entity.manager.Monitor;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
@@ -24,4 +25,11 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
||||
* push monitor dao
|
||||
*/
|
||||
public interface PushMonitorDao extends JpaRepository<Monitor, Long> {
|
||||
|
||||
/**
|
||||
* Find all monitoring entities by type
|
||||
* @param type Monitoring type
|
||||
* @return Monitoring entity list
|
||||
*/
|
||||
List<Monitor> findMonitorsByType(byte type);
|
||||
}
|
||||
|
||||
+10
-3
@@ -19,17 +19,24 @@
|
||||
|
||||
package org.apache.hertzbeat.push.service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* push gateway metrics
|
||||
* push gateway service
|
||||
*/
|
||||
|
||||
@Service
|
||||
public interface PushGatewayService {
|
||||
|
||||
boolean pushMetricsData(InputStream inputStream) throws IOException;
|
||||
|
||||
/**
|
||||
* push prometheus metrics data
|
||||
* @param inputStream input stream
|
||||
* @param job job name, maybe null
|
||||
* @param instance instance name, maybe null
|
||||
* @return push success or not
|
||||
*/
|
||||
boolean pushPrometheusMetrics(InputStream inputStream, String job, String instance);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,31 +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.push.service;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.push.PushMetricsDto;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* push metrics
|
||||
*/
|
||||
@Service
|
||||
public interface PushService {
|
||||
void pushMetricsData(PushMetricsDto pushMetricsData);
|
||||
|
||||
PushMetricsDto getPushMetricData(Long monitorId, Long time);
|
||||
}
|
||||
+96
-3
@@ -19,9 +19,23 @@
|
||||
|
||||
package org.apache.hertzbeat.push.service.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.time.Instant;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.collector.collect.prometheus.parser.MetricFamily;
|
||||
import org.apache.hertzbeat.collector.collect.prometheus.parser.OnlineParser;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.manager.Monitor;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.queue.CommonDataQueue;
|
||||
import org.apache.hertzbeat.common.util.SnowFlakeIdGenerator;
|
||||
import org.apache.hertzbeat.push.dao.PushMonitorDao;
|
||||
import org.apache.hertzbeat.push.service.PushGatewayService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -32,9 +46,88 @@ import org.springframework.stereotype.Service;
|
||||
@Slf4j
|
||||
@Service
|
||||
public class PushGatewayServiceImpl implements PushGatewayService {
|
||||
|
||||
private final CommonDataQueue commonDataQueue;
|
||||
|
||||
private final PushMonitorDao pushMonitorDao;
|
||||
|
||||
private final Map<String, Long> jobInstanceMap;
|
||||
|
||||
public PushGatewayServiceImpl(CommonDataQueue commonDataQueue, PushMonitorDao pushMonitorDao) {
|
||||
this.commonDataQueue = commonDataQueue;
|
||||
this.pushMonitorDao = pushMonitorDao;
|
||||
jobInstanceMap = new ConcurrentHashMap<>();
|
||||
pushMonitorDao.findMonitorsByType((byte) 1).forEach(monitor ->
|
||||
jobInstanceMap.put(monitor.getApp() + "_" + monitor.getName(), monitor.getId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean pushMetricsData(InputStream inputStream) throws IOException {
|
||||
return true;
|
||||
public boolean pushPrometheusMetrics(InputStream inputStream, String job, String instance) {
|
||||
try {
|
||||
long curTime = Instant.now().toEpochMilli();
|
||||
Map<String, MetricFamily> metricFamilyMap = OnlineParser.parseMetrics(inputStream);
|
||||
if (metricFamilyMap == null) {
|
||||
log.error("parse prometheus metrics is null, job: {}, instance: {}", job, instance);
|
||||
return false;
|
||||
}
|
||||
long id = 0L;
|
||||
if (job != null && instance != null) {
|
||||
// auto create monitor when job and instance not null
|
||||
// job is app, instance is the name
|
||||
id = jobInstanceMap.computeIfAbsent(job + "_" + instance, key -> {
|
||||
log.info("auto create monitor by prometheus push, job: {}, instance: {}", job, instance);
|
||||
long monitorId = SnowFlakeIdGenerator.generateId();
|
||||
Monitor monitor = Monitor.builder()
|
||||
.id(monitorId)
|
||||
.app(job)
|
||||
.name(instance)
|
||||
.host(instance)
|
||||
.type((byte) 1)
|
||||
.status(CommonConstants.MONITOR_UP_CODE)
|
||||
.build();
|
||||
this.pushMonitorDao.save(monitor);
|
||||
return monitorId;
|
||||
});
|
||||
}
|
||||
for (Map.Entry<String, MetricFamily> entry : metricFamilyMap.entrySet()) {
|
||||
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
|
||||
builder.setId(id);
|
||||
builder.setApp(job);
|
||||
builder.setTime(curTime);
|
||||
String metricsName = entry.getKey();
|
||||
builder.setMetrics(metricsName);
|
||||
MetricFamily metricFamily = entry.getValue();
|
||||
if (!metricFamily.getMetricList().isEmpty()) {
|
||||
List<String> metricsFields = new LinkedList<>();
|
||||
for (int index = 0; index < metricFamily.getMetricList().size(); index++) {
|
||||
MetricFamily.Metric metric = metricFamily.getMetricList().get(index);
|
||||
if (index == 0) {
|
||||
metric.getLabels().forEach(label -> {
|
||||
metricsFields.add(label.getName());
|
||||
builder.addField(CollectRep.Field.newBuilder().setName(label.getName())
|
||||
.setType(CommonConstants.TYPE_STRING).setLabel(true).build());
|
||||
});
|
||||
builder.addField(CollectRep.Field.newBuilder().setName("value")
|
||||
.setType(CommonConstants.TYPE_NUMBER).setLabel(false).build());
|
||||
}
|
||||
Map<String, String> labelMap = metric.getLabels()
|
||||
.stream()
|
||||
.collect(Collectors.toMap(MetricFamily.Label::getName, MetricFamily.Label::getValue));
|
||||
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
|
||||
for (String field : metricsFields) {
|
||||
String fieldValue = labelMap.get(field);
|
||||
valueRowBuilder.addColumn(fieldValue == null ? CommonConstants.NULL_VALUE : fieldValue);
|
||||
}
|
||||
valueRowBuilder.addColumn(String.valueOf(metric.getValue()));
|
||||
builder.addValueRow(valueRowBuilder.build());
|
||||
}
|
||||
commonDataQueue.sendMetricsData(builder.build());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("push prometheus metrics error", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-143
@@ -1,143 +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.push.service.impl;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.common.entity.manager.Monitor;
|
||||
import org.apache.hertzbeat.common.entity.push.PushMetrics;
|
||||
import org.apache.hertzbeat.common.entity.push.PushMetricsDto;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.apache.hertzbeat.push.dao.PushMetricsDao;
|
||||
import org.apache.hertzbeat.push.dao.PushMonitorDao;
|
||||
import org.apache.hertzbeat.push.service.PushService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* push service impl
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class PushServiceImpl implements PushService {
|
||||
|
||||
@Autowired
|
||||
private PushMonitorDao monitorDao;
|
||||
|
||||
@Autowired
|
||||
private PushMetricsDao metricsDao;
|
||||
|
||||
private final Map<Long, Long> monitorIdCache; // key: monitorId, value: time stamp of last query
|
||||
|
||||
private static final long cacheTimeout = 5000L; // ms
|
||||
|
||||
private final Map<Long, PushMetricsDto.Metrics> lastPushMetrics;
|
||||
|
||||
private static final long deleteMetricsPeriod = 1000 * 60 * 60 * 12L;
|
||||
|
||||
private static final long deleteBeforeTime = deleteMetricsPeriod / 2;
|
||||
|
||||
public PushServiceImpl(){
|
||||
monitorIdCache = new HashMap<>();
|
||||
lastPushMetrics = new HashMap<>();
|
||||
|
||||
new Timer().schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
deletePeriodically();
|
||||
} catch (Exception e) {
|
||||
log.error("periodical deletion failed. {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}, 1000, deleteMetricsPeriod);
|
||||
}
|
||||
|
||||
public void deletePeriodically(){
|
||||
metricsDao.deleteAllByTimeBefore(System.currentTimeMillis() - deleteBeforeTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pushMetricsData(PushMetricsDto pushMetricsDto) throws RuntimeException {
|
||||
List<PushMetrics> pushMetricsList = new ArrayList<>();
|
||||
long curTime = System.currentTimeMillis();
|
||||
for (PushMetricsDto.Metrics metrics : pushMetricsDto.getMetricsList()) {
|
||||
long monitorId = metrics.getMonitorId();
|
||||
metrics.setTime(curTime);
|
||||
|
||||
if (!monitorIdCache.containsKey(monitorId) || (monitorIdCache.containsKey(monitorId) && curTime > monitorIdCache.get(monitorId) + cacheTimeout)) {
|
||||
Optional<Monitor> queryOption = monitorDao.findById(monitorId);
|
||||
if (queryOption.isEmpty()) {
|
||||
monitorIdCache.remove(monitorId);
|
||||
continue;
|
||||
}
|
||||
monitorIdCache.put(monitorId, curTime);
|
||||
}
|
||||
|
||||
PushMetrics pushMetrics = PushMetrics.builder()
|
||||
.monitorId(metrics.getMonitorId())
|
||||
.time(curTime)
|
||||
.metrics(JsonUtil.toJson(metrics.getMetrics())).build();
|
||||
lastPushMetrics.put(monitorId, metrics);
|
||||
pushMetricsList.add(pushMetrics);
|
||||
}
|
||||
|
||||
metricsDao.saveAll(pushMetricsList);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public PushMetricsDto getPushMetricData(final Long monitorId, final Long time) {
|
||||
PushMetricsDto.Metrics metrics;
|
||||
PushMetricsDto pushMetricsDto = new PushMetricsDto();
|
||||
if (lastPushMetrics.containsKey(monitorId)) {
|
||||
metrics = lastPushMetrics.get(monitorId);
|
||||
}
|
||||
else {
|
||||
try {
|
||||
PushMetrics pushMetrics = metricsDao.findFirstByMonitorIdOrderByTimeDesc(monitorId);
|
||||
if (pushMetrics == null || pushMetrics.getMetrics() == null) {
|
||||
return pushMetricsDto;
|
||||
}
|
||||
List<Map<String, String>> jsonMap = JsonUtil.fromJson(pushMetrics.getMetrics(), new TypeReference<>() {
|
||||
});
|
||||
metrics = PushMetricsDto.Metrics.builder().monitorId(monitorId).metrics(jsonMap).time(pushMetrics.getTime()).build();
|
||||
lastPushMetrics.put(monitorId, metrics);
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.error("no metrics found, monitor id: {}, {}", monitorId, e.getMessage(), e);
|
||||
return pushMetricsDto;
|
||||
}
|
||||
}
|
||||
if (time > metrics.getTime()) {
|
||||
// return void because time param is invalid
|
||||
return pushMetricsDto;
|
||||
}
|
||||
pushMetricsDto.getMetricsList().add(metrics);
|
||||
return pushMetricsDto;
|
||||
}
|
||||
|
||||
}
|
||||
-93
@@ -1,93 +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.push.controller;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.push.PushMetricsDto;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.apache.hertzbeat.push.service.PushService;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
|
||||
/**
|
||||
* test case for {@link PushController}
|
||||
*/
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PushControllerTest {
|
||||
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Mock
|
||||
private PushService pushService;
|
||||
|
||||
@InjectMocks
|
||||
private PushController pushController;
|
||||
|
||||
private PushMetricsDto mockPushMetricsDto;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
|
||||
this.mockMvc = standaloneSetup(this.pushController).build();
|
||||
|
||||
mockPushMetricsDto = PushMetricsDto.builder().build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPushMetrics() throws Exception {
|
||||
|
||||
this.mockMvc.perform(MockMvcRequestBuilders.post("/api/push")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(JsonUtil.toJson(mockPushMetricsDto)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetMetrics() throws Exception {
|
||||
|
||||
Long id = 6565463543L;
|
||||
Long time = 6565463543L;
|
||||
|
||||
when(pushService.getPushMetricData(id, time)).thenReturn(mockPushMetricsDto);
|
||||
|
||||
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/push")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.param("id", id.toString())
|
||||
.param("time", time.toString()))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
}
|
||||
+9
-12
@@ -22,12 +22,11 @@ package org.apache.hertzbeat.push.controller;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
import java.io.InputStream;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.push.service.PushGatewayService;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
@@ -38,11 +37,12 @@ import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
|
||||
/**
|
||||
* test case for {@link PushGatewayController}
|
||||
* test case for {@link PushPrometheusController}
|
||||
*/
|
||||
|
||||
@Disabled
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PushGatewayControllerTest {
|
||||
class PushPrometheusControllerTest {
|
||||
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@@ -50,7 +50,7 @@ class PushGatewayControllerTest {
|
||||
private PushGatewayService pushGatewayService;
|
||||
|
||||
@InjectMocks
|
||||
private PushGatewayController gatewayController;
|
||||
private PushPrometheusController gatewayController;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
@@ -63,14 +63,12 @@ class PushGatewayControllerTest {
|
||||
|
||||
String mockData = "some metric data";
|
||||
|
||||
when(pushGatewayService.pushMetricsData(any(InputStream.class))).thenReturn(true);
|
||||
when(pushGatewayService.pushPrometheusMetrics(any(InputStream.class), any(), any())).thenReturn(true);
|
||||
|
||||
mockMvc.perform(post("/api/push/pushgateway")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(mockData))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
|
||||
.andExpect(jsonPath("$.msg").value("Push success"));
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -78,13 +76,12 @@ class PushGatewayControllerTest {
|
||||
|
||||
String mockData = "some metric data";
|
||||
|
||||
when(pushGatewayService.pushMetricsData(any(InputStream.class))).thenReturn(false);
|
||||
when(pushGatewayService.pushPrometheusMetrics(any(InputStream.class), any(), any())).thenReturn(false);
|
||||
|
||||
mockMvc.perform(post("/api/push/pushgateway")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(mockData))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.msg").value("Push failed"));
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,77 +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.push.dao;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.push.PushMetrics;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
/**
|
||||
* test case for {@link PushMetricsDao}
|
||||
*/
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class PushMetricsDaoTest {
|
||||
@Mock
|
||||
private PushMetricsDao pushMetricsDao;
|
||||
|
||||
@InjectMocks
|
||||
private PushMetricsDaoTest pushMetricsDaoTest;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shallFindFirstByMonitorIdOrderByTimeDesc() {
|
||||
|
||||
PushMetrics expectedMetrics = new PushMetrics();
|
||||
expectedMetrics.setMonitorId(1L);
|
||||
expectedMetrics.setTime(System.currentTimeMillis());
|
||||
|
||||
when(pushMetricsDao.findFirstByMonitorIdOrderByTimeDesc(1L)).thenReturn(expectedMetrics);
|
||||
|
||||
PushMetrics actualMetrics = pushMetricsDao.findFirstByMonitorIdOrderByTimeDesc(1L);
|
||||
|
||||
assertEquals(expectedMetrics, actualMetrics);
|
||||
verify(pushMetricsDao, times(1)).findFirstByMonitorIdOrderByTimeDesc(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shallDeleteAllByTimeBefore() {
|
||||
|
||||
doNothing().when(pushMetricsDao).deleteAllByTimeBefore(anyLong());
|
||||
|
||||
pushMetricsDao.deleteAllByTimeBefore(1000L);
|
||||
|
||||
verify(pushMetricsDao, times(1)).deleteAllByTimeBefore(1000L);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user