diff --git a/.github/workflows/ci-hertzbeat.yml b/.github/workflows/ci-hertzbeat.yml
new file mode 100644
index 0000000..88d0b79
--- /dev/null
+++ b/.github/workflows/ci-hertzbeat.yml
@@ -0,0 +1,25 @@
+name: HertzBeat CI
+on:
+ pull_request:
+ paths:
+ - 'applications/hertzbeat/**'
+
+jobs:
+ application-hertzbeat:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Setup Java Env
+ uses: actions/setup-java@v4
+ with:
+ java-version: '17'
+ distribution: 'zulu'
+ cache: 'maven'
+ - name: Start OceanBase container
+ uses: oceanbase/setup-oceanbase-ce@v1.0
+ with:
+ network: 'host'
+ - name: Build and Test
+ run: |
+ cd applications/hertzbeat
+ mvn clean test
diff --git a/.gitignore b/.gitignore
index 500bfaf..3abdae4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -95,7 +95,6 @@ build/Release
# Dependency directories
node_modules/
-config/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
@@ -311,4 +310,4 @@ venv.bak/
# mypy
.mypy_cache/
.dmypy.json
-dmypy.json
\ No newline at end of file
+dmypy.json
diff --git a/applications/hertzbeat/.asf.yaml b/applications/hertzbeat/.asf.yaml
new file mode 100644
index 0000000..a857a2c
--- /dev/null
+++ b/applications/hertzbeat/.asf.yaml
@@ -0,0 +1,60 @@
+#
+# 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.
+#
+
+github:
+ description: Apache HertzBeat(incubating) is a real-time monitoring system with agentless, performance cluster, prometheus-compatible, custom monitoring and status page building capabilities.
+ homepage: https://hertzbeat.apache.org/
+ labels:
+ - monitoring
+ - monitor
+ - notifications
+ - alerting
+ - self-hosted
+ - prometheus
+ - zabbix
+ - grafana
+ - metrics
+ - observability
+ - uptime
+ - uptime-monitoring
+ - status
+ - status-page
+ - devops
+ - server
+ - linux
+ - database
+ - mysql
+ - cloud
+ enabled_merge_buttons:
+ squash: true
+ merge: false
+ rebase: false
+ protected_branches:
+ master:
+ required_status_checks:
+ strict: true
+ contexts:
+ - check-license-header
+ required_pull_request_reviews:
+ dismiss_stale_reviews: true
+ required_approving_review_count: 1
+notifications:
+ commits: notifications@hertzbeat.apache.org
+ issues: notifications@hertzbeat.apache.org
+ pullrequests: notifications@hertzbeat.apache.org
+ jobs: notifications@hertzbeat.apache.org
+ discussions: dev@hertzbeat.apache.org
diff --git a/applications/hertzbeat/.gitattributes b/applications/hertzbeat/.gitattributes
new file mode 100644
index 0000000..b071320
--- /dev/null
+++ b/applications/hertzbeat/.gitattributes
@@ -0,0 +1,23 @@
+#
+# 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.
+#
+
+/home export-ignore
+/hip export-ignore
+/.github export-ignore
+/.idea export-ignore
+.gitattributes export-ignore
+.gitignore export-ignore
diff --git a/applications/hertzbeat/.gitignore b/applications/hertzbeat/.gitignore
new file mode 100644
index 0000000..bad93d6
--- /dev/null
+++ b/applications/hertzbeat/.gitignore
@@ -0,0 +1,56 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+Thumbs.db
+.DS_Store
+.gradle
+build/
+out/
+micronaut-cli.yml
+.mvn/
+mvnw
+mvnw.bat
+*.log
+package-lock.json
+*.zip
+jdk/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### For icon.png ###
+!.idea/icon.png
+
+### NetBeans ###
+nbproject/private/
+build/
+nbbuild/
+dist/
+nbdist/
+.nb-gradle/
+
+### VS Code ###
+.vscode/
+
+# dependencies
+node_modules
+.docusaurus
+
+# h2 data
+*.mv.db
+*.trace.db
+
+# debug env
+application-dev.yml
+application-mysql.yml
+application-pg.yml
diff --git a/applications/hertzbeat/.licenserc.yaml b/applications/hertzbeat/.licenserc.yaml
new file mode 100644
index 0000000..8eaea84
--- /dev/null
+++ b/applications/hertzbeat/.licenserc.yaml
@@ -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.
+#
+
+header:
+ license:
+ spdx-id: Apache-2.0
+ copyright-owner: Apache Software Foundation
+
+ paths-ignore:
+ - '**/*.md'
+ - '**/*.json'
+ - '**/*.iml'
+ - '**/*.ini'
+ - '**/*.svg'
+ - '**/*.png'
+ - '**/*.MD'
+ - '**/*.ftl'
+ - '**/*.tpl'
+ - '**/*.pl'
+ - '**/*.dict'
+ - '**/*.awk'
+ - "**/*.lock"
+ - '**/*.crt'
+ - '**/*.pem'
+ - '**/*.js'
+ - '**/*.less'
+ - '**/*.txt'
+ - '**/target/**'
+ - '.gitattributes'
+ - '**/.gitignore'
+ - '**/.gitkeep'
+ - 'home/**'
+ - 'material/**'
+ - 'LICENSE'
+ - 'NOTICE'
+ - 'DISCLAIMER'
+ - '**/LICENSE'
+ - '**/NOTICE'
+ - '.all-contributorsrc'
+ - '**/*.AbstractCollect'
+ - '**/*.Plugin'
+ - '**/*.MockMaker'
+ - '.prettierrc'
+ - '.browserslistrc'
+ - '.editorconfig'
+ - '.eslintignore'
+ - '.eslintrc.js'
+ - '.stylelintrc'
+ - '.prettierignore'
+ - '.prettierrc.js'
+ - 'karma.conf.js'
+ - 'proxy.conf.js'
+ - '.helmignore'
+ - 'web-app/src/app/core/**'
+ - 'web-app/src/app/layout/**'
+ - 'web-app/src/app/routes/exception/**'
+ - 'web-app/src/app/routes/routes.module.ts'
+ - 'web-app/src/app/routes/routes-routing.module.ts'
+ - 'web-app/src/app/shared/json-schema/**'
+ - 'web-app/src/app/shared/index.ts'
+ - 'web-app/src/app/shared/shared.module.ts'
+ - 'web-app/src/app/shared/shared-delon.module.ts'
+ - 'web-app/src/app/shared/shared-zorro.module.ts'
+ - 'web-app/src/app/app.module.ts'
+ - 'web-app/src/app/app.component.ts'
+ - 'web-app/src/app/global-config.module.ts'
+ - 'web-app/src/app/icons-provider.module.ts'
+ - 'web-app/src/assets/color.less'
+ - 'web-app/src/assets/style.compact.less'
+ - 'web-app/src/assets/style.dark.less'
+ - 'web-app/src/environments/**'
+ - 'web-app/src/styles/**'
+ - 'web-app/src/index.html'
+ - 'web-app/src/main.ts'
+ - 'web-app/src/polyfills.ts'
+ - 'web-app/src/style-icons.ts'
+ - 'web-app/src/style-icons-auto.ts'
+ - 'web-app/src/style.less'
+ - 'web-app/src/test.ts'
+ - 'web-app/src/typings.d.ts'
+
+ comment: on-failure
+
+dependency:
+ files:
+ - pom.xml
+ - web-app/package.json
+
+
diff --git a/applications/hertzbeat/DISCLAIMER b/applications/hertzbeat/DISCLAIMER
new file mode 100644
index 0000000..15089ac
--- /dev/null
+++ b/applications/hertzbeat/DISCLAIMER
@@ -0,0 +1,10 @@
+Apache HertzBeat (incubating) is an effort undergoing incubation at the Apache
+Software Foundation (ASF), sponsored by the Apache Incubator PMC.
+
+Incubation is required of all newly accepted projects until a further review
+indicates that the infrastructure, communications, and decision making process
+have stabilized in a manner consistent with other successful ASF projects.
+
+While incubation status is not necessarily a reflection of the completeness
+or stability of the code, it does indicate that the project has yet to be
+fully endorsed by the ASF.
diff --git a/applications/hertzbeat/LICENSE b/applications/hertzbeat/LICENSE
new file mode 100644
index 0000000..415987b
--- /dev/null
+++ b/applications/hertzbeat/LICENSE
@@ -0,0 +1,237 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed 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.
+
+========================================================================
+MIT License
+========================================================================
+
+The following components are provided under the MIT License
+These files from https://github.com/ng-alain/ng-alain
+The text of the license is included in material/licenses/frontend/LICENSE-ng-alain.txt
+
+web-app/src/app/core/**
+web-app/src/app/layout/**
+web-app/src/app/routes/exception/**
+web-app/src/app/routes/routes.module.ts
+web-app/src/app/routes/routes-routing.module.ts
+web-app/src/app/shared/json-schema/**
+web-app/src/app/shared/index.ts
+web-app/src/app/shared/shared.module.ts
+web-app/src/app/shared/shared-delon.module.ts
+web-app/src/app/shared/shared-zorro.module.ts
+web-app/src/app/app.module.ts
+web-app/src/app/app.component.ts
+web-app/src/app/global-config.module.ts
+web-app/src/app/icons-provider.module.ts
+web-app/src/assets/color.less
+web-app/src/assets/style.compact.less
+web-app/src/assets/style.dark.less
+web-app/src/environments/**
+web-app/src/styles/**
+web-app/src/index.html
+web-app/src/main.ts
+web-app/src/polyfills.ts
+web-app/src/style-icons.ts
+web-app/src/style-icons-auto.ts
+web-app/src/style.less
+web-app/src/test.ts
+web-app/src/typings.d.ts
diff --git a/applications/hertzbeat/NOTICE b/applications/hertzbeat/NOTICE
new file mode 100644
index 0000000..6839b47
--- /dev/null
+++ b/applications/hertzbeat/NOTICE
@@ -0,0 +1,5 @@
+Apache HertzBeat (incubating)
+Copyright 2024 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
diff --git a/applications/hertzbeat/README.md b/applications/hertzbeat/README.md
new file mode 100644
index 0000000..fe9a91e
--- /dev/null
+++ b/applications/hertzbeat/README.md
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+> A real-time monitoring system with agentless, performance cluster, prometheus-compatible, custom monitoring and status page building capabilities.
+
+
+## HertzBeat Start With OceanBase
+
+1. Start the OceanBase Database
+
+> here we use docker to start an ob standalone.
+
+```shell
+docker run -p 2881:2881 --name obstandalone -e MINI_MODE=1 -d oceanbase/oceanbase-ce
+```
+
+2. Create the database name `hertzbeat`
+
+```shell
+create database if not exists hertzbeat default charset utf8mb4;
+```
+
+3. Build the HertzBeat
+
+```shell
+mvn clean install
+```
+
+4. Start the HertzBeat Backend
+
+Start SpringBoot Instance `org.apache.hertzbeat.manager.Manager`
+
+5. Start the HertzBeat Webapp
+
+```shell
+cd web-app
+
+yarn install
+
+yarn start
+```
+
+6. Access `http://localhost:4200` to start, account `admin/hertzbeat`
+
+## Custom Database Configuration
+
+### Custom Local or Prod Env Configuration
+
+Modify the `application.yml` file in the `manager` module `manager/src/main/resources`
+
+```yaml
+spring:
+ config:
+ activate:
+ on-profile: prod
+
+ datasource:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ username: root
+ password:
+ url: jdbc:mysql://localhost:2881/hertzbeat?createDatabaseIfNotExist=true&useUnicode=true&characterEncoding=utf-8&useSSL=false
+ hikari:
+ max-lifetime: 120000
+```
+
+### Custom Test Env Configuration
+
+This if for the test environment, `mvn clean test`
+
+Modify the `application-test.yml` file in the `manager` module `manager/src/main/resources`
+
+```yaml
+spring:
+ datasource:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ username: root
+ password:
+ url: jdbc:mysql://localhost:2881/test?createDatabaseIfNotExist=true&useUnicode=true&characterEncoding=utf-8&useSSL=false
+ hikari:
+ max-lifetime: 120000
+```
diff --git a/applications/hertzbeat/README_CN.md b/applications/hertzbeat/README_CN.md
new file mode 100644
index 0000000..4c37778
--- /dev/null
+++ b/applications/hertzbeat/README_CN.md
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+> 实时监控系统,无需 Agent,性能集群,兼容 Prometheus,自定义监控和状态页构建能力。
+
+## HertzBeat Start With OceanBase
+
+1. Start the OceanBase Database
+
+> here we use docker to start an ob standalone.
+
+```shell
+docker run -p 2881:2881 --name obstandalone -e MINI_MODE=1 -d oceanbase/oceanbase-ce
+```
+
+2. Create the database name `hertzbeat`
+
+```shell
+create database if not exists hertzbeat default charset utf8mb4;
+```
+
+3. Build the HertzBeat
+
+```shell
+mvn clean install
+```
+
+4. Start the HertzBeat Backend
+
+Start SpringBoot Instance `org.apache.hertzbeat.manager.Manager`
+
+5. Start the HertzBeat Webapp
+
+```shell
+cd web-app
+
+yarn install
+
+yarn start
+```
+
+6. Access `http://localhost:4200` to start, account `admin/hertzbeat`
+
+## Custom Database Configuration
+
+### Custom Local or Prod Env Configuration
+
+Modify the `application.yml` file in the `manager` module `manager/src/main/resources`
+
+```yaml
+spring:
+ config:
+ activate:
+ on-profile: prod
+
+ datasource:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ username: root
+ password:
+ url: jdbc:mysql://localhost:2881/hertzbeat?createDatabaseIfNotExist=true&useUnicode=true&characterEncoding=utf-8&useSSL=false
+ hikari:
+ max-lifetime: 120000
+```
+
+### Custom Test Env Configuration
+
+This if for the test environment, `mvn clean test`
+
+Modify the `application-test.yml` file in the `manager` module `manager/src/main/resources`
+
+```yaml
+spring:
+ datasource:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ username: root
+ password:
+ url: jdbc:mysql://localhost:2881/test?createDatabaseIfNotExist=true&useUnicode=true&characterEncoding=utf-8&useSSL=false
+ hikari:
+ max-lifetime: 120000
+```
diff --git a/applications/hertzbeat/alerter/pom.xml b/applications/hertzbeat/alerter/pom.xml
new file mode 100644
index 0000000..dcbce50
--- /dev/null
+++ b/applications/hertzbeat/alerter/pom.xml
@@ -0,0 +1,87 @@
+
+
+
+
+ hertzbeat
+ org.apache.hertzbeat
+ 2.0-SNAPSHOT
+
+ 4.0.0
+
+ hertzbeat-alerter
+ ${project.artifactId}
+
+
+
+
+
+
+
+ org.apache.hertzbeat
+ hertzbeat-common
+ provided
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+ provided
+
+
+ org.springframework.boot
+ spring-boot-autoconfigure
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+
+ org.apache.kafka
+ kafka-clients
+
+
+
+ io.lettuce
+ lettuce-core
+ provided
+
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ provided
+
+
+ cn.afterturn
+ easypoi-annotation
+ 4.3.0
+ compile
+
+
+ org.apache.poi
+ poi
+ 4.1.1
+ compile
+
+
+
+
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/AlerterProperties.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/AlerterProperties.java
new file mode 100644
index 0000000..486dbae
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/AlerterProperties.java
@@ -0,0 +1,116 @@
+/*
+ * 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;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * alerter prop config
+ */
+@Component
+@ConfigurationProperties(prefix = "alerter")
+@Getter
+@Setter
+public class AlerterProperties {
+
+ /**
+ * Alarm content console link
+ */
+ private String consoleUrl = "https://console.tancloud.cn";
+
+ /**
+ * WeWork webhook url
+ */
+ private String weWorkWebhookUrl = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=";
+
+ /**
+ * DingDing talk webhook url
+ */
+ private String dingTalkWebhookUrl = "https://oapi.dingtalk.com/robot/send?access_token=";
+
+ /**
+ * FlyBook webhook url
+ */
+ private String flyBookWebhookUrl = "https://open.feishu.cn/open-apis/bot/v2/hook/";
+
+ /**
+ * Telegram Bot api url
+ */
+ private String telegramWebhookUrl = "https://api.telegram.org/bot%s/sendMessage";
+
+ /**
+ * Discord Notify url
+ */
+ private String discordWebhookUrl = "https://discord.com/api/v9/channels/%s/messages";
+
+ /**
+ * ServerChan Notify url
+ */
+ private String serverChanWebhookUrl = "https://sctapi.ftqq.com/%s.send";
+ /**
+ * Gotify Notify url
+ */
+ private String gotifyWebhookUrl = "https://push.example.de/message?token=";
+
+ /**
+ * Data entry configuration properties
+ */
+ private EntranceProperties entrance;
+
+ /**
+ * Data entry configuration properties
+ */
+ @Getter
+ @Setter
+ public static class EntranceProperties {
+
+ /**
+ * kafka configuration information
+ */
+ private KafkaProperties kafka;
+
+ /**
+ * kafka configuration information
+ */
+ @Getter
+ @Setter
+ public static class KafkaProperties {
+ /**
+ * Whether the kafka data entry is started
+ */
+ private boolean enabled = true;
+
+ /**
+ * kafka's connection server url
+ */
+ private String servers = "127.0.0.1:9092";
+ /**
+ * The name of the topic that receives the data
+ */
+ private String topic;
+ /**
+ * Consumer Group ID
+ */
+ private String groupId;
+
+ }
+ }
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/AlerterWorkerPool.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/AlerterWorkerPool.java
new file mode 100644
index 0000000..a8ebf4a
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/AlerterWorkerPool.java
@@ -0,0 +1,68 @@
+/*
+ * 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;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+/**
+ * alarm module thread pool
+ */
+@Component
+@Slf4j
+public class AlerterWorkerPool {
+
+ private ThreadPoolExecutor workerExecutor;
+
+ public AlerterWorkerPool() {
+ initWorkExecutor();
+ }
+
+ private void initWorkExecutor() {
+ ThreadFactory threadFactory = new ThreadFactoryBuilder()
+ .setUncaughtExceptionHandler((thread, throwable) -> {
+ log.error("workerExecutor has uncaughtException.");
+ log.error(throwable.getMessage(), throwable);
+ })
+ .setDaemon(true)
+ .setNameFormat("alerter-worker-%d")
+ .build();
+ workerExecutor = new ThreadPoolExecutor(6,
+ 10,
+ 10,
+ TimeUnit.SECONDS,
+ new SynchronousQueue<>(),
+ threadFactory,
+ new ThreadPoolExecutor.AbortPolicy());
+ }
+
+ /**
+ * Run the alerter task
+ * @param runnable task
+ * @throws RejectedExecutionException when The thread pool is full of
+ */
+ public void executeJob(Runnable runnable) throws RejectedExecutionException {
+ workerExecutor.execute(runnable);
+ }
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/calculate/CalculateAlarm.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/calculate/CalculateAlarm.java
new file mode 100644
index 0000000..fb60f50
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/calculate/CalculateAlarm.java
@@ -0,0 +1,482 @@
+/*
+ * 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.calculate;
+
+import static org.apache.hertzbeat.common.constants.CommonConstants.ALERT_STATUS_CODE_NOT_REACH;
+import static org.apache.hertzbeat.common.constants.CommonConstants.ALERT_STATUS_CODE_PENDING;
+import static org.apache.hertzbeat.common.constants.CommonConstants.ALERT_STATUS_CODE_SOLVED;
+import static org.apache.hertzbeat.common.constants.CommonConstants.TAG_CODE;
+import static org.apache.hertzbeat.common.constants.CommonConstants.TAG_METRIC;
+import static org.apache.hertzbeat.common.constants.CommonConstants.TAG_METRICS;
+import static org.apache.hertzbeat.common.constants.CommonConstants.TAG_MONITOR_APP;
+import static org.apache.hertzbeat.common.constants.CommonConstants.TAG_MONITOR_ID;
+import static org.apache.hertzbeat.common.constants.CommonConstants.TAG_MONITOR_NAME;
+import jakarta.persistence.criteria.Predicate;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.jexl3.JexlException;
+import org.apache.commons.jexl3.JexlExpression;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hertzbeat.alert.AlerterWorkerPool;
+import org.apache.hertzbeat.alert.dao.AlertMonitorDao;
+import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
+import org.apache.hertzbeat.alert.service.AlertDefineService;
+import org.apache.hertzbeat.alert.service.AlertService;
+import org.apache.hertzbeat.alert.util.AlertTemplateUtil;
+import org.apache.hertzbeat.common.constants.CommonConstants;
+import org.apache.hertzbeat.common.entity.alerter.Alert;
+import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
+import org.apache.hertzbeat.common.entity.manager.Monitor;
+import org.apache.hertzbeat.common.entity.manager.TagItem;
+import org.apache.hertzbeat.common.entity.message.CollectRep;
+import org.apache.hertzbeat.common.queue.CommonDataQueue;
+import org.apache.hertzbeat.common.support.event.MonitorDeletedEvent;
+import org.apache.hertzbeat.common.support.event.SystemConfigChangeEvent;
+import org.apache.hertzbeat.common.util.CommonUtil;
+import org.apache.hertzbeat.common.util.JexlExpressionRunner;
+import org.apache.hertzbeat.common.util.ResourceBundleUtil;
+import org.springframework.context.event.EventListener;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+
+/**
+ * Calculate alarms based on the alarm definition rules and collected data
+ */
+@Component
+@Slf4j
+public class CalculateAlarm {
+
+ private static final String SYSTEM_VALUE_ROW_COUNT = "system_value_row_count";
+
+ /**
+ * The alarm in the process is triggered
+ * key - monitorId+alertDefineId+tags | The alarm is a common threshold alarm
+ * key - monitorId | Indicates the monitoring status availability reachability alarm
+ */
+ private final Map triggeredAlertMap;
+ /**
+ * The not recover alert
+ * key - monitorId + alertDefineId + tags
+ */
+ private final Map notRecoveredAlertMap;
+ private final AlerterWorkerPool workerPool;
+ private final CommonDataQueue dataQueue;
+ private final AlertDefineService alertDefineService;
+ private final AlarmCommonReduce alarmCommonReduce;
+ private ResourceBundle bundle;
+ private final AlertService alertService;
+
+ public CalculateAlarm(AlerterWorkerPool workerPool, CommonDataQueue dataQueue,
+ AlertDefineService alertDefineService, AlertMonitorDao monitorDao,
+ AlarmCommonReduce alarmCommonReduce, AlertService alertService) {
+ this.workerPool = workerPool;
+ this.dataQueue = dataQueue;
+ this.alarmCommonReduce = alarmCommonReduce;
+ this.alertDefineService = alertDefineService;
+ this.alertService = alertService;
+ this.bundle = ResourceBundleUtil.getBundle("alerter");
+ this.triggeredAlertMap = new ConcurrentHashMap<>(16);
+ this.notRecoveredAlertMap = new ConcurrentHashMap<>(16);
+ // Initialize stateAlertMap
+ List monitors = monitorDao.findMonitorsByStatus(CommonConstants.UN_AVAILABLE_CODE);
+ if (monitors != null) {
+ for (Monitor monitor : monitors) {
+ HashMap tags = new HashMap<>(8);
+ tags.put(TAG_MONITOR_ID, String.valueOf(monitor.getId()));
+ tags.put(TAG_MONITOR_NAME, monitor.getName());
+ tags.put(TAG_MONITOR_APP, monitor.getApp());
+ this.notRecoveredAlertMap.put(monitor.getId() + CommonConstants.AVAILABILITY,
+ Alert.builder().tags(tags).target(CommonConstants.AVAILABILITY).status(ALERT_STATUS_CODE_PENDING).build());
+ }
+ }
+ startCalculate();
+ }
+
+ private void startCalculate() {
+ Runnable runnable = () -> {
+ while (!Thread.currentThread().isInterrupted()) {
+ try {
+ CollectRep.MetricsData metricsData = dataQueue.pollMetricsDataToAlerter();
+ if (metricsData != null) {
+ calculate(metricsData);
+ }
+ } catch (InterruptedException ignored) {
+
+ } catch (Exception e) {
+ log.error("calculate alarm error: {}.", e.getMessage(), e);
+ }
+ }
+ };
+ workerPool.executeJob(runnable);
+ workerPool.executeJob(runnable);
+ workerPool.executeJob(runnable);
+ }
+
+ private void calculate(CollectRep.MetricsData metricsData) {
+ long currentTimeMilli = System.currentTimeMillis();
+ long monitorId = metricsData.getId();
+ String app = metricsData.getApp();
+ if (app.startsWith(CommonConstants.PROMETHEUS_APP_PREFIX)) {
+ app = CommonConstants.PROMETHEUS;
+ }
+ String metrics = metricsData.getMetrics();
+ // If the metrics whose scheduling priority is 0 has the status of collecting response data UN_REACHABLE/UN_CONNECTABLE,
+ // the highest severity alarm is generated to monitor the status change
+ if (metricsData.getPriority() == 0) {
+ handlerAvailableMetrics(monitorId, app, metricsData);
+ }
+ // Query the alarm definitions associated with the metrics of the monitoring type
+ // field - define[]
+ Map> defineMap = alertDefineService.getMonitorBindAlertDefines(monitorId, app, metrics);
+ if (defineMap.isEmpty()) {
+ return;
+ }
+ List fields = metricsData.getFieldsList();
+ Map fieldValueMap = new HashMap<>(8);
+ int valueRowCount = metricsData.getValuesCount();
+ for (Map.Entry> entry : defineMap.entrySet()) {
+ List defines = entry.getValue();
+ for (AlertDefine define : defines) {
+ final String expr = define.getExpr();
+ if (StringUtils.isBlank(expr)) {
+ continue;
+ }
+ if (expr.contains(SYSTEM_VALUE_ROW_COUNT) && metricsData.getValuesCount() == 0) {
+ fieldValueMap.put(SYSTEM_VALUE_ROW_COUNT, valueRowCount);
+ try {
+ boolean match = execAlertExpression(fieldValueMap, expr);
+ try {
+ if (match) {
+ // If the threshold rule matches, the number of times the threshold has been triggered is determined and an alarm is triggered
+ afterThresholdRuleMatch(currentTimeMilli, monitorId, app, metrics, "", fieldValueMap, define);
+ // if the threshold is triggered, ignore other data rows
+ continue;
+ } else {
+ String alarmKey = String.valueOf(monitorId) + define.getId();
+ triggeredAlertMap.remove(alarmKey);
+ if (define.isRecoverNotice()) {
+ handleRecoveredAlert(currentTimeMilli, define, expr, alarmKey);
+ }
+ }
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ }
+ } catch (Exception ignored) {}
+ }
+ for (CollectRep.ValueRow valueRow : metricsData.getValuesList()) {
+
+ if (CollectionUtils.isEmpty(valueRow.getColumnsList())) {
+ continue;
+ }
+ fieldValueMap.clear();
+ fieldValueMap.put(SYSTEM_VALUE_ROW_COUNT, valueRowCount);
+ StringBuilder tagBuilder = new StringBuilder();
+ for (int index = 0; index < valueRow.getColumnsList().size(); index++) {
+ String valueStr = valueRow.getColumns(index);
+ if (CommonConstants.NULL_VALUE.equals(valueStr)) {
+ continue;
+ }
+
+ final CollectRep.Field field = fields.get(index);
+ final int fieldType = field.getType();
+
+ if (fieldType == CommonConstants.TYPE_NUMBER) {
+ final Double doubleValue;
+ if ((doubleValue = CommonUtil.parseStrDouble(valueStr)) != null) {
+ fieldValueMap.put(field.getName(), doubleValue);
+ }
+ } else if (fieldType == CommonConstants.TYPE_TIME) {
+ final Integer integerValue;
+ if ((integerValue = CommonUtil.parseStrInteger(valueStr)) != null) {
+ fieldValueMap.put(field.getName(), integerValue);
+ }
+ } else {
+ if (StringUtils.isNotEmpty(valueStr)) {
+ fieldValueMap.put(field.getName(), valueStr);
+ }
+ }
+
+ if (field.getLabel()) {
+ tagBuilder.append("-").append(valueStr);
+ }
+ }
+ try {
+ boolean match = execAlertExpression(fieldValueMap, expr);
+ try {
+ if (match) {
+ // If the threshold rule matches, the number of times the threshold has been triggered is determined and an alarm is triggered
+ afterThresholdRuleMatch(currentTimeMilli, monitorId, app, metrics, tagBuilder.toString(), fieldValueMap, define);
+ } else {
+ String alarmKey = String.valueOf(monitorId) + define.getId() + tagBuilder;
+ triggeredAlertMap.remove(alarmKey);
+ if (define.isRecoverNotice()) {
+ handleRecoveredAlert(currentTimeMilli, define, expr, alarmKey);
+ }
+ }
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ }
+ } catch (Exception ignored) {}
+ }
+ }
+ }
+ }
+
+ private void handleRecoveredAlert(long currentTimeMilli, AlertDefine define, String expr, String alarmKey) {
+ Alert notResolvedAlert = notRecoveredAlertMap.remove(alarmKey);
+ if (notResolvedAlert != null) {
+ // Sending an alarm Restore
+ Map tags = notResolvedAlert.getTags();
+ String content = this.bundle.getString("alerter.alarm.recover") + " : " + expr;
+ Alert resumeAlert = Alert.builder()
+ .tags(tags)
+ .target(define.getApp() + "." + define.getMetric() + "." + define.getField())
+ .content(content)
+ .priority(CommonConstants.ALERT_PRIORITY_CODE_WARNING)
+ .status(CommonConstants.ALERT_STATUS_CODE_RESTORED)
+ .firstAlarmTime(currentTimeMilli)
+ .lastAlarmTime(notResolvedAlert.getLastAlarmTime())
+ .triggerTimes(1)
+ .build();
+ alarmCommonReduce.reduceAndSendAlarm(resumeAlert);
+ }
+ }
+
+ private void afterThresholdRuleMatch(long currentTimeMilli, long monitorId, String app, String metrics, String tagStr,
+ Map fieldValueMap, AlertDefine define) {
+ String alarmKey = String.valueOf(monitorId) + define.getId() + tagStr;
+ Alert triggeredAlert = triggeredAlertMap.get(alarmKey);
+ if (triggeredAlert != null) {
+ int times = triggeredAlert.getTriggerTimes() + 1;
+ triggeredAlert.setTriggerTimes(times);
+ triggeredAlert.setFirstAlarmTime(currentTimeMilli);
+ triggeredAlert.setLastAlarmTime(currentTimeMilli);
+ int defineTimes = define.getTimes() == null ? 1 : define.getTimes();
+ if (times >= defineTimes) {
+ triggeredAlert.setStatus(ALERT_STATUS_CODE_PENDING);
+ triggeredAlertMap.remove(alarmKey);
+ notRecoveredAlertMap.put(alarmKey, triggeredAlert);
+ alarmCommonReduce.reduceAndSendAlarm(triggeredAlert.clone());
+ }
+ } else {
+ fieldValueMap.put(TAG_MONITOR_APP, app);
+ fieldValueMap.put(TAG_METRICS, metrics);
+ fieldValueMap.put(TAG_METRIC, define.getField());
+ Map tags = new HashMap<>(8);
+ tags.put(TAG_MONITOR_ID, String.valueOf(monitorId));
+ tags.put(TAG_MONITOR_APP, app);
+ tags.put(CommonConstants.TAG_THRESHOLD_ID, String.valueOf(define.getId()));
+ if (!CollectionUtils.isEmpty(define.getTags())) {
+ for (TagItem tagItem : define.getTags()) {
+ fieldValueMap.put(tagItem.getName(), tagItem.getValue());
+ tags.put(tagItem.getName(), tagItem.getValue());
+ }
+ }
+ Alert alert = Alert.builder()
+ .tags(tags)
+ .priority(define.getPriority())
+ .status(ALERT_STATUS_CODE_NOT_REACH)
+ .target(app + "." + metrics + "." + define.getField())
+ .triggerTimes(1)
+ .firstAlarmTime(currentTimeMilli)
+ .lastAlarmTime(currentTimeMilli)
+ // Keyword matching and substitution in the template
+ .content(AlertTemplateUtil.render(define.getTemplate(), fieldValueMap))
+ .build();
+ int defineTimes = define.getTimes() == null ? 1 : define.getTimes();
+ if (1 >= defineTimes) {
+ alert.setStatus(ALERT_STATUS_CODE_PENDING);
+ notRecoveredAlertMap.put(alarmKey, alert);
+ alarmCommonReduce.reduceAndSendAlarm(alert.clone());
+ } else {
+ triggeredAlertMap.put(alarmKey, alert);
+ }
+ }
+ }
+
+ private boolean execAlertExpression(Map fieldValueMap, String expr) {
+ Boolean match;
+ JexlExpression expression;
+ try {
+ expression = JexlExpressionRunner.compile(expr);
+ } catch (JexlException jexlException) {
+ log.error("Alarm Rule: {} Compile Error: {}.", expr, jexlException.getMessage());
+ throw jexlException;
+ } catch (Exception e) {
+ log.error("Alarm Rule: {} Unknown Error: {}.", expr, e.getMessage());
+ throw e;
+ }
+
+ try {
+ match = (Boolean) JexlExpressionRunner.evaluate(expression, fieldValueMap);
+ } catch (JexlException jexlException) {
+ log.error("Alarm Rule: {} Run Error: {}.", expr, jexlException.getMessage());
+ throw jexlException;
+ } catch (Exception e) {
+ log.error("Alarm Rule: {} Unknown Error: {}.", expr, e.getMessage());
+ throw e;
+ }
+ return match != null && match;
+ }
+
+ private void handlerAvailableMetrics(long monitorId, String app, CollectRep.MetricsData metricsData) {
+ if (metricsData.getCode() == CollectRep.Code.TIMEOUT) {
+ return;
+ }
+ // TODO CACHE getMonitorBindAlertAvaDefine
+ AlertDefine avaAlertDefine = alertDefineService.getMonitorBindAlertAvaDefine(monitorId, app, CommonConstants.AVAILABILITY);
+ if (avaAlertDefine == null) {
+ return;
+ }
+ long currentTimeMill = System.currentTimeMillis();
+ if (metricsData.getCode() != CollectRep.Code.SUCCESS) {
+ Alert preAlert = triggeredAlertMap.get(String.valueOf(monitorId));
+ Map tags = new HashMap<>(6);
+ tags.put(TAG_MONITOR_ID, String.valueOf(monitorId));
+ tags.put(TAG_MONITOR_APP, app);
+ tags.put(CommonConstants.TAG_THRESHOLD_ID, String.valueOf(avaAlertDefine.getId()));
+ tags.put(TAG_METRICS, CommonConstants.AVAILABILITY);
+ tags.put(TAG_CODE, metricsData.getCode().name());
+ Map valueMap = tags.entrySet().stream()
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+
+ if (!CollectionUtils.isEmpty(avaAlertDefine.getTags())) {
+ for (TagItem tagItem : avaAlertDefine.getTags()) {
+ valueMap.put(tagItem.getName(), tagItem.getValue());
+ tags.put(tagItem.getName(), tagItem.getValue());
+ }
+ }
+ if (preAlert == null) {
+ Alert.AlertBuilder alertBuilder = Alert.builder()
+ .tags(tags)
+ .priority(avaAlertDefine.getPriority())
+ .status(ALERT_STATUS_CODE_NOT_REACH)
+ .target(CommonConstants.AVAILABILITY)
+ .content(AlertTemplateUtil.render(avaAlertDefine.getTemplate(), valueMap))
+ .firstAlarmTime(currentTimeMill)
+ .lastAlarmTime(currentTimeMill)
+ .triggerTimes(1);
+ if (avaAlertDefine.getTimes() == null || avaAlertDefine.getTimes() <= 1) {
+ String notResolvedAlertKey = monitorId + CommonConstants.AVAILABILITY;
+ alertBuilder.status(ALERT_STATUS_CODE_PENDING);
+ notRecoveredAlertMap.put(notResolvedAlertKey, alertBuilder.build());
+ alarmCommonReduce.reduceAndSendAlarm(alertBuilder.build());
+ } else {
+ triggeredAlertMap.put(String.valueOf(monitorId), alertBuilder.build());
+ }
+ } else {
+ int times = preAlert.getTriggerTimes() + 1;
+ preAlert.setTriggerTimes(times);
+ preAlert.setFirstAlarmTime(currentTimeMill);
+ preAlert.setLastAlarmTime(currentTimeMill);
+ int defineTimes = avaAlertDefine.getTimes() == null ? 1 : avaAlertDefine.getTimes();
+ if (times >= defineTimes) {
+ preAlert.setStatus(ALERT_STATUS_CODE_PENDING);
+ String notResolvedAlertKey = monitorId + CommonConstants.AVAILABILITY;
+ notRecoveredAlertMap.put(notResolvedAlertKey, preAlert.clone());
+ alarmCommonReduce.reduceAndSendAlarm(preAlert.clone());
+ triggeredAlertMap.remove(String.valueOf(monitorId));
+ }
+ }
+ } else {
+ // Check whether an availability or unreachable alarm is generated before the association monitoring
+ // and send a clear alarm to clear the monitoring status
+ triggeredAlertMap.remove(String.valueOf(monitorId));
+ String notResolvedAlertKey = monitorId + CommonConstants.AVAILABILITY;
+ Alert notResolvedAlert = notRecoveredAlertMap.remove(notResolvedAlertKey);
+ if (notResolvedAlert != null) {
+ // Sending an alarm Restore
+ Map tags = notResolvedAlert.getTags();
+ if (!avaAlertDefine.isRecoverNotice()) {
+ tags.put(CommonConstants.IGNORE, CommonConstants.IGNORE);
+ }
+ String content = this.bundle.getString("alerter.availability.recover");
+ Alert resumeAlert = Alert.builder()
+ .tags(tags)
+ .target(CommonConstants.AVAILABILITY)
+ .content(content)
+ .priority(CommonConstants.ALERT_PRIORITY_CODE_WARNING)
+ .status(CommonConstants.ALERT_STATUS_CODE_RESTORED)
+ .firstAlarmTime(currentTimeMill)
+ .lastAlarmTime(notResolvedAlert.getLastAlarmTime())
+ .triggerTimes(1)
+ .build();
+ alarmCommonReduce.reduceAndSendAlarm(resumeAlert);
+ Runnable updateStatusJob = () -> {
+ // todo update pre all type alarm status
+ updateAvailabilityAlertStatus(monitorId, resumeAlert);
+ };
+ workerPool.executeJob(updateStatusJob);
+ }
+ }
+ }
+
+ private void updateAvailabilityAlertStatus(long monitorId, Alert restoreAlert) {
+ List availabilityAlerts = queryAvailabilityAlerts(monitorId, restoreAlert);
+ availabilityAlerts.stream().parallel().forEach(alert -> {
+ log.info("updating alert status solved id: {}", alert.getId());
+ alertService.editAlertStatus(ALERT_STATUS_CODE_SOLVED, List.of(alert.getId()));
+ });
+ }
+
+ private List queryAvailabilityAlerts(long monitorId, Alert restoreAlert) {
+ //create query condition
+ Specification specification = (root, query, criteriaBuilder) -> {
+ List andList = new ArrayList<>();
+
+ Predicate predicateTags = criteriaBuilder.like(root.get("tags").as(String.class), "%" + monitorId + "%");
+ andList.add(predicateTags);
+
+ Predicate predicatePriority = criteriaBuilder.equal(root.get("priority"), CommonConstants.ALERT_PRIORITY_CODE_EMERGENCY);
+ andList.add(predicatePriority);
+
+ Predicate predicateStatus = criteriaBuilder.equal(root.get("status"), ALERT_STATUS_CODE_PENDING);
+ andList.add(predicateStatus);
+
+ Predicate predicateAlertTime = criteriaBuilder.lessThanOrEqualTo(root.get("lastAlarmTime"), restoreAlert.getLastAlarmTime());
+ andList.add(predicateAlertTime);
+
+ Predicate[] predicates = new Predicate[andList.size()];
+ return criteriaBuilder.and(andList.toArray(predicates));
+ };
+
+ //query results
+ return alertService.getAlerts(specification);
+ }
+
+ @EventListener(SystemConfigChangeEvent.class)
+ public void onSystemConfigChangeEvent(SystemConfigChangeEvent event) {
+ log.info("calculate alarm receive system config change event: {}.", event.getSource());
+ this.bundle = ResourceBundleUtil.getBundle("alerter");
+ }
+
+ @EventListener(MonitorDeletedEvent.class)
+ public void onMonitorDeletedEvent(MonitorDeletedEvent event) {
+ log.info("calculate alarm receive monitor {} has been deleted.", event.getMonitorId());
+ this.triggeredAlertMap.remove(String.valueOf(event.getMonitorId()));
+ }
+
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/config/AlerterAutoConfiguration.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/config/AlerterAutoConfiguration.java
new file mode 100644
index 0000000..b0afa2c
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/config/AlerterAutoConfiguration.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.alert.config;
+
+import org.springframework.context.annotation.ComponentScan;
+
+/**
+ * Alert auto configuration.
+ */
+@ComponentScan(basePackages = "org.apache.hertzbeat.alert")
+public class AlerterAutoConfiguration {
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/controller/AlertConvergeController.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/controller/AlertConvergeController.java
new file mode 100644
index 0000000..df38ea5
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/controller/AlertConvergeController.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.alert.controller;
+
+import static org.apache.hertzbeat.common.constants.CommonConstants.MONITOR_NOT_EXIST_CODE;
+import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import org.apache.hertzbeat.alert.service.AlertConvergeService;
+import org.apache.hertzbeat.common.entity.alerter.AlertConverge;
+import org.apache.hertzbeat.common.entity.dto.Message;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * Alarm Converge management API
+ */
+@Tag(name = "Alert Converge API")
+@RestController
+@RequestMapping(path = "/api/alert/converge", produces = {APPLICATION_JSON_VALUE})
+public class AlertConvergeController {
+
+ @Autowired
+ private AlertConvergeService alertConvergeService;
+
+ @PostMapping
+ @Operation(summary = "New Alarm Converge", description = "Added an alarm Converge")
+ public ResponseEntity> addNewAlertConverge(@Valid @RequestBody AlertConverge alertConverge) {
+ alertConvergeService.validate(alertConverge, false);
+ alertConvergeService.addAlertConverge(alertConverge);
+ return ResponseEntity.ok(Message.success("Add success"));
+ }
+
+ @PutMapping
+ @Operation(summary = "Modifying an Alarm Converge", description = "Modify an existing alarm Converge")
+ public ResponseEntity> modifyAlertConverge(@Valid @RequestBody AlertConverge alertConverge) {
+ alertConvergeService.validate(alertConverge, true);
+ alertConvergeService.modifyAlertConverge(alertConverge);
+ return ResponseEntity.ok(Message.success("Modify success"));
+ }
+
+ @GetMapping(path = "/{id}")
+ @Operation(summary = "Querying Alarm Converge",
+ description = "You can obtain alarm Converge information based on the alarm Converge ID")
+ public ResponseEntity> getAlertConverge(
+ @Parameter(description = "Alarm Converge ID", example = "6565463543") @PathVariable("id") long id) {
+ AlertConverge alertConverge = alertConvergeService.getAlertConverge(id);
+ if (alertConverge == null) {
+ return ResponseEntity.ok(Message.fail(MONITOR_NOT_EXIST_CODE, "AlertConverge not exist."));
+ } else {
+ return ResponseEntity.ok(Message.success(alertConverge));
+ }
+ }
+
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/controller/AlertConvergesController.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/controller/AlertConvergesController.java
new file mode 100644
index 0000000..3d6fb58
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/controller/AlertConvergesController.java
@@ -0,0 +1,106 @@
+/*
+ * 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.springframework.http.MediaType.APPLICATION_JSON_VALUE;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.persistence.criteria.CriteriaBuilder;
+import jakarta.persistence.criteria.Predicate;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import org.apache.hertzbeat.alert.service.AlertConvergeService;
+import org.apache.hertzbeat.common.entity.alerter.AlertConverge;
+import org.apache.hertzbeat.common.entity.dto.Message;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.http.ResponseEntity;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * Converge the batch API for alarms
+ */
+@Tag(name = "Alert Converge Batch API")
+@RestController
+@RequestMapping(path = "/api/alert/converges", produces = {APPLICATION_JSON_VALUE})
+public class AlertConvergesController {
+
+ @Autowired
+ private AlertConvergeService alertConvergeService;
+
+ @GetMapping
+ @Operation(summary = "Query the alarm converge list",
+ description = "You can obtain the list of alarm converge by querying filter items")
+ public ResponseEntity>> getAlertConverges(
+ @Parameter(description = "Alarm Converge ID", example = "6565463543") @RequestParam(required = false) List ids,
+ @Parameter(description = "Search Name", example = "x") @RequestParam(required = false) String search,
+ @Parameter(description = "Sort field, default id", example = "id") @RequestParam(defaultValue = "id") String sort,
+ @Parameter(description = "Sort mode: asc: ascending, desc: descending", example = "desc") @RequestParam(defaultValue = "desc") String order,
+ @Parameter(description = "List current page", example = "0") @RequestParam(defaultValue = "0") int pageIndex,
+ @Parameter(description = "Number of list pages", example = "8") @RequestParam(defaultValue = "8") int pageSize) {
+
+ Specification specification = (root, query, criteriaBuilder) -> {
+ List andList = new ArrayList<>();
+ if (ids != null && !ids.isEmpty()) {
+ CriteriaBuilder.In inPredicate = criteriaBuilder.in(root.get("id"));
+ for (long id : ids) {
+ inPredicate.value(id);
+ }
+ andList.add(inPredicate);
+ }
+ if (StringUtils.hasText(search)) {
+ Predicate predicate = criteriaBuilder.or(
+ criteriaBuilder.like(
+ criteriaBuilder.lower(root.get("name")),
+ "%" + search.toLowerCase() + "%"
+ )
+ );
+ andList.add(predicate);
+ }
+ Predicate[] predicates = new Predicate[andList.size()];
+ return criteriaBuilder.and(andList.toArray(predicates));
+ };
+ Sort sortExp = Sort.by(new Sort.Order(Sort.Direction.fromString(order), sort));
+ PageRequest pageRequest = PageRequest.of(pageIndex, pageSize, sortExp);
+ Page alertConvergePage = alertConvergeService.getAlertConverges(specification, pageRequest);
+ return ResponseEntity.ok(Message.success(alertConvergePage));
+ }
+
+ @DeleteMapping
+ @Operation(summary = "Delete alarm converge in batches",
+ description = "Delete alarm converge in batches based on the alarm converge ID list")
+ public ResponseEntity> deleteAlertDefines(
+ @Parameter(description = "Alarm Converge IDs", example = "6565463543") @RequestParam(required = false) List ids
+ ) {
+ if (ids != null && !ids.isEmpty()) {
+ alertConvergeService.deleteAlertConverges(new HashSet<>(ids));
+ }
+ return ResponseEntity.ok(Message.success());
+ }
+
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/controller/AlertDefineController.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/controller/AlertDefineController.java
new file mode 100644
index 0000000..33f4292
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/controller/AlertDefineController.java
@@ -0,0 +1,116 @@
+/*
+ * 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.apache.hertzbeat.common.constants.CommonConstants.MONITOR_NOT_EXIST_CODE;
+import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.hertzbeat.alert.service.AlertDefineService;
+import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
+import org.apache.hertzbeat.common.entity.alerter.AlertDefineMonitorBind;
+import org.apache.hertzbeat.common.entity.dto.Message;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * Alarm definition management API
+ */
+@Tag(name = "Alert Define API")
+@RestController
+@RequestMapping(path = "/api/alert/define", produces = {APPLICATION_JSON_VALUE})
+public class AlertDefineController {
+
+ @Autowired
+ private AlertDefineService alertDefineService;
+
+ @PostMapping
+ @Operation(summary = "New Alarm Definition", description = "Added an alarm definition")
+ public ResponseEntity> addNewAlertDefine(@Valid @RequestBody AlertDefine alertDefine) {
+ // Verify request data
+ alertDefineService.validate(alertDefine, false);
+ alertDefineService.addAlertDefine(alertDefine);
+ return ResponseEntity.ok(Message.success("Add success"));
+ }
+
+ @PutMapping
+ @Operation(summary = "Modifying an Alarm Definition", description = "Modify an existing alarm definition")
+ public ResponseEntity> modifyAlertDefine(@Valid @RequestBody AlertDefine alertDefine) {
+ // Verify request data
+ alertDefineService.validate(alertDefine, true);
+ alertDefineService.modifyAlertDefine(alertDefine);
+ return ResponseEntity.ok(Message.success("Modify success"));
+ }
+
+ @GetMapping(path = "/{id}")
+ @Operation(summary = "Querying Alarm Definitions",
+ description = "You can obtain alarm definition information based on the alarm definition ID")
+ public ResponseEntity> getAlertDefine(
+ @Parameter(description = "Alarm Definition ID", example = "6565463543") @PathVariable("id") long id) {
+ // Obtaining Monitoring Information
+ AlertDefine alertDefine = alertDefineService.getAlertDefine(id);
+ if (alertDefine == null) {
+ return ResponseEntity.ok(Message.fail(MONITOR_NOT_EXIST_CODE, "AlertDefine not exist."));
+ } else {
+ return ResponseEntity.ok(Message.success(alertDefine));
+ }
+ }
+
+ @DeleteMapping(path = "/{id}")
+ @Operation(summary = "Deleting an Alarm Definition",
+ description = "If the alarm definition does not exist, the alarm is deleted successfully")
+ public ResponseEntity> deleteAlertDefine(
+ @Parameter(description = "Alarm Definition ID", example = "6565463543") @PathVariable("id") long id) {
+ // If the alarm definition does not exist or is deleted successfully, the deletion succeeds
+ alertDefineService.deleteAlertDefine(id);
+ return ResponseEntity.ok(Message.success("Delete success"));
+ }
+
+ @PostMapping(path = "/{alertDefineId}/monitors")
+ @Operation(summary = "Application alarm definition is associated with monitoring",
+ description = "Applies the association between specified alarm definitions and monitoring")
+ public ResponseEntity> applyAlertDefineMonitorsBind(
+ @Parameter(description = "Alarm Definition ID", example = "6565463543") @PathVariable("alertDefineId") long alertDefineId,
+ @RequestBody List alertDefineMonitorBinds) {
+ alertDefineService.applyBindAlertDefineMonitors(alertDefineId, alertDefineMonitorBinds);
+ return ResponseEntity.ok(Message.success("Apply success"));
+ }
+
+ @GetMapping(path = "/{alertDefineId}/monitors")
+ @Operation(summary = "Application alarm definition is associated with monitoring",
+ description = "Applies the association between specified alarm definitions and monitoring")
+ public ResponseEntity>> getAlertDefineMonitorsBind(
+ @Parameter(description = "Alarm Definition ID", example = "6565463543") @PathVariable("alertDefineId") long alertDefineId) {
+ List defineBinds = alertDefineService.getBindAlertDefineMonitors(alertDefineId);
+ defineBinds = defineBinds.stream().filter(item -> item.getMonitor() != null).collect(Collectors.toList());
+ return ResponseEntity.ok(Message.success(defineBinds));
+ }
+
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/controller/AlertDefinesController.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/controller/AlertDefinesController.java
new file mode 100644
index 0000000..2150f06
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/controller/AlertDefinesController.java
@@ -0,0 +1,145 @@
+/*
+ * 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.springframework.http.MediaType.APPLICATION_JSON_VALUE;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.persistence.criteria.CriteriaBuilder;
+import jakarta.persistence.criteria.Predicate;
+import jakarta.servlet.http.HttpServletResponse;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import org.apache.hertzbeat.alert.service.AlertDefineService;
+import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
+import org.apache.hertzbeat.common.entity.dto.Message;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.http.ResponseEntity;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * Define the batch API for alarms
+ */
+@Tag(name = "Alert Define Batch API")
+@RestController
+@RequestMapping(path = "/api/alert/defines", produces = {APPLICATION_JSON_VALUE})
+public class AlertDefinesController {
+
+ @Autowired
+ private AlertDefineService alertDefineService;
+
+ @GetMapping
+ @Operation(summary = "Example Query the alarm definition list",
+ description = "You can obtain the list of alarm definitions by querying filter items")
+ public ResponseEntity>> getAlertDefines(
+ @Parameter(description = "Alarm Definition ID", example = "6565463543") @RequestParam(required = false) List ids,
+ @Parameter(description = "Search-Target Expr Template", example = "x") @RequestParam(required = false) String search,
+ @Parameter(description = "Alarm Definition Severity", example = "6565463543") @RequestParam(required = false) Byte priority,
+ @Parameter(description = "Sort field, default id", example = "id") @RequestParam(defaultValue = "id") String sort,
+ @Parameter(description = "Sort mode: asc: ascending, desc: descending", example = "desc") @RequestParam(defaultValue = "desc") String order,
+ @Parameter(description = "List current page", example = "0") @RequestParam(defaultValue = "0") int pageIndex,
+ @Parameter(description = "Number of list pages", example = "8") @RequestParam(defaultValue = "8") int pageSize) {
+
+ Specification specification = (root, query, criteriaBuilder) -> {
+ List andList = new ArrayList<>();
+ if (ids != null && !ids.isEmpty()) {
+ CriteriaBuilder.In inPredicate = criteriaBuilder.in(root.get("id"));
+ for (long id : ids) {
+ inPredicate.value(id);
+ }
+ andList.add(inPredicate);
+ }
+ if (StringUtils.hasText(search)) {
+ Predicate predicate = criteriaBuilder.or(
+ criteriaBuilder.like(
+ criteriaBuilder.lower(root.get("app")),
+ "%" + search.toLowerCase() + "%"
+ ),
+ criteriaBuilder.like(
+ criteriaBuilder.lower(root.get("metric")),
+ "%" + search.toLowerCase() + "%"
+ ),
+ criteriaBuilder.like(
+ criteriaBuilder.lower(root.get("field")),
+ "%" + search.toLowerCase() + "%"
+ ),
+ criteriaBuilder.like(
+ criteriaBuilder.lower(root.get("expr")),
+ "%" + search.toLowerCase() + "%"
+ ),
+ criteriaBuilder.like(
+ criteriaBuilder.lower(root.get("template")),
+ "%" + search.toLowerCase() + "%"
+ )
+ );
+ andList.add(predicate);
+ }
+ if (priority != null) {
+ Predicate predicate = criteriaBuilder.equal(root.get("priority"), priority);
+ andList.add(predicate);
+ }
+ Predicate[] predicates = new Predicate[andList.size()];
+ return criteriaBuilder.and(andList.toArray(predicates));
+ };
+ Sort sortExp = Sort.by(new Sort.Order(Sort.Direction.fromString(order), sort));
+ PageRequest pageRequest = PageRequest.of(pageIndex, pageSize, sortExp);
+ Page alertDefinePage = alertDefineService.getAlertDefines(specification, pageRequest);
+ return ResponseEntity.ok(Message.success(alertDefinePage));
+ }
+
+ @DeleteMapping
+ @Operation(summary = "Delete alarm definitions in batches",
+ description = "Delete alarm definitions in batches based on the alarm definition ID list")
+ public ResponseEntity> deleteAlertDefines(
+ @Parameter(description = "Alarm Definition IDs", example = "6565463543") @RequestParam(required = false) List ids
+ ) {
+ if (ids != null && !ids.isEmpty()) {
+ alertDefineService.deleteAlertDefines(new HashSet<>(ids));
+ }
+ return ResponseEntity.ok(Message.success());
+ }
+
+ @GetMapping("/export")
+ @Operation(summary = "export alertDefine config", description = "export alarm definition configuration")
+ public void export(
+ @Parameter(description = "AlertDefine ID List", example = "656937901") @RequestParam List ids,
+ @Parameter(description = "Export Type:JSON,EXCEL,YAML") @RequestParam(defaultValue = "JSON") String type,
+ HttpServletResponse res) throws Exception {
+ alertDefineService.export(ids, type, res);
+ }
+
+ @PostMapping("/import")
+ @Operation(summary = "import alertDefine config", description = "import alarm definition configuration")
+ public ResponseEntity> importDefines(MultipartFile file) throws Exception {
+ alertDefineService.importConfig(file);
+ return ResponseEntity.ok(Message.success("Import success"));
+ }
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/controller/AlertReportController.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/controller/AlertReportController.java
new file mode 100644
index 0000000..89b67fd
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/controller/AlertReportController.java
@@ -0,0 +1,102 @@
+/*
+ * 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.springframework.http.MediaType.APPLICATION_JSON_VALUE;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import java.util.Date;
+import java.util.Optional;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.hertzbeat.alert.dto.CloudAlertReportAbstract;
+import org.apache.hertzbeat.alert.dto.GeneralCloudAlertReport;
+import org.apache.hertzbeat.alert.enums.CloudServiceAlarmInformationEnum;
+import org.apache.hertzbeat.alert.service.AlertService;
+import org.apache.hertzbeat.common.entity.dto.AlertReport;
+import org.apache.hertzbeat.common.entity.dto.Message;
+import org.apache.hertzbeat.common.util.JsonUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * Extern Alarm Manage API
+ */
+@Tag(name = "Extern Alarm Manage API")
+@RestController
+@RequestMapping(path = "/api/alerts/report", produces = {APPLICATION_JSON_VALUE})
+@Slf4j
+public class AlertReportController {
+
+ @Autowired
+ private AlertService alertService;
+
+ @PostMapping("/{cloud}")
+ @Operation(summary = "Interface for reporting external alarm information of cloud service")
+ public ResponseEntity> addNewAlertReportFromCloud(@PathVariable("cloud") String cloudServiceName,
+ @RequestBody String alertReport) {
+ CloudServiceAlarmInformationEnum cloudService = CloudServiceAlarmInformationEnum
+ .getEnumFromCloudServiceName(cloudServiceName);
+
+ AlertReport alert = null;
+ if (cloudService != null) {
+ try {
+ CloudAlertReportAbstract cloudAlertReport = JsonUtil
+ .fromJson(alertReport, cloudService.getCloudServiceAlarmInformationEntity());
+ assert cloudAlertReport != null;
+ alert = AlertReport.builder()
+ .content(cloudAlertReport.getContent())
+ .alertName(cloudAlertReport.getAlertName())
+ .alertTime(cloudAlertReport.getAlertTime())
+ .alertDuration(cloudAlertReport.getAlertDuration())
+ .priority(cloudAlertReport.getPriority())
+ .reportType(cloudAlertReport.getReportType())
+ .labels(cloudAlertReport.getLabels())
+ .annotations(cloudAlertReport.getAnnotations())
+ .build();
+ } catch (Exception e) {
+ log.error("[alert report] parse cloud service alarm content failed! cloud service: {} conrent: {}",
+ cloudService.name(), alertReport);
+ }
+ } else {
+ alert = AlertReport.builder()
+ .content("error do not has cloud service api")
+ .alertName("/api/alerts/report/" + cloudServiceName)
+ .alertTime(new Date().getTime())
+ .priority(1)
+ .reportType(1)
+ .build();
+ }
+ Optional.ofNullable(alert).ifPresent(alertReportPresent ->
+ alertService.addNewAlertReport(alertReportPresent));
+ return ResponseEntity.ok(Message.success("Add report success"));
+ }
+
+ @PostMapping
+ @Operation(summary = "Interface for reporting external and general alarm information",
+ description = "The interface is used to report external and general alarm information")
+ public ResponseEntity> addNewAlertReport(@RequestBody GeneralCloudAlertReport alertReport) {
+ alertReport.refreshAlertTime();
+ alertService.addNewAlertReport(alertReport);
+ return ResponseEntity.ok(Message.success("Add report success"));
+ }
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/controller/AlertSilenceController.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/controller/AlertSilenceController.java
new file mode 100644
index 0000000..56e9af3
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/controller/AlertSilenceController.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.alert.controller;
+
+import static org.apache.hertzbeat.common.constants.CommonConstants.MONITOR_NOT_EXIST_CODE;
+import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import org.apache.hertzbeat.alert.service.AlertSilenceService;
+import org.apache.hertzbeat.common.entity.alerter.AlertSilence;
+import org.apache.hertzbeat.common.entity.dto.Message;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * Alarm Silence management API
+ */
+@Tag(name = "Alert Silence API")
+@RestController
+@RequestMapping(path = "/api/alert/silence", produces = {APPLICATION_JSON_VALUE})
+public class AlertSilenceController {
+
+ @Autowired
+ private AlertSilenceService alertSilenceService;
+
+ @PostMapping
+ @Operation(summary = "New Alarm Silence", description = "Added an alarm Silence")
+ public ResponseEntity> addNewAlertSilence(@Valid @RequestBody AlertSilence alertSilence) {
+ alertSilenceService.validate(alertSilence, false);
+ alertSilenceService.addAlertSilence(alertSilence);
+ return ResponseEntity.ok(Message.success("Add success"));
+ }
+
+ @PutMapping
+ @Operation(summary = "Modifying an Alarm Silence", description = "Modify an existing alarm Silence")
+ public ResponseEntity> modifyAlertSilence(@Valid @RequestBody AlertSilence alertSilence) {
+ alertSilenceService.validate(alertSilence, true);
+ alertSilenceService.modifyAlertSilence(alertSilence);
+ return ResponseEntity.ok(Message.success("Modify success"));
+ }
+
+ @GetMapping(path = "/{id}")
+ @Operation(summary = "Querying Alarm Silence",
+ description = "You can obtain alarm Silence information based on the alarm Silence ID")
+ public ResponseEntity> getAlertSilence(
+ @Parameter(description = "Alarm Silence ID", example = "6565463543") @PathVariable("id") long id) {
+ AlertSilence alertSilence = alertSilenceService.getAlertSilence(id);
+ if (alertSilence == null) {
+ return ResponseEntity.ok(Message.fail(MONITOR_NOT_EXIST_CODE, "AlertSilence not exist."));
+ } else {
+ return ResponseEntity.ok(Message.success(alertSilence));
+ }
+ }
+
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/controller/AlertSilencesController.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/controller/AlertSilencesController.java
new file mode 100644
index 0000000..60fa156
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/controller/AlertSilencesController.java
@@ -0,0 +1,108 @@
+/*
+ * 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.springframework.http.MediaType.APPLICATION_JSON_VALUE;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.persistence.criteria.CriteriaBuilder;
+import jakarta.persistence.criteria.Predicate;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import org.apache.hertzbeat.alert.service.AlertSilenceService;
+import org.apache.hertzbeat.common.entity.alerter.AlertSilence;
+import org.apache.hertzbeat.common.entity.dto.Message;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.http.ResponseEntity;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * Silence the batch API for alarms
+ */
+@Tag(name = "Alert Silence Batch API")
+@RestController
+@RequestMapping(path = "/api/alert/silences", produces = {APPLICATION_JSON_VALUE})
+public class AlertSilencesController {
+
+ @Autowired
+ private AlertSilenceService alertSilenceService;
+
+ @GetMapping
+ @Operation(summary = "Query the alarm silence list",
+ description = "You can obtain the list of alarm silence by querying filter items")
+ public ResponseEntity>> getAlertSilences(
+ @Parameter(description = "Alarm Silence ID", example = "6565463543") @RequestParam(required = false) List ids,
+ @Parameter(description = "Search Name", example = "x") @RequestParam(required = false) String search,
+ @Parameter(description = "Sort field, default id", example = "id") @RequestParam(defaultValue = "id") String sort,
+ @Parameter(description = "Sort mode: asc: ascending, desc: descending", example = "desc") @RequestParam(defaultValue = "desc") String order,
+ @Parameter(description = "List current page", example = "0") @RequestParam(defaultValue = "0") int pageIndex,
+ @Parameter(description = "Number of list pages", example = "8") @RequestParam(defaultValue = "8") int pageSize) {
+
+ Specification specification = (root, query, criteriaBuilder) -> {
+ List andList = new ArrayList<>();
+ if (ids != null && !ids.isEmpty()) {
+ CriteriaBuilder.In inPredicate = criteriaBuilder.in(root.get("id"));
+ for (long id : ids) {
+ inPredicate.value(id);
+ }
+ andList.add(inPredicate);
+ }
+ if (StringUtils.hasText(search)) {
+ Predicate predicate = criteriaBuilder.or(
+ criteriaBuilder.like(
+ criteriaBuilder.lower(root.get("name")),
+ "%" + search.toLowerCase() + "%"
+ )
+ );
+ andList.add(predicate);
+ }
+ Predicate[] predicates = new Predicate[andList.size()];
+ return criteriaBuilder.and(andList.toArray(predicates));
+ };
+ Sort sortExp = Sort.by(new Sort.Order(Sort.Direction.fromString(order), sort));
+ PageRequest pageRequest = PageRequest.of(pageIndex, pageSize, sortExp);
+ Page alertSilencePage = alertSilenceService.getAlertSilences(specification, pageRequest);
+ Message> message = Message.success(alertSilencePage);
+ return ResponseEntity.ok(message);
+ }
+
+ @DeleteMapping
+ @Operation(summary = "Delete alarm silence in batches",
+ description = "Delete alarm silence in batches based on the alarm silence ID list")
+ public ResponseEntity> deleteAlertDefines(
+ @Parameter(description = "Alarm Silence IDs", example = "6565463543") @RequestParam(required = false) List ids
+ ) {
+ if (ids != null && !ids.isEmpty()) {
+ alertSilenceService.deleteAlertSilences(new HashSet<>(ids));
+ }
+ Message message = Message.success();
+ return ResponseEntity.ok(message);
+ }
+
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/controller/AlertsController.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/controller/AlertsController.java
new file mode 100644
index 0000000..65aac1e
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/controller/AlertsController.java
@@ -0,0 +1,146 @@
+/*
+ * 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.springframework.http.MediaType.APPLICATION_JSON_VALUE;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.persistence.criteria.CriteriaBuilder;
+import jakarta.persistence.criteria.Predicate;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import org.apache.hertzbeat.alert.dto.AlertSummary;
+import org.apache.hertzbeat.alert.service.AlertService;
+import org.apache.hertzbeat.common.entity.alerter.Alert;
+import org.apache.hertzbeat.common.entity.dto.Message;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * Alarm Management API
+ */
+@Tag(name = "Alarm Manage Batch API")
+@RestController
+@RequestMapping(path = "/api/alerts", produces = {APPLICATION_JSON_VALUE})
+public class AlertsController {
+
+ @Autowired
+ private AlertService alertService;
+
+ @GetMapping
+ @Operation(summary = "Get a list of alarm information based on query filter items", description = "according to the query filter items to obtain a list of alarm information")
+ public ResponseEntity>> getAlerts(
+ @Parameter(description = "Alarm ID List", example = "6565466456") @RequestParam(required = false) List ids,
+ @Parameter(description = "Alarm monitor object ID", example = "6565463543") @RequestParam(required = false) Long monitorId,
+ @Parameter(description = "Alarm level", example = "6565463543") @RequestParam(required = false) Byte priority,
+ @Parameter(description = "Alarm Status", example = "6565463543") @RequestParam(required = false) Byte status,
+ @Parameter(description = "Alarm content fuzzy query", example = "linux") @RequestParam(required = false) String content,
+ @Parameter(description = "Sort field, default id", example = "name") @RequestParam(defaultValue = "id") String sort,
+ @Parameter(description = "Sort Type", example = "desc") @RequestParam(defaultValue = "desc") String order,
+ @Parameter(description = "List current page", example = "0") @RequestParam(defaultValue = "0") int pageIndex,
+ @Parameter(description = "Number of list pagination", example = "8") @RequestParam(defaultValue = "8") int pageSize) {
+
+ Specification specification = (root, query, criteriaBuilder) -> {
+ List andList = new ArrayList<>();
+
+ if (ids != null && !ids.isEmpty()) {
+ CriteriaBuilder.In inPredicate = criteriaBuilder.in(root.get("id"));
+ for (long id : ids) {
+ inPredicate.value(id);
+ }
+ andList.add(inPredicate);
+ }
+ if (monitorId != null) {
+ Predicate predicate = criteriaBuilder.like(root.get("tags").as(String.class), "%" + monitorId + "%");
+ andList.add(predicate);
+ }
+ if (priority != null) {
+ Predicate predicate = criteriaBuilder.equal(root.get("priority"), priority);
+ andList.add(predicate);
+ }
+ if (status != null) {
+ Predicate predicate = criteriaBuilder.equal(root.get("status"), status);
+ andList.add(predicate);
+ }
+ if (content != null && !content.isEmpty()) {
+ Predicate predicateContent = criteriaBuilder.like(root.get("content"), "%" + content + "%");
+ andList.add(predicateContent);
+ }
+ Predicate[] predicates = new Predicate[andList.size()];
+ return criteriaBuilder.and(andList.toArray(predicates));
+ };
+ Sort sortExp = Sort.by(new Sort.Order(Sort.Direction.fromString(order), sort));
+ PageRequest pageRequest = PageRequest.of(pageIndex, pageSize, sortExp);
+ Page alertPage = alertService.getAlerts(specification, pageRequest);
+ Message> message = Message.success(alertPage);
+ return ResponseEntity.ok(message);
+ }
+
+ @DeleteMapping
+ @Operation(summary = "Delete alarms in batches", description = "according to the alarm ID list to delete the alarm information in batches")
+ public ResponseEntity> deleteAlerts(
+ @Parameter(description = "Alarm List ID", example = "6565463543") @RequestParam(required = false) List ids) {
+ if (ids != null && !ids.isEmpty()) {
+ alertService.deleteAlerts(new HashSet<>(ids));
+ }
+ Message message = Message.success();
+ return ResponseEntity.ok(message);
+ }
+
+ @DeleteMapping("/clear")
+ @Operation(summary = "Delete alarms in batches", description = "delete all alarm information")
+ public ResponseEntity> clearAllAlerts() {
+ alertService.clearAlerts();
+ Message message = Message.success();
+ return ResponseEntity.ok(message);
+ }
+
+ @PutMapping(path = "/status/{status}")
+ @Operation(summary = "Batch modify alarm status, set read and unread", description = "Batch modify alarm status, set read and unread")
+ public ResponseEntity> applyAlertDefinesStatus(
+ @Parameter(description = "Alarm status value", example = "0") @PathVariable Byte status,
+ @Parameter(description = "Alarm List IDS", example = "6565463543") @RequestParam(required = false) List ids) {
+ if (ids != null && status != null && !ids.isEmpty()) {
+ alertService.editAlertStatus(status, ids);
+ }
+ Message message = Message.success();
+ return ResponseEntity.ok(message);
+ }
+
+ @GetMapping(path = "/summary")
+ @Operation(summary = "Get alarm statistics", description = "Get alarm statistics information")
+ public ResponseEntity> getAlertsSummary() {
+ AlertSummary alertSummary = alertService.getAlertsSummary();
+ Message message = Message.success(alertSummary);
+ return ResponseEntity.ok(message);
+ }
+
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/dao/AlertConvergeDao.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/dao/AlertConvergeDao.java
new file mode 100644
index 0000000..b638f0d
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/dao/AlertConvergeDao.java
@@ -0,0 +1,37 @@
+/*
+ * 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.dao;
+
+import java.util.Set;
+import org.apache.hertzbeat.common.entity.alerter.AlertConverge;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
+
+/**
+ * AlertConverge Dao
+ */
+public interface AlertConvergeDao extends JpaRepository, JpaSpecificationExecutor {
+
+ /**
+ * Delete alarm converge based on the ID list
+ * @param convergeIds alert converge id list
+ */
+ @Modifying
+ void deleteAlertConvergesByIdIn(Set convergeIds);
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/dao/AlertDao.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/dao/AlertDao.java
new file mode 100644
index 0000000..80603d6
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/dao/AlertDao.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.alert.dao;
+
+import java.util.List;
+import java.util.Set;
+import org.apache.hertzbeat.alert.dto.AlertPriorityNum;
+import org.apache.hertzbeat.common.entity.alerter.Alert;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+
+/**
+ * Alert Database Operations
+ */
+public interface AlertDao extends JpaRepository, JpaSpecificationExecutor {
+
+ /**
+ * Delete alerts based on ID list
+ * @param alertIds Alert ID List
+ */
+ void deleteAlertsByIdIn(Set alertIds);
+
+ /**
+ * Updates the alarm status based on the alarm ID-status value
+ * @param status status value
+ * @param ids alarm ids
+ */
+ @Modifying
+ @Query("update Alert set status = :status where id in :ids")
+ void updateAlertsStatus(@Param(value = "status") Byte status, @Param(value = "ids") List ids);
+
+ /**
+ * Query the number of unhandled alarms of each alarm severity
+ * @return List of alerts num
+ */
+ @Query("select new org.apache.hertzbeat.alert.dto.AlertPriorityNum(mo.priority, count(mo.id)) from Alert mo where mo.status = 0 group by mo.priority")
+ List findAlertPriorityNum();
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/dao/AlertDefineBindDao.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/dao/AlertDefineBindDao.java
new file mode 100644
index 0000000..76bb187
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/dao/AlertDefineBindDao.java
@@ -0,0 +1,55 @@
+/*
+ * 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.dao;
+
+import java.util.List;
+import java.util.Set;
+import org.apache.hertzbeat.common.entity.alerter.AlertDefineMonitorBind;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+/**
+ * AlertDefineBind database operations
+ */
+public interface AlertDefineBindDao extends JpaRepository, JpaSpecificationExecutor {
+
+ /**
+ * Delete the alarm definition and monitor association based on the alarm definition ID
+ * @param alertDefineId Alarm Definition ID
+ */
+ void deleteAlertDefineBindsByAlertDefineIdEquals(Long alertDefineId);
+
+ /**
+ * Deleting alarms based on monitoring IDs defines monitoring associations
+ * @param monitorId Monitor Id
+ */
+ void deleteAlertDefineMonitorBindsByMonitorIdEquals(Long monitorId);
+
+ /**
+ * Delete alarm definition monitoring association based on monitoring ID list
+ * @param monitorIds Monitoring ID List
+ */
+ void deleteAlertDefineMonitorBindsByMonitorIdIn(Set monitorIds);
+
+ /**
+ * Query monitoring related information based on alarm definition ID
+ * @param alertDefineId Alarm Definition ID
+ * @return Associated monitoring information
+ */
+ List getAlertDefineBindsByAlertDefineIdEquals(Long alertDefineId);
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/dao/AlertDefineDao.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/dao/AlertDefineDao.java
new file mode 100644
index 0000000..8a13bee
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/dao/AlertDefineDao.java
@@ -0,0 +1,67 @@
+/*
+ * 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.dao;
+
+import java.util.List;
+import java.util.Set;
+import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+
+/**
+ * AlertDefine Dao
+ */
+public interface AlertDefineDao extends JpaRepository, JpaSpecificationExecutor {
+
+ /**
+ * Delete alarm definitions based on the ID list
+ * @param alertDefineIds alarm define ids
+ */
+ void deleteAlertDefinesByIdIn(Set alertDefineIds);
+
+ /**
+ * Query the default alarm thresholds based on the monitoring metrics type
+ * @param app monitoring type
+ * @param metric metrics
+ * @return alarm defines
+ */
+ List queryAlertDefinesByAppAndMetricAndPresetTrueAndEnableTrue(String app, String metric);
+
+ /**
+ * Query app metric alert define
+ * @param app app
+ * @param metric metric
+ * @return alert define
+ */
+ List queryAlertDefineByAppAndMetric(String app, String metric);
+
+ /**
+ * Query the alarm definition list associated with the monitoring ID
+ * @param monitorId monitor id
+ * @param app monitor type
+ * @param metrics metrics
+ * @return Alarm Definition List
+ */
+ @Query("select define from AlertDefine define join AlertDefineMonitorBind bind on bind.alertDefineId = define.id "
+ + "where bind.monitorId = :monitorId and define.app = :app and define.metric = :metrics and define.enable = true and define.preset = false")
+ List queryAlertDefinesByMonitor(@Param(value = "monitorId") Long monitorId,
+ @Param(value = "app") String app,
+ @Param(value = "metrics") String metrics);
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/dao/AlertMonitorDao.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/dao/AlertMonitorDao.java
new file mode 100644
index 0000000..8fd7614
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/dao/AlertMonitorDao.java
@@ -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.alert.dao;
+
+import java.util.List;
+import org.apache.hertzbeat.common.entity.manager.Monitor;
+import org.apache.hertzbeat.common.entity.manager.Tag;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+
+/**
+ * Alert Monitor Dao
+ */
+public interface AlertMonitorDao extends JpaRepository, JpaSpecificationExecutor {
+
+ /**
+ * Query the monitoring status of a specified monitoring state
+ * @param status status value
+ * @return Monitor the list
+ */
+ List findMonitorsByStatus(Byte status);
+
+ /**
+ * find monitor bind tags by monitorId
+ * @param monitorId monitorId
+ * @return bind tags
+ */
+ @Query("select tag from Tag tag join TagMonitorBind bind on bind.tagId = tag.id where bind.monitorId = :monitorId")
+ List findMonitorIdBindTags(@Param(value = "monitorId") Long monitorId);
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/dao/AlertSilenceDao.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/dao/AlertSilenceDao.java
new file mode 100644
index 0000000..4a409b8
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/dao/AlertSilenceDao.java
@@ -0,0 +1,38 @@
+/*
+ * 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.dao;
+
+import java.util.Set;
+import org.apache.hertzbeat.common.entity.alerter.AlertSilence;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
+
+/**
+ * AlertSilence Dao
+ */
+public interface AlertSilenceDao extends JpaRepository, JpaSpecificationExecutor {
+
+ /**
+ * Delete alarm silence based on the ID list
+ *
+ * @param silenceIds alert silence id list
+ */
+ @Modifying
+ void deleteAlertSilencesByIdIn(Set silenceIds);
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/dto/AlertPriorityNum.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/dto/AlertPriorityNum.java
new file mode 100644
index 0000000..8e85eff
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/dto/AlertPriorityNum.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.alert.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+/**
+ * Number of monitoring level alarms
+ */
+@Data
+@AllArgsConstructor
+public class AlertPriorityNum {
+
+ /**
+ * Alarm level
+ */
+ private byte priority;
+
+ /**
+ * Alarm count
+ */
+ private long num;
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/dto/AlertSummary.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/dto/AlertSummary.java
new file mode 100644
index 0000000..f75a680
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/dto/AlertSummary.java
@@ -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.alert.dto;
+
+import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+
+/**
+ * Alarm Statistics Information
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Schema(description = "Alarm Statistics Information")
+public class AlertSummary {
+
+ @Schema(title = "Total number of alerts (including processed and unprocessed alerts)",
+ example = "134", accessMode = READ_ONLY)
+ private long total;
+
+ @Schema(title = "Number of alerts handled",
+ example = "34", accessMode = READ_ONLY)
+ private long dealNum;
+
+ @Schema(title = "Alarm handling rate",
+ example = "39.34", accessMode = READ_ONLY)
+ private float rate;
+
+ @Schema(title = "Number of alarms whose alarm severity is warning alarms (referring to unhandled alarms)",
+ example = "43", accessMode = READ_ONLY)
+ private long priorityWarningNum;
+
+ @Schema(title = "Number of alarms whose alarm severity is critical alarms (referring to unhandled alarms)",
+ example = "56", accessMode = READ_ONLY)
+ private long priorityCriticalNum;
+
+ @Schema(title = "Number of alarms whose alarm severity is urgent alarms (referring to unhandled alarms)",
+ example = "23", accessMode = READ_ONLY)
+ private long priorityEmergencyNum;
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/dto/CloudAlertReportAbstract.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/dto/CloudAlertReportAbstract.java
new file mode 100644
index 0000000..eeecd5a
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/dto/CloudAlertReportAbstract.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.alert.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import java.util.Map;
+import org.apache.hertzbeat.common.entity.dto.AlertReport;
+
+/**
+ * Abstract parent of cloud service alarm information
+ * {@link AlertReport} - The method corresponds to the parameter of this class one by one. For details about the method, see this class
+ */
+public abstract class CloudAlertReportAbstract {
+
+ /**
+ * This parameter specifies the alarm name when a cloud alarm is converted to an internal alarm
+ */
+ @JsonIgnore
+ public abstract String getAlertName();
+
+ /**
+ * Interval when a cloud alarm is converted to an internal alarm
+ */
+ @JsonIgnore
+ public abstract Integer getAlertDuration();
+
+ /**
+ * Specifies the alarm time when a cloud alarm is converted to an internal alarm
+ */
+ @JsonIgnore
+ public abstract long getAlertTime();
+
+ /**
+ * Severity of an alarm when a cloud alarm is converted to an internal alarm
+ */
+ @JsonIgnore
+ public abstract Integer getPriority();
+
+ /**
+ * This parameter specifies the alarm type when a cloud alarm is converted to an internal alarm
+ */
+ @JsonIgnore
+ public abstract Integer getReportType();
+
+ /**
+ * Alarm label information about a cloud alarm that is converted to an internal alarm
+ */
+ @JsonIgnore
+ public abstract Map getLabels();
+
+ /**
+ * Alarm labels when a cloud alarm is converted to an internal alarm
+ */
+ @JsonIgnore
+ public abstract Map getAnnotations();
+
+ /**
+ * This topic describes the alarm content when a cloud alarm is converted to an internal alarm
+ */
+ @JsonIgnore
+ public abstract String getContent();
+
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/dto/GeneralCloudAlertReport.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/dto/GeneralCloudAlertReport.java
new file mode 100644
index 0000000..8d71953
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/dto/GeneralCloudAlertReport.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.alert.dto;
+
+import java.util.Optional;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hertzbeat.alert.util.DateUtil;
+import org.apache.hertzbeat.common.entity.dto.AlertReport;
+
+/**
+ * Generic cloud alarm entity class
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+public class GeneralCloudAlertReport extends AlertReport {
+
+ /**
+ * Alarm date and time
+ */
+ private String alertDateTime;
+
+ /**
+ * DATE TIME FORMAT
+ */
+ private String dateTimeFormat;
+
+ /**
+ * You can refresh the timestamp of the alarm time with enhanced properties
+ */
+ public void refreshAlertTime() {
+ if (getAlertTime() != 0L) {
+ return;
+ }
+ if (StringUtils.isNotBlank(alertDateTime)) {
+ Long timeStamp = null;
+ if (StringUtils.isNotBlank(dateTimeFormat)) {
+ Optional tsf = DateUtil.getTimeStampFromFormat(alertDateTime, dateTimeFormat);
+ boolean present = tsf.isPresent();
+ if (present) {
+ timeStamp = tsf.get();
+ }
+ }
+ if (timeStamp == null) {
+ Optional tsf = DateUtil.getTimeStampFromSomeFormats(alertDateTime);
+ boolean present = tsf.isPresent();
+ if (present) {
+ timeStamp = tsf.get();
+ }
+ }
+ if (timeStamp != null) {
+ setAlertTime(timeStamp);
+ return;
+ }
+ }
+ throw new RuntimeException("parse alarm time error");
+ }
+
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/dto/TenCloudAlertReport.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/dto/TenCloudAlertReport.java
new file mode 100644
index 0000000..6ea51c1
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/dto/TenCloudAlertReport.java
@@ -0,0 +1,229 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.alert.dto;
+
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.io.Serializable;
+import java.util.Map;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import org.apache.hertzbeat.alert.util.DateUtil;
+
+/**
+ *
+ * Tencent cloud alarm entity class
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+@Builder
+public class TenCloudAlertReport extends CloudAlertReportAbstract implements Serializable {
+ @JsonProperty("sessionID")
+ private String sessionId;
+ private String alarmStatus;
+ private String alarmType;
+ private AlarmObjInfo alarmObjInfo;
+ private AlarmPolicyInfo alarmPolicyInfo;
+ private String firstOccurTime;
+ private int durationTime;
+ private String recoverTime;
+
+ /**
+ * Alarm Object information
+ */
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ public static class AlarmObjInfo {
+ private String region;
+ private String namespace;
+ @JsonProperty("appID")
+ private String appId;
+ private String uin;
+ private Dimensions dimensions;
+ }
+
+ /**
+ * Uniform Resource ID information
+ */
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ public static class Dimensions {
+ @JsonProperty("unInstanceID")
+ private String unInstanceId;
+ @JsonProperty("objID")
+ private String objId;
+ }
+
+ /**
+ * Alarm policy information
+ */
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ public static class AlarmPolicyInfo {
+ @JsonProperty("policyID")
+ private String policyId;
+ private String policyType;
+ private String policyName;
+ @JsonProperty("policyTypeCName")
+ private String policyTypeCname;
+ private Conditions conditions;
+ }
+
+ /**
+ * Parameters of indicator alarms
+ */
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ public static class Conditions {
+ // alarm metrics parameters
+ private String metricName;
+ private String metricShowName;
+ private String calcType;
+ private String calcValue;
+ private String calcUnit;
+ private String currentValue;
+ private String historyValue;
+ private String unit;
+ private String period;
+ private String periodNum;
+ private String alarmNotifyType;
+ private long alarmNotifyPeriod;
+
+ // alarm event parameters
+ private String productName;
+ private String productShowName;
+ private String eventName;
+ private String eventShowName;
+ }
+
+ @Override
+ public String getAlertName() {
+ return "TenCloud|腾讯云";
+ }
+
+ @Override
+ public Integer getAlertDuration() {
+ return this.durationTime;
+ }
+
+ @Override
+ public long getAlertTime() {
+
+ return DateUtil.getTimeStampFromFormat(getFirstOccurTime(), "yyyy-MM-dd HH:mm:ss")
+ .orElse(0L);
+ }
+
+ @Override
+ public Integer getPriority() {
+ return 1;
+ }
+
+ @Override
+ public Integer getReportType() {
+ return 1;
+ }
+
+ @Override
+ public Map getLabels() {
+ return Map.of("app", "TenCloud");
+ }
+
+ @Override
+ public Map getAnnotations() {
+ return Map.of("app", "TenCloud");
+ }
+
+ /**
+ * Transaction alarm
+ */
+ private static final String EVENT = "event";
+
+ /**
+ * Indicator alarm
+ */
+ private static final String METRIC = "metric";
+
+ /**
+ * If the following alarm types increase, the entity class can be divided into a parent class and multiple subclasses, and then the method can be implemented in the subclass
+ * Since there are only two, it will not be split for the time being
+ */
+ @Override
+ public String getContent() {
+ StringBuilder contentBuilder = new StringBuilder();
+ // 判断类型
+ if (EVENT.equals(getAlarmType())) {
+ contentBuilder
+ .append("[")
+ .append("告警状态 | ")
+ .append("0".equals(alarmStatus) ? "恢复" : "告警")
+ .append("]\n")
+ .append("[")
+ .append("告警对象信息 | ")
+ .append(getAlarmObjInfo().getRegion() == null ? "" : "region:" + getAlarmObjInfo().getRegion())
+ .append(";").append("appId:").append(getAlarmObjInfo().getAppId())
+ .append(";").append("uni:").append(getAlarmObjInfo().getUin())
+ .append(";").append("unInstanceId:").append(getAlarmObjInfo().getDimensions().getUnInstanceId())
+ .append("]\n")
+ .append("[")
+ .append("告警策略组信息 | ")
+ .append("名称:").append(getAlarmPolicyInfo().getPolicyName())
+ .append(";")
+ .append("策略类型展示名称:").append(getAlarmPolicyInfo().getConditions().getProductName())
+ .append(",").append(getAlarmPolicyInfo().getConditions().getProductShowName())
+ .append(";")
+ .append("事件告警名称:").append(getAlarmPolicyInfo().getConditions().getEventName())
+ .append(",").append(getAlarmPolicyInfo().getConditions().getEventShowName())
+ .append("]");
+ } else if (METRIC.equals(getAlarmType())) {
+ contentBuilder
+ .append("[")
+ .append("告警对象:")
+ .append(getAlarmObjInfo().getRegion() == null ? "" : getAlarmObjInfo().getRegion())
+ .append(getAlarmObjInfo().getRegion() == null ? "" : "|")
+ .append(getAlarmObjInfo().getNamespace())
+ .append("]")
+ .append("[")
+ .append("告警内容:")
+ .append(getAlarmPolicyInfo().getPolicyTypeCname()).append("|")
+ .append(getAlarmPolicyInfo().getConditions().getMetricShowName()).append("|")
+ .append(getAlarmPolicyInfo().getConditions().getMetricName())
+ .append(getAlarmPolicyInfo().getConditions().getCalcType())
+ .append(getAlarmPolicyInfo().getConditions().getCalcValue())
+ .append(getAlarmPolicyInfo().getConditions().getCalcUnit())
+ .append("]")
+ .append("[")
+ .append("当前数据")
+ .append(getAlarmPolicyInfo().getConditions().getCurrentValue())
+ .append(getAlarmPolicyInfo().getConditions().getCalcUnit())
+ .append("]");
+ }
+ return contentBuilder.toString();
+ }
+
+}
+
+
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/enums/CloudServiceAlarmInformationEnum.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/enums/CloudServiceAlarmInformationEnum.java
new file mode 100644
index 0000000..ebaeb0a
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/enums/CloudServiceAlarmInformationEnum.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.alert.enums;
+
+import java.util.Arrays;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.apache.hertzbeat.alert.dto.CloudAlertReportAbstract;
+import org.apache.hertzbeat.alert.dto.TenCloudAlertReport;
+
+/**
+ * Cloud server alarm enum
+ */
+@AllArgsConstructor
+@Getter
+public enum CloudServiceAlarmInformationEnum {
+
+ TencentCloud("tencloud", TenCloudAlertReport.class);
+
+ /**
+ * cloud service name
+ */
+ private final String cloudServiceName;
+
+ /**
+ * cloud service body
+ */
+ private final Class extends CloudAlertReportAbstract> cloudServiceAlarmInformationEntity;
+
+ public static CloudServiceAlarmInformationEnum getEnumFromCloudServiceName(String name) {
+ return Arrays.stream(CloudServiceAlarmInformationEnum.values())
+ .filter(cloudService -> cloudService.cloudServiceName.equals(name))
+ .findFirst()
+ .orElse(null);
+ }
+
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/reduce/AlarmCommonReduce.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/reduce/AlarmCommonReduce.java
new file mode 100644
index 0000000..a2f7b8d
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/reduce/AlarmCommonReduce.java
@@ -0,0 +1,73 @@
+/*
+ * 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.reduce;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.hertzbeat.alert.dao.AlertMonitorDao;
+import org.apache.hertzbeat.common.constants.CommonConstants;
+import org.apache.hertzbeat.common.entity.alerter.Alert;
+import org.apache.hertzbeat.common.entity.manager.Tag;
+import org.apache.hertzbeat.common.queue.CommonDataQueue;
+import org.springframework.stereotype.Service;
+
+/**
+ * reduce alarm and send alert data
+ */
+@Service
+@RequiredArgsConstructor
+@Slf4j
+public class AlarmCommonReduce {
+
+ private final AlarmSilenceReduce alarmSilenceReduce;
+
+ private final AlarmConvergeReduce alarmConvergeReduce;
+
+ private final CommonDataQueue dataQueue;
+
+ private final AlertMonitorDao alertMonitorDao;
+
+ public void reduceAndSendAlarm(Alert alert) {
+ alert.setTimes(1);
+ Map tags = alert.getTags();
+ if (tags == null) {
+ tags = new HashMap<>(8);
+ alert.setTags(tags);
+ }
+ String monitorIdStr = tags.get(CommonConstants.TAG_MONITOR_ID);
+ if (monitorIdStr == null) {
+ log.debug("receiver extern alarm message: {}", alert);
+ } else {
+ long monitorId = Long.parseLong(monitorIdStr);
+ List tagList = alertMonitorDao.findMonitorIdBindTags(monitorId);
+ for (Tag tag : tagList) {
+ if (!tags.containsKey(tag.getName())) {
+ tags.put(tag.getName(), tag.getTagValue());
+ }
+ }
+ }
+ // converge -> silence
+ if (alarmConvergeReduce.filterConverge(alert) && alarmSilenceReduce.filterSilence(alert)) {
+ dataQueue.sendAlertsData(alert);
+ }
+ }
+
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/reduce/AlarmConvergeReduce.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/reduce/AlarmConvergeReduce.java
new file mode 100644
index 0000000..b2c1350
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/reduce/AlarmConvergeReduce.java
@@ -0,0 +1,160 @@
+/*
+ * 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.reduce;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.hertzbeat.alert.dao.AlertConvergeDao;
+import org.apache.hertzbeat.common.cache.CacheFactory;
+import org.apache.hertzbeat.common.cache.CommonCacheService;
+import org.apache.hertzbeat.common.constants.CommonConstants;
+import org.apache.hertzbeat.common.entity.alerter.Alert;
+import org.apache.hertzbeat.common.entity.alerter.AlertConverge;
+import org.apache.hertzbeat.common.entity.manager.TagItem;
+import org.springframework.stereotype.Service;
+
+/**
+ * alarm converge
+ */
+@Service
+public class AlarmConvergeReduce {
+
+ private final AlertConvergeDao alertConvergeDao;
+
+ private final Map converageAlertMap;
+
+ public AlarmConvergeReduce(AlertConvergeDao alertConvergeDao) {
+ this.alertConvergeDao = alertConvergeDao;
+ this.converageAlertMap = new ConcurrentHashMap<>(16);
+ }
+
+ /**
+ * currentAlert converge filter data
+ *
+ * @param currentAlert currentAlert
+ * @return true when not filter
+ */
+ @SuppressWarnings("unchecked")
+ public boolean filterConverge(Alert currentAlert) {
+ // ignore monitor status auto recover notice
+ if (currentAlert.getTags() != null && currentAlert.getTags().containsKey(CommonConstants.IGNORE)) {
+ return true;
+ }
+ if (currentAlert.getStatus() == CommonConstants.ALERT_STATUS_CODE_RESTORED) {
+ // restored alert
+ int alertHash = Objects.hash(CommonConstants.ALERT_PRIORITY_CODE_CRITICAL)
+ + Arrays.hashCode(currentAlert.getTags().keySet().toArray(new String[0]))
+ + Arrays.hashCode(currentAlert.getTags().values().toArray(new String[0]));
+ converageAlertMap.remove(alertHash);
+ alertHash = Objects.hash(CommonConstants.ALERT_PRIORITY_CODE_EMERGENCY)
+ + Arrays.hashCode(currentAlert.getTags().keySet().toArray(new String[0]))
+ + Arrays.hashCode(currentAlert.getTags().values().toArray(new String[0]));
+ converageAlertMap.remove(alertHash);
+ alertHash = Objects.hash(CommonConstants.ALERT_PRIORITY_CODE_WARNING)
+ + Arrays.hashCode(currentAlert.getTags().keySet().toArray(new String[0]))
+ + Arrays.hashCode(currentAlert.getTags().values().toArray(new String[0]));
+ converageAlertMap.remove(alertHash);
+ return true;
+ }
+ CommonCacheService convergeCache = CacheFactory.getAlertConvergeCache();
+ List alertConvergeList = (List) convergeCache.get(CommonConstants.CACHE_ALERT_CONVERGE);
+ if (alertConvergeList == null) {
+ alertConvergeList = alertConvergeDao.findAll();
+ // matchAll is in the last
+ alertConvergeList.sort((item1, item2) -> {
+ if (item1.isMatchAll()) {
+ return 1;
+ } else if (item2.isMatchAll()) {
+ return -1;
+ } else {
+ return 0;
+ }
+ });
+ convergeCache.put(CommonConstants.CACHE_ALERT_CONVERGE, alertConvergeList);
+ }
+ for (AlertConverge alertConverge : alertConvergeList) {
+ if (!alertConverge.isEnable()) {
+ continue;
+ }
+ boolean match = alertConverge.isMatchAll();
+ if (!match) {
+ List tags = alertConverge.getTags();
+ if (currentAlert.getTags() != null && !currentAlert.getTags().isEmpty()) {
+ Map alertTagMap = currentAlert.getTags();
+ match = tags.stream().anyMatch(item -> {
+ if (alertTagMap.containsKey(item.getName())) {
+ String tagValue = alertTagMap.get(item.getName());
+ if (tagValue == null && item.getValue() == null) {
+ return true;
+ } else {
+ return tagValue != null && tagValue.equals(item.getValue());
+ }
+ } else {
+ return false;
+ }
+ });
+ } else {
+ match = true;
+ }
+ if (match && alertConverge.getPriorities() != null && !alertConverge.getPriorities().isEmpty()) {
+ match = alertConverge.getPriorities().stream().anyMatch(item -> item != null && item == currentAlert.getPriority());
+ }
+ }
+ if (match) {
+ long evalInterval = alertConverge.getEvalInterval() * 1000;
+ long now = System.currentTimeMillis();
+ if (evalInterval <= 0) {
+ return true;
+ }
+ int alertHash = Objects.hash(currentAlert.getPriority())
+ + Arrays.hashCode(currentAlert.getTags().keySet().toArray(new String[0]))
+ + Arrays.hashCode(currentAlert.getTags().values().toArray(new String[0]));
+ Alert preAlert = converageAlertMap.get(alertHash);
+ if (preAlert == null) {
+ currentAlert.setTimes(1);
+ currentAlert.setFirstAlarmTime(now);
+ currentAlert.setLastAlarmTime(now);
+ converageAlertMap.put(alertHash, currentAlert.clone());
+ return true;
+ } else {
+ if (now - preAlert.getFirstAlarmTime() < evalInterval) {
+ preAlert.setTimes(preAlert.getTimes() + 1);
+ preAlert.setLastAlarmTime(now);
+ return false;
+ } else {
+ currentAlert.setTimes(preAlert.getTimes());
+ if (preAlert.getTimes() == 1) {
+ currentAlert.setFirstAlarmTime(now);
+ } else {
+ currentAlert.setFirstAlarmTime(preAlert.getFirstAlarmTime());
+ }
+ currentAlert.setLastAlarmTime(now);
+ preAlert.setFirstAlarmTime(now);
+ preAlert.setLastAlarmTime(now);
+ preAlert.setTimes(1);
+ return true;
+ }
+ }
+ }
+ }
+ return true;
+ }
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/reduce/AlarmSilenceReduce.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/reduce/AlarmSilenceReduce.java
new file mode 100644
index 0000000..8a807a0
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/reduce/AlarmSilenceReduce.java
@@ -0,0 +1,120 @@
+/*
+ * 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.reduce;
+
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import lombok.RequiredArgsConstructor;
+import org.apache.hertzbeat.alert.dao.AlertSilenceDao;
+import org.apache.hertzbeat.common.cache.CacheFactory;
+import org.apache.hertzbeat.common.cache.CommonCacheService;
+import org.apache.hertzbeat.common.constants.CommonConstants;
+import org.apache.hertzbeat.common.entity.alerter.Alert;
+import org.apache.hertzbeat.common.entity.alerter.AlertSilence;
+import org.apache.hertzbeat.common.entity.manager.TagItem;
+import org.springframework.stereotype.Service;
+
+/**
+ * silence alarm
+ */
+@Service
+@RequiredArgsConstructor
+public class AlarmSilenceReduce {
+
+ private final AlertSilenceDao alertSilenceDao;
+
+ /**
+ * alert silence filter data
+ * @param alert alert
+ * @return true when not filter
+ */
+ @SuppressWarnings("unchecked")
+ public boolean filterSilence(Alert alert) {
+ CommonCacheService silenceCache = CacheFactory.getAlertSilenceCache();
+ List alertSilenceList = (List) silenceCache.get(CommonConstants.CACHE_ALERT_SILENCE);
+ if (alertSilenceList == null) {
+ alertSilenceList = alertSilenceDao.findAll();
+ silenceCache.put(CommonConstants.CACHE_ALERT_SILENCE, alertSilenceList);
+ }
+ for (AlertSilence alertSilence : alertSilenceList) {
+ if (!alertSilence.isEnable()) {
+ continue;
+ }
+ // if match the silence rule, return
+ boolean match = alertSilence.isMatchAll();
+ if (!match) {
+ List tags = alertSilence.getTags();
+ if (alert.getTags() != null && !alert.getTags().isEmpty()) {
+ Map alertTagMap = alert.getTags();
+ match = tags.stream().anyMatch(item -> {
+ if (alertTagMap.containsKey(item.getName())) {
+ String tagValue = alertTagMap.get(item.getName());
+ if (tagValue == null && item.getValue() == null) {
+ return true;
+ } else {
+ return tagValue != null && tagValue.equals(item.getValue());
+ }
+ } else {
+ return false;
+ }
+ });
+ } else {
+ match = true;
+ }
+ if (match && alertSilence.getPriorities() != null && !alertSilence.getPriorities().isEmpty()) {
+ match = alertSilence.getPriorities().stream().anyMatch(item -> item != null && item == alert.getPriority());
+ }
+ }
+ if (match) {
+ LocalDateTime nowDate = LocalDateTime.now();
+ if (alertSilence.getType() == 0) {
+ // once time
+ boolean startMatch = alertSilence.getPeriodStart() == null || nowDate.isAfter(alertSilence.getPeriodStart().toLocalDateTime());
+ boolean endMatch = alertSilence.getPeriodEnd() == null || nowDate.isBefore(alertSilence.getPeriodEnd().toLocalDateTime());
+ if (startMatch && endMatch) {
+ int times = Optional.ofNullable(alertSilence.getTimes()).orElse(0);
+ alertSilence.setTimes(times + 1);
+ alertSilenceDao.save(alertSilence);
+ return false;
+ }
+ } else if (alertSilence.getType() == 1) {
+ // cyc time
+ int currentDayOfWeek = nowDate.toLocalDate().getDayOfWeek().getValue();
+ if (alertSilence.getDays() != null && !alertSilence.getDays().isEmpty()) {
+ boolean dayMatch = alertSilence.getDays().stream().anyMatch(item -> item == currentDayOfWeek);
+ if (dayMatch) {
+ LocalTime nowTime = nowDate.toLocalTime();
+ boolean startMatch = alertSilence.getPeriodStart() == null || nowTime.isAfter(alertSilence.getPeriodStart().toLocalTime());
+ boolean endMatch = alertSilence.getPeriodEnd() == null || nowTime.isBefore(alertSilence.getPeriodEnd().toLocalTime());
+ if (startMatch && endMatch) {
+ int times = Optional.ofNullable(alertSilence.getTimes()).orElse(0);
+ alertSilence.setTimes(times + 1);
+ alertSilenceDao.save(alertSilence);
+ return false;
+ }
+ }
+ }
+ }
+ }
+ }
+ return true;
+ }
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/AlertConvergeService.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/AlertConvergeService.java
new file mode 100644
index 0000000..b41fdc5
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/AlertConvergeService.java
@@ -0,0 +1,75 @@
+/*
+ * 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 java.util.Set;
+import org.apache.hertzbeat.common.entity.alerter.AlertConverge;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.jpa.domain.Specification;
+
+/**
+ * management interface service for alert converge
+ */
+public interface AlertConvergeService {
+ /**
+ * Verify the correctness of the request data parameters
+ * @param alertConverge AlertConverge
+ * @param isModify whether modify
+ * @throws IllegalArgumentException A checksum parameter error is thrown
+ */
+ void validate(AlertConverge alertConverge, boolean isModify) throws IllegalArgumentException;
+
+ /**
+ * New AlertConverge
+ * @param alertConverge AlertConverge Entity
+ * @throws RuntimeException Added procedure exception throwing
+ */
+ void addAlertConverge(AlertConverge alertConverge) throws RuntimeException;
+
+ /**
+ * Modifying an AlertConverge
+ * @param alertConverge Alarm definition Entity
+ * @throws RuntimeException Exception thrown during modification
+ */
+ void modifyAlertConverge(AlertConverge alertConverge) throws RuntimeException;
+
+ /**
+ * Obtain AlertConverge information
+ * @param convergeId AlertConverge ID
+ * @return AlertConverge
+ * @throws RuntimeException An exception was thrown during the query
+ */
+ AlertConverge getAlertConverge(long convergeId) throws RuntimeException;
+
+
+ /**
+ * Delete AlertConverge in batches
+ * @param convergeIds AlertConverge IDs
+ * @throws RuntimeException Exception thrown during deletion
+ */
+ void deleteAlertConverges(Set convergeIds) throws RuntimeException;
+
+ /**
+ * Dynamic conditional query
+ * @param specification Query conditions
+ * @param pageRequest Paging parameters
+ * @return The query results
+ */
+ Page getAlertConverges(Specification specification, PageRequest pageRequest);
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/AlertDefineImExportService.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/AlertDefineImExportService.java
new file mode 100644
index 0000000..dfcbb19
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/AlertDefineImExportService.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.alert.service;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+
+/**
+ * Configuration Import Export
+ */
+public interface AlertDefineImExportService {
+ /**
+ * Import Configuration
+ * @param is input stream
+ */
+ void importConfig(InputStream is);
+
+ /**
+ * Export Configuration
+ * @param os output stream
+ * @param configList configuration list
+ */
+ void exportConfig(OutputStream os, List configList);
+
+ /**
+ * Export file type
+ * @return file type
+ */
+ String type();
+
+ /**
+ * Get Export File Name
+ * @return file name
+ */
+ String getFileName();
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/AlertDefineService.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/AlertDefineService.java
new file mode 100644
index 0000000..7431916
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/AlertDefineService.java
@@ -0,0 +1,144 @@
+/*
+ * 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 jakarta.servlet.http.HttpServletResponse;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
+import org.apache.hertzbeat.common.entity.alerter.AlertDefineMonitorBind;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * Alarm define manager service
+ */
+public interface AlertDefineService {
+
+ /**
+ * Verify the correctness of the request data parameters
+ * @param alertDefine alertDefine
+ * @param isModify whether modify
+ * @throws IllegalArgumentException A checksum parameter error is thrown
+ */
+ void validate(AlertDefine alertDefine, boolean isModify) throws IllegalArgumentException;
+
+ /**
+ * New Alarm Definition
+ * @param alertDefine Alarm definition Entity
+ * @throws RuntimeException Added procedure exception throwing
+ */
+ void addAlertDefine(AlertDefine alertDefine) throws RuntimeException;
+
+ /**
+ * Modifying an Alarm Definition
+ * @param alertDefine Alarm definition Entity
+ * @throws RuntimeException Exception thrown during modification
+ */
+ void modifyAlertDefine(AlertDefine alertDefine) throws RuntimeException;
+
+ /**
+ * Deleting an Alarm Definition
+ * @param alertId Alarm Definition ID
+ * @throws RuntimeException Exception thrown during deletion
+ */
+ void deleteAlertDefine(long alertId) throws RuntimeException;
+
+ /**
+ * Obtain alarm definition information
+ * @param alertId Monitor the ID
+ * @return AlertDefine
+ * @throws RuntimeException An exception was thrown during the query
+ */
+ AlertDefine getAlertDefine(long alertId) throws RuntimeException;
+
+
+ /**
+ * Delete alarm definitions in batches
+ * @param alertIds Alarm Definition IDs
+ * @throws RuntimeException Exception thrown during deletion
+ */
+ void deleteAlertDefines(Set alertIds) throws RuntimeException;
+
+ /**
+ * Dynamic conditional query
+ * @param specification Query conditions
+ * @param pageRequest Paging parameters
+ * @return The query results
+ */
+ Page getMonitorBindAlertDefines(Specification specification, PageRequest pageRequest);
+
+ /**
+ * Association between application alarm schedule and monitoring
+ * @param alertId Alarm Definition ID
+ * @param alertDefineBinds correlation
+ */
+ void applyBindAlertDefineMonitors(Long alertId, List alertDefineBinds);
+
+ /**
+ * Query the alarm definitions that match the specified metrics associated with the monitoring ID
+ * @param monitorId Monitor the ID
+ * @param app Monitoring type
+ * @param metrics metrics
+ * @return field - define[]
+ */
+ Map> getMonitorBindAlertDefines(long monitorId, String app, String metrics);
+
+ /**
+ * Query the alarm definitions that match the specified metrics associated with the monitoring ID
+ * @param monitorId Monitor the ID
+ * @param app Monitoring type
+ * @param metrics metrics
+ * @return field - define[]
+ */
+ AlertDefine getMonitorBindAlertAvaDefine(long monitorId, String app, String metrics);
+
+ /**
+ * Dynamic conditional query
+ * @param specification Query conditions
+ * @param pageRequest Paging parameters
+ * @return The query results
+ */
+ Page getAlertDefines(Specification specification, PageRequest pageRequest);
+
+ /**
+ * Query the associated monitoring list information based on the alarm definition ID
+ * @param alertDefineId Alarm Definition ID
+ * @return Associated information about the monitoring list
+ */
+ List getBindAlertDefineMonitors(long alertDefineId);
+
+ /**
+ * Export file configuration of specified type based on ID list and export file type
+ * @param ids AlertDefine ID
+ * @param type File Type
+ * @param res Response
+ * @throws Exception An exception was thrown during the export
+ */
+ void export(List ids, String type, HttpServletResponse res) throws Exception;
+
+ /**
+ * Add alarm threshold rules based on the uploaded alarm threshold file
+ * @param file Upload File
+ * @throws Exception An exception was thrown during the importConfig
+ */
+ void importConfig(MultipartFile file) throws Exception;
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/AlertService.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/AlertService.java
new file mode 100644
index 0000000..2bc6b18
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/AlertService.java
@@ -0,0 +1,85 @@
+/*
+ * 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 java.util.HashSet;
+import java.util.List;
+import org.apache.hertzbeat.alert.dto.AlertSummary;
+import org.apache.hertzbeat.common.entity.alerter.Alert;
+import org.apache.hertzbeat.common.entity.dto.AlertReport;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.jpa.domain.Specification;
+
+/**
+ * Alarm information management interface
+ */
+public interface AlertService {
+
+ /**
+ * Add alarm record
+ * @param alert Alert entity
+ * @throws RuntimeException Add process exception throw
+ */
+ void addAlert(Alert alert) throws RuntimeException;
+
+ /**
+ * Dynamic conditional query
+ * @param specification Query conditions
+ * @param pageRequest pagination parameters
+ * @return search result
+ */
+ Page getAlerts(Specification specification, PageRequest pageRequest);
+
+ /**
+ * Delete alarms in batches according to the alarm ID list
+ * @param ids Alarm ID List
+ */
+ void deleteAlerts(HashSet ids);
+
+ /**
+ * Clear all alerts
+ */
+ void clearAlerts();
+
+ /**
+ * Update the alarm status according to the alarm ID-status value
+ * @param status Alarm status to be modified
+ * @param ids Alarm ID List to be modified
+ */
+ void editAlertStatus(Byte status, List ids);
+
+ /**
+ * Get alarm statistics information
+ * @return Alarm statistics information
+ */
+ AlertSummary getAlertsSummary();
+
+ /**
+ * A third party reports an alarm
+ * @param alertReport The alarm information
+ */
+ void addNewAlertReport(AlertReport alertReport);
+
+ /**
+ * Dynamic conditional query
+ * @param specification Query conditions
+ * @return search result
+ */
+ List getAlerts(Specification specification);
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/AlertSilenceService.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/AlertSilenceService.java
new file mode 100644
index 0000000..3e7e114
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/AlertSilenceService.java
@@ -0,0 +1,75 @@
+/*
+ * 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 java.util.Set;
+import org.apache.hertzbeat.common.entity.alerter.AlertSilence;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.jpa.domain.Specification;
+
+/**
+ * management interface service for alert silence
+ */
+public interface AlertSilenceService {
+ /**
+ * Verify the correctness of the request data parameters
+ * @param alertSilence AlertSilence
+ * @param isModify whether modify
+ * @throws IllegalArgumentException A checksum parameter error is thrown
+ */
+ void validate(AlertSilence alertSilence, boolean isModify) throws IllegalArgumentException;
+
+ /**
+ * New AlertSilence
+ * @param alertSilence AlertSilence Entity
+ * @throws RuntimeException Added procedure exception throwing
+ */
+ void addAlertSilence(AlertSilence alertSilence) throws RuntimeException;
+
+ /**
+ * Modifying an AlertSilence
+ * @param alertSilence Alarm definition Entity
+ * @throws RuntimeException Exception thrown during modification
+ */
+ void modifyAlertSilence(AlertSilence alertSilence) throws RuntimeException;
+
+ /**
+ * Obtain AlertSilence information
+ * @param silenceId AlertSilence ID
+ * @return AlertSilence
+ * @throws RuntimeException An exception was thrown during the query
+ */
+ AlertSilence getAlertSilence(long silenceId) throws RuntimeException;
+
+
+ /**
+ * Delete AlertSilence in batches
+ * @param silenceIds AlertSilence IDs
+ * @throws RuntimeException Exception thrown during deletion
+ */
+ void deleteAlertSilences(Set silenceIds) throws RuntimeException;
+
+ /**
+ * Dynamic conditional query
+ * @param specification Query conditions
+ * @param pageRequest Paging parameters
+ * @return The query results
+ */
+ Page getAlertSilences(Specification specification, PageRequest pageRequest);
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlertConvergeServiceImpl.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlertConvergeServiceImpl.java
new file mode 100644
index 0000000..a4d05b4
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlertConvergeServiceImpl.java
@@ -0,0 +1,83 @@
+/*
+ * 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 java.util.Set;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.hertzbeat.alert.dao.AlertConvergeDao;
+import org.apache.hertzbeat.alert.service.AlertConvergeService;
+import org.apache.hertzbeat.common.cache.CacheFactory;
+import org.apache.hertzbeat.common.cache.CommonCacheService;
+import org.apache.hertzbeat.common.constants.CommonConstants;
+import org.apache.hertzbeat.common.entity.alerter.AlertConverge;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * implement for alert converge service
+ */
+@Service
+@Transactional(rollbackFor = Exception.class)
+@Slf4j
+public class AlertConvergeServiceImpl implements AlertConvergeService {
+
+ @Autowired
+ private AlertConvergeDao alertConvergeDao;
+
+ @Override
+ public void validate(AlertConverge alertConverge, boolean isModify) throws IllegalArgumentException {
+ // todo
+ }
+
+ @Override
+ public void addAlertConverge(AlertConverge alertConverge) throws RuntimeException {
+ alertConvergeDao.save(alertConverge);
+ clearAlertConvergesCache();
+ }
+
+ @Override
+ public void modifyAlertConverge(AlertConverge alertConverge) throws RuntimeException {
+ alertConvergeDao.save(alertConverge);
+ clearAlertConvergesCache();
+ }
+
+ @Override
+ public AlertConverge getAlertConverge(long convergeId) throws RuntimeException {
+ return alertConvergeDao.findById(convergeId).orElse(null);
+ }
+
+ @Override
+ public void deleteAlertConverges(Set convergeIds) throws RuntimeException {
+ alertConvergeDao.deleteAlertConvergesByIdIn(convergeIds);
+ clearAlertConvergesCache();
+ }
+
+ @Override
+ public Page getAlertConverges(Specification specification, PageRequest pageRequest) {
+ return alertConvergeDao.findAll(specification, pageRequest);
+ }
+
+ private void clearAlertConvergesCache() {
+ CommonCacheService convergeCache = CacheFactory.getAlertConvergeCache();
+ convergeCache.remove(CommonConstants.CACHE_ALERT_CONVERGE);
+ }
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlertDefineAbstractImExportServiceImpl.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlertDefineAbstractImExportServiceImpl.java
new file mode 100644
index 0000000..2900b0f
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlertDefineAbstractImExportServiceImpl.java
@@ -0,0 +1,151 @@
+/*
+ * 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 cn.afterturn.easypoi.excel.annotation.Excel;
+import cn.afterturn.easypoi.excel.annotation.ExcelTarget;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import jakarta.annotation.Resource;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.time.LocalDate;
+import java.util.List;
+import java.util.stream.Collectors;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.hertzbeat.alert.service.AlertDefineImExportService;
+import org.apache.hertzbeat.alert.service.AlertDefineService;
+import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
+import org.apache.hertzbeat.common.entity.manager.TagItem;
+import org.springframework.beans.BeanUtils;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.util.CollectionUtils;
+
+/**
+ * Configuration Import Export
+ */
+@Slf4j
+public abstract class AlertDefineAbstractImExportServiceImpl implements AlertDefineImExportService {
+ @Resource
+ @Lazy
+ private AlertDefineService alertDefineService;
+
+ @Override
+ public void importConfig(InputStream is) {
+ var formList = parseImport(is)
+ .stream()
+ .map(this::convert)
+ .collect(Collectors.toUnmodifiableList());
+ if (!CollectionUtils.isEmpty(formList)) {
+ formList.forEach(alertDefine -> {
+ alertDefineService.validate(alertDefine, false);
+ alertDefineService.addAlertDefine(alertDefine);
+ });
+ }
+ }
+
+ @Override
+ public void exportConfig(OutputStream os, List configList) {
+ var monitorList = configList.stream()
+ .map(it -> alertDefineService.getAlertDefine(it))
+ .map(this::convert)
+ .collect(Collectors.toUnmodifiableList());
+ writeOs(monitorList, os);
+ }
+
+
+ /**
+ * Parsing an input stream into a form
+ *
+ * @param is input stream
+ * @return form list
+ */
+ abstract List parseImport(InputStream is);
+
+ /**
+ * Export Configuration to Output Stream
+ * @param exportAlertDefineList configuration list
+ * @param os output stream
+ */
+ abstract void writeOs(List exportAlertDefineList, OutputStream os);
+
+
+ private ExportAlertDefineDTO convert(AlertDefine alertDefine) {
+ var exportAlertDefine = new ExportAlertDefineDTO();
+ var alertDefineDTO = new AlertDefineDTO();
+ BeanUtils.copyProperties(alertDefine, alertDefineDTO);
+ exportAlertDefine.setAlertDefine(alertDefineDTO);
+ return exportAlertDefine;
+ }
+
+ private AlertDefine convert(ExportAlertDefineDTO exportAlertDefineDTO) {
+ var alertDefine = new AlertDefine();
+ var alertDefineDTO = exportAlertDefineDTO.getAlertDefine();
+ BeanUtils.copyProperties(alertDefineDTO, alertDefine);
+ return alertDefine;
+ }
+
+ protected String fileNamePrefix() {
+ return "hertzbeat_alertDefine_" + LocalDate.now();
+ }
+
+ /**
+ * Export data transfer objects for alert configurations
+ */
+ @Data
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ @ExcelTarget(value = "ExportAlertDefineDTO")
+ protected static class ExportAlertDefineDTO {
+ @Excel(name = "AlertDefine")
+ private AlertDefineDTO alertDefine;
+ }
+
+ /**
+ * Data transfer object for alert configuration
+ */
+ @Data
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ @ExcelTarget(value = "AlertDefineDTO")
+ protected static class AlertDefineDTO {
+ @Excel(name = "App")
+ private String app;
+ @Excel(name = "Metric")
+ private String metric;
+ @Excel(name = "Field")
+ private String field;
+ @Excel(name = "Preset")
+ private Boolean preset;
+ @Excel(name = "Expr")
+ private String expr;
+ @Excel(name = "Priority")
+ private Byte priority;
+ @Excel(name = "Times")
+ private Integer times;
+ @Excel(name = "Tags")
+ private List tags;
+ @Excel(name = "Enable")
+ private Boolean enable;
+ @Excel(name = "RecoverNotice")
+ private Boolean recoverNotice;
+ @Excel(name = "Template")
+ private String template;
+ }
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlertDefineExcelImExportServiceImpl.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlertDefineExcelImExportServiceImpl.java
new file mode 100644
index 0000000..88fa777
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlertDefineExcelImExportServiceImpl.java
@@ -0,0 +1,265 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.alert.service.impl;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.hertzbeat.common.entity.manager.TagItem;
+import org.apache.hertzbeat.common.util.JsonUtil;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.CellType;
+import org.apache.poi.ss.usermodel.Font;
+import org.apache.poi.ss.usermodel.HorizontalAlignment;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.usermodel.WorkbookFactory;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+/**
+ * Configure the import and export EXCEL format
+ */
+@Slf4j
+@Service
+public class AlertDefineExcelImExportServiceImpl extends AlertDefineAbstractImExportServiceImpl {
+ public static final String TYPE = "EXCEL";
+ public static final String FILE_SUFFIX = ".xlsx";
+
+ /**
+ * Export file type
+ * @return file type
+ */
+ @Override
+ public String type() {
+ return TYPE;
+ }
+
+ /**
+ * Get Export File Name
+ * @return file name
+ */
+ @Override
+ public String getFileName() {
+ return fileNamePrefix() + FILE_SUFFIX;
+ }
+
+
+ /**
+ * Parsing an input stream into a form
+ * @param is input stream
+ * @return form list
+ */
+ @Override
+ List parseImport(InputStream is) {
+ try (Workbook workbook = WorkbookFactory.create(is)) {
+ Sheet sheet = workbook.getSheetAt(0);
+ List alertDefines = new ArrayList<>();
+ for (Row row : sheet) {
+ if (row.getRowNum() == 0) {
+ continue;
+ }
+ String app = getCellValueAsString(row.getCell(0));
+ if (StringUtils.hasText(app)) {
+ AlertDefineDTO alertDefineDTO = extractAlertDefineDataFromRow(row);
+ ExportAlertDefineDTO exportAlertDefineDTO = new ExportAlertDefineDTO();
+ exportAlertDefineDTO.setAlertDefine(alertDefineDTO);
+ alertDefines.add(exportAlertDefineDTO);
+ }
+ }
+ return alertDefines;
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to parse alertDefine data", e);
+ }
+ }
+
+ private TagItem extractTagDataFromRow(Row row) {
+ String name = getCellValueAsString(row.getCell(7));
+ if (StringUtils.hasText(name)) {
+ TagItem tagItem = new TagItem();
+ tagItem.setName(name);
+ tagItem.setValue(getCellValueAsString(row.getCell(8)));
+ return tagItem;
+ }
+ return null;
+ }
+
+ private List extractTagDataFromRow(Cell cell) {
+ String jsonStr = getCellValueAsString(cell);
+ if (StringUtils.hasText(jsonStr)) {
+ return JsonUtil.fromJson(jsonStr, new TypeReference<>() {
+ });
+ }
+ return null;
+ }
+
+
+ private String getCellValueAsString(Cell cell) {
+ if (cell == null) {
+ return null;
+ }
+ return switch (cell.getCellType()) {
+ case STRING -> cell.getStringCellValue();
+ case NUMERIC -> String.valueOf(cell.getNumericCellValue());
+ default -> null;
+ };
+ }
+
+
+ private boolean getCellValueAsBoolean(Cell cell) {
+ if (cell == null) {
+ return false;
+ }
+ if (Objects.requireNonNull(cell.getCellType()) == CellType.BOOLEAN) {
+ return cell.getBooleanCellValue();
+ }
+ return false;
+ }
+
+
+ private Integer getCellValueAsInteger(Cell cell) {
+ if (cell == null) {
+ return null;
+ }
+ if (Objects.requireNonNull(cell.getCellType()) == CellType.NUMERIC) {
+ return (int) cell.getNumericCellValue();
+ }
+ return null;
+ }
+
+
+ private Byte getCellValueAsByte(Cell cell) {
+ if (cell == null) {
+ return null;
+ }
+ if (Objects.requireNonNull(cell.getCellType()) == CellType.NUMERIC) {
+ return (byte) cell.getNumericCellValue();
+ }
+ return null;
+ }
+
+
+ private AlertDefineDTO extractAlertDefineDataFromRow(Row row) {
+ AlertDefineDTO alertDefineDTO = new AlertDefineDTO();
+ alertDefineDTO.setApp(getCellValueAsString(row.getCell(0)));
+ alertDefineDTO.setMetric(getCellValueAsString(row.getCell(1)));
+ alertDefineDTO.setField(getCellValueAsString(row.getCell(2)));
+ alertDefineDTO.setPreset(getCellValueAsBoolean(row.getCell(3)));
+ alertDefineDTO.setExpr(getCellValueAsString(row.getCell(4)));
+ alertDefineDTO.setPriority(getCellValueAsByte(row.getCell(5)));
+ alertDefineDTO.setTimes(getCellValueAsInteger(row.getCell(6)));
+ alertDefineDTO.setTags(extractTagDataFromRow(row.getCell(7)));
+ alertDefineDTO.setEnable(getCellValueAsBoolean(row.getCell(8)));
+ alertDefineDTO.setRecoverNotice(getCellValueAsBoolean(row.getCell(9)));
+ alertDefineDTO.setTemplate(getCellValueAsString(row.getCell(10)));
+ return alertDefineDTO;
+ }
+
+
+ /**
+ * Export Configuration to Output Stream
+ * @param exportAlertDefineList exportAlertDefineList
+ * @param os output stream
+ */
+ @Override
+ void writeOs(List exportAlertDefineList, OutputStream os) {
+ try {
+ Workbook workbook = WorkbookFactory.create(true);
+ String sheetName = "Export AlertDefine";
+ Sheet sheet = workbook.createSheet(sheetName);
+ sheet.setDefaultColumnWidth(20);
+ sheet.setColumnWidth(9, 40 * 256);
+ sheet.setColumnWidth(10, 40 * 256);
+ // set header style
+ CellStyle headerCellStyle = workbook.createCellStyle();
+ Font headerFont = workbook.createFont();
+ headerFont.setBold(true);
+ headerCellStyle.setFont(headerFont);
+ headerCellStyle.setAlignment(HorizontalAlignment.CENTER);
+ // set cell style
+ CellStyle cellStyle = workbook.createCellStyle();
+ cellStyle.setAlignment(HorizontalAlignment.CENTER);
+ // set header
+ String[] headers = {"app", "metric", "field", "preset", "expr", "priority", "times", "tags",
+ "enable", "recoverNotice", "template"};
+ Row headerRow = sheet.createRow(0);
+ for (int i = 0; i < headers.length; i++) {
+ Cell cell = headerRow.createCell(i);
+ cell.setCellValue(headers[i]);
+ cell.setCellStyle(headerCellStyle);
+ }
+
+ // Traverse the threshold rule list, each threshold rule object corresponds to a row of data
+ int rowIndex = 1;
+ for (ExportAlertDefineDTO alertDefine : exportAlertDefineList) {
+ AlertDefineDTO alertDefineDTO = alertDefine.getAlertDefine();
+ Row row = sheet.createRow(rowIndex++);
+ // Threshold rule information only needs to be written once
+ Cell appCell = row.createCell(0);
+ appCell.setCellValue(alertDefineDTO.getApp());
+ appCell.setCellStyle(cellStyle);
+ Cell metricCell = row.createCell(1);
+ metricCell.setCellValue(alertDefineDTO.getMetric());
+ metricCell.setCellStyle(cellStyle);
+ Cell fieldCell = row.createCell(2);
+ fieldCell.setCellValue(alertDefineDTO.getField());
+ fieldCell.setCellStyle(cellStyle);
+ Cell presetCell = row.createCell(3);
+ presetCell.setCellValue(alertDefineDTO.getPreset() != null
+ && alertDefineDTO.getPreset());
+ presetCell.setCellStyle(cellStyle);
+ Cell exprCell = row.createCell(4);
+ exprCell.setCellValue(alertDefineDTO.getExpr());
+ exprCell.setCellStyle(cellStyle);
+ Cell priorityCell = row.createCell(5);
+ priorityCell.setCellValue(alertDefineDTO.getPriority());
+ priorityCell.setCellStyle(cellStyle);
+ Cell timesCell = row.createCell(6);
+ timesCell.setCellValue(alertDefineDTO.getTimes());
+ Cell tagCell = row.createCell(7);
+ // get tags
+ List tagList = alertDefineDTO.getTags();
+ String tagValue = tagList == null || tagList.isEmpty() ? "" : JsonUtil.toJson(tagList);
+ tagCell.setCellValue(tagValue);
+ tagCell.setCellStyle(cellStyle);
+ Cell enableCell = row.createCell(8);
+ enableCell.setCellValue(alertDefineDTO.getEnable() != null
+ && alertDefineDTO.getEnable());
+ enableCell.setCellStyle(cellStyle);
+ Cell recoverNoticeCell = row.createCell(9);
+ recoverNoticeCell.setCellValue(alertDefineDTO.getRecoverNotice() != null
+ && alertDefineDTO.getRecoverNotice());
+ recoverNoticeCell.setCellStyle(cellStyle);
+ Cell templateCell = row.createCell(10);
+ templateCell.setCellValue(alertDefineDTO.getTemplate());
+ recoverNoticeCell.setCellStyle(cellStyle);
+ }
+ workbook.write(os);
+ os.close();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlertDefineJsonImExportServiceImpl.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlertDefineJsonImExportServiceImpl.java
new file mode 100644
index 0000000..75bf8b0
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlertDefineJsonImExportServiceImpl.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.alert.service.impl;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+/**
+ * Configure the import and export JSON format
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class AlertDefineJsonImExportServiceImpl extends AlertDefineAbstractImExportServiceImpl {
+ public static final String TYPE = "JSON";
+ public static final String FILE_SUFFIX = ".json";
+
+ private final ObjectMapper objectMapper;
+
+ @Override
+ public String type() {
+ return TYPE;
+ }
+
+ @Override
+ public String getFileName() {
+ return fileNamePrefix() + FILE_SUFFIX;
+ }
+
+ @Override
+ List parseImport(InputStream is) {
+ try {
+ return objectMapper.readValue(is, new TypeReference<>() {
+ });
+ } catch (IOException ex) {
+ log.error("import alertDefine failed.", ex);
+ throw new RuntimeException("import alertDefine failed");
+ }
+ }
+
+ @Override
+ void writeOs(List exportAlertDefineList, OutputStream os) {
+ try {
+ objectMapper.writeValue(os, exportAlertDefineList);
+ } catch (IOException ex) {
+ log.error("export alertDefine failed.", ex);
+ throw new RuntimeException("export alertDefine failed");
+ }
+ }
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlertDefineServiceImpl.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlertDefineServiceImpl.java
new file mode 100644
index 0000000..21d07b2
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlertDefineServiceImpl.java
@@ -0,0 +1,189 @@
+/*
+ * 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 jakarta.servlet.http.HttpServletResponse;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.hertzbeat.alert.dao.AlertDefineBindDao;
+import org.apache.hertzbeat.alert.dao.AlertDefineDao;
+import org.apache.hertzbeat.alert.service.AlertDefineImExportService;
+import org.apache.hertzbeat.alert.service.AlertDefineService;
+import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
+import org.apache.hertzbeat.common.entity.alerter.AlertDefineMonitorBind;
+import org.apache.hertzbeat.common.util.JexlExpressionRunner;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.http.HttpHeaders;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.StringUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * Alarm definition management interface implementation
+ */
+@Service
+@Transactional(rollbackFor = Exception.class)
+@Slf4j
+public class AlertDefineServiceImpl implements AlertDefineService {
+
+ @Autowired
+ private AlertDefineDao alertDefineDao;
+
+ @Autowired
+ private AlertDefineBindDao alertDefineBindDao;
+
+ private final Map alertDefineImExportServiceMap = new HashMap<>();
+
+ public AlertDefineServiceImpl(List alertDefineImExportServiceList) {
+ alertDefineImExportServiceList.forEach(it -> alertDefineImExportServiceMap.put(it.type(), it));
+ }
+
+ @Override
+ public void validate(AlertDefine alertDefine, boolean isModify) throws IllegalArgumentException {
+ // todo
+ if (StringUtils.hasText(alertDefine.getExpr())) {
+ try {
+ JexlExpressionRunner.compile(alertDefine.getExpr());
+ } catch (Exception e) {
+ throw new IllegalArgumentException("alert expr error: " + e.getMessage());
+ }
+ }
+ }
+
+ @Override
+ public void addAlertDefine(AlertDefine alertDefine) throws RuntimeException {
+ alertDefineDao.save(alertDefine);
+ }
+
+ @Override
+ public void modifyAlertDefine(AlertDefine alertDefine) throws RuntimeException {
+ alertDefineDao.save(alertDefine);
+ }
+
+ @Override
+ public void deleteAlertDefine(long alertId) throws RuntimeException {
+ alertDefineDao.deleteById(alertId);
+ }
+
+ @Override
+ public AlertDefine getAlertDefine(long alertId) throws RuntimeException {
+ Optional optional = alertDefineDao.findById(alertId);
+ return optional.orElse(null);
+ }
+
+ @Override
+ public void deleteAlertDefines(Set alertIds) throws RuntimeException {
+ alertDefineDao.deleteAlertDefinesByIdIn(alertIds);
+ }
+
+ @Override
+ public Page getMonitorBindAlertDefines(Specification specification, PageRequest pageRequest) {
+ return alertDefineDao.findAll(specification, pageRequest);
+ }
+
+ @Override
+ public void applyBindAlertDefineMonitors(Long alertId, List alertDefineBinds) {
+ // todo checks whether the alarm definition and monitoring exist
+ if (!alertDefineBindDao.existsById(alertId)){
+ alertDefineBindDao.deleteAlertDefineBindsByAlertDefineIdEquals(alertId);
+ }
+ // Delete all associations of this alarm
+ alertDefineBindDao.deleteAlertDefineBindsByAlertDefineIdEquals(alertId);
+ // Save the associated
+ alertDefineBindDao.saveAll(alertDefineBinds);
+ }
+
+ @Override
+ public Map> getMonitorBindAlertDefines(long monitorId, String app, String metrics) {
+ List defines = alertDefineDao.queryAlertDefinesByMonitor(monitorId, app, metrics);
+ List defaultDefines = alertDefineDao.queryAlertDefinesByAppAndMetricAndPresetTrueAndEnableTrue(app, metrics);
+ defines.addAll(defaultDefines);
+ Set defineSet = defines.stream().filter(item -> item.getField() != null).collect(Collectors.toSet());
+ // The alarm thresholds are defined in ascending order of the alarm severity from 0 to 3.
+ // The lower the number, the higher the alarm is. That is, the alarm is calculated from the highest alarm threshold
+ return defineSet.stream().sorted(Comparator.comparing(AlertDefine::getPriority))
+ .collect(Collectors.groupingBy(AlertDefine::getField));
+ }
+
+ @Override
+ public AlertDefine getMonitorBindAlertAvaDefine(long monitorId, String app, String metrics) {
+ List defines = alertDefineDao.queryAlertDefinesByMonitor(monitorId, app, metrics);
+ List defaultDefines = alertDefineDao.queryAlertDefinesByAppAndMetricAndPresetTrueAndEnableTrue(app, metrics);
+ defines.addAll(defaultDefines);
+ return defines.stream().findFirst().orElse(null);
+ }
+
+ @Override
+ public Page getAlertDefines(Specification specification, PageRequest pageRequest) {
+ return alertDefineDao.findAll(specification, pageRequest);
+ }
+
+ @Override
+ public List getBindAlertDefineMonitors(long alertDefineId) {
+ return alertDefineBindDao.getAlertDefineBindsByAlertDefineIdEquals(alertDefineId);
+ }
+
+ @Override
+ public void export(List ids, String type, HttpServletResponse res) throws Exception {
+ var imExportService = alertDefineImExportServiceMap.get(type);
+ if (imExportService == null) {
+ throw new IllegalArgumentException("not support export type: " + type);
+ }
+ var fileName = imExportService.getFileName();
+ res.setHeader("content-type", "application/octet-stream;charset=UTF-8");
+ res.setContentType("application/octet-stream;charset=UTF-8");
+ res.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8));
+ res.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
+ imExportService.exportConfig(res.getOutputStream(), ids);
+ }
+
+ @Override
+ public void importConfig(MultipartFile file) throws Exception {
+ var fileName = file.getOriginalFilename();
+ if (!StringUtils.hasText(fileName)) {
+ return;
+ }
+ var type = "";
+ if (fileName.toLowerCase().endsWith(AlertDefineJsonImExportServiceImpl.FILE_SUFFIX)) {
+ type = AlertDefineJsonImExportServiceImpl.TYPE;
+ }
+ if (fileName.toLowerCase().endsWith(AlertDefineExcelImExportServiceImpl.FILE_SUFFIX)) {
+ type = AlertDefineExcelImExportServiceImpl.TYPE;
+ }
+ if (fileName.toLowerCase().endsWith(AlertDefineYamlImExportServiceImpl.FILE_SUFFIX)) {
+ type = AlertDefineYamlImExportServiceImpl.TYPE;
+ }
+ if (!alertDefineImExportServiceMap.containsKey(type)) {
+ throw new RuntimeException("file " + fileName + " is not supported.");
+ }
+ var imExportService = alertDefineImExportServiceMap.get(type);
+ imExportService.importConfig(file.getInputStream());
+ }
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlertDefineYamlImExportServiceImpl.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlertDefineYamlImExportServiceImpl.java
new file mode 100644
index 0000000..2dacffe
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlertDefineYamlImExportServiceImpl.java
@@ -0,0 +1,65 @@
+/*
+ * 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 java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.yaml.snakeyaml.DumperOptions;
+import org.yaml.snakeyaml.Yaml;
+
+/**
+ * Configure the import and export Yaml format
+ */
+@Slf4j
+@Service
+public class AlertDefineYamlImExportServiceImpl extends AlertDefineAbstractImExportServiceImpl {
+
+ public static final String TYPE = "YAML";
+ public static final String FILE_SUFFIX = ".yaml";
+
+ @Override
+ public String type() {
+ return TYPE;
+ }
+
+ @Override
+ public String getFileName() {
+ return fileNamePrefix() + FILE_SUFFIX;
+ }
+
+ @Override
+ List parseImport(InputStream is) {
+ Yaml yaml = new Yaml();
+ return yaml.load(is);
+ }
+
+ @Override
+ void writeOs(List exportAlertDefineList, OutputStream os) {
+ DumperOptions options = new DumperOptions();
+ options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
+ options.setIndent(2);
+ options.setPrettyFlow(true);
+ Yaml yaml = new Yaml(options);
+ yaml.dump(exportAlertDefineList, new OutputStreamWriter(os, StandardCharsets.UTF_8));
+ }
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlertServiceImpl.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlertServiceImpl.java
new file mode 100644
index 0000000..ffdca29
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlertServiceImpl.java
@@ -0,0 +1,161 @@
+/*
+ * 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 java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.hertzbeat.alert.dao.AlertDao;
+import org.apache.hertzbeat.alert.dto.AlertPriorityNum;
+import org.apache.hertzbeat.alert.dto.AlertSummary;
+import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
+import org.apache.hertzbeat.alert.service.AlertService;
+import org.apache.hertzbeat.common.constants.CommonConstants;
+import org.apache.hertzbeat.common.entity.alerter.Alert;
+import org.apache.hertzbeat.common.entity.dto.AlertReport;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Realization of Alarm Information Service
+ */
+@Service
+@Transactional(rollbackFor = Exception.class)
+@Slf4j
+public class AlertServiceImpl implements AlertService {
+
+ @Autowired
+ private AlertDao alertDao;
+
+ @Autowired
+ private AlarmCommonReduce alarmCommonReduce;
+
+ @Override
+ public void addAlert(Alert alert) throws RuntimeException {
+ alertDao.save(alert);
+ }
+
+ @Override
+ public Page getAlerts(Specification specification, PageRequest pageRequest) {
+ return alertDao.findAll(specification, pageRequest);
+ }
+
+ @Override
+ public void deleteAlerts(HashSet ids) {
+ alertDao.deleteAlertsByIdIn(ids);
+ }
+
+ @Override
+ public void clearAlerts() {
+ alertDao.deleteAll();
+ }
+
+ @Override
+ public void editAlertStatus(Byte status, List ids) {
+ alertDao.updateAlertsStatus(status, ids);
+ }
+
+ @Override
+ public AlertSummary getAlertsSummary() {
+ AlertSummary alertSummary = new AlertSummary();
+ // Statistics on the alarm information in the alarm state
+ List priorityNums = alertDao.findAlertPriorityNum();
+ if (priorityNums != null) {
+ for (AlertPriorityNum priorityNum : priorityNums) {
+ switch (priorityNum.getPriority()) {
+ case CommonConstants
+ .ALERT_PRIORITY_CODE_WARNING -> alertSummary.setPriorityWarningNum(priorityNum.getNum());
+ case CommonConstants.ALERT_PRIORITY_CODE_CRITICAL -> alertSummary.setPriorityCriticalNum(priorityNum.getNum());
+ case CommonConstants.ALERT_PRIORITY_CODE_EMERGENCY -> alertSummary.setPriorityEmergencyNum(priorityNum.getNum());
+ default -> {}
+ }
+ }
+ }
+ long total = alertDao.count();
+ alertSummary.setTotal(total);
+ long dealNum = total - alertSummary.getPriorityCriticalNum()
+ - alertSummary.getPriorityEmergencyNum() - alertSummary.getPriorityWarningNum();
+ alertSummary.setDealNum(dealNum);
+ try {
+ if (total == 0) {
+ alertSummary.setRate(100);
+ } else {
+ float rate = BigDecimal.valueOf(100 * (float) dealNum / total)
+ .setScale(2, RoundingMode.HALF_UP)
+ .floatValue();
+ alertSummary.setRate(rate);
+ }
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ }
+ return alertSummary;
+ }
+
+ @Override
+ public void addNewAlertReport(AlertReport alertReport) {
+ alarmCommonReduce.reduceAndSendAlarm(buildAlertData(alertReport));
+ }
+
+ @Override
+ public List getAlerts(Specification specification) {
+
+ return alertDao.findAll(specification);
+ }
+
+ /**
+ * The external alarm information is converted to Alert
+ * @param alertReport alarm body
+ * @return Alert entity
+ */
+ private Alert buildAlertData(AlertReport alertReport){
+ Map annotations = alertReport.getAnnotations();
+ StringBuilder sb = new StringBuilder();
+ if (alertReport.getContent() == null || alertReport.getContent().length() <= 0){
+ StringBuilder finalSb = sb;
+ annotations.forEach((k, v) -> {
+ finalSb.append(k).append(":").append(v).append("\n");
+ });
+ } else {
+ sb = new StringBuilder(alertReport.getContent());
+ }
+ LocalDateTime dateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(alertReport.getAlertTime()),
+ ZoneId.systemDefault());
+ return Alert.builder()
+ .content("Alert Center\n" + sb)
+ .priority(alertReport.getPriority().byteValue())
+ .status(CommonConstants.ALERT_STATUS_CODE_PENDING)
+ .tags(alertReport.getLabels())
+ .target(alertReport.getAlertName())
+ .triggerTimes(1)
+ .firstAlarmTime(alertReport.getAlertTime())
+ .lastAlarmTime(alertReport.getAlertTime())
+ .gmtCreate(dateTime)
+ .gmtUpdate(dateTime)
+ .build();
+ }
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlertSilenceServiceImpl.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlertSilenceServiceImpl.java
new file mode 100644
index 0000000..a8a347a
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlertSilenceServiceImpl.java
@@ -0,0 +1,88 @@
+/*
+ * 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 java.util.Arrays;
+import java.util.Set;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.hertzbeat.alert.dao.AlertSilenceDao;
+import org.apache.hertzbeat.alert.service.AlertSilenceService;
+import org.apache.hertzbeat.common.cache.CacheFactory;
+import org.apache.hertzbeat.common.cache.CommonCacheService;
+import org.apache.hertzbeat.common.constants.CommonConstants;
+import org.apache.hertzbeat.common.entity.alerter.AlertSilence;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * management interface service implement for alert silence
+ */
+@Service
+@Transactional(rollbackFor = Exception.class)
+@Slf4j
+public class AlertSilenceServiceImpl implements AlertSilenceService {
+
+ @Autowired
+ private AlertSilenceDao alertSilenceDao;
+
+ @Override
+ public void validate(AlertSilence alertSilence, boolean isModify) throws IllegalArgumentException {
+ // todo
+ // if the alarm silent selection date set in periodic situations is empty, it will be deemed to be all checked.
+ if (alertSilence.getType() == 1 && alertSilence.getDays() == null) {
+ alertSilence.setDays(Arrays.asList((byte) 7, (byte) 1, (byte) 2, (byte) 3, (byte) 4, (byte) 5, (byte) 6));
+ }
+ }
+
+ @Override
+ public void addAlertSilence(AlertSilence alertSilence) throws RuntimeException {
+ alertSilenceDao.save(alertSilence);
+ clearAlertSilencesCache();
+ }
+
+ @Override
+ public void modifyAlertSilence(AlertSilence alertSilence) throws RuntimeException {
+ alertSilenceDao.save(alertSilence);
+ clearAlertSilencesCache();
+ }
+
+ @Override
+ public AlertSilence getAlertSilence(long silenceId) throws RuntimeException {
+ return alertSilenceDao.findById(silenceId).orElse(null);
+ }
+
+ @Override
+ public void deleteAlertSilences(Set silenceIds) throws RuntimeException {
+ alertSilenceDao.deleteAlertSilencesByIdIn(silenceIds);
+ clearAlertSilencesCache();
+ }
+
+ @Override
+ public Page getAlertSilences(Specification specification, PageRequest pageRequest) {
+ return alertSilenceDao.findAll(specification, pageRequest);
+ }
+
+ private void clearAlertSilencesCache() {
+ CommonCacheService silenceCache = CacheFactory.getAlertSilenceCache();
+ silenceCache.remove(CommonConstants.CACHE_ALERT_SILENCE);
+ }
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/util/AlertTemplateUtil.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/util/AlertTemplateUtil.java
new file mode 100644
index 0000000..8ae100b
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/util/AlertTemplateUtil.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.alert.util;
+
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Alarm template keyword matching replacement engine tool
+ */
+@Slf4j
+public final class AlertTemplateUtil {
+
+ private AlertTemplateUtil() {
+ }
+
+ /**
+ * Match the variable ${key}
+ * eg: Alert, the instance: ${instance} metrics: ${metrics} is over flow.
+ */
+ private static final Pattern PATTERN = Pattern.compile("\\$\\{(\\w+)\\}");
+
+ public static String render(String template, Map replaceData) {
+ if (template == null) {
+ return null;
+ }
+ if (replaceData == null) {
+ log.warn("The replaceData is null.");
+ return template;
+ }
+ try {
+ Matcher matcher = PATTERN.matcher(template);
+ StringBuilder builder = new StringBuilder();
+ while (matcher.find()) {
+ Object objectValue = replaceData.getOrDefault(matcher.group(1), "NullValue");
+ String value = objectValue.toString();
+ matcher.appendReplacement(builder, Matcher.quoteReplacement(value));
+ }
+ matcher.appendTail(builder);
+ return builder.toString();
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ return template;
+ }
+ }
+}
diff --git a/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/util/DateUtil.java b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/util/DateUtil.java
new file mode 100644
index 0000000..55b0104
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/java/org/apache/hertzbeat/alert/util/DateUtil.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.alert.util;
+
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.util.Optional;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * date time common util
+ */
+@Slf4j
+public final class DateUtil {
+
+ private DateUtil() {
+ }
+
+ private static final String[] DATE_FORMATS = {
+ "yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS'Z'",
+ "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
+ "yyyy-MM-dd HH:mm:ss"
+ };
+
+ /**
+ * convert date to timestamp
+ * @param date date
+ * @return timestamp
+ */
+ public static Optional getTimeStampFromSomeFormats(String date) {
+ for (String dateFormat : DATE_FORMATS) {
+ try {
+ DateTimeFormatter dateTimeFormatter = new DateTimeFormatterBuilder()
+ .appendPattern(dateFormat)
+ // enable string conversion in strict mode.
+ .parseStrict()
+ .toFormatter();
+ LocalDateTime time = LocalDateTime.parse(date, dateTimeFormatter);
+ return Optional.of(time.toInstant(ZoneOffset.UTC).toEpochMilli());
+ } catch (Exception e) {
+ log.warn("Error parsing date '{}' with format '{}': {}",
+ date, dateFormat, e.getMessage());
+ }
+ }
+
+ log.error("Error parsing date '{}', no corresponding date format", date);
+ return Optional.empty();
+ }
+
+ /**
+ * convert format data to timestamp
+ */
+ public static Optional getTimeStampFromFormat(String date, String format) {
+ try {
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
+ LocalDateTime dateTime = LocalDateTime.parse(date, formatter);
+ return Optional.of(dateTime.toInstant(java.time.ZoneOffset.UTC).toEpochMilli());
+ } catch (Exception e) {
+ log.error("Error parsing date '{}' with format '{}': {}",
+ date, format, e.getMessage());
+ }
+
+ return Optional.empty();
+ }
+
+}
diff --git a/applications/hertzbeat/alerter/src/main/resources/META-INF/spring.factories b/applications/hertzbeat/alerter/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000..13843a7
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/resources/META-INF/spring.factories
@@ -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.alert.config.AlerterAutoConfiguration
diff --git a/applications/hertzbeat/alerter/src/main/resources/alerter_en_US.properties b/applications/hertzbeat/alerter/src/main/resources/alerter_en_US.properties
new file mode 100644
index 0000000..938da98
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/resources/alerter_en_US.properties
@@ -0,0 +1,32 @@
+# 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.
+
+alerter.availability.recover = Availability Alert Resolved, Monitor Status Normal Now
+alerter.alarm.recover = Alert Resolved Notice
+alerter.notify.title = HertzBeat Alert Notify
+alerter.notify.target = Monitor Target
+alerter.notify.monitorId = Monitor ID
+alerter.notify.monitorName = Monitor Name
+alerter.notify.monitorHost = Monitor Host
+alerter.notify.priority = Alert Priority
+alerter.notify.triggerTime = Alert Trigger Time
+alerter.notify.restoreTime = Alert Restore Time
+alerter.notify.times = Alert Trigger Times
+alerter.notify.tags = Alert Labels
+alerter.notify.content = Alert Content
+alerter.notify.console = Console Login
+alerter.priority.0 = Emergency Alert
+alerter.priority.1 = Critical Alert
+alerter.priority.2 = Warning Alert
diff --git a/applications/hertzbeat/alerter/src/main/resources/alerter_zh_CN.properties b/applications/hertzbeat/alerter/src/main/resources/alerter_zh_CN.properties
new file mode 100644
index 0000000..b1f11e2
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/main/resources/alerter_zh_CN.properties
@@ -0,0 +1,32 @@
+# 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.
+
+alerter.availability.recover = 可用性告警恢复通知, 任务状态已恢复正常
+alerter.alarm.recover = 告警恢复通知
+alerter.notify.title = HertzBeat告警通知
+alerter.notify.target = 告警目标对象
+alerter.notify.monitorId = 所属监控任务ID
+alerter.notify.monitorName = 所属任务名称
+alerter.notify.monitorHost = 所属任务主机
+alerter.notify.priority = 告警级别
+alerter.notify.triggerTime = 告警触发时间
+alerter.notify.restoreTime = 告警恢复时间
+alerter.notify.times = 告警触发次数
+alerter.notify.tags = 告警标签
+alerter.notify.content = 内容详情
+alerter.notify.console = 登入控制台
+alerter.priority.0 = 紧急告警
+alerter.priority.1 = 严重告警
+alerter.priority.2 = 警告告警
diff --git a/applications/hertzbeat/alerter/src/test/java/org/apache/hertzbeat/alert/AlerterWorkerPoolTest.java b/applications/hertzbeat/alerter/src/test/java/org/apache/hertzbeat/alert/AlerterWorkerPoolTest.java
new file mode 100644
index 0000000..4b7a61a
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/test/java/org/apache/hertzbeat/alert/AlerterWorkerPoolTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test case for {@link AlerterWorkerPool}
+ */
+class AlerterWorkerPoolTest {
+
+ private static final int NUMBER_OF_THREADS = 10;
+ private AlerterWorkerPool pool;
+ private AtomicInteger counter;
+ private CountDownLatch latch;
+
+ @BeforeEach
+ void setUp() {
+ pool = new AlerterWorkerPool();
+ counter = new AtomicInteger();
+ latch = new CountDownLatch(NUMBER_OF_THREADS);
+ }
+
+ @Test
+ void executeJob() throws InterruptedException {
+ for (int i = 0; i < NUMBER_OF_THREADS; i++) {
+ pool.executeJob(() -> {
+ counter.incrementAndGet();
+ latch.countDown();
+ });
+ }
+ latch.await();
+
+ assertEquals(NUMBER_OF_THREADS, counter.get());
+ }
+}
diff --git a/applications/hertzbeat/alerter/src/test/java/org/apache/hertzbeat/alert/controller/AlertDefineControllerTest.java b/applications/hertzbeat/alerter/src/test/java/org/apache/hertzbeat/alert/controller/AlertDefineControllerTest.java
new file mode 100644
index 0000000..fa2fe11
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/test/java/org/apache/hertzbeat/alert/controller/AlertDefineControllerTest.java
@@ -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.controller;
+
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import java.util.Collections;
+import java.util.List;
+import org.apache.hertzbeat.alert.service.impl.AlertDefineServiceImpl;
+import org.apache.hertzbeat.common.constants.CommonConstants;
+import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
+import org.apache.hertzbeat.common.entity.alerter.AlertDefineMonitorBind;
+import org.apache.hertzbeat.common.entity.manager.Monitor;
+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.Mockito;
+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;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+/**
+ * Test case for {@link AlertDefineController}
+ */
+@ExtendWith(MockitoExtension.class)
+class AlertDefineControllerTest {
+
+ private MockMvc mockMvc;
+
+ private AlertDefine alertDefine;
+
+ private List alertDefineMonitorBinds;
+
+ @Mock
+ private AlertDefineServiceImpl alertDefineService;
+
+ @InjectMocks
+ private AlertDefineController alertDefineController;
+
+ @BeforeEach
+ void setUp() {
+ // standaloneSetup: Standalone setup, not integrated with a web environment for testing
+ this.mockMvc = MockMvcBuilders.standaloneSetup(alertDefineController).build();
+
+ this.alertDefine = AlertDefine.builder()
+ .id(1L)
+ .app("app")
+ .metric("test")
+ .field("test")
+ .preset(false)
+ .expr("1 > 0")
+ .priority((byte) 1)
+ .times(1)
+ .template("template")
+ .creator("tom")
+ .modifier("tom")
+ .build();
+
+ this.alertDefineMonitorBinds = Collections.singletonList(
+ AlertDefineMonitorBind.builder()
+ .id(1L)
+ .alertDefineId(this.alertDefine.getId())
+ .monitorId(1L)
+ .monitor(
+ Monitor.builder()
+ .id(1L)
+ .app("app")
+ .host("localhost")
+ .name("monitor")
+ .build()
+ )
+ .build()
+ );
+ }
+
+ @Test
+ void addNewAlertDefine() throws Exception {
+ // Simulate the client sending a request to the server
+ mockMvc.perform(MockMvcRequestBuilders.post("/api/alert/define")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(JsonUtil.toJson(this.alertDefine)))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
+ .andReturn();
+ }
+
+ @Test
+ void modifyAlertDefine() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders.put("/api/alert/define")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(JsonUtil.toJson(this.alertDefine)))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
+ .andReturn();
+ }
+
+ @Test
+ void getAlertDefine() throws Exception {
+ // Simulate returning data from getAlertDefine
+ Mockito.when(alertDefineService.getAlertDefine(this.alertDefine.getId()))
+ .thenReturn(this.alertDefine);
+
+ mockMvc.perform(MockMvcRequestBuilders.get("/api/alert/define/" + this.alertDefine.getId())
+ .contentType(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
+ .andExpect(jsonPath("$.data.id").value(alertDefine.getId()))
+ .andExpect(jsonPath("$.data.app").value(alertDefine.getApp()))
+ .andExpect(jsonPath("$.data.metric").value(alertDefine.getMetric()))
+ .andExpect(jsonPath("$.data.field").value(alertDefine.getField()))
+ .andExpect(jsonPath("$.data.expr").value(alertDefine.getExpr()))
+ .andExpect(jsonPath("$.data.template").value(alertDefine.getTemplate()))
+ .andExpect(jsonPath("$.data.gmtCreate").value(alertDefine.getGmtCreate()))
+ .andExpect(jsonPath("$.data.gmtUpdate").value(alertDefine.getGmtUpdate()))
+ .andReturn();
+ }
+
+ @Test
+ void deleteAlertDefine() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders.delete("/api/alert/define/" + this.alertDefine.getId())
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(JsonUtil.toJson(this.alertDefine)))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
+ .andReturn();
+ }
+
+ @Test
+ void applyAlertDefineMonitorsBind() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders.post("/api/alert/define/" + this.alertDefine.getId() + "/monitors")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(JsonUtil.toJson(this.alertDefineMonitorBinds)))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
+ .andReturn();
+ }
+
+ @Test
+ void getAlertDefineMonitorsBind() throws Exception {
+ Mockito.when(alertDefineService.getBindAlertDefineMonitors(this.alertDefine.getId()))
+ .thenReturn(this.alertDefineMonitorBinds);
+
+ mockMvc.perform(MockMvcRequestBuilders.get("/api/alert/define/" + this.alertDefine.getId() + "/monitors")
+ .contentType(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
+ .andExpect(jsonPath("$.data[0].id").value(alertDefineMonitorBinds.get(0).getId()))
+ .andExpect(jsonPath("$.data[0].monitor.id").value(alertDefineMonitorBinds.get(0).getMonitor().getId()))
+ .andReturn();
+ }
+}
diff --git a/applications/hertzbeat/alerter/src/test/java/org/apache/hertzbeat/alert/controller/AlertDefinesControllerTest.java b/applications/hertzbeat/alerter/src/test/java/org/apache/hertzbeat/alert/controller/AlertDefinesControllerTest.java
new file mode 100644
index 0000000..62ce3fb
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/test/java/org/apache/hertzbeat/alert/controller/AlertDefinesControllerTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.hertzbeat.alert.service.AlertDefineService;
+import org.apache.hertzbeat.common.constants.CommonConstants;
+import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
+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.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+/**
+ * Test case for {@link AlertDefinesController}
+ * Test whether the data mocked at the mock is correct, and test whether the format of the returned data is correct
+ */
+@ExtendWith(MockitoExtension.class)
+class AlertDefinesControllerTest {
+
+ private MockMvc mockMvc;
+
+ @InjectMocks
+ private AlertDefinesController alertDefinesController;
+
+ @Mock
+ AlertDefineService alertDefineService;
+
+ // Parameters to avoid default values interference, default values have been replaced
+ List ids = Stream.of(6565463543L, 6565463544L).collect(Collectors.toList());
+ Byte priority = Byte.parseByte("1");
+ String sort = "gmtCreate";
+ String order = "asc";
+ Integer pageIndex = 1;
+ Integer pageSize = 7;
+
+ // Parameter collection
+ Map content = new HashMap<>();
+
+ // Object for mock
+ PageRequest pageRequest;
+
+ // Since the specification is used in dynamic proxy, it cannot be mocked
+ // Missing debugging parameters are ids, priority
+ // The missing part has been manually output for testing
+
+ @BeforeEach
+ void setUp() {
+ this.mockMvc = MockMvcBuilders.standaloneSetup(alertDefinesController).build();
+ content.put("ids", ids);
+ content.put("priority", priority);
+ content.put("sort", sort);
+ content.put("order", order);
+ content.put("pageIndex", pageIndex);
+ content.put("pageSize", pageSize);
+ Sort sortExp = Sort.by(new Sort.Order(Sort.Direction.fromString(content.get("order").toString()), content.get("sort").toString()));
+ pageRequest = PageRequest.of((Integer) content.get("pageIndex"), (Integer) content.get("pageSize"), sortExp);
+ }
+
+ // @Test
+ // todo: fix this test
+ void getAlertDefines() throws Exception {
+
+ // Test the correctness of the mock
+ // Although objects cannot be mocked, stubs can be stored using class files
+// Mockito.when(alertDefineService.getAlertDefines(Mockito.any(Specification.class), Mockito.argThat(new ArgumentMatcher() {
+// @Override
+// public boolean matches(PageRequest pageRequestMidden) {
+// // There are three methods in the source code that need to be compared, namely getPageNumber(), getPageSize(), getSort()
+// if(pageRequestMidden.getPageSize() == pageRequest.getPageSize() &&
+// pageRequestMidden.getPageNumber() == pageRequest.getPageNumber() &&
+// pageRequestMidden.getSort().equals(pageRequest.getSort())) {
+// return true;
+// }
+// return false;
+// }
+// }))).thenReturn(new PageImpl(new ArrayList()));
+ AlertDefine define = AlertDefine.builder().id(9L).app("linux").metric("disk").field("usage").expr("x").times(1).tags(new LinkedList<>()).build();
+ Mockito.when(alertDefineService.getAlertDefines(Mockito.any(), Mockito.any())).thenReturn(new PageImpl<>(Collections.singletonList(define)));
+
+ mockMvc.perform(MockMvcRequestBuilders.get(
+ "/api/alert/defines")
+ .param("ids", ids.toString().substring(1, ids.toString().length() - 1))
+ .param("priority", priority.toString())
+ .param("sort", sort)
+ .param("order", order)
+ .param("pageIndex", pageIndex.toString())
+ .param("pageSize", pageSize.toString()))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
+ .andExpect(jsonPath("$.data.content").value(new ArrayList<>()))
+ .andExpect(jsonPath("$.data.pageable").value("INSTANCE"))
+ .andExpect(jsonPath("$.data.totalPages").value(1))
+ .andExpect(jsonPath("$.data.totalElements").value(0))
+ .andExpect(jsonPath("$.data.last").value(true))
+ .andExpect(jsonPath("$.data.number").value(0))
+ .andExpect(jsonPath("$.data.size").value(0))
+ .andExpect(jsonPath("$.data.first").value(true))
+ .andExpect(jsonPath("$.data.numberOfElements").value(0))
+ .andExpect(jsonPath("$.data.empty").value(true))
+ .andExpect(jsonPath("$.data.sort.empty").value(true))
+ .andExpect(jsonPath("$.data.sort.sorted").value(false))
+ .andExpect(jsonPath("$.data.sort.unsorted").value(true))
+ .andReturn();
+ }
+
+ @Test
+ void deleteAlertDefines() throws Exception {
+ this.mockMvc.perform(MockMvcRequestBuilders.delete("/api/alert/defines")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(JsonUtil.toJson(ids)))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
+ .andReturn();
+ }
+}
diff --git a/applications/hertzbeat/alerter/src/test/java/org/apache/hertzbeat/alert/controller/AlertReportControllerTest.java b/applications/hertzbeat/alerter/src/test/java/org/apache/hertzbeat/alert/controller/AlertReportControllerTest.java
new file mode 100644
index 0000000..cb7f4e0
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/test/java/org/apache/hertzbeat/alert/controller/AlertReportControllerTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import org.apache.hertzbeat.alert.dto.GeneralCloudAlertReport;
+import org.apache.hertzbeat.alert.dto.TenCloudAlertReport;
+import org.apache.hertzbeat.alert.service.AlertService;
+import org.apache.hertzbeat.common.constants.CommonConstants;
+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 org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+/**
+ * unit test for {@link AlertReportController }
+ */
+@ExtendWith(MockitoExtension.class)
+class AlertReportControllerTest {
+
+ private MockMvc mockMvc;
+
+ @Mock
+ private AlertService alertService;
+
+ @InjectMocks
+ private AlertReportController alertReportController;
+
+ @BeforeEach
+ void setUp() {
+ this.mockMvc = MockMvcBuilders.standaloneSetup(alertReportController).build();
+ }
+
+ @Test
+ void addNewAlertReportTencent() throws Exception {
+ TenCloudAlertReport.Dimensions dimensions = new TenCloudAlertReport.Dimensions();
+ dimensions.setUnInstanceId("3333");
+
+ TenCloudAlertReport.AlarmObjInfo alarmObjInfo = new TenCloudAlertReport.AlarmObjInfo();
+ alarmObjInfo.setRegion("Guangzhou");
+ alarmObjInfo.setNamespace("Guangzhou1");
+ alarmObjInfo.setAppId("1111");
+ alarmObjInfo.setUin("2222");
+ alarmObjInfo.setDimensions(dimensions);
+
+ TenCloudAlertReport.Conditions conditions = new TenCloudAlertReport.Conditions();
+ conditions.setMetricName("xx");
+ conditions.setMetricShowName("xxx");
+ conditions.setCalcType("a");
+ conditions.setCalcValue("aa");
+ conditions.setCalcUnit("aaa");
+ conditions.setCurrentValue("b");
+ conditions.setCalcUnit("bb");
+ conditions.setProductName("guangzhou");
+ conditions.setProductShowName("Guangzhou1");
+ conditions.setEventName("CVS");
+ conditions.setEventShowName("Core error");
+
+ TenCloudAlertReport.AlarmPolicyInfo alarmPolicyInfo = new TenCloudAlertReport.AlarmPolicyInfo();
+ alarmPolicyInfo.setPolicyTypeCname("x");
+ alarmPolicyInfo.setPolicyName("Test1");
+ alarmPolicyInfo.setConditions(conditions);
+
+ TenCloudAlertReport report = TenCloudAlertReport.builder()
+ .sessionId("123")
+ .alarmStatus("1")
+ .alarmType("event")
+ .durationTime(2)
+ .firstOccurTime("2023-08-14 11:11:11")
+ .alarmObjInfo(alarmObjInfo)
+ .alarmPolicyInfo(alarmPolicyInfo)
+ .build();
+ mockMvc.perform(
+ MockMvcRequestBuilders
+ .post("/api/alerts/report/tencloud")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(JsonUtil.toJson(report))
+ )
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
+ .andExpect(content().json("{\"data\":null,\"msg\":\"Add report success\",\"code\":0}"))
+ .andReturn();
+ }
+
+ @Test
+ void addNewAlertReport() throws Exception {
+ GeneralCloudAlertReport generalCloudAlertReport = new GeneralCloudAlertReport();
+ generalCloudAlertReport.setAlertDateTime("2023-02-22T07:27:15.404000000Z");
+
+ mockMvc.perform(MockMvcRequestBuilders
+ .post("/api/alerts/report")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(JsonUtil.toJson(generalCloudAlertReport))
+ )
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
+ .andExpect(content().json("{\"data\":null,\"msg\":\"Add report success\",\"code\":0}"))
+ .andReturn();
+ }
+}
diff --git a/applications/hertzbeat/alerter/src/test/java/org/apache/hertzbeat/alert/controller/AlertsControllerTest.java b/applications/hertzbeat/alerter/src/test/java/org/apache/hertzbeat/alert/controller/AlertsControllerTest.java
new file mode 100644
index 0000000..c35ee78
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/test/java/org/apache/hertzbeat/alert/controller/AlertsControllerTest.java
@@ -0,0 +1,161 @@
+/*
+ * 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.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.LongStream;
+import org.apache.hertzbeat.alert.dto.AlertSummary;
+import org.apache.hertzbeat.alert.service.AlertService;
+import org.apache.hertzbeat.common.constants.CommonConstants;
+import org.apache.hertzbeat.common.entity.alerter.Alert;
+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.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+/**
+ * Test case for {@link AlertsController}
+ */
+@ExtendWith(MockitoExtension.class)
+class AlertsControllerTest {
+
+ private MockMvc mockMvc;
+
+ @InjectMocks
+ private AlertsController alertsController;
+
+ @Mock
+ private AlertService alertService;
+
+ private List ids;
+
+
+ @BeforeEach
+ void setUp() {
+ this.mockMvc = MockMvcBuilders.standaloneSetup(alertsController).build();
+ ids = LongStream.rangeClosed(1, 10).boxed().collect(Collectors.toList());
+ }
+
+ // todo: fix this test
+ void getAlerts() throws Exception {
+ String sortField = "id";
+ String orderType = "asc";
+ int pageIndex = 0;
+ int pageSize = 10;
+ PageRequest pageRequest = PageRequest.of(pageIndex, pageSize, Sort.by(new Sort.Order(Sort.Direction.fromString(orderType), sortField)));
+ Page alertPage = new PageImpl<>(Collections.singletonList(Alert.builder().build()));
+ Mockito.when(
+ alertService.getAlerts(
+ Mockito.any(Specification.class)
+ , Mockito.argThat(
+ argument ->
+ argument.getPageNumber() == pageRequest.getPageNumber()
+ && argument.getPageSize() == pageRequest.getPageSize()
+ && argument.getSort().equals(pageRequest.getSort())
+ )
+ )
+ )
+ .thenReturn(alertPage);
+
+ mockMvc.perform(
+ MockMvcRequestBuilders
+ .get("/api/alerts")
+ .param("ids", ids.stream().map(String::valueOf).collect(Collectors.joining(",")))
+ .param("monitorId", "1")
+ .param("priority", "1")
+ .param("status", "1")
+ .param("content", "test")
+ .param("sort", sortField)
+ .param("order", orderType)
+ .param("pageIndex", String.valueOf(pageIndex))
+ .param("pageSize", String.valueOf(pageSize))
+ )
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
+ .andExpect(jsonPath("$.data.content.length()").value(1))
+ .andReturn();
+ }
+
+ @Test
+ void deleteAlerts() throws Exception {
+ mockMvc.perform(
+ MockMvcRequestBuilders
+ .delete("/api/alerts")
+ .param("ids", ids.stream().map(String::valueOf).collect(Collectors.joining(",")))
+ )
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
+ .andExpect(content().json("{\"data\":null,\"msg\":null,\"code\":0}"))
+ .andReturn();
+ }
+
+ @Test
+ void clearAllAlerts() throws Exception {
+ mockMvc.perform(
+ MockMvcRequestBuilders
+ .delete("/api/alerts/clear")
+ )
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
+ .andExpect(content().json("{\"data\":null,\"msg\":null,\"code\":0}"))
+ .andReturn();
+ }
+
+ @Test
+ void applyAlertDefinesStatus() throws Exception {
+ mockMvc.perform(
+ MockMvcRequestBuilders
+ .put("/api/alerts/status/1")
+ .param("ids", ids.stream().map(String::valueOf).collect(Collectors.joining(",")))
+ )
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
+ .andExpect(content().json("{\"data\":null,\"msg\":null,\"code\":0}"))
+ .andReturn();
+ }
+
+ @Test
+ void getAlertsSummary() throws Exception {
+ Mockito.when(alertService.getAlertsSummary()).thenReturn(new AlertSummary());
+
+ mockMvc.perform(
+ MockMvcRequestBuilders
+ .get("/api/alerts/summary")
+ )
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
+ .andExpect(content().json("{\"data\":{\"total\":0,\"dealNum\":0,\"rate\":0.0,\"priorityWarningNum\":0,\"priorityCriticalNum\":0,\"priorityEmergencyNum\":0},\"msg\":null,\"code\":0}"))
+ .andReturn();
+ }
+}
diff --git a/applications/hertzbeat/alerter/src/test/java/org/apache/hertzbeat/alert/service/AlertDefineServiceTest.java b/applications/hertzbeat/alerter/src/test/java/org/apache/hertzbeat/alert/service/AlertDefineServiceTest.java
new file mode 100644
index 0000000..b3b317d
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/test/java/org/apache/hertzbeat/alert/service/AlertDefineServiceTest.java
@@ -0,0 +1,190 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.alert.service;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.anySet;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.when;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import org.apache.hertzbeat.alert.dao.AlertDefineBindDao;
+import org.apache.hertzbeat.alert.dao.AlertDefineDao;
+import org.apache.hertzbeat.alert.service.impl.AlertDefineServiceImpl;
+import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
+import org.apache.hertzbeat.common.entity.alerter.AlertDefineMonitorBind;
+import org.apache.hertzbeat.common.entity.manager.Monitor;
+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.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.test.util.ReflectionTestUtils;
+
+/**
+ * Test case for {@link AlertDefineService}
+ */
+@ExtendWith(MockitoExtension.class)
+class AlertDefineServiceTest {
+
+ private AlertDefine alertDefine;
+
+ private List alertDefineMonitorBinds;
+
+ @Mock
+ private AlertDefineDao alertDefineDao;
+
+ @Mock
+ private AlertDefineBindDao alertDefineBindDao;
+
+ @Mock
+ private List alertDefineImExportServiceList;
+
+ @InjectMocks
+ private AlertDefineServiceImpl alertDefineService;
+
+ @BeforeEach
+ void setUp() {
+ ReflectionTestUtils.setField(this.alertDefineService, "alertDefineDao", alertDefineDao);
+ ReflectionTestUtils.setField(this.alertDefineService, "alertDefineBindDao", alertDefineBindDao);
+
+ this.alertDefine = AlertDefine.builder()
+ .id(1L)
+ .app("app")
+ .metric("test")
+ .field("test")
+ .preset(false)
+ .expr("1 > 0")
+ .priority((byte) 1)
+ .times(1)
+ .template("template")
+ .creator("tom")
+ .modifier("tom")
+ .build();
+
+ this.alertDefineMonitorBinds = Collections.singletonList(
+ AlertDefineMonitorBind.builder()
+ .id(1L)
+ .alertDefineId(this.alertDefine.getId())
+ .monitorId(1L)
+ .monitor(
+ Monitor.builder()
+ .id(1L)
+ .app("app")
+ .host("localhost")
+ .name("monitor")
+ .build()
+ )
+ .build()
+ );
+ }
+
+ @Test
+ void validate() {
+ assertDoesNotThrow(() -> alertDefineService.validate(alertDefine, true));
+ assertDoesNotThrow(() -> alertDefineService.validate(alertDefine, false));
+ }
+
+ @Test
+ void addAlertDefine() {
+ assertDoesNotThrow(() -> alertDefineService.addAlertDefine(alertDefine));
+ when(alertDefineDao.save(alertDefine)).thenThrow(new RuntimeException());
+ assertThrows(RuntimeException.class, () -> alertDefineService.addAlertDefine(alertDefine));
+ }
+
+ @Test
+ void modifyAlertDefine() {
+ AlertDefine alertDefine = AlertDefine.builder().id(1L).build();
+ when(alertDefineDao.save(alertDefine)).thenReturn(alertDefine);
+ assertDoesNotThrow(() -> alertDefineService.modifyAlertDefine(alertDefine));
+ reset();
+ when(alertDefineDao.save(alertDefine)).thenThrow(new RuntimeException());
+ assertThrows(RuntimeException.class, () -> alertDefineService.modifyAlertDefine(alertDefine));
+ }
+
+ @Test
+ void deleteAlertDefine() {
+ long id = 1L;
+ doNothing().doThrow(new RuntimeException()).when(alertDefineDao).deleteById(id);
+ assertDoesNotThrow(() -> alertDefineService.deleteAlertDefine(id));
+ assertThrows(RuntimeException.class, () -> alertDefineService.deleteAlertDefine(id));
+ }
+
+ @Test
+ void getAlertDefine() {
+ long id = 1L;
+ AlertDefine alertDefine = AlertDefine.builder().id(id).build();
+ when(alertDefineDao.findById(id)).thenReturn(Optional.of(alertDefine));
+ assertDoesNotThrow(() -> alertDefineService.getAlertDefine(id));
+ }
+
+ @Test
+ void deleteAlertDefines() {
+ doNothing().when(alertDefineDao).deleteAlertDefinesByIdIn(anySet());
+ assertDoesNotThrow(() -> alertDefineService.deleteAlertDefines(new HashSet<>(1)));
+ }
+
+ @Test
+ void getMonitorBindAlertDefines() {
+ Specification specification = mock(Specification.class);
+ when(alertDefineDao.findAll(specification, PageRequest.of(1, 1))).thenReturn(Page.empty());
+ assertNotNull(alertDefineService.getMonitorBindAlertDefines(specification, PageRequest.of(1, 1)));
+ }
+
+ @Test
+ void applyBindAlertDefineMonitors() {
+ long id = 1L;
+ doNothing().when(alertDefineBindDao).deleteAlertDefineBindsByAlertDefineIdEquals(id);
+ when(alertDefineBindDao.saveAll(alertDefineMonitorBinds)).thenReturn(alertDefineMonitorBinds);
+ assertDoesNotThrow(() -> alertDefineService.applyBindAlertDefineMonitors(id, alertDefineMonitorBinds));
+ }
+
+ @Test
+ void testGetMonitorBindAlertDefines() {
+ List alertDefineList = new ArrayList<>();
+ alertDefineList.add(this.alertDefine);
+ when(alertDefineDao.queryAlertDefinesByMonitor(1L, "app", "test")).thenReturn(alertDefineList);
+ when(alertDefineDao.queryAlertDefinesByAppAndMetricAndPresetTrueAndEnableTrue("app", "test")).thenReturn(alertDefineList);
+ assertNotNull(alertDefineService.getMonitorBindAlertDefines(1L, "app", "test"));
+ }
+
+ @Test
+ void getAlertDefines() {
+ Specification specification = mock(Specification.class);
+ when(alertDefineDao.findAll(specification, PageRequest.of(1, 1))).thenReturn(Page.empty());
+ assertNotNull(alertDefineService.getAlertDefines(specification, PageRequest.of(1, 1)));
+ }
+
+ @Test
+ void getBindAlertDefineMonitors() {
+ long id = 1L;
+ when(alertDefineBindDao.getAlertDefineBindsByAlertDefineIdEquals(id)).thenReturn(alertDefineMonitorBinds);
+ assertDoesNotThrow(() -> alertDefineService.getBindAlertDefineMonitors(id));
+ }
+}
diff --git a/applications/hertzbeat/alerter/src/test/java/org/apache/hertzbeat/alert/service/AlertServiceTest.java b/applications/hertzbeat/alerter/src/test/java/org/apache/hertzbeat/alert/service/AlertServiceTest.java
new file mode 100644
index 0000000..9a76063
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/test/java/org/apache/hertzbeat/alert/service/AlertServiceTest.java
@@ -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.alert.service;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test case for {@link AlertService}
+ */
+class AlertServiceTest {
+
+ @BeforeEach
+ void setUp() {
+ }
+
+ @Test
+ void addAlert() {
+ }
+
+ @Test
+ void getAlerts() {
+ }
+
+ @Test
+ void deleteAlerts() {
+ }
+
+ @Test
+ void clearAlerts() {
+ }
+
+ @Test
+ void editAlertStatus() {
+ }
+
+ @Test
+ void getAlertsSummary() {
+ }
+
+ @Test
+ void addNewAlertReport() {
+ }
+}
diff --git a/applications/hertzbeat/alerter/src/test/java/org/apache/hertzbeat/alert/util/AlertTemplateUtilTest.java b/applications/hertzbeat/alerter/src/test/java/org/apache/hertzbeat/alert/util/AlertTemplateUtilTest.java
new file mode 100644
index 0000000..096a76e
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/test/java/org/apache/hertzbeat/alert/util/AlertTemplateUtilTest.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.alert.util;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test case for {@link AlertTemplateUtil}
+ */
+class AlertTemplateUtilTest {
+
+ @BeforeEach
+ void setUp() {
+ }
+
+ @Test
+ void render() {
+ // test null template case
+ Map param = new HashMap<>();
+ String template = null;
+ assertNull(AlertTemplateUtil.render(null, param));
+ // test null map case
+ template = "${key} for testing";
+ assertEquals(AlertTemplateUtil.render(template, null), template);
+ // test null template and null map case
+ assertNull(AlertTemplateUtil.render(null, null));
+ // test illegal template case
+ template = "";
+ param.put("key", "Just");
+ assertEquals(AlertTemplateUtil.render(template, param), template);
+ template = "key for testing!";
+ assertEquals(AlertTemplateUtil.render(template, param), template);
+ // test empty map case
+ param.clear();
+ template = "${key} for testing";
+ assertEquals(AlertTemplateUtil.render(template, param), "NullValue for testing");
+ // test illegal template and empty map case
+ param.clear();
+ template = "key for testing";
+ assertEquals(AlertTemplateUtil.render(template, param), template);
+ // test one param
+ param.put("key", "Just");
+ template = "${key} for testing";
+ assertEquals(AlertTemplateUtil.render(template, param), "Just for testing");
+ // test two param
+ param.put("key1", "Just");
+ param.put("key2", "testing");
+ template = "${key1} for ${key2}";
+ assertEquals(AlertTemplateUtil.render(template, param), "Just for testing");
+ // test all param
+ param.put("key1", "Just");
+ param.put("key2", "for");
+ param.put("key3", "testing");
+ template = "${key1} ${key2} ${key3}";
+ assertEquals(AlertTemplateUtil.render(template, param), "Just for testing");
+ }
+
+ @Test
+ void renderSpecialCharacters() {
+ Map param = new HashMap<>();
+ param.put("valueWithDollar", "$100");
+ param.put("valueWithBackslash", "C:\\Users");
+
+ String template = "The price is ${valueWithDollar} and the path is ${valueWithBackslash}";
+
+ // Expected to handle the dollar sign and backslash correctly without throwing an exception
+ String expectedResult = "The price is $100 and the path is C:\\Users";
+ assertEquals(expectedResult, AlertTemplateUtil.render(template, param));
+ }
+}
diff --git a/applications/hertzbeat/alerter/src/test/java/org/apache/hertzbeat/alert/util/DateUtilTest.java b/applications/hertzbeat/alerter/src/test/java/org/apache/hertzbeat/alert/util/DateUtilTest.java
new file mode 100644
index 0000000..df4cff2
--- /dev/null
+++ b/applications/hertzbeat/alerter/src/test/java/org/apache/hertzbeat/alert/util/DateUtilTest.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.alert.util;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Test case for {@link DateUtil}
+ */
+class DateUtilTest {
+
+ @Test
+ void getTimeStampFromSomeFormats() {
+ String date = "2024-05-13";
+ Optional actualTimestamp = DateUtil.getTimeStampFromSomeFormats(date);
+ assertFalse(actualTimestamp.isPresent());
+
+ date = "2024-05-13T12:34:56.789Z";
+ actualTimestamp = DateUtil.getTimeStampFromSomeFormats(date);
+ assertTrue(actualTimestamp.isPresent());
+ assertEquals(1715603696789L, actualTimestamp.get());
+
+ date = "2023-02-22T07:27:15.404000000Z";
+ actualTimestamp = DateUtil.getTimeStampFromSomeFormats(date);
+ assertTrue(actualTimestamp.isPresent());
+ assertEquals(1677050835404L, actualTimestamp.get());
+ }
+
+ @Test
+ void getTimeStampFromFormat() {
+ String date = "2024-05-13 10:30:00";
+ String format = "yyyy-MM-dd HH:mm:ss";
+ Optional actualTimestamp = DateUtil.getTimeStampFromFormat(date, format);
+ assertTrue(actualTimestamp.isPresent());
+ assertEquals(1715596200000L, actualTimestamp.get());
+
+ date = "2024-05-13";
+ format = "yyyy-MM-dd HH:mm:ss.SSS";
+ actualTimestamp = DateUtil.getTimeStampFromFormat(date, format);
+ assertFalse(actualTimestamp.isPresent());
+ }
+}
diff --git a/applications/hertzbeat/collector/pom.xml b/applications/hertzbeat/collector/pom.xml
new file mode 100644
index 0000000..4edd3af
--- /dev/null
+++ b/applications/hertzbeat/collector/pom.xml
@@ -0,0 +1,454 @@
+
+
+
+
+ hertzbeat
+ org.apache.hertzbeat
+ 2.0-SNAPSHOT
+
+ 4.0.0
+
+ hertzbeat-collector
+ ${project.artifactId}
+
+ 17
+ ${java.version}
+ ${java.version}
+ 3.2.0
+ 3.3.0
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+ org.springframework.boot
+ spring-boot-autoconfigure
+
+
+
+ org.apache.hertzbeat
+ hertzbeat-common
+
+
+
+ org.apache.hertzbeat
+ hertzbeat-remoting
+
+
+
+ org.springframework
+ spring-jdbc
+
+
+
+ org.apache.kafka
+ kafka-clients
+
+
+
+ org.apache.httpcomponents
+ httpclient
+
+
+
+ commons-net
+ commons-net
+ 3.8.0
+
+
+
+ com.jayway.jsonpath
+ json-path
+
+
+
+ com.googlecode.concurrentlinkedhashmap
+ concurrentlinkedhashmap-lru
+ 1.4.2
+
+
+ com.google.guava
+ guava
+
+
+ com.google.code.gson
+ gson
+
+
+
+
+ mysql
+ mysql-connector-java
+ provided
+
+
+
+ com.clickhouse
+ clickhouse-jdbc
+
+
+
+
+ com.dameng
+ DmJdbcDriver18
+
+
+
+ org.postgresql
+ postgresql
+
+
+
+ org.apache.sshd
+ sshd-core
+ 2.8.0
+
+
+ net.i2p.crypto
+ eddsa
+ 0.3.0
+
+
+
+ com.microsoft.sqlserver
+ mssql-jdbc
+
+
+
+ com.oracle.database.jdbc
+ ojdbc8
+ provided
+
+
+ com.oracle.database.nls
+ orai18n
+ provided
+
+
+
+ io.lettuce
+ lettuce-core
+
+
+
+ org.mongodb
+ mongodb-driver-sync
+
+
+
+ org.snmp4j
+ snmp4j
+ 3.6.7
+
+
+
+
+ org.apache.rocketmq
+ rocketmq-tools
+ 4.9.4
+
+
+
+
+ dnsjava
+ dnsjava
+ 3.5.2
+
+
+
+
+ com.ecwid.consul
+ consul-api
+ 1.4.5
+
+
+
+ com.alibaba.nacos
+ nacos-client
+ 2.2.1
+
+
+
+ com.vesoft
+ client
+ 3.6.0
+
+
+
+
+ apache-hertzbeat-collector-${hzb.version}
+
+
+
+
+ inner
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ ${maven-jar-plugin.version}
+
+
+ package
+
+
+
+
+
+
+
+ cluster
+
+
+
+ src/main/resources
+ true
+
+ *.yml
+ *.properties
+ *.xml
+ banner.txt
+ META-INF/**
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 17
+ 17
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ ${maven-jar-plugin.version}
+
+ target/classes/
+
+
+ false
+
+
+ org.apache.hertzbeat.collector.Collector
+ false
+
+ true
+
+ lib/
+
+
+ . config
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ ${maven-assembly-plugin.version}
+
+
+ without-jdk
+
+ package
+
+
+ single
+
+
+
+ ../script/assembly/collector/assembly.xml
+
+ ../dist
+
+
+
+
+
+
+
+
+ runtime
+
+
+
+ src/main/resources
+ true
+
+ *.yml
+ *.properties
+ *.xml
+ banner.txt
+ META-INF/**
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 17
+ 17
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ ${maven-jar-plugin.version}
+
+ target/classes/
+
+
+ false
+
+
+ org.apache.hertzbeat.collector.Collector
+ false
+
+ true
+
+ lib/
+
+
+ . config
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ ${maven-assembly-plugin.version}
+
+
+ without-jdk
+
+ package
+
+
+ single
+
+
+
+ ../script/assembly/collector/assembly.xml
+
+ ../dist
+
+
+
+ make-macos-arm64
+
+ package
+
+
+ single
+
+
+
+ ../script/assembly/collector/assembly-macos-arm64.xml
+
+ ../dist
+
+
+
+ make-macos-amd64
+
+ package
+
+
+ single
+
+
+
+ ../script/assembly/collector/assembly-macos-amd64.xml
+
+ ../dist
+
+
+
+ make-linux-arm64
+
+ package
+
+
+ single
+
+
+
+ ../script/assembly/collector/assembly-linux-arm64.xml
+
+ ../dist
+
+
+
+ make-linux-amd64
+
+ package
+
+
+ single
+
+
+
+ ../script/assembly/collector/assembly-linux-amd64.xml
+
+ ../dist
+
+
+
+ make-windows-64
+
+ package
+
+
+ single
+
+
+
+ ../script/assembly/collector/assembly-windows-64.xml
+
+ ../dist
+
+
+
+
+
+
+
+
+
+
diff --git a/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/Collector.java b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/Collector.java
new file mode 100644
index 0000000..3e41f22
--- /dev/null
+++ b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/Collector.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+import jakarta.annotation.PostConstruct;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
+import org.springframework.context.annotation.ComponentScan;
+
+/**
+ * collector startup
+ */
+@ComponentScan(basePackages = {"org.apache.hertzbeat"})
+@ConfigurationPropertiesScan(basePackages = {"org.apache.hertzbeat"})
+@SpringBootApplication
+public class Collector {
+ public static void main(String[] args) {
+ SpringApplication.run(Collector.class, args);
+ }
+
+ @PostConstruct
+ public void init() {
+ System.setProperty("jdk.jndi.object.factoriesFilter", "!com.zaxxer.hikari.HikariJNDIFactory");
+ }
+}
diff --git a/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/AbstractCollect.java b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/AbstractCollect.java
new file mode 100644
index 0000000..82caa00
--- /dev/null
+++ b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/AbstractCollect.java
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+
+import org.apache.hertzbeat.common.entity.job.Metrics;
+import org.apache.hertzbeat.common.entity.message.CollectRep;
+
+/**
+ * Specific metrics collection implementation abstract class
+ */
+public abstract class AbstractCollect {
+
+ /**
+ * Pre-check metrics
+ * @param metrics metric configuration
+ * @throws IllegalArgumentException when validation failed
+ */
+ public abstract void preCheck(Metrics metrics) throws IllegalArgumentException;
+
+
+ /**
+ * Real acquisition implementation interface
+ * @param builder response builder
+ * @param monitorId monitor id
+ * @param app monitor type
+ * @param metrics metric configuration
+ */
+ public abstract void collect(CollectRep.MetricsData.Builder builder, long monitorId, String app, Metrics metrics);
+
+ /**
+ * the protocol this collect instance support
+ * @return protocol str
+ */
+ public abstract String supportProtocol();
+}
diff --git a/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/cache/AbstractConnection.java b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/cache/AbstractConnection.java
new file mode 100644
index 0000000..7b3ce9f
--- /dev/null
+++ b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/cache/AbstractConnection.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.collector.collect.common.cache;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * AbstractConnection
+ */
+@Slf4j
+public abstract class AbstractConnection implements AutoCloseable {
+
+ /**
+ * @return Returns the connection.
+ */
+ public abstract T getConnection();
+
+ /**
+ * Close connection
+ */
+ public abstract void closeConnection() throws Exception;
+
+ @Override
+ public void close() throws Exception{
+ closeConnection();
+ }
+}
diff --git a/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/cache/CacheIdentifier.java b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/cache/CacheIdentifier.java
new file mode 100644
index 0000000..603ae17
--- /dev/null
+++ b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/cache/CacheIdentifier.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.collector.collect.common.cache;
+
+import java.util.Objects;
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * resource identifier in cache
+ */
+@Data
+@Builder
+public class CacheIdentifier {
+
+ private String ip;
+
+ private String port;
+
+ private String username;
+
+ private String password;
+
+ private String customArg;
+
+ @Override
+ public String toString() {
+ return "CacheIdentifier {"
+ + "ip='" + ip + '\''
+ + ", port='" + port + '\''
+ + ", username+password=>hash='" + Objects.hash(username, password) + '\''
+ + ", customArg='" + customArg + '\''
+ + '}';
+ }
+}
diff --git a/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/cache/ConnectionCommonCache.java b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/cache/ConnectionCommonCache.java
new file mode 100644
index 0000000..8bd6321
--- /dev/null
+++ b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/cache/ConnectionCommonCache.java
@@ -0,0 +1,230 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.collector.collect.common.cache;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * lru common resource cache for client-server connection
+ */
+@Slf4j
+public class ConnectionCommonCache> {
+
+ /**
+ * default cache time 200s
+ */
+ private static final long DEFAULT_CACHE_TIMEOUT = 200 * 1000L;
+
+ /**
+ * default max cache num
+ */
+ private static final int DEFAULT_MAX_CAPACITY = 10000;
+
+ /**
+ * cacheTime length
+ */
+ private static final int CACHE_TIME_LENGTH = 2;
+
+ /**
+ * cache timeout map
+ */
+ private Map timeoutMap;
+
+ /**
+ * object cache
+ */
+ private ConcurrentLinkedHashMap cacheMap;
+
+ /**
+ * the executor who clean cache when timeout
+ */
+ private ThreadPoolExecutor timeoutCleanerExecutor;
+
+ public ConnectionCommonCache() {
+ init();
+ }
+
+ private void init() {
+ cacheMap = new ConcurrentLinkedHashMap
+ .Builder()
+ .maximumWeightedCapacity(DEFAULT_MAX_CAPACITY)
+ .listener((key, value) -> {
+ timeoutMap.remove(key);
+ try {
+ value.close();
+ } catch (Exception e) {
+ log.error("connection close error: {}.", e.getMessage(), e);
+ }
+ log.info("connection common cache discard key: {}, value: {}.", key, value);
+ }).build();
+ timeoutMap = new ConcurrentHashMap<>(DEFAULT_MAX_CAPACITY >> 6);
+ // last-first-coverage algorithm, run the first and last thread, discard mid
+ timeoutCleanerExecutor = new ThreadPoolExecutor(1, 1, 1, TimeUnit.SECONDS,
+ new ArrayBlockingQueue<>(1),
+ r -> new Thread(r, "connection-cache-timeout-cleaner"),
+ new ThreadPoolExecutor.DiscardOldestPolicy());
+ // init monitor available detector cyc task
+ ThreadFactory threadFactory = new ThreadFactoryBuilder()
+ .setNameFormat("connection-cache-ava-detector-%d")
+ .setDaemon(true)
+ .build();
+ ScheduledThreadPoolExecutor scheduledExecutor = new ScheduledThreadPoolExecutor(1, threadFactory);
+ scheduledExecutor.scheduleWithFixedDelay(this::detectCacheAvailable, 2, 20, TimeUnit.MINUTES);
+ }
+
+ /**
+ * detect all cache available, cleanup not ava connection
+ */
+ private void detectCacheAvailable() {
+ try {
+ cacheMap.forEach((key, value) -> {
+ Long[] cacheTime = timeoutMap.get(key);
+ long currentTime = System.currentTimeMillis();
+ if (cacheTime == null || cacheTime.length != CACHE_TIME_LENGTH
+ || cacheTime[0] + cacheTime[1] < currentTime) {
+ cacheMap.remove(key);
+ timeoutMap.remove(key);
+ try {
+ value.close();
+ } catch (Exception e) {
+ log.error("connection close error: {}.", e.getMessage(), e);
+ }
+
+ }
+ });
+ } catch (Exception e) {
+ log.error("connection common cache detect cache available error: {}.", e.getMessage(), e);
+ }
+ }
+
+ /**
+ * clean timeout cache
+ */
+ private void cleanTimeoutCache() {
+ try {
+ cacheMap.forEach((key, value) -> {
+ // index 0 is startTime, 1 is timeDiff
+ Long[] cacheTime = timeoutMap.get(key);
+ long currentTime = System.currentTimeMillis();
+ if (cacheTime == null || cacheTime.length != CACHE_TIME_LENGTH) {
+ timeoutMap.put(key, new Long[]{currentTime, DEFAULT_CACHE_TIMEOUT});
+ } else if (cacheTime[0] + cacheTime[1] < currentTime) {
+ // timeout, remove this object cache
+ log.warn("[connection common cache] clean the timeout cache, key {}", key);
+ timeoutMap.remove(key);
+ cacheMap.remove(key);
+ try {
+ value.close();
+ } catch (Exception e) {
+ log.error("connection close error: {}.", e.getMessage(), e);
+ }
+ }
+ });
+ Thread.sleep(20 * 1000);
+ } catch (Exception e) {
+ log.error("[connection common cache] clean timeout cache error: {}.", e.getMessage(), e);
+ }
+ }
+
+ /**
+ * add update cache
+ *
+ * @param key cache key
+ * @param value cache value
+ * @param timeDiff cache time millis
+ */
+ public void addCache(T key, C value, Long timeDiff) {
+ removeCache(key);
+ if (timeDiff == null) {
+ timeDiff = DEFAULT_CACHE_TIMEOUT;
+ }
+ cacheMap.put(key, value);
+ timeoutMap.put(key, new Long[]{System.currentTimeMillis(), timeDiff});
+ timeoutCleanerExecutor.execute(this::cleanTimeoutCache);
+ }
+
+ /**
+ * add update cache
+ *
+ * @param key cache key
+ * @param value cache value
+ */
+ public void addCache(T key, C value) {
+ addCache(key, value, DEFAULT_CACHE_TIMEOUT);
+ }
+
+ /**
+ * get cache by key
+ *
+ * @param key cache key
+ * @param refreshCache is refresh cache
+ * @return cache object
+ */
+ public Optional getCache(T key, boolean refreshCache) {
+ Long[] cacheTime = timeoutMap.get(key);
+ if (cacheTime == null || cacheTime.length != CACHE_TIME_LENGTH) {
+ log.info("[connection common cache] not hit the cache, key {}.", key);
+ return Optional.empty();
+ }
+ if (cacheTime[0] + cacheTime[1] < System.currentTimeMillis()) {
+ log.warn("[connection common cache] is timeout, remove it, key {}.", key);
+ timeoutMap.remove(key);
+ cacheMap.remove(key);
+ return Optional.empty();
+ }
+ C value = cacheMap.get(key);
+ if (value == null) {
+ log.error("[connection common cache] value is null, remove it, key {}.", key);
+ cacheMap.remove(key);
+ timeoutMap.remove(key);
+ } else if (refreshCache) {
+ cacheTime[0] = System.currentTimeMillis();
+ timeoutMap.put(key, cacheTime);
+ }
+ return Optional.ofNullable(value);
+ }
+
+ /**
+ * remove cache by key
+ *
+ * @param key key
+ */
+ public void removeCache(T key) {
+ timeoutMap.remove(key);
+ C value = cacheMap.remove(key);
+ try {
+ if (value == null) {
+ return;
+ }
+ value.close();
+ } catch (Exception e) {
+ log.error("connection close error: {}.", e.getMessage(), e);
+ }
+ }
+
+}
diff --git a/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/cache/JdbcConnect.java b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/cache/JdbcConnect.java
new file mode 100644
index 0000000..ad82887
--- /dev/null
+++ b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/cache/JdbcConnect.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.collector.collect.common.cache;
+
+import java.sql.Connection;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * jdbc common connection
+ */
+@Slf4j
+public class JdbcConnect extends AbstractConnection {
+
+ private final Connection connection;
+
+ public JdbcConnect(Connection connection) {
+ this.connection = connection;
+ }
+
+ @Override
+ public void closeConnection() throws Exception {
+ if (connection != null) {
+ connection.close();
+ }
+ }
+
+ @Override
+ public Connection getConnection() {
+ return connection;
+ }
+}
diff --git a/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/cache/JmxConnect.java b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/cache/JmxConnect.java
new file mode 100644
index 0000000..f0e0360
--- /dev/null
+++ b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/cache/JmxConnect.java
@@ -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.collector.collect.common.cache;
+
+import javax.management.remote.JMXConnector;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * jmx connect object
+ **/
+@Slf4j
+public class JmxConnect extends AbstractConnection {
+
+ private final JMXConnector connection;
+
+ public JmxConnect(JMXConnector connection) {
+ this.connection = connection;
+ }
+
+
+ @Override
+ public void closeConnection() throws Exception {
+ if (connection != null) {
+ connection.close();
+ }
+ }
+
+ @Override
+ public JMXConnector getConnection() {
+ return connection;
+ }
+}
diff --git a/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/cache/MongodbConnect.java b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/cache/MongodbConnect.java
new file mode 100644
index 0000000..73e1e92
--- /dev/null
+++ b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/cache/MongodbConnect.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.collector.collect.common.cache;
+
+import com.mongodb.client.MongoClient;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * mongodb connect client
+ */
+@Slf4j
+public class MongodbConnect extends AbstractConnection {
+ private final MongoClient mongoClient;
+
+ public MongodbConnect(MongoClient mongoClient) {
+ this.mongoClient = mongoClient;
+ }
+
+ @Override
+ public void closeConnection() throws Exception {
+ if (this.mongoClient != null) {
+ this.mongoClient.close();
+ }
+ }
+
+ @Override
+ public MongoClient getConnection() {
+ return mongoClient;
+ }
+}
diff --git a/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/cache/RedfishConnect.java b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/cache/RedfishConnect.java
new file mode 100644
index 0000000..b0750f7
--- /dev/null
+++ b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/cache/RedfishConnect.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.collector.collect.common.cache;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.hertzbeat.collector.collect.redfish.ConnectSession;
+
+/**
+ * redfish connect session
+ */
+@Slf4j
+public class RedfishConnect extends AbstractConnection {
+ private final ConnectSession reddishConnectSession;
+
+ public RedfishConnect(ConnectSession reddishConnectSession) {
+ this.reddishConnectSession = reddishConnectSession;
+ }
+
+ @Override
+ public void closeConnection() throws Exception {
+ if (reddishConnectSession != null) {
+ reddishConnectSession.close();
+ }
+ }
+
+ @Override
+ public ConnectSession getConnection() {
+ return reddishConnectSession;
+ }
+}
diff --git a/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/cache/RedisConnect.java b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/cache/RedisConnect.java
new file mode 100644
index 0000000..e24be18
--- /dev/null
+++ b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/cache/RedisConnect.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.collector.collect.common.cache;
+
+import io.lettuce.core.api.StatefulConnection;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * redis connection
+ */
+@Slf4j
+public class RedisConnect extends AbstractConnection> {
+
+ private final StatefulConnection connection;
+
+ public RedisConnect(StatefulConnection connection) {
+ this.connection = connection;
+ }
+
+ @Override
+ public void closeConnection() throws Exception {
+ if (connection != null) {
+ connection.closeAsync();
+ }
+ }
+
+ @Override
+ public StatefulConnection getConnection() {
+ return connection;
+ }
+}
diff --git a/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/cache/SshConnect.java b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/cache/SshConnect.java
new file mode 100644
index 0000000..5e05aad
--- /dev/null
+++ b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/cache/SshConnect.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.collector.collect.common.cache;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.sshd.client.session.ClientSession;
+
+/**
+ * ssh connection holder
+ */
+@Slf4j
+public class SshConnect extends AbstractConnection {
+ private final ClientSession clientSession;
+
+ public SshConnect(ClientSession clientSession) {
+ this.clientSession = clientSession;
+ }
+
+ @Override
+ public void closeConnection() throws Exception {
+ if (clientSession != null) {
+ clientSession.close();
+ }
+ }
+
+ public ClientSession getConnection() {
+ return clientSession;
+ }
+}
diff --git a/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/http/CommonHttpClient.java b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/http/CommonHttpClient.java
new file mode 100644
index 0000000..5212e71
--- /dev/null
+++ b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/http/CommonHttpClient.java
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.collector.collect.common.http;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.config.Registry;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.conn.socket.PlainConnectionSocketFactory;
+import org.apache.http.conn.ssl.NoopHostnameVerifier;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.apache.http.ssl.SSLContexts;
+
+/**
+ * common http client
+ */
+@Slf4j
+public class CommonHttpClient {
+
+ private static CloseableHttpClient httpClient;
+
+ private static PoolingHttpClientConnectionManager connectionManager;
+
+ /**
+ * all max total connection
+ */
+ private static final int MAX_TOTAL_CONNECTIONS = 50000;
+
+ /**
+ * peer route max total connection
+ */
+ private static final int MAX_PER_ROUTE_CONNECTIONS = 80;
+
+ /**
+ * timeout for get connect from pool(ms)
+ */
+ private static final int REQUIRE_CONNECT_TIMEOUT = 4000;
+
+ /**
+ * tcp connect timeout(ms)
+ */
+ private static final int CONNECT_TIMEOUT = 4000;
+
+ /**
+ * socket read timeout(ms)
+ */
+ private static final int SOCKET_TIMEOUT = 60000;
+
+ /**
+ * validated time for idle connection. if when reuse this connection after this time, we will check it available.
+ */
+ private static final int INACTIVITY_VALIDATED_TIME = 10000;
+
+ /**
+ * ssl supported version
+ */
+ private static final String[] SUPPORTED_SSL = {"TLSv1", "TLSv1.1", "TLSv1.2", "SSLv3"};
+
+ static {
+ try {
+ SSLContext sslContext = SSLContexts.createDefault();
+ X509TrustManager x509TrustManager = new X509TrustManager() {
+ @Override
+ public void checkClientTrusted(X509Certificate[] x509Certificates, String s) { }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
+ // check server ssl certificate expired
+ Date now = new Date();
+ if (x509Certificates != null) {
+ for (X509Certificate certificate : x509Certificates) {
+ Date deadline = certificate.getNotAfter();
+ if (deadline != null && now.after(deadline)) {
+ throw new CertificateExpiredException();
+ }
+ }
+ }
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() { return null; }
+ };
+ sslContext.init(null, new TrustManager[]{x509TrustManager}, null);
+ SSLConnectionSocketFactory sslFactory = new SSLConnectionSocketFactory(sslContext, SUPPORTED_SSL, null, new NoopHostnameVerifier());
+ Registry registry = RegistryBuilder.create()
+ .register("http", PlainConnectionSocketFactory.INSTANCE)
+ .register("https", sslFactory)
+ .build();
+ RequestConfig requestConfig = RequestConfig.custom()
+ .setConnectionRequestTimeout(REQUIRE_CONNECT_TIMEOUT)
+ .setConnectTimeout(CONNECT_TIMEOUT)
+ .setSocketTimeout(SOCKET_TIMEOUT)
+ // auto redirect when 301 302 response status
+ .setRedirectsEnabled(true)
+ .build();
+ // connection pool
+ connectionManager = new PoolingHttpClientConnectionManager(registry);
+ connectionManager.setMaxTotal(MAX_TOTAL_CONNECTIONS);
+ connectionManager.setDefaultMaxPerRoute(MAX_PER_ROUTE_CONNECTIONS);
+ connectionManager.setValidateAfterInactivity(INACTIVITY_VALIDATED_TIME);
+ httpClient = HttpClients.custom()
+ .setConnectionManager(connectionManager)
+ .setDefaultRequestConfig(requestConfig)
+ // clean up unavailable expired connections
+ .evictExpiredConnections()
+ // clean up available but idle connections
+ .evictIdleConnections(100, TimeUnit.SECONDS)
+ .build();
+ ThreadFactory threadFactory = new ThreadFactoryBuilder()
+ .setNameFormat("http-connection-pool-cleaner-%d")
+ .setDaemon(true)
+ .build();
+ ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(1, threadFactory);
+ scheduledExecutor.scheduleWithFixedDelay(() -> {
+ connectionManager.closeExpiredConnections();
+ connectionManager.closeIdleConnections(100, TimeUnit.SECONDS);
+ }, 40L, 40L, TimeUnit.SECONDS);
+ } catch (Exception ignored) {}
+ }
+
+ public static CloseableHttpClient getHttpClient() {
+ return httpClient;
+ }
+}
diff --git a/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/ssh/CommonSshClient.java b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/ssh/CommonSshClient.java
new file mode 100644
index 0000000..639ca24
--- /dev/null
+++ b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/common/ssh/CommonSshClient.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.collector.collect.common.ssh;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.sshd.client.ClientBuilder;
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.keyverifier.AcceptAllServerKeyVerifier;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.PropertyResolverUtils;
+import org.apache.sshd.common.kex.BuiltinDHFactories;
+import org.apache.sshd.core.CoreModuleProperties;
+
+/**
+ * common ssh pool client
+ */
+@Slf4j
+public class CommonSshClient {
+
+ private static final SshClient SSH_CLIENT;
+
+ static {
+ SSH_CLIENT = SshClient.setUpDefaultClient();
+ // accept all server key verifier, will print warn log : Server at {} presented unverified {} key: {}
+ AcceptAllServerKeyVerifier verifier = AcceptAllServerKeyVerifier.INSTANCE;
+ SSH_CLIENT.setServerKeyVerifier(verifier);
+ // set connection heartbeat interval time 2000ms, wait for heartbeat response timeout 300_000ms
+ PropertyResolverUtils.updateProperty(
+ SSH_CLIENT, CoreModuleProperties.HEARTBEAT_INTERVAL.getName(), 2000);
+ PropertyResolverUtils.updateProperty(
+ SSH_CLIENT, CoreModuleProperties.HEARTBEAT_REPLY_WAIT.getName(), 300_000);
+ PropertyResolverUtils.updateProperty(
+ SSH_CLIENT, CoreModuleProperties.SOCKET_KEEPALIVE.getName(), true);
+ // set support all KeyExchange
+ SSH_CLIENT.setKeyExchangeFactories(NamedFactory.setUpTransformedFactories(
+ false,
+ BuiltinDHFactories.VALUES,
+ ClientBuilder.DH2KEX
+ ));
+ // todo when connect AlibabaCloud ubuntu server, custom signature factories will cause error, why?
+ // SSH_CLIENT.setSignatureFactories(new ArrayList<>(BuiltinSignatures.VALUES));
+ SSH_CLIENT.start();
+ }
+
+ public static SshClient getSshClient() {
+ return SSH_CLIENT;
+ }
+}
diff --git a/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/database/JdbcCommonCollect.java b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/database/JdbcCommonCollect.java
new file mode 100644
index 0000000..2294369
--- /dev/null
+++ b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/database/JdbcCommonCollect.java
@@ -0,0 +1,320 @@
+/*
+ * 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.database;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.hertzbeat.collector.collect.AbstractCollect;
+import org.apache.hertzbeat.collector.collect.common.cache.CacheIdentifier;
+import org.apache.hertzbeat.collector.collect.common.cache.ConnectionCommonCache;
+import org.apache.hertzbeat.collector.collect.common.cache.JdbcConnect;
+import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
+import org.apache.hertzbeat.collector.util.CollectUtil;
+import org.apache.hertzbeat.common.constants.CollectorConstants;
+import org.apache.hertzbeat.common.constants.CommonConstants;
+import org.apache.hertzbeat.common.entity.job.Metrics;
+import org.apache.hertzbeat.common.entity.job.protocol.JdbcProtocol;
+import org.apache.hertzbeat.common.entity.message.CollectRep;
+import org.apache.hertzbeat.common.util.CommonUtil;
+import org.postgresql.util.PSQLException;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.jdbc.datasource.init.ScriptUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * common query for database query
+ */
+@Slf4j
+public class JdbcCommonCollect extends AbstractCollect {
+
+ private static final String QUERY_TYPE_ONE_ROW = "oneRow";
+ private static final String QUERY_TYPE_MULTI_ROW = "multiRow";
+ private static final String QUERY_TYPE_COLUMNS = "columns";
+ private static final String RUN_SCRIPT = "runScript";
+
+ private static final String[] VULNERABLE_KEYWORDS = {"allowLoadLocalInfile", "allowLoadLocalInfileInPath", "useLocalInfile"};
+
+ private final ConnectionCommonCache connectionCommonCache;
+
+ public JdbcCommonCollect(){
+ connectionCommonCache = new ConnectionCommonCache<>();
+ }
+
+ @Override
+ public void preCheck(Metrics metrics) throws IllegalArgumentException {
+ if (metrics == null || metrics.getJdbc() == null) {
+ throw new IllegalArgumentException("Database collect must has jdbc params");
+ }
+ if (StringUtils.hasText(metrics.getJdbc().getUrl())) {
+ for (String keyword : VULNERABLE_KEYWORDS) {
+ if (metrics.getJdbc().getUrl().contains(keyword)) {
+ throw new IllegalArgumentException("Jdbc url prohibit contains vulnerable param " + keyword);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void collect(CollectRep.MetricsData.Builder builder, long monitorId, String app, Metrics metrics) {
+ long startTime = System.currentTimeMillis();
+ JdbcProtocol jdbcProtocol = metrics.getJdbc();
+ String databaseUrl = constructDatabaseUrl(jdbcProtocol);
+ int timeout = CollectUtil.getTimeout(jdbcProtocol.getTimeout());
+ Statement statement = null;
+ try {
+ statement = getConnection(jdbcProtocol.getUsername(),
+ jdbcProtocol.getPassword(), databaseUrl, timeout);
+ switch (jdbcProtocol.getQueryType()) {
+ case QUERY_TYPE_ONE_ROW -> queryOneRow(statement, jdbcProtocol.getSql(), metrics.getAliasFields(), builder, startTime);
+ case QUERY_TYPE_MULTI_ROW -> queryMultiRow(statement, jdbcProtocol.getSql(), metrics.getAliasFields(), builder, startTime);
+ case QUERY_TYPE_COLUMNS -> queryOneRowByMatchTwoColumns(statement, jdbcProtocol.getSql(), metrics.getAliasFields(), builder, startTime);
+ case RUN_SCRIPT -> {
+ Connection connection = statement.getConnection();
+ FileSystemResource rc = new FileSystemResource(jdbcProtocol.getSql());
+ ScriptUtils.executeSqlScript(connection, rc);
+ }
+ default -> {
+ builder.setCode(CollectRep.Code.FAIL);
+ builder.setMsg("Not support database query type: " + jdbcProtocol.getQueryType());
+ }
+ }
+ } catch (PSQLException psqlException) {
+ // for PostgreSQL 08001
+ if (CollectorConstants.POSTGRESQL_UN_REACHABLE_CODE.equals(psqlException.getSQLState())) {
+ // Peer connection failed, unreachable
+ builder.setCode(CollectRep.Code.UN_REACHABLE);
+ } else {
+ builder.setCode(CollectRep.Code.FAIL);
+ }
+ builder.setMsg("Error: " + psqlException.getMessage() + " Code: " + psqlException.getSQLState());
+ } catch (SQLException sqlException) {
+ log.warn("Jdbc sql error: {}, code: {}.", sqlException.getMessage(), sqlException.getErrorCode());
+ builder.setCode(CollectRep.Code.FAIL);
+ builder.setMsg("Query Error: " + sqlException.getMessage() + " Code: " + sqlException.getErrorCode());
+ } catch (Exception e) {
+ String errorMessage = CommonUtil.getMessageFromThrowable(e);
+ log.error("Jdbc error: {}.", errorMessage, e);
+ builder.setCode(CollectRep.Code.FAIL);
+ builder.setMsg("Query Error: " + errorMessage);
+ } finally {
+ if (statement != null) {
+ try {
+ statement.close();
+ } catch (Exception e) {
+ log.error("Jdbc close statement error: {}", e.getMessage());
+ }
+ }
+ }
+ }
+
+ @Override
+ public String supportProtocol() {
+ return DispatchConstants.PROTOCOL_JDBC;
+ }
+
+
+ private Statement getConnection(String username, String password, String url, Integer timeout) throws Exception {
+ CacheIdentifier identifier = CacheIdentifier.builder()
+ .ip(url)
+ .username(username).password(password).build();
+ Optional cacheOption = connectionCommonCache.getCache(identifier, true);
+ Statement statement = null;
+ if (cacheOption.isPresent()) {
+ JdbcConnect jdbcConnect = cacheOption.get();
+ try {
+ statement = jdbcConnect.getConnection().createStatement();
+ // set query timeout
+ int timeoutSecond = timeout / 1000;
+ timeoutSecond = timeoutSecond <= 0 ? 1 : timeoutSecond;
+ statement.setQueryTimeout(timeoutSecond);
+ // set query max row number
+ statement.setMaxRows(1000);
+ } catch (Exception e) {
+ log.info("The jdbc connect from cache, create statement error: {}", e.getMessage());
+ try {
+ if (statement != null) {
+ statement.close();
+ }
+ jdbcConnect.close();
+ } catch (Exception e2) {
+ log.error(e2.getMessage());
+ }
+ statement = null;
+ connectionCommonCache.removeCache(identifier);
+ }
+ }
+ if (statement != null) {
+ return statement;
+ }
+ // renew connection when failed
+ Connection connection = DriverManager.getConnection(url, username, password);
+ statement = connection.createStatement();
+ int timeoutSecond = timeout / 1000;
+ timeoutSecond = timeoutSecond <= 0 ? 1 : timeoutSecond;
+ statement.setQueryTimeout(timeoutSecond);
+ statement.setMaxRows(1000);
+ JdbcConnect jdbcConnect = new JdbcConnect(connection);
+ connectionCommonCache.addCache(identifier, jdbcConnect);
+ return statement;
+ }
+
+ /**
+ * query one row record, response metrics header and one value row
+ * eg:
+ * query metrics:one tow three four
+ * query sql:select one, tow, three, four from book limit 1;
+ * @param statement statement
+ * @param sql sql
+ * @param columns query metrics field list
+ * @throws Exception when error happen
+ */
+ private void queryOneRow(Statement statement, String sql, List columns,
+ CollectRep.MetricsData.Builder builder, long startTime) throws Exception {
+ statement.setMaxRows(1);
+ try (ResultSet resultSet = statement.executeQuery(sql)) {
+ if (resultSet.next()) {
+ CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
+ for (String column : columns) {
+ if (CollectorConstants.RESPONSE_TIME.equals(column)) {
+ long time = System.currentTimeMillis() - startTime;
+ valueRowBuilder.addColumns(String.valueOf(time));
+ } else {
+ String value = resultSet.getString(column);
+ value = value == null ? CommonConstants.NULL_VALUE : value;
+ valueRowBuilder.addColumns(value);
+ }
+ }
+ builder.addValues(valueRowBuilder.build());
+ }
+ }
+ }
+
+ /**
+ * query two columns to mapping one row
+ * eg:
+ * query metrics:one two three four
+ * query sql:select key, value from book; the key is the query metrics fields
+ * select key, value from book;
+ * one - value1
+ * two - value2
+ * three - value3
+ * four - value4
+ * @param statement statement
+ * @param sql sql
+ * @param columns query metrics field list
+ * @throws Exception when error happen
+ */
+ private void queryOneRowByMatchTwoColumns(Statement statement, String sql, List columns,
+ CollectRep.MetricsData.Builder builder, long startTime) throws Exception {
+ try (ResultSet resultSet = statement.executeQuery(sql)) {
+ HashMap values = new HashMap<>(columns.size());
+ while (resultSet.next()) {
+ if (resultSet.getString(1) != null) {
+ values.put(resultSet.getString(1).toLowerCase().trim(), resultSet.getString(2));
+ }
+ }
+ CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
+ for (String column : columns) {
+ if (CollectorConstants.RESPONSE_TIME.equals(column)) {
+ long time = System.currentTimeMillis() - startTime;
+ valueRowBuilder.addColumns(String.valueOf(time));
+ } else {
+ String value = values.get(column.toLowerCase());
+ value = value == null ? CommonConstants.NULL_VALUE : value;
+ valueRowBuilder.addColumns(value);
+ }
+ }
+ builder.addValues(valueRowBuilder.build());
+ }
+ }
+
+ /**
+ * query multi row record, response metrics header and multi value row
+ * eg:
+ * query metrics:one tow three four
+ * query sql:select one, tow, three, four from book;
+ * and return multi row record mapping with the metrics
+ * @param statement statement
+ * @param sql sql
+ * @param columns query metrics field list
+ * @throws Exception when error happen
+ */
+ private void queryMultiRow(Statement statement, String sql, List columns,
+ CollectRep.MetricsData.Builder builder, long startTime) throws Exception {
+ try (ResultSet resultSet = statement.executeQuery(sql)) {
+ while (resultSet.next()) {
+ CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
+ for (String column : columns) {
+ if (CollectorConstants.RESPONSE_TIME.equals(column)) {
+ long time = System.currentTimeMillis() - startTime;
+ valueRowBuilder.addColumns(String.valueOf(time));
+ } else {
+ String value = resultSet.getString(column);
+ value = value == null ? CommonConstants.NULL_VALUE : value;
+ valueRowBuilder.addColumns(value);
+ }
+ }
+ builder.addValues(valueRowBuilder.build());
+ }
+ }
+ }
+
+ /**
+ * construct jdbc url due the jdbc protocol
+ * @param jdbcProtocol jdbc
+ * @return URL
+ */
+ private String constructDatabaseUrl(JdbcProtocol jdbcProtocol) {
+ if (Objects.nonNull(jdbcProtocol.getUrl())
+ && !Objects.equals("", jdbcProtocol.getUrl())
+ && jdbcProtocol.getUrl().startsWith("jdbc")) {
+ // when has config jdbc url, use it
+ return jdbcProtocol.getUrl();
+ }
+ return switch (jdbcProtocol.getPlatform()) {
+ case "mysql", "mariadb" ->
+ "jdbc:mysql://" + jdbcProtocol.getHost() + ":" + jdbcProtocol.getPort()
+ + "/" + (jdbcProtocol.getDatabase() == null ? "" : jdbcProtocol.getDatabase())
+ + "?useUnicode=true&characterEncoding=utf-8&useSSL=false";
+ case "postgresql" ->
+ "jdbc:postgresql://" + jdbcProtocol.getHost() + ":" + jdbcProtocol.getPort()
+ + "/" + (jdbcProtocol.getDatabase() == null ? "" : jdbcProtocol.getDatabase());
+ case "clickhouse" ->
+ "jdbc:clickhouse://" + jdbcProtocol.getHost() + ":" + jdbcProtocol.getPort()
+ + "/" + (jdbcProtocol.getDatabase() == null ? "" : jdbcProtocol.getDatabase());
+ case "sqlserver" ->
+ "jdbc:sqlserver://" + jdbcProtocol.getHost() + ":" + jdbcProtocol.getPort()
+ + ";" + (jdbcProtocol.getDatabase() == null ? "" : "DatabaseName=" + jdbcProtocol.getDatabase())
+ + ";trustServerCertificate=true;";
+ case "oracle" ->
+ "jdbc:oracle:thin:@" + jdbcProtocol.getHost() + ":" + jdbcProtocol.getPort()
+ + "/" + (jdbcProtocol.getDatabase() == null ? "" : jdbcProtocol.getDatabase());
+ case "dm" ->
+ "jdbc:dm://" + jdbcProtocol.getHost() + ":" + jdbcProtocol.getPort();
+ default -> throw new IllegalArgumentException("Not support database platform: " + jdbcProtocol.getPlatform());
+ };
+ }
+}
diff --git a/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/database/JdbcSpiLoader.java b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/database/JdbcSpiLoader.java
new file mode 100644
index 0000000..a52edb0
--- /dev/null
+++ b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/database/JdbcSpiLoader.java
@@ -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.collector.collect.database;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Service;
+
+/**
+ * load the jdbc driver first to avoid spi concurrent deadlock
+ */
+@Service
+@Slf4j
+@Order(value = Ordered.HIGHEST_PRECEDENCE)
+public class JdbcSpiLoader implements CommandLineRunner {
+
+ @Override
+ public void run(String... args) throws Exception {
+ log.info("start load jdbc drivers");
+ try {
+ Class.forName("org.postgresql.Driver");
+ Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
+ Class.forName("dm.jdbc.driver.DmDriver");
+ Class.forName("com.clickhouse.jdbc.ClickHouseDriver");
+ } catch (Exception e) {
+ log.error("load jdbc error: {}", e.getMessage());
+ }
+ log.info("end load jdbc drivers");
+ }
+}
diff --git a/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/dns/DnsCollectImpl.java b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/dns/DnsCollectImpl.java
new file mode 100644
index 0000000..0d81a07
--- /dev/null
+++ b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/dns/DnsCollectImpl.java
@@ -0,0 +1,231 @@
+/*
+ * 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.dns;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import java.io.IOException;
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hertzbeat.collector.collect.AbstractCollect;
+import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
+import org.apache.hertzbeat.common.constants.CommonConstants;
+import org.apache.hertzbeat.common.entity.job.Metrics;
+import org.apache.hertzbeat.common.entity.job.protocol.DnsProtocol;
+import org.apache.hertzbeat.common.entity.message.CollectRep;
+import org.apache.hertzbeat.common.util.CommonUtil;
+import org.springframework.util.StopWatch;
+import org.xbill.DNS.DClass;
+import org.xbill.DNS.Message;
+import org.xbill.DNS.Name;
+import org.xbill.DNS.Opcode;
+import org.xbill.DNS.RRset;
+import org.xbill.DNS.Rcode;
+import org.xbill.DNS.Record;
+import org.xbill.DNS.Resolver;
+import org.xbill.DNS.Section;
+import org.xbill.DNS.SimpleResolver;
+import org.xbill.DNS.Type;
+
+/**
+ * dns protocol collection implementation
+ */
+@Slf4j
+public class DnsCollectImpl extends AbstractCollect {
+ /*
+ each part of dig command output
+ */
+ private static final String HEADER = "header";
+ private static final String QUESTION = "question";
+ private static final String ANSWER = "answer";
+ private static final String AUTHORITY = "authority";
+ private static final String ADDITIONAL = "additional";
+ /*
+ * used for header key
+ * example:
+ * ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 3221
+ * ;; flags: qr rd ra ; qd: 1 an: 1 au: 0 ad: 0
+ *
+ *
+ * opcode -> opcode
+ * status -> status
+ * flags -> flags
+ * qd -> questionRowCount
+ * an -> answerRowCount
+ * au -> authorityRowCount
+ * ad -> additionalRowCount
+ */
+ private static final String RESPONSE_TIME = "responseTime";
+ private static final String OP_CODE = "opcode";
+ private static final String STATUS = "status";
+ private static final String FLAGS = "flags";
+ private static final String QUESTION_ROW_COUNT = "questionRowCount";
+ private static final String ANSWER_ROW_COUNT = "answerRowCount";
+ private static final String AUTHORITY_ROW_COUNT = "authorityRowCount";
+ private static final String ADDITIONAL_ROW_COUNT = "additionalRowCount";
+
+ @Override
+ public void preCheck(Metrics metrics) throws IllegalArgumentException {
+ // compatible with monitoring template configurations of older versions
+ if (StringUtils.isBlank(metrics.getDns().getQueryClass())) {
+ metrics.getDns().setQueryClass(DClass.string(DClass.IN));
+ }
+ // check params
+ if (checkDnsProtocolFailed(metrics.getDns())) {
+ throw new IllegalArgumentException("DNS collect must have a valid DNS protocol param! ");
+ }
+ }
+
+ @Override
+ public void collect(CollectRep.MetricsData.Builder builder, long monitorId, String app, Metrics metrics) {
+
+ DnsResolveResult dnsResolveResult;
+ try {
+ // run dig command
+ dnsResolveResult = dig(metrics.getDns());
+ } catch (IOException e) {
+ log.info(CommonUtil.getMessageFromThrowable(e));
+ builder.setCode(CollectRep.Code.UN_CONNECTABLE);
+ builder.setMsg(e.getMessage());
+ return;
+ } catch (Exception e) {
+ String errorMsg = CommonUtil.getMessageFromThrowable(e);
+ log.warn("[dns collect] error: {}", e.getMessage(), e);
+ builder.setCode(CollectRep.Code.FAIL);
+ builder.setMsg(errorMsg);
+ return;
+ }
+
+ // build dns metrics data
+ CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
+ if (StringUtils.equals(HEADER, metrics.getName())) {
+ // add header columns
+ Map headerInfo = dnsResolveResult.getHeaderInfo();
+ metrics.getAliasFields().forEach(field -> valueRowBuilder.addColumns(headerInfo.getOrDefault(field, CommonConstants.NULL_VALUE)));
+ } else {
+ // add question/answer/authority/additional columns
+ List currentMetricsResolveResultList = dnsResolveResult.getList(metrics.getName());
+ for (int index = 0; index < metrics.getAliasFields().size(); index++) {
+ valueRowBuilder.addColumns(index >= currentMetricsResolveResultList.size()
+ ? CommonConstants.NULL_VALUE
+ : currentMetricsResolveResultList.get(index));
+ }
+ }
+
+ builder.addValues(valueRowBuilder.build());
+ }
+
+ @Override
+ public String supportProtocol() {
+ return DispatchConstants.PROTOCOL_DNS;
+ }
+
+ private boolean checkDnsProtocolFailed(DnsProtocol dnsProtocol) {
+ return Objects.isNull(dnsProtocol) || dnsProtocol.isInvalid();
+ }
+
+ /**
+ * run dig command
+ */
+ private DnsResolveResult dig(DnsProtocol dns) throws IOException {
+ StopWatch responseTimeStopWatch = new StopWatch("responseTime");
+ responseTimeStopWatch.start();
+
+ Name name = Name.fromString(dns.getAddress(), Name.root);
+ Message query = Message.newQuery(Record.newRecord(name, Type.ANY, DClass.ANY));
+ Resolver res = new SimpleResolver(dns.getDnsServerIP());
+ res.setTimeout(Duration.of(Long.parseLong(dns.getTimeout()), ChronoUnit.MILLIS));
+ res.setTCP(Boolean.parseBoolean(dns.getTcp()));
+ res.setPort(Integer.parseInt(dns.getPort()));
+
+ Message response = res.send(query);
+ responseTimeStopWatch.stop();
+ return resolve(response, responseTimeStopWatch.lastTaskInfo().getTimeMillis());
+ }
+
+ private DnsResolveResult resolve(Message message, Long responseTime) {
+ return DnsResolveResult.builder()
+ .headerInfo(getHeaderInfo(message, responseTime))
+ .questionList(getSectionInfo(message, Section.QUESTION))
+ .answerList(getSectionInfo(message, Section.ANSWER))
+ .authorityList(getSectionInfo(message, Section.AUTHORITY))
+ .additionalList(getSectionInfo(message, Section.ADDITIONAL))
+ .build();
+ }
+
+ private Map getHeaderInfo(Message message, Long responseTime) {
+ Map resultMap = Maps.newHashMap();
+ resultMap.put(RESPONSE_TIME, String.valueOf(responseTime));
+ resultMap.put(OP_CODE, Opcode.string(message.getHeader().getOpcode()));
+ resultMap.put(STATUS, Rcode.string(message.getHeader().getRcode()));
+ resultMap.put(FLAGS, message.getHeader().printFlags());
+ resultMap.put(QUESTION_ROW_COUNT, String.valueOf(message.getHeader().getCount(Section.QUESTION)));
+ resultMap.put(ANSWER_ROW_COUNT, String.valueOf(message.getHeader().getCount(Section.ANSWER)));
+ resultMap.put(AUTHORITY_ROW_COUNT, String.valueOf(message.getHeader().getCount(Section.AUTHORITY)));
+ resultMap.put(ADDITIONAL_ROW_COUNT, String.valueOf(message.getHeader().getCount(Section.ADDITIONAL)));
+
+ return resultMap;
+ }
+
+ private List getSectionInfo(Message message, int section) {
+ List currentSetList = message.getSectionRRsets(section);
+
+ if (CollectionUtils.isEmpty(currentSetList)) {
+ return Lists.newArrayList();
+ }
+
+ List infoList = Lists.newArrayListWithCapacity(currentSetList.size());
+ currentSetList.forEach(res -> infoList.add(res.toString()));
+
+ return infoList;
+ }
+
+ @Data
+ @Builder
+ @NoArgsConstructor
+ @AllArgsConstructor
+ private static class DnsResolveResult {
+ private Map headerInfo;
+ /** example: www.google.com. 140 IN A 192.133.77.133 **/
+ private List questionList;
+ private List answerList;
+ private List authorityList;
+ private List additionalList;
+
+ public List getList(String metricsName) {
+ return switch (metricsName) {
+ case QUESTION -> questionList;
+ case ANSWER -> answerList;
+ case AUTHORITY -> authorityList;
+ case ADDITIONAL -> additionalList;
+ default -> Collections.emptyList();
+ };
+ }
+ }
+}
diff --git a/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/ftp/FtpCollectImpl.java b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/ftp/FtpCollectImpl.java
new file mode 100644
index 0000000..e287eea
--- /dev/null
+++ b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/ftp/FtpCollectImpl.java
@@ -0,0 +1,153 @@
+/*
+ * 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.ftp;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.net.ftp.FTPClient;
+import org.apache.hertzbeat.collector.collect.AbstractCollect;
+import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
+import org.apache.hertzbeat.common.constants.CommonConstants;
+import org.apache.hertzbeat.common.entity.job.Metrics;
+import org.apache.hertzbeat.common.entity.job.protocol.FtpProtocol;
+import org.apache.hertzbeat.common.entity.message.CollectRep;
+import org.apache.hertzbeat.common.util.CommonUtil;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * ftp protocol collection implementation
+ */
+@Slf4j
+public class FtpCollectImpl extends AbstractCollect {
+
+ private static final String ANONYMOUS = "anonymous";
+ private static final String PASSWORD = "password";
+
+ /**
+ * preCheck params
+ */
+ public void preCheck(Metrics metrics) throws IllegalArgumentException{
+ if (metrics == null || metrics.getFtp() == null) {
+ throw new IllegalArgumentException("Ftp collect must has ftp params.");
+ }
+ FtpProtocol ftpProtocol = metrics.getFtp();
+ Assert.hasText(ftpProtocol.getHost(), "Ftp Protocol host is required.");
+ Assert.hasText(ftpProtocol.getPort(), "Ftp Protocol port is required.");
+ Assert.hasText(ftpProtocol.getDirection(), "Ftp Protocol direction is required.");
+ Assert.hasText(ftpProtocol.getTimeout(), "Ftp Protocol timeout is required.");
+ }
+
+
+ @Override
+ public void collect(CollectRep.MetricsData.Builder builder, long monitorId, String app, Metrics metrics) {
+ FTPClient ftpClient = new FTPClient();
+ FtpProtocol ftpProtocol = metrics.getFtp();
+ // Set timeout
+ ftpClient.setControlKeepAliveReplyTimeout(Integer.parseInt(ftpProtocol.getTimeout()));
+
+ // Collect data to load in CollectRep.ValueRow.Builder's object
+ CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
+ Map valueMap;
+ try {
+ valueMap = collectValue(ftpClient, ftpProtocol);
+ metrics.getAliasFields().forEach(it -> {
+ if (valueMap.containsKey(it)) {
+ String fieldValue = valueMap.get(it);
+ valueRowBuilder.addColumns(Objects.requireNonNullElse(fieldValue, CommonConstants.NULL_VALUE));
+ } else {
+ valueRowBuilder.addColumns(CommonConstants.NULL_VALUE);
+ }
+ });
+ } catch (Exception e) {
+ builder.setCode(CollectRep.Code.UN_CONNECTABLE);
+ builder.setMsg(e.getMessage());
+ return;
+ }
+ builder.addValues(valueRowBuilder.build());
+ }
+
+ /**
+ * collect data: key-value
+ * Please modify this, if you want to add some metrics.
+ */
+ private Map collectValue(FTPClient ftpClient, FtpProtocol ftpProtocol) {
+ boolean isActive;
+ String responseTime;
+ try {
+ long startTime = System.currentTimeMillis();
+ connect(ftpClient, ftpProtocol);
+ login(ftpClient, ftpProtocol);
+ // In here, we can do some extended operation without changing the architecture
+ isActive = ftpClient.changeWorkingDirectory(ftpProtocol.getDirection());
+ long endTime = System.currentTimeMillis();
+ responseTime = String.valueOf(endTime - startTime);
+ ftpClient.disconnect();
+ } catch (Exception e) {
+ log.info("[FTPClient] error: {}", CommonUtil.getMessageFromThrowable(e), e);
+ throw new IllegalArgumentException(e.getMessage());
+ }
+ return new HashMap<>(8) {
+ {
+ put("isActive", Boolean.toString(isActive));
+ put("responseTime", responseTime);
+ }
+ };
+ }
+
+ /**
+ * login
+ */
+ private void login(FTPClient ftpClient, FtpProtocol ftpProtocol) {
+ try {
+ // username: not empty, password: not empty
+ if (StringUtils.hasText(ftpProtocol.getUsername()) && StringUtils.hasText(ftpProtocol.getPassword())) {
+ if (!ftpClient.login(ftpProtocol.getUsername(), ftpProtocol.getPassword())) {
+ throw new IllegalArgumentException("The username or password may be wrong.");
+ }
+ return;
+ }
+ // anonymous access
+ if (!ftpClient.login(ANONYMOUS, PASSWORD)) {
+ throw new IllegalArgumentException("The server may not allow anonymous access, we need to username and password.");
+ }
+ } catch (Exception e) {
+ log.info("[ftp login] error: {}", CommonUtil.getMessageFromThrowable(e), e);
+ throw new IllegalArgumentException(e.getMessage());
+ }
+ }
+
+ /**
+ * connect
+ */
+ private void connect(FTPClient ftpClient, FtpProtocol ftpProtocol) {
+ try {
+ ftpClient.connect(ftpProtocol.getHost(), Integer.parseInt(ftpProtocol.getPort()));
+ } catch (Exception e) {
+ log.info("[ftp connection] error: {}", CommonUtil.getMessageFromThrowable(e), e);
+ throw new IllegalArgumentException("The host or port may be wrong.");
+ }
+ }
+
+ @Override
+ public String supportProtocol() {
+ return DispatchConstants.PROTOCOL_FTP;
+ }
+}
diff --git a/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/http/HttpCollectImpl.java b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/http/HttpCollectImpl.java
new file mode 100644
index 0000000..2aa08bf
--- /dev/null
+++ b/applications/hertzbeat/collector/src/main/java/org/apache/hertzbeat/collector/collect/http/HttpCollectImpl.java
@@ -0,0 +1,598 @@
+/*
+ * 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;
+
+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.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.ConnectException;
+import java.net.UnknownHostException;
+import java.nio.charset.StandardCharsets;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
+import java.util.LinkedList;
+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 lombok.extern.slf4j.Slf4j;
+import org.apache.commons.net.util.Base64;
+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.PrometheusParseCreater;
+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.dispatch.DispatchConstants;
+import org.apache.hertzbeat.collector.util.CollectUtil;
+import org.apache.hertzbeat.collector.util.JsonPathParser;
+import org.apache.hertzbeat.common.constants.CollectorConstants;
+import org.apache.hertzbeat.common.constants.CommonConstants;
+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;
+import org.apache.hertzbeat.common.util.CommonUtil;
+import org.apache.hertzbeat.common.util.IpDomainUtil;
+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;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+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;
+import org.springframework.http.HttpMethod;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * http https collect
+ */
+@Slf4j
+public class HttpCollectImpl extends AbstractCollect {
+
+ public static final String OpenAIHost = "api.openai.com";
+ public static final String OpenAIUsageAPI = "/dashboard/billing/usage";
+ public static final String startDate = "start_date";
+ public static final String endDate = "end_date";
+
+ private final Set 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());
+
+ public HttpCollectImpl() {
+ }
+
+ @Override
+ public void preCheck(Metrics metrics) throws IllegalArgumentException {
+ if (metrics == null || metrics.getHttp() == null) {
+ throw new IllegalArgumentException("Http/Https collect must has http params");
+ }
+ }
+
+ @Override
+ public void collect(CollectRep.MetricsData.Builder builder,
+ long monitorId, String app, Metrics metrics) {
+ long startTime = System.currentTimeMillis();
+
+ HttpProtocol httpProtocol = metrics.getHttp();
+ String url = httpProtocol.getUrl();
+ if (!StringUtils.hasText(url) || !url.startsWith(RIGHT_DASH)) {
+ httpProtocol.setUrl(StringUtils.hasText(url) ? RIGHT_DASH + url.trim() : RIGHT_DASH);
+ }
+ if (CollectionUtils.isEmpty(httpProtocol.getSuccessCodes())) {
+ httpProtocol.setSuccessCodes(List.of("200"));
+ }
+
+ HttpContext httpContext = createHttpContext(metrics.getHttp());
+ HttpUriRequest request = createHttpRequest(metrics.getHttp());
+ try (CloseableHttpResponse response = CommonHttpClient.getHttpClient().execute(request, httpContext)) {
+ int statusCode = response.getStatusLine().getStatusCode();
+ boolean isSuccessInvoke = checkSuccessInvoke(metrics, statusCode);
+ log.debug("http response status: {}", statusCode);
+ if (!isSuccessInvoke) {
+ builder.setCode(CollectRep.Code.FAIL);
+ builder.setMsg("StatusCode " + 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.
+ String resp = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
+ if (StringUtils.hasText(resp)) {
+ log.info("http response entity is empty, status: {}.", statusCode);
+ }
+ Long responseTime = System.currentTimeMillis() - startTime;
+ String parseType = metrics.getHttp().getParseType();
+ try {
+ switch (parseType) {
+ case DispatchConstants.PARSE_JSON_PATH ->
+ parseResponseByJsonPath(resp, metrics.getAliasFields(), metrics.getHttp(), builder, responseTime);
+ case DispatchConstants.PARSE_PROM_QL ->
+ parseResponseByPromQl(resp, metrics.getAliasFields(), metrics.getHttp(), builder);
+ case DispatchConstants.PARSE_PROMETHEUS ->
+ parseResponseByPrometheusExporter(resp, metrics.getAliasFields(), builder);
+ case DispatchConstants.PARSE_XML_PATH ->
+ parseResponseByXmlPath(resp, metrics.getAliasFields(), metrics.getHttp(), builder);
+ case DispatchConstants.PARSE_WEBSITE ->
+ parseResponseByWebsite(resp, metrics.getAliasFields(), metrics.getHttp(), builder, responseTime);
+ case DispatchConstants.PARSE_SITE_MAP ->
+ parseResponseBySiteMap(resp, metrics.getAliasFields(), builder);
+ default ->
+ parseResponseByDefault(resp, metrics.getAliasFields(), metrics.getHttp(), builder, responseTime);
+ }
+ } catch (Exception e) {
+ log.info("parse error: {}.", e.getMessage(), e);
+ builder.setCode(CollectRep.Code.FAIL);
+ builder.setMsg("parse response data error:" + e.getMessage());
+ }
+ } catch (ClientProtocolException e1) {
+ String errorMsg = CommonUtil.getMessageFromThrowable(e1);
+ log.error(errorMsg);
+ builder.setCode(CollectRep.Code.UN_CONNECTABLE);
+ builder.setMsg(errorMsg);
+ } catch (UnknownHostException e2) {
+ String errorMsg = CommonUtil.getMessageFromThrowable(e2);
+ log.info(errorMsg);
+ builder.setCode(CollectRep.Code.UN_REACHABLE);
+ builder.setMsg("unknown host:" + errorMsg);
+ } catch (InterruptedIOException | ConnectException | SSLException e3) {
+ String errorMsg = CommonUtil.getMessageFromThrowable(e3);
+ log.info(errorMsg);
+ builder.setCode(CollectRep.Code.UN_CONNECTABLE);
+ builder.setMsg(errorMsg);
+ } catch (IOException e4) {
+ String errorMsg = CommonUtil.getMessageFromThrowable(e4);
+ log.info(errorMsg);
+ builder.setCode(CollectRep.Code.FAIL);
+ builder.setMsg(errorMsg);
+ } catch (Exception e) {
+ String errorMsg = CommonUtil.getMessageFromThrowable(e);
+ log.error(errorMsg, e);
+ builder.setCode(CollectRep.Code.FAIL);
+ builder.setMsg(errorMsg);
+ } finally {
+ if (request != null) {
+ request.abort();
+ }
+ }
+ }
+
+ @Override
+ public String supportProtocol() {
+ return DispatchConstants.PROTOCOL_HTTP;
+ }
+
+ private void parseResponseByWebsite(String resp, List aliasFields, HttpProtocol http,
+ CollectRep.MetricsData.Builder builder, Long responseTime) {
+ CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
+ int keywordNum = CollectUtil.countMatchKeyword(resp, http.getKeyword());
+ for (String alias : aliasFields) {
+ if (CollectorConstants.RESPONSE_TIME.equalsIgnoreCase(alias)) {
+ valueRowBuilder.addColumns(responseTime.toString());
+ } else if (CollectorConstants.KEYWORD.equalsIgnoreCase(alias)) {
+ valueRowBuilder.addColumns(Integer.toString(keywordNum));
+ } else {
+ valueRowBuilder.addColumns(CommonConstants.NULL_VALUE);
+ }
+ }
+ builder.addValues(valueRowBuilder.build());
+ }
+
+ private void parseResponseBySiteMap(String resp, List