mirror of
https://github.com/dromara/hertzbeat.git
synced 2026-09-17 18:19:02 +00:00
Compare commits
44
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0db72b0f02 | ||
|
|
6c7cb62fd8 | ||
|
|
a5b8a95991 | ||
|
|
e5693476f2 | ||
|
|
f05e91247a | ||
|
|
650d63d6eb | ||
|
|
1f46260469 | ||
|
|
c73e47711e | ||
|
|
826e7b1dd9 | ||
|
|
ecb7f82321 | ||
|
|
5ac13827fb | ||
|
|
383ba7d976 | ||
|
|
1592c93d25 | ||
|
|
46d609a95e | ||
|
|
f17fb02441 | ||
|
|
e6cc71ed56 | ||
|
|
e2a9160e59 | ||
|
|
7ebeee2c1f | ||
|
|
ccfc8dc8ce | ||
|
|
ca9db17241 | ||
|
|
7a40bdee12 | ||
|
|
5db20d33f4 | ||
|
|
896869c611 | ||
|
|
32ce88b2c0 | ||
|
|
2288d8e900 | ||
|
|
dbb81d44ff | ||
|
|
227666dd46 | ||
|
|
b14bc6569f | ||
|
|
7c7102da17 | ||
|
|
489eac8e11 | ||
|
|
900655680a | ||
|
|
e87ed449f2 | ||
|
|
ddf4759b34 | ||
|
|
a8ce48b50d | ||
|
|
a0f3890137 | ||
|
|
b02837b362 | ||
|
|
9f9cdcf1a9 | ||
|
|
326497939d | ||
|
|
d881aff90d | ||
|
|
78b0a428ff | ||
|
|
ba173acc09 | ||
|
|
9d842a241b | ||
|
|
c480d63833 | ||
|
|
ce479c6ba9 |
+116
-37
@@ -267,7 +267,9 @@
|
||||
"contributions": [
|
||||
"code",
|
||||
"bug",
|
||||
"design"
|
||||
"design",
|
||||
"test",
|
||||
"blog"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -361,7 +363,9 @@
|
||||
"contributions": [
|
||||
"bug",
|
||||
"code",
|
||||
"doc"
|
||||
"doc",
|
||||
"test",
|
||||
"design"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -683,7 +687,8 @@
|
||||
"contributions": [
|
||||
"code",
|
||||
"test",
|
||||
"doc"
|
||||
"doc",
|
||||
"design"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -797,7 +802,11 @@
|
||||
"profile": "https://github.com/zqr10159",
|
||||
"contributions": [
|
||||
"doc",
|
||||
"code"
|
||||
"code",
|
||||
"blog",
|
||||
"bug",
|
||||
"test",
|
||||
"design"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -889,7 +898,8 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/50475131?v=4",
|
||||
"profile": "https://github.com/l646505418",
|
||||
"contributions": [
|
||||
"code"
|
||||
"code",
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -898,7 +908,8 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/78642589?v=4",
|
||||
"profile": "http://www.carpewang.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
"code",
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1263,7 +1274,11 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/105542329?v=4",
|
||||
"profile": "https://github.com/a-little-fool",
|
||||
"contributions": [
|
||||
"code"
|
||||
"code",
|
||||
"doc",
|
||||
"blog",
|
||||
"bug",
|
||||
"design"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1326,7 +1341,8 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/67301054?v=4",
|
||||
"profile": "http://www.jimmyqiao.top",
|
||||
"contributions": [
|
||||
"code"
|
||||
"code",
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1419,7 +1435,8 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/28394742?v=4",
|
||||
"profile": "https://github.com/Clownsw",
|
||||
"contributions": [
|
||||
"code"
|
||||
"code",
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1430,7 +1447,9 @@
|
||||
"contributions": [
|
||||
"doc",
|
||||
"code",
|
||||
"design"
|
||||
"design",
|
||||
"bug",
|
||||
"test"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1469,7 +1488,8 @@
|
||||
"contributions": [
|
||||
"code",
|
||||
"design",
|
||||
"doc"
|
||||
"doc",
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1541,7 +1561,9 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/42080876?v=4",
|
||||
"profile": "https://github.com/yqxxgh",
|
||||
"contributions": [
|
||||
"doc"
|
||||
"doc",
|
||||
"code",
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1586,7 +1608,8 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/55646681?v=4",
|
||||
"profile": "https://github.com/starmilkxin",
|
||||
"contributions": [
|
||||
"code"
|
||||
"code",
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1605,7 +1628,11 @@
|
||||
"profile": "https://github.com/LiuTianyou",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
"doc",
|
||||
"bug",
|
||||
"test",
|
||||
"blog",
|
||||
"design"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1651,9 +1678,11 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/71874373?v=4",
|
||||
"profile": "https://github.com/gjjjj0101",
|
||||
"contributions": [
|
||||
"test",
|
||||
"code",
|
||||
"doc",
|
||||
"design",
|
||||
"test"
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1662,7 +1691,8 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/37864182?v=4",
|
||||
"profile": "https://jangto.tistory.com/",
|
||||
"contributions": [
|
||||
"code"
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1672,7 +1702,10 @@
|
||||
"profile": "https://crossoverjie.top/",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc"
|
||||
"doc",
|
||||
"blog",
|
||||
"test",
|
||||
"design"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1708,7 +1741,9 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/8465969?v=4",
|
||||
"profile": "https://github.com/xuziyang",
|
||||
"contributions": [
|
||||
"code"
|
||||
"code",
|
||||
"doc",
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1762,7 +1797,13 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/29418975?v=4",
|
||||
"profile": "http://www.hadoop.wiki/",
|
||||
"contributions": [
|
||||
"code"
|
||||
"code",
|
||||
"ideas",
|
||||
"doc",
|
||||
"blog",
|
||||
"bug",
|
||||
"test",
|
||||
"design"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1771,7 +1812,11 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/61108539?v=4",
|
||||
"profile": "https://github.com/zuobiao-zhou",
|
||||
"contributions": [
|
||||
"code"
|
||||
"code",
|
||||
"doc",
|
||||
"bug",
|
||||
"blog",
|
||||
"test"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1807,7 +1852,11 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/25810623?v=4",
|
||||
"profile": "https://github.com/Aias00",
|
||||
"contributions": [
|
||||
"code"
|
||||
"code",
|
||||
"doc",
|
||||
"bug",
|
||||
"ideas",
|
||||
"test"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1825,7 +1874,10 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/77964041?v=4",
|
||||
"profile": "https://blog.csdn.net/qq_52397471",
|
||||
"contributions": [
|
||||
"code"
|
||||
"code",
|
||||
"bug",
|
||||
"test",
|
||||
"blog"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1834,7 +1886,9 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/118667222?v=4",
|
||||
"profile": "https://github.com/Yanshuming1",
|
||||
"contributions": [
|
||||
"code"
|
||||
"code",
|
||||
"doc",
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1934,8 +1988,10 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/3371163?v=4",
|
||||
"profile": "https://github.com/kerwin612",
|
||||
"contributions": [
|
||||
"design",
|
||||
"code",
|
||||
"design"
|
||||
"doc",
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1962,7 +2018,9 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/69385076?v=4",
|
||||
"profile": "https://github.com/pwallk",
|
||||
"contributions": [
|
||||
"code"
|
||||
"code",
|
||||
"doc",
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -2007,7 +2065,8 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/83876361?v=4",
|
||||
"profile": "https://github.com/keaifafafa",
|
||||
"contributions": [
|
||||
"code"
|
||||
"code",
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -2142,7 +2201,9 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/59016860?v=4",
|
||||
"profile": "https://github.com/Rancho-7",
|
||||
"contributions": [
|
||||
"doc"
|
||||
"doc",
|
||||
"code",
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -2160,7 +2221,11 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/91836599?v=4",
|
||||
"profile": "https://zzrl.cc/",
|
||||
"contributions": [
|
||||
"code"
|
||||
"code",
|
||||
"doc",
|
||||
"bug",
|
||||
"test",
|
||||
"blog"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -2178,7 +2243,8 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/14932913?v=4",
|
||||
"profile": "https://simonsigre.com/",
|
||||
"contributions": [
|
||||
"doc"
|
||||
"doc",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -2241,7 +2307,9 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/11783444?v=4",
|
||||
"profile": "https://medium.com/@pjfanning",
|
||||
"contributions": [
|
||||
"code"
|
||||
"code",
|
||||
"bug",
|
||||
"test"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -2259,7 +2327,9 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/22274133?v=4",
|
||||
"profile": "https://github.com/MasamiYui",
|
||||
"contributions": [
|
||||
"code"
|
||||
"code",
|
||||
"doc",
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -2314,7 +2384,8 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/147966331?v=4",
|
||||
"profile": "https://github.com/notbugggg",
|
||||
"contributions": [
|
||||
"code"
|
||||
"code",
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -2332,7 +2403,10 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/44892459?v=4",
|
||||
"profile": "https://github.com/JuJinPark",
|
||||
"contributions": [
|
||||
"code"
|
||||
"code",
|
||||
"doc",
|
||||
"ideas",
|
||||
"blog"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -2341,7 +2415,8 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/43002118?v=4",
|
||||
"profile": "https://github.com/LL-LIN",
|
||||
"contributions": [
|
||||
"code"
|
||||
"code",
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -2350,7 +2425,9 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/73413979?v=4",
|
||||
"profile": "https://bigcyy.github.io/",
|
||||
"contributions": [
|
||||
"code"
|
||||
"code",
|
||||
"doc",
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -2359,7 +2436,8 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/74091160?v=4",
|
||||
"profile": "https://github.com/sarthakeash",
|
||||
"contributions": [
|
||||
"code"
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -2395,7 +2473,8 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/153884653?v=4",
|
||||
"profile": "https://github.com/Cyanty",
|
||||
"contributions": [
|
||||
"code"
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# 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.
|
||||
|
||||
# This workflow will build a Java project with Maven
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
|
||||
|
||||
name: Monitor E2E Test
|
||||
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# trigger at 01:00 everyday
|
||||
- cron: '0 1 * * *'
|
||||
push:
|
||||
branches: [ action* ]
|
||||
jobs:
|
||||
check-license-header:
|
||||
name: Monitor E2E Test
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./script/ci/github-actions/setup-deps
|
||||
- name: Build with Maven
|
||||
run: mvn clean -B package -Prelease -Dmaven.test.skip=true --file pom.xml
|
||||
- name: Build Image
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: ./dist
|
||||
file: ./script/docker/server/Dockerfile
|
||||
push: false
|
||||
tags: apache/hertzbeat:test
|
||||
- name: Run K8s Monitor E2E Test
|
||||
run: |
|
||||
sudo curl -L https://github.com/docker/compose/releases/download/v2.23.0/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose
|
||||
sudo chmod u+x /usr/local/bin/docker-compose
|
||||
cd e2e/k8s
|
||||
sudo docker-compose version
|
||||
sudo docker-compose up --exit-code-from testing --remove-orphans
|
||||
+2
-2
@@ -7,14 +7,14 @@ build/
|
||||
out/
|
||||
micronaut-cli.yml
|
||||
.mvn/
|
||||
mvnw
|
||||
mvnw.bat
|
||||
*.log
|
||||
package-lock.json
|
||||
*.zip
|
||||
jdk/
|
||||
data/
|
||||
|
||||
!/e2e/**/data/
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
{
|
||||
"globs": ["home/**/*.md"],
|
||||
"config": {
|
||||
"default": true,
|
||||
"MD001": true,
|
||||
@@ -39,9 +40,9 @@
|
||||
"MD047": true,
|
||||
"MD052": true
|
||||
},
|
||||
"ignore": [
|
||||
"node_modules/",
|
||||
"target/",
|
||||
"dist/"
|
||||
"ignores": [
|
||||
"**/node_modules/**",
|
||||
"**/target/**",
|
||||
"**/dist/**"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -222,7 +222,7 @@ Thanks to these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://mroldx.xyz/"><img src="https://avatars.githubusercontent.com/u/34847828?v=4?s=100" width="100px;" alt="mroldx"/><br /><sub><b>mroldx</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=mroldx" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/woshiniusange"><img src="https://avatars.githubusercontent.com/u/91513022?v=4?s=100" width="100px;" alt="woshiniusange"/><br /><sub><b>woshiniusange</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=woshiniusange" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://vampireachao.github.io/"><img src="https://avatars.githubusercontent.com/u/52746628?v=4?s=100" width="100px;" alt="VampireAchao"/><br /><sub><b>VampireAchao</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=VampireAchao" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Ceilzcx"><img src="https://avatars.githubusercontent.com/u/48920254?v=4?s=100" width="100px;" alt="zcx"/><br /><sub><b>zcx</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Ceilzcx" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3ACeilzcx" title="Bug reports">🐛</a> <a href="#design-Ceilzcx" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Ceilzcx"><img src="https://avatars.githubusercontent.com/u/48920254?v=4?s=100" width="100px;" alt="zcx"/><br /><sub><b>zcx</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Ceilzcx" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3ACeilzcx" title="Bug reports">🐛</a> <a href="#design-Ceilzcx" title="Design">🎨</a> <a href="https://github.com/apache/hertzbeat/commits?author=Ceilzcx" title="Tests">⚠️</a> <a href="#blog-Ceilzcx" title="Blogposts">📝</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/CharlieXCL"><img src="https://avatars.githubusercontent.com/u/91540487?v=4?s=100" width="100px;" alt="CharlieXCL"/><br /><sub><b>CharlieXCL</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=CharlieXCL" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Privauto"><img src="https://avatars.githubusercontent.com/u/36581456?v=4?s=100" width="100px;" alt="Privauto"/><br /><sub><b>Privauto</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Privauto" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=Privauto" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
@@ -236,7 +236,7 @@ Thanks to these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.zhihu.com/people/kevinbauer"><img src="https://avatars.githubusercontent.com/u/28581579?v=4?s=100" width="100px;" alt="Kevin Huang"/><br /><sub><b>Kevin Huang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=kevinhuangwl" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/TJxiaobao"><img src="https://avatars.githubusercontent.com/u/85919258?v=4?s=100" width="100px;" alt="铁甲小宝"/><br /><sub><b>铁甲小宝</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/issues?q=author%3ATJxiaobao" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=TJxiaobao" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=TJxiaobao" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/TJxiaobao"><img src="https://avatars.githubusercontent.com/u/85919258?v=4?s=100" width="100px;" alt="铁甲小宝"/><br /><sub><b>铁甲小宝</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/issues?q=author%3ATJxiaobao" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=TJxiaobao" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=TJxiaobao" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=TJxiaobao" title="Tests">⚠️</a> <a href="#design-TJxiaobao" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Jack-123-power"><img src="https://avatars.githubusercontent.com/u/84333501?v=4?s=100" width="100px;" alt="Captain Jack"/><br /><sub><b>Captain Jack</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Jack-123-power" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/haibo-duan"><img src="https://avatars.githubusercontent.com/u/7974845?v=4?s=100" width="100px;" alt="haibo.duan"/><br /><sub><b>haibo.duan</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=haibo-duan" title="Tests">⚠️</a> <a href="https://github.com/apache/hertzbeat/commits?author=haibo-duan" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/assassinfym"><img src="https://avatars.githubusercontent.com/u/15188754?v=4?s=100" width="100px;" alt="assassin"/><br /><sub><b>assassin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/issues?q=author%3Aassassinfym" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=assassinfym" title="Code">💻</a></td>
|
||||
@@ -278,7 +278,7 @@ Thanks to these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://wjl110.xyz/"><img src="https://avatars.githubusercontent.com/u/53851034?v=4?s=100" width="100px;" alt="wjl110"/><br /><sub><b>wjl110</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=wjl110" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ngyhd"><img src="https://avatars.githubusercontent.com/u/29095207?v=4?s=100" width="100px;" alt="Sean"/><br /><sub><b>Sean</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ngyhd" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Daydreamer-ia"><img src="https://avatars.githubusercontent.com/u/83362909?v=4?s=100" width="100px;" alt="chenyiqin"/><br /><sub><b>chenyiqin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Daydreamer-ia" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=Daydreamer-ia" title="Tests">⚠️</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/hudongdong129"><img src="https://avatars.githubusercontent.com/u/34374227?v=4?s=100" width="100px;" alt="hudongdong129"/><br /><sub><b>hudongdong129</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=hudongdong129" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=hudongdong129" title="Tests">⚠️</a> <a href="https://github.com/apache/hertzbeat/commits?author=hudongdong129" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/hudongdong129"><img src="https://avatars.githubusercontent.com/u/34374227?v=4?s=100" width="100px;" alt="hudongdong129"/><br /><sub><b>hudongdong129</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=hudongdong129" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=hudongdong129" title="Tests">⚠️</a> <a href="https://github.com/apache/hertzbeat/commits?author=hudongdong129" title="Documentation">📖</a> <a href="#design-hudongdong129" title="Design">🎨</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/TherChenYang"><img src="https://avatars.githubusercontent.com/u/124348939?v=4?s=100" width="100px;" alt="TherChenYang"/><br /><sub><b>TherChenYang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=TherChenYang" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=TherChenYang" title="Tests">⚠️</a></td>
|
||||
@@ -294,7 +294,7 @@ Thanks to these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/baiban114"><img src="https://avatars.githubusercontent.com/u/59152619?v=4?s=100" width="100px;" alt="TableRow"/><br /><sub><b>TableRow</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=baiban114" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=baiban114" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ByteIDance"><img src="https://avatars.githubusercontent.com/u/100207562?v=4?s=100" width="100px;" alt="ByteIDance"/><br /><sub><b>ByteIDance</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ByteIDance" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mangel2002"><img src="https://avatars.githubusercontent.com/u/9348020?v=4?s=100" width="100px;" alt="Jangfe"/><br /><sub><b>Jangfe</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=mangel2002" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zqr10159"><img src="https://avatars.githubusercontent.com/u/30048352?v=4?s=100" width="100px;" alt="zqr10159"/><br /><sub><b>zqr10159</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=zqr10159" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=zqr10159" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zqr10159"><img src="https://avatars.githubusercontent.com/u/30048352?v=4?s=100" width="100px;" alt="zqr10159"/><br /><sub><b>zqr10159</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=zqr10159" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=zqr10159" title="Code">💻</a> <a href="#blog-zqr10159" title="Blogposts">📝</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Azqr10159" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=zqr10159" title="Tests">⚠️</a> <a href="#design-zqr10159" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/vinci-897"><img src="https://avatars.githubusercontent.com/u/55838224?v=4?s=100" width="100px;" alt="vinci"/><br /><sub><b>vinci</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=vinci-897" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=vinci-897" title="Documentation">📖</a> <a href="#design-vinci-897" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/js110"><img src="https://avatars.githubusercontent.com/u/51191863?v=4?s=100" width="100px;" alt="js110"/><br /><sub><b>js110</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=js110" title="Code">💻</a></td>
|
||||
</tr>
|
||||
@@ -308,8 +308,8 @@ Thanks to these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ZangJuxy"><img src="https://avatars.githubusercontent.com/u/71380295?v=4?s=100" width="100px;" alt="ZangJuxy"/><br /><sub><b>ZangJuxy</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ZangJuxy" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/l646505418"><img src="https://avatars.githubusercontent.com/u/50475131?v=4?s=100" width="100px;" alt="l646505418"/><br /><sub><b>l646505418</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=l646505418" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://www.carpewang.com"><img src="https://avatars.githubusercontent.com/u/78642589?v=4?s=100" width="100px;" alt="Carpe-Wang"/><br /><sub><b>Carpe-Wang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Carpe-Wang" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/l646505418"><img src="https://avatars.githubusercontent.com/u/50475131?v=4?s=100" width="100px;" alt="l646505418"/><br /><sub><b>l646505418</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=l646505418" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Al646505418" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://www.carpewang.com"><img src="https://avatars.githubusercontent.com/u/78642589?v=4?s=100" width="100px;" alt="Carpe-Wang"/><br /><sub><b>Carpe-Wang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Carpe-Wang" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3ACarpe-Wang" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/moshu023"><img src="https://avatars.githubusercontent.com/u/48593205?v=4?s=100" width="100px;" alt="莫枢"/><br /><sub><b>莫枢</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=moshu023" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/huangcanda"><img src="https://avatars.githubusercontent.com/u/4470566?v=4?s=100" width="100px;" alt="huangcanda"/><br /><sub><b>huangcanda</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=huangcanda" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.zrkizzy.com"><img src="https://avatars.githubusercontent.com/u/85340613?v=4?s=100" width="100px;" alt="世纪末的架构师"/><br /><sub><b>世纪末的架构师</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Architect-Java" title="Code">💻</a></td>
|
||||
@@ -359,7 +359,7 @@ Thanks to these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lisongning"><img src="https://avatars.githubusercontent.com/u/93140178?v=4?s=100" width="100px;" alt="lisongning"/><br /><sub><b>lisongning</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=lisongning" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/YutingNie"><img src="https://avatars.githubusercontent.com/u/104416402?v=4?s=100" width="100px;" alt="YutingNie"/><br /><sub><b>YutingNie</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=YutingNie" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=YutingNie" title="Documentation">📖</a> <a href="#design-YutingNie" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mikezzb"><img src="https://avatars.githubusercontent.com/u/23418428?v=4?s=100" width="100px;" alt="Mike Zhou"/><br /><sub><b>Mike Zhou</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=mikezzb" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=mikezzb" title="Documentation">📖</a> <a href="#design-mikezzb" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/a-little-fool"><img src="https://avatars.githubusercontent.com/u/105542329?v=4?s=100" width="100px;" alt="小笨蛋"/><br /><sub><b>小笨蛋</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=a-little-fool" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/a-little-fool"><img src="https://avatars.githubusercontent.com/u/105542329?v=4?s=100" width="100px;" alt="小笨蛋"/><br /><sub><b>小笨蛋</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=a-little-fool" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=a-little-fool" title="Documentation">📖</a> <a href="#blog-a-little-fool" title="Blogposts">📝</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Aa-little-fool" title="Bug reports">🐛</a> <a href="#design-a-little-fool" title="Design">🎨</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/littlezhongzer"><img src="https://avatars.githubusercontent.com/u/33685289?v=4?s=100" width="100px;" alt="littlezhongzer"/><br /><sub><b>littlezhongzer</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=littlezhongzer" title="Code">💻</a></td>
|
||||
@@ -368,7 +368,7 @@ Thanks to these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/XimfengYao"><img src="https://avatars.githubusercontent.com/u/17541537?v=4?s=100" width="100px;" alt="姚贤丰"/><br /><sub><b>姚贤丰</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=XimfengYao" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LINGLUOJUN"><img src="https://avatars.githubusercontent.com/u/16778977?v=4?s=100" width="100px;" alt="lingluojun"/><br /><sub><b>lingluojun</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LINGLUOJUN" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://www.luelueking.com"><img src="https://avatars.githubusercontent.com/u/93204032?v=4?s=100" width="100px;" alt="1ue"/><br /><sub><b>1ue</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=luelueking" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://www.jimmyqiao.top"><img src="https://avatars.githubusercontent.com/u/67301054?v=4?s=100" width="100px;" alt="qyaaaa"/><br /><sub><b>qyaaaa</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=qyaaaa" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://www.jimmyqiao.top"><img src="https://avatars.githubusercontent.com/u/67301054?v=4?s=100" width="100px;" alt="qyaaaa"/><br /><sub><b>qyaaaa</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=qyaaaa" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Aqyaaaa" title="Bug reports">🐛</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://novohit.top"><img src="https://avatars.githubusercontent.com/u/101090395?v=4?s=100" width="100px;" alt="novohit"/><br /><sub><b>novohit</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=novohit" title="Code">💻</a></td>
|
||||
@@ -382,14 +382,14 @@ Thanks to these wonderful people, welcome to join us:
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/1036664317"><img src="https://avatars.githubusercontent.com/u/7696697?v=4?s=100" width="100px;" alt="moghn"/><br /><sub><b>moghn</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=1036664317" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/xiaoguolong"><img src="https://avatars.githubusercontent.com/u/33684988?v=4?s=100" width="100px;" alt="xiaoguolong"/><br /><sub><b>xiaoguolong</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=xiaoguolong" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Clownsw"><img src="https://avatars.githubusercontent.com/u/28394742?v=4?s=100" width="100px;" alt="Smliexx"/><br /><sub><b>Smliexx</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Clownsw" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Calvin979"><img src="https://avatars.githubusercontent.com/u/131688897?v=4?s=100" width="100px;" alt="Naruse"/><br /><sub><b>Naruse</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Calvin979" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=Calvin979" title="Code">💻</a> <a href="#design-Calvin979" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Clownsw"><img src="https://avatars.githubusercontent.com/u/28394742?v=4?s=100" width="100px;" alt="Smliexx"/><br /><sub><b>Smliexx</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Clownsw" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3AClownsw" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Calvin979"><img src="https://avatars.githubusercontent.com/u/131688897?v=4?s=100" width="100px;" alt="Naruse"/><br /><sub><b>Naruse</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Calvin979" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=Calvin979" title="Code">💻</a> <a href="#design-Calvin979" title="Design">🎨</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3ACalvin979" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=Calvin979" title="Tests">⚠️</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bbelide2"><img src="https://avatars.githubusercontent.com/u/26840796?v=4?s=100" width="100px;" alt="Bala Sukesh"/><br /><sub><b>Bala Sukesh</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=bbelide2" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jinyaoMa"><img src="https://avatars.githubusercontent.com/u/25066570?v=4?s=100" width="100px;" alt="Jinyao Ma"/><br /><sub><b>Jinyao Ma</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=jinyaoMa" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://linuxsuren.github.io/open-source-best-practice/"><img src="https://avatars.githubusercontent.com/u/1450685?v=4?s=100" width="100px;" alt="Rick"/><br /><sub><b>Rick</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LinuxSuRen" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=LinuxSuRen" title="Tests">⚠️</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ZY945"><img src="https://avatars.githubusercontent.com/u/74083801?v=4?s=100" width="100px;" alt="东风"/><br /><sub><b>东风</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ZY945" title="Code">💻</a> <a href="#design-ZY945" title="Design">🎨</a> <a href="https://github.com/apache/hertzbeat/commits?author=ZY945" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ZY945"><img src="https://avatars.githubusercontent.com/u/74083801?v=4?s=100" width="100px;" alt="东风"/><br /><sub><b>东风</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ZY945" title="Code">💻</a> <a href="#design-ZY945" title="Design">🎨</a> <a href="https://github.com/apache/hertzbeat/commits?author=ZY945" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3AZY945" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/prolevel1"><img src="https://avatars.githubusercontent.com/u/51995525?v=4?s=100" width="100px;" alt="sonam singh"/><br /><sub><b>sonam singh</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=prolevel1" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ZhangZixuan1994"><img src="https://avatars.githubusercontent.com/u/20011653?v=4?s=100" width="100px;" alt="ZhangZixuan1994"/><br /><sub><b>ZhangZixuan1994</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ZhangZixuan1994" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/hurenjie1"><img src="https://avatars.githubusercontent.com/u/40120355?v=4?s=100" width="100px;" alt="SHIG"/><br /><sub><b>SHIG</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=hurenjie1" title="Code">💻</a></td>
|
||||
@@ -399,29 +399,29 @@ Thanks to these wonderful people, welcome to join us:
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/avvCode"><img src="https://avatars.githubusercontent.com/u/113538532?v=4?s=100" width="100px;" alt="avv"/><br /><sub><b>avv</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=avvCode" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yqxxgh"><img src="https://avatars.githubusercontent.com/u/42080876?v=4?s=100" width="100px;" alt="yqxxgh"/><br /><sub><b>yqxxgh</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yqxxgh" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yqxxgh"><img src="https://avatars.githubusercontent.com/u/42080876?v=4?s=100" width="100px;" alt="yqxxgh"/><br /><sub><b>yqxxgh</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yqxxgh" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=yqxxgh" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Ayqxxgh" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/CharlieShi46"><img src="https://avatars.githubusercontent.com/u/149798885?v=4?s=100" width="100px;" alt="CharlieShi46"/><br /><sub><b>CharlieShi46</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=CharlieShi46" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Nctllnty"><img src="https://avatars.githubusercontent.com/u/33241818?v=4?s=100" width="100px;" alt="Nctllnty"/><br /><sub><b>Nctllnty</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Nctllnty" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Wang-Yonghao"><img src="https://avatars.githubusercontent.com/u/48146606?v=4?s=100" width="100px;" alt="Wang-Yonghao"/><br /><sub><b>Wang-Yonghao</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Wang-Yonghao" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.yuque.com/dudiao/yy"><img src="https://avatars.githubusercontent.com/u/38355949?v=4?s=100" width="100px;" alt="读钓"/><br /><sub><b>读钓</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=dudiao" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/starmilkxin"><img src="https://avatars.githubusercontent.com/u/55646681?v=4?s=100" width="100px;" alt="Xin"/><br /><sub><b>Xin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=starmilkxin" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/starmilkxin"><img src="https://avatars.githubusercontent.com/u/55646681?v=4?s=100" width="100px;" alt="Xin"/><br /><sub><b>Xin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=starmilkxin" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Astarmilkxin" title="Bug reports">🐛</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/handy-git"><img src="https://avatars.githubusercontent.com/u/32837980?v=4?s=100" width="100px;" alt="handy"/><br /><sub><b>handy</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=handy-git" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LiuTianyou"><img src="https://avatars.githubusercontent.com/u/30208283?v=4?s=100" width="100px;" alt="LiuTianyou"/><br /><sub><b>LiuTianyou</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LiuTianyou" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=LiuTianyou" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LiuTianyou"><img src="https://avatars.githubusercontent.com/u/30208283?v=4?s=100" width="100px;" alt="LiuTianyou"/><br /><sub><b>LiuTianyou</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LiuTianyou" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=LiuTianyou" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3ALiuTianyou" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=LiuTianyou" title="Tests">⚠️</a> <a href="#blog-LiuTianyou" title="Blogposts">📝</a> <a href="#design-LiuTianyou" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/WinterKi1ler"><img src="https://avatars.githubusercontent.com/u/160592092?v=4?s=100" width="100px;" alt="WinterKi1ler"/><br /><sub><b>WinterKi1ler</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=WinterKi1ler" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://sharehoo.cn/"><img src="https://avatars.githubusercontent.com/u/45377370?v=4?s=100" width="100px;" alt="miki"/><br /><sub><b>miki</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=miki-hmt" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://codeflex.substack.com/"><img src="https://avatars.githubusercontent.com/u/85513042?v=4?s=100" width="100px;" alt="Keshav Carpenter"/><br /><sub><b>Keshav Carpenter</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=alpha951" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=alpha951" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/makechoicenow"><img src="https://avatars.githubusercontent.com/u/9911918?v=4?s=100" width="100px;" alt="makechoicenow"/><br /><sub><b>makechoicenow</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=makechoicenow" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gjjjj0101"><img src="https://avatars.githubusercontent.com/u/71874373?v=4?s=100" width="100px;" alt="Gao Jian"/><br /><sub><b>Gao Jian</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=gjjjj0101" title="Code">💻</a> <a href="#design-gjjjj0101" title="Design">🎨</a> <a href="https://github.com/apache/hertzbeat/commits?author=gjjjj0101" title="Tests">⚠️</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gjjjj0101"><img src="https://avatars.githubusercontent.com/u/71874373?v=4?s=100" width="100px;" alt="Gao Jian"/><br /><sub><b>Gao Jian</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=gjjjj0101" title="Tests">⚠️</a> <a href="https://github.com/apache/hertzbeat/commits?author=gjjjj0101" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=gjjjj0101" title="Documentation">📖</a> <a href="#design-gjjjj0101" title="Design">🎨</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Agjjjj0101" title="Bug reports">🐛</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://jangto.tistory.com/"><img src="https://avatars.githubusercontent.com/u/37864182?v=4?s=100" width="100px;" alt="Hyeon Sung"/><br /><sub><b>Hyeon Sung</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=dukbong" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://crossoverjie.top/"><img src="https://avatars.githubusercontent.com/u/15684156?v=4?s=100" width="100px;" alt="crossoverJie"/><br /><sub><b>crossoverJie</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=crossoverJie" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=crossoverJie" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://jangto.tistory.com/"><img src="https://avatars.githubusercontent.com/u/37864182?v=4?s=100" width="100px;" alt="Hyeon Sung"/><br /><sub><b>Hyeon Sung</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=dukbong" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=dukbong" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://crossoverjie.top/"><img src="https://avatars.githubusercontent.com/u/15684156?v=4?s=100" width="100px;" alt="crossoverJie"/><br /><sub><b>crossoverJie</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=crossoverJie" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=crossoverJie" title="Documentation">📖</a> <a href="#blog-crossoverJie" title="Blogposts">📝</a> <a href="https://github.com/apache/hertzbeat/commits?author=crossoverJie" title="Tests">⚠️</a> <a href="#design-crossoverJie" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/PeixyJ"><img src="https://avatars.githubusercontent.com/u/45998593?v=4?s=100" width="100px;" alt="PeixyJ"/><br /><sub><b>PeixyJ</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=PeixyJ" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Hi-Mr-Wind"><img src="https://avatars.githubusercontent.com/u/85803831?v=4?s=100" width="100px;" alt="风如歌"/><br /><sub><b>风如歌</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Hi-Mr-Wind" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MananPoojara"><img src="https://avatars.githubusercontent.com/u/104253184?v=4?s=100" width="100px;" alt="Manan Pujara"/><br /><sub><b>Manan Pujara</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=MananPoojara" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/xuziyang"><img src="https://avatars.githubusercontent.com/u/8465969?v=4?s=100" width="100px;" alt="xuziyang"/><br /><sub><b>xuziyang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=xuziyang" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/xuziyang"><img src="https://avatars.githubusercontent.com/u/8465969?v=4?s=100" width="100px;" alt="xuziyang"/><br /><sub><b>xuziyang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=xuziyang" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=xuziyang" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Axuziyang" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lwqzz"><img src="https://avatars.githubusercontent.com/u/62584513?v=4?s=100" width="100px;" alt="lwqzz"/><br /><sub><b>lwqzz</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=lwqzz" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -429,17 +429,17 @@ Thanks to these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tomorrowshipyltm"><img src="https://avatars.githubusercontent.com/u/61336903?v=4?s=100" width="100px;" alt="tomorrowshipyltm"/><br /><sub><b>tomorrowshipyltm</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=tomorrowshipyltm" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/15613060203"><img src="https://avatars.githubusercontent.com/u/41351615?v=4?s=100" width="100px;" alt="栗磊"/><br /><sub><b>栗磊</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=15613060203" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Alanxtl"><img src="https://avatars.githubusercontent.com/u/25652981?v=4?s=100" width="100px;" alt="Alan"/><br /><sub><b>Alan</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Alanxtl" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://www.hadoop.wiki/"><img src="https://avatars.githubusercontent.com/u/29418975?v=4?s=100" width="100px;" alt="Jast"/><br /><sub><b>Jast</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=zhangshenghang" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zuobiao-zhou"><img src="https://avatars.githubusercontent.com/u/61108539?v=4?s=100" width="100px;" alt="Zhang Yuxuan"/><br /><sub><b>Zhang Yuxuan</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=zuobiao-zhou" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://www.hadoop.wiki/"><img src="https://avatars.githubusercontent.com/u/29418975?v=4?s=100" width="100px;" alt="Jast"/><br /><sub><b>Jast</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=zhangshenghang" title="Code">💻</a> <a href="#ideas-zhangshenghang" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/apache/hertzbeat/commits?author=zhangshenghang" title="Documentation">📖</a> <a href="#blog-zhangshenghang" title="Blogposts">📝</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Azhangshenghang" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=zhangshenghang" title="Tests">⚠️</a> <a href="#design-zhangshenghang" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zuobiao-zhou"><img src="https://avatars.githubusercontent.com/u/61108539?v=4?s=100" width="100px;" alt="Zhang Yuxuan"/><br /><sub><b>Zhang Yuxuan</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=zuobiao-zhou" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=zuobiao-zhou" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Azuobiao-zhou" title="Bug reports">🐛</a> <a href="#blog-zuobiao-zhou" title="Blogposts">📝</a> <a href="https://github.com/apache/hertzbeat/commits?author=zuobiao-zhou" title="Tests">⚠️</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Pzz-2021"><img src="https://avatars.githubusercontent.com/u/118056735?v=4?s=100" width="100px;" alt="P.P."/><br /><sub><b>P.P.</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Pzz-2021" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LLP2333"><img src="https://avatars.githubusercontent.com/u/61670545?v=4?s=100" width="100px;" alt="llp2333"/><br /><sub><b>llp2333</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LLP2333" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/HeartLinked"><img src="https://avatars.githubusercontent.com/u/78212101?v=4?s=100" width="100px;" alt="feiyang li"/><br /><sub><b>feiyang li</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=HeartLinked" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Aias00"><img src="https://avatars.githubusercontent.com/u/25810623?v=4?s=100" width="100px;" alt="aias00"/><br /><sub><b>aias00</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Aias00" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Aias00"><img src="https://avatars.githubusercontent.com/u/25810623?v=4?s=100" width="100px;" alt="aias00"/><br /><sub><b>aias00</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Aias00" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=Aias00" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3AAias00" title="Bug reports">🐛</a> <a href="#ideas-Aias00" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/apache/hertzbeat/commits?author=Aias00" title="Tests">⚠️</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/westboy"><img src="https://avatars.githubusercontent.com/u/6385565?v=4?s=100" width="100px;" alt="Jin"/><br /><sub><b>Jin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=westboy" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://blog.csdn.net/qq_52397471"><img src="https://avatars.githubusercontent.com/u/77964041?v=4?s=100" width="100px;" alt="YuLuo"/><br /><sub><b>YuLuo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yuluo-yx" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Yanshuming1"><img src="https://avatars.githubusercontent.com/u/118667222?v=4?s=100" width="100px;" alt="linDong"/><br /><sub><b>linDong</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Yanshuming1" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://blog.csdn.net/qq_52397471"><img src="https://avatars.githubusercontent.com/u/77964041?v=4?s=100" width="100px;" alt="YuLuo"/><br /><sub><b>YuLuo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yuluo-yx" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Ayuluo-yx" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=yuluo-yx" title="Tests">⚠️</a> <a href="#blog-yuluo-yx" title="Blogposts">📝</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Yanshuming1"><img src="https://avatars.githubusercontent.com/u/118667222?v=4?s=100" width="100px;" alt="linDong"/><br /><sub><b>linDong</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Yanshuming1" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=Yanshuming1" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3AYanshuming1" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lwjxy"><img src="https://avatars.githubusercontent.com/u/52726400?v=4?s=100" width="100px;" alt="lwjxy"/><br /><sub><b>lwjxy</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=lwjxy" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -454,17 +454,17 @@ Thanks to these wonderful people, welcome to join us:
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ileonli"><img src="https://avatars.githubusercontent.com/u/45332412?v=4?s=100" width="100px;" alt="Leon Li"/><br /><sub><b>Leon Li</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ileonli" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://fnil.net/"><img src="https://avatars.githubusercontent.com/u/14142?v=4?s=100" width="100px;" alt="dennis zhuang"/><br /><sub><b>dennis zhuang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=killme2008" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/kerwin612"><img src="https://avatars.githubusercontent.com/u/3371163?v=4?s=100" width="100px;" alt="Kerwin Bryant"/><br /><sub><b>Kerwin Bryant</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=kerwin612" title="Code">💻</a> <a href="#design-kerwin612" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/kerwin612"><img src="https://avatars.githubusercontent.com/u/3371163?v=4?s=100" width="100px;" alt="Kerwin Bryant"/><br /><sub><b>Kerwin Bryant</b></sub></a><br /><a href="#design-kerwin612" title="Design">🎨</a> <a href="https://github.com/apache/hertzbeat/commits?author=kerwin612" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=kerwin612" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Akerwin612" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ShineDevelopment"><img src="https://avatars.githubusercontent.com/u/59306780?v=4?s=100" width="100px;" alt="daixianglong"/><br /><sub><b>daixianglong</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ShineDevelopment" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mchgood"><img src="https://avatars.githubusercontent.com/u/38482005?v=4?s=100" width="100px;" alt="mchgood"/><br /><sub><b>mchgood</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=mchgood" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/pwallk"><img src="https://avatars.githubusercontent.com/u/69385076?v=4?s=100" width="100px;" alt="kangli"/><br /><sub><b>kangli</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=pwallk" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/pwallk"><img src="https://avatars.githubusercontent.com/u/69385076?v=4?s=100" width="100px;" alt="kangli"/><br /><sub><b>kangli</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=pwallk" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=pwallk" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Apwallk" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/cdphantom"><img src="https://avatars.githubusercontent.com/u/12674795?v=4?s=100" width="100px;" alt="cdphantom"/><br /><sub><b>cdphantom</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=cdphantom" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/asd108908382"><img src="https://avatars.githubusercontent.com/u/77717999?v=4?s=100" width="100px;" alt="jiawei.guo"/><br /><sub><b>jiawei.guo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=asd108908382" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/QBH-insist"><img src="https://avatars.githubusercontent.com/u/39401478?v=4?s=100" width="100px;" alt="QBH-insist"/><br /><sub><b>QBH-insist</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=QBH-insist" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jiangsh-ui"><img src="https://avatars.githubusercontent.com/u/86990361?v=4?s=100" width="100px;" alt="jiangsh"/><br /><sub><b>jiangsh</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=jiangsh-ui" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/keaifafafa"><img src="https://avatars.githubusercontent.com/u/83876361?v=4?s=100" width="100px;" alt="Keaifa"/><br /><sub><b>Keaifa</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=keaifafafa" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/keaifafafa"><img src="https://avatars.githubusercontent.com/u/83876361?v=4?s=100" width="100px;" alt="Keaifa"/><br /><sub><b>Keaifa</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=keaifafafa" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Akeaifafafa" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/loong95"><img src="https://avatars.githubusercontent.com/u/16333958?v=4?s=100" width="100px;" alt="Loong"/><br /><sub><b>Loong</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=loong95" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ceekay47"><img src="https://avatars.githubusercontent.com/u/104664857?v=4?s=100" width="100px;" alt="Chandrakant Vankayalapati"/><br /><sub><b>Chandrakant Vankayalapati</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ceekay47" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MRgenial"><img src="https://avatars.githubusercontent.com/u/49973336?v=4?s=100" width="100px;" alt="b_mountain"/><br /><sub><b>b_mountain</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=MRgenial" title="Code">💻</a></td>
|
||||
@@ -483,13 +483,13 @@ Thanks to these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/starryCoder"><img src="https://avatars.githubusercontent.com/u/46510059?v=4?s=100" width="100px;" alt="starryCoder"/><br /><sub><b>starryCoder</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=starryCoder" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/hasimmollah"><img src="https://avatars.githubusercontent.com/u/32538599?v=4?s=100" width="100px;" alt="hasimmollah"/><br /><sub><b>hasimmollah</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=hasimmollah" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ayu-v0"><img src="https://avatars.githubusercontent.com/u/127600988?v=4?s=100" width="100px;" alt="Ayu"/><br /><sub><b>Ayu</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ayu-v0" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Rancho-7"><img src="https://avatars.githubusercontent.com/u/59016860?v=4?s=100" width="100px;" alt="Nick Guo"/><br /><sub><b>Nick Guo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Rancho-7" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Rancho-7"><img src="https://avatars.githubusercontent.com/u/59016860?v=4?s=100" width="100px;" alt="Nick Guo"/><br /><sub><b>Nick Guo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Rancho-7" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=Rancho-7" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3ARancho-7" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/doveLin0818"><img src="https://avatars.githubusercontent.com/u/190927907?v=4?s=100" width="100px;" alt="doveLin"/><br /><sub><b>doveLin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=doveLin0818" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://zzrl.cc/"><img src="https://avatars.githubusercontent.com/u/91836599?v=4?s=100" width="100px;" alt="yunfan24"/><br /><sub><b>yunfan24</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yunfan24" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://zzrl.cc/"><img src="https://avatars.githubusercontent.com/u/91836599?v=4?s=100" width="100px;" alt="yunfan24"/><br /><sub><b>yunfan24</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yunfan24" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=yunfan24" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Ayunfan24" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=yunfan24" title="Tests">⚠️</a> <a href="#blog-yunfan24" title="Blogposts">📝</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lctking"><img src="https://avatars.githubusercontent.com/u/168249998?v=4?s=100" width="100px;" alt="nullwli"/><br /><sub><b>nullwli</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=lctking" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://simonsigre.com/"><img src="https://avatars.githubusercontent.com/u/14932913?v=4?s=100" width="100px;" alt="Simon Sigré"/><br /><sub><b>Simon Sigré</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=simonsigre" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://simonsigre.com/"><img src="https://avatars.githubusercontent.com/u/14932913?v=4?s=100" width="100px;" alt="Simon Sigré"/><br /><sub><b>Simon Sigré</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=simonsigre" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=simonsigre" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://www.ponfee.cn/"><img src="https://avatars.githubusercontent.com/u/46117331?v=4?s=100" width="100px;" alt="ponfee"/><br /><sub><b>ponfee</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ponfee" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Vedant7789"><img src="https://avatars.githubusercontent.com/u/147625492?v=4?s=100" width="100px;" alt="Vedant7789"/><br /><sub><b>Vedant7789</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Vedant7789" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Craaaaazy77"><img src="https://avatars.githubusercontent.com/u/23025522?v=4?s=100" width="100px;" alt="Craaaaazy77"/><br /><sub><b>Craaaaazy77</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Craaaaazy77" title="Documentation">📖</a></td>
|
||||
@@ -498,9 +498,9 @@ Thanks to these wonderful people, welcome to join us:
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/helei1030"><img src="https://avatars.githubusercontent.com/u/11839080?v=4?s=100" width="100px;" alt="helei1030"/><br /><sub><b>helei1030</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=helei1030" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://medium.com/@pjfanning"><img src="https://avatars.githubusercontent.com/u/11783444?v=4?s=100" width="100px;" alt="PJ Fanning"/><br /><sub><b>PJ Fanning</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=pjfanning" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://medium.com/@pjfanning"><img src="https://avatars.githubusercontent.com/u/11783444?v=4?s=100" width="100px;" alt="PJ Fanning"/><br /><sub><b>PJ Fanning</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=pjfanning" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Apjfanning" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=pjfanning" title="Tests">⚠️</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MonsterChenzhuo"><img src="https://avatars.githubusercontent.com/u/60029759?v=4?s=100" width="100px;" alt="monster"/><br /><sub><b>monster</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=MonsterChenzhuo" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MasamiYui"><img src="https://avatars.githubusercontent.com/u/22274133?v=4?s=100" width="100px;" alt="Sherlock Yin"/><br /><sub><b>Sherlock Yin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=MasamiYui" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MasamiYui"><img src="https://avatars.githubusercontent.com/u/22274133?v=4?s=100" width="100px;" alt="Sherlock Yin"/><br /><sub><b>Sherlock Yin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=MasamiYui" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=MasamiYui" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3AMasamiYui" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/wanhao23"><img src="https://avatars.githubusercontent.com/u/29560961?v=4?s=100" width="100px;" alt="wanhao"/><br /><sub><b>wanhao</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=wanhao23" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=wanhao23" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jonasHanhan"><img src="https://avatars.githubusercontent.com/u/130035609?v=4?s=100" width="100px;" alt="jonasHanhan"/><br /><sub><b>jonasHanhan</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=jonasHanhan" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/NikhilMurugesan"><img src="https://avatars.githubusercontent.com/u/49281792?v=4?s=100" width="100px;" alt="NikhilMurugesan"/><br /><sub><b>NikhilMurugesan</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=NikhilMurugesan" title="Code">💻</a></td>
|
||||
@@ -508,18 +508,18 @@ Thanks to these wonderful people, welcome to join us:
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/myangle1120"><img src="https://avatars.githubusercontent.com/u/19237013?v=4?s=100" width="100px;" alt="myangle1120"/><br /><sub><b>myangle1120</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=myangle1120" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yasminvo"><img src="https://avatars.githubusercontent.com/u/107528848?v=4?s=100" width="100px;" alt="yasminvo"/><br /><sub><b>yasminvo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yasminvo" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/notbugggg"><img src="https://avatars.githubusercontent.com/u/147966331?v=4?s=100" width="100px;" alt="不关银渐层的事哦"/><br /><sub><b>不关银渐层的事哦</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=notbugggg" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/notbugggg"><img src="https://avatars.githubusercontent.com/u/147966331?v=4?s=100" width="100px;" alt="不关银渐层的事哦"/><br /><sub><b>不关银渐层的事哦</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=notbugggg" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Anotbugggg" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yyahang"><img src="https://avatars.githubusercontent.com/u/90464876?v=4?s=100" width="100px;" alt="yyahang"/><br /><sub><b>yyahang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yyahang" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/JuJinPark"><img src="https://avatars.githubusercontent.com/u/44892459?v=4?s=100" width="100px;" alt="jujin"/><br /><sub><b>jujin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=JuJinPark" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LL-LIN"><img src="https://avatars.githubusercontent.com/u/43002118?v=4?s=100" width="100px;" alt="LL-LIN"/><br /><sub><b>LL-LIN</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LL-LIN" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://bigcyy.github.io/"><img src="https://avatars.githubusercontent.com/u/73413979?v=4?s=100" width="100px;" alt="Yang Chen"/><br /><sub><b>Yang Chen</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=bigcyy" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/JuJinPark"><img src="https://avatars.githubusercontent.com/u/44892459?v=4?s=100" width="100px;" alt="jujin"/><br /><sub><b>jujin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=JuJinPark" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=JuJinPark" title="Documentation">📖</a> <a href="#ideas-JuJinPark" title="Ideas, Planning, & Feedback">🤔</a> <a href="#blog-JuJinPark" title="Blogposts">📝</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LL-LIN"><img src="https://avatars.githubusercontent.com/u/43002118?v=4?s=100" width="100px;" alt="LL-LIN"/><br /><sub><b>LL-LIN</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LL-LIN" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3ALL-LIN" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://bigcyy.github.io/"><img src="https://avatars.githubusercontent.com/u/73413979?v=4?s=100" width="100px;" alt="Yang Chen"/><br /><sub><b>Yang Chen</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=bigcyy" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=bigcyy" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Abigcyy" title="Bug reports">🐛</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sarthakeash"><img src="https://avatars.githubusercontent.com/u/74091160?v=4?s=100" width="100px;" alt="Sarthak Arora"/><br /><sub><b>Sarthak Arora</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=sarthakeash" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sarthakeash"><img src="https://avatars.githubusercontent.com/u/74091160?v=4?s=100" width="100px;" alt="Sarthak Arora"/><br /><sub><b>Sarthak Arora</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=sarthakeash" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=sarthakeash" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/PengJingzhao"><img src="https://avatars.githubusercontent.com/u/97368949?v=4?s=100" width="100px;" alt="彭镜肇"/><br /><sub><b>彭镜肇</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=PengJingzhao" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gagaradio"><img src="https://avatars.githubusercontent.com/u/18532370?v=4?s=100" width="100px;" alt="Walter Jia"/><br /><sub><b>Walter Jia</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=gagaradio" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/boyucjz"><img src="https://avatars.githubusercontent.com/u/18730041?v=4?s=100" width="100px;" alt="boyucjz"/><br /><sub><b>boyucjz</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=boyucjz" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Cyanty"><img src="https://avatars.githubusercontent.com/u/153884653?v=4?s=100" width="100px;" alt="Cyanty"/><br /><sub><b>Cyanty</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Cyanty" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Cyanty"><img src="https://avatars.githubusercontent.com/u/153884653?v=4?s=100" width="100px;" alt="Cyanty"/><br /><sub><b>Cyanty</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Cyanty" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=Cyanty" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/KevinLLF"><img src="https://avatars.githubusercontent.com/u/85452733?v=4?s=100" width="100px;" alt="Jay丿167"/><br /><sub><b>Jay丿167</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=KevinLLF" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
+37
-37
@@ -221,7 +221,7 @@ Thanks these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://mroldx.xyz/"><img src="https://avatars.githubusercontent.com/u/34847828?v=4?s=100" width="100px;" alt="mroldx"/><br /><sub><b>mroldx</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=mroldx" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/woshiniusange"><img src="https://avatars.githubusercontent.com/u/91513022?v=4?s=100" width="100px;" alt="woshiniusange"/><br /><sub><b>woshiniusange</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=woshiniusange" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://vampireachao.github.io/"><img src="https://avatars.githubusercontent.com/u/52746628?v=4?s=100" width="100px;" alt="VampireAchao"/><br /><sub><b>VampireAchao</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=VampireAchao" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Ceilzcx"><img src="https://avatars.githubusercontent.com/u/48920254?v=4?s=100" width="100px;" alt="zcx"/><br /><sub><b>zcx</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Ceilzcx" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3ACeilzcx" title="Bug reports">🐛</a> <a href="#design-Ceilzcx" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Ceilzcx"><img src="https://avatars.githubusercontent.com/u/48920254?v=4?s=100" width="100px;" alt="zcx"/><br /><sub><b>zcx</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Ceilzcx" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3ACeilzcx" title="Bug reports">🐛</a> <a href="#design-Ceilzcx" title="Design">🎨</a> <a href="https://github.com/apache/hertzbeat/commits?author=Ceilzcx" title="Tests">⚠️</a> <a href="#blog-Ceilzcx" title="Blogposts">📝</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/CharlieXCL"><img src="https://avatars.githubusercontent.com/u/91540487?v=4?s=100" width="100px;" alt="CharlieXCL"/><br /><sub><b>CharlieXCL</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=CharlieXCL" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Privauto"><img src="https://avatars.githubusercontent.com/u/36581456?v=4?s=100" width="100px;" alt="Privauto"/><br /><sub><b>Privauto</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Privauto" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=Privauto" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
@@ -235,7 +235,7 @@ Thanks these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.zhihu.com/people/kevinbauer"><img src="https://avatars.githubusercontent.com/u/28581579?v=4?s=100" width="100px;" alt="Kevin Huang"/><br /><sub><b>Kevin Huang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=kevinhuangwl" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/TJxiaobao"><img src="https://avatars.githubusercontent.com/u/85919258?v=4?s=100" width="100px;" alt="铁甲小宝"/><br /><sub><b>铁甲小宝</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/issues?q=author%3ATJxiaobao" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=TJxiaobao" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=TJxiaobao" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/TJxiaobao"><img src="https://avatars.githubusercontent.com/u/85919258?v=4?s=100" width="100px;" alt="铁甲小宝"/><br /><sub><b>铁甲小宝</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/issues?q=author%3ATJxiaobao" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=TJxiaobao" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=TJxiaobao" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=TJxiaobao" title="Tests">⚠️</a> <a href="#design-TJxiaobao" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Jack-123-power"><img src="https://avatars.githubusercontent.com/u/84333501?v=4?s=100" width="100px;" alt="Captain Jack"/><br /><sub><b>Captain Jack</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Jack-123-power" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/haibo-duan"><img src="https://avatars.githubusercontent.com/u/7974845?v=4?s=100" width="100px;" alt="haibo.duan"/><br /><sub><b>haibo.duan</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=haibo-duan" title="Tests">⚠️</a> <a href="https://github.com/apache/hertzbeat/commits?author=haibo-duan" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/assassinfym"><img src="https://avatars.githubusercontent.com/u/15188754?v=4?s=100" width="100px;" alt="assassin"/><br /><sub><b>assassin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/issues?q=author%3Aassassinfym" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=assassinfym" title="Code">💻</a></td>
|
||||
@@ -277,7 +277,7 @@ Thanks these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://wjl110.xyz/"><img src="https://avatars.githubusercontent.com/u/53851034?v=4?s=100" width="100px;" alt="wjl110"/><br /><sub><b>wjl110</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=wjl110" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ngyhd"><img src="https://avatars.githubusercontent.com/u/29095207?v=4?s=100" width="100px;" alt="Sean"/><br /><sub><b>Sean</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ngyhd" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Daydreamer-ia"><img src="https://avatars.githubusercontent.com/u/83362909?v=4?s=100" width="100px;" alt="chenyiqin"/><br /><sub><b>chenyiqin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Daydreamer-ia" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=Daydreamer-ia" title="Tests">⚠️</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/hudongdong129"><img src="https://avatars.githubusercontent.com/u/34374227?v=4?s=100" width="100px;" alt="hudongdong129"/><br /><sub><b>hudongdong129</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=hudongdong129" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=hudongdong129" title="Tests">⚠️</a> <a href="https://github.com/apache/hertzbeat/commits?author=hudongdong129" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/hudongdong129"><img src="https://avatars.githubusercontent.com/u/34374227?v=4?s=100" width="100px;" alt="hudongdong129"/><br /><sub><b>hudongdong129</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=hudongdong129" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=hudongdong129" title="Tests">⚠️</a> <a href="https://github.com/apache/hertzbeat/commits?author=hudongdong129" title="Documentation">📖</a> <a href="#design-hudongdong129" title="Design">🎨</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/TherChenYang"><img src="https://avatars.githubusercontent.com/u/124348939?v=4?s=100" width="100px;" alt="TherChenYang"/><br /><sub><b>TherChenYang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=TherChenYang" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=TherChenYang" title="Tests">⚠️</a></td>
|
||||
@@ -293,7 +293,7 @@ Thanks these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/baiban114"><img src="https://avatars.githubusercontent.com/u/59152619?v=4?s=100" width="100px;" alt="TableRow"/><br /><sub><b>TableRow</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=baiban114" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=baiban114" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ByteIDance"><img src="https://avatars.githubusercontent.com/u/100207562?v=4?s=100" width="100px;" alt="ByteIDance"/><br /><sub><b>ByteIDance</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ByteIDance" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mangel2002"><img src="https://avatars.githubusercontent.com/u/9348020?v=4?s=100" width="100px;" alt="Jangfe"/><br /><sub><b>Jangfe</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=mangel2002" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zqr10159"><img src="https://avatars.githubusercontent.com/u/30048352?v=4?s=100" width="100px;" alt="zqr10159"/><br /><sub><b>zqr10159</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=zqr10159" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=zqr10159" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zqr10159"><img src="https://avatars.githubusercontent.com/u/30048352?v=4?s=100" width="100px;" alt="zqr10159"/><br /><sub><b>zqr10159</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=zqr10159" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=zqr10159" title="Code">💻</a> <a href="#blog-zqr10159" title="Blogposts">📝</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Azqr10159" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=zqr10159" title="Tests">⚠️</a> <a href="#design-zqr10159" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/vinci-897"><img src="https://avatars.githubusercontent.com/u/55838224?v=4?s=100" width="100px;" alt="vinci"/><br /><sub><b>vinci</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=vinci-897" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=vinci-897" title="Documentation">📖</a> <a href="#design-vinci-897" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/js110"><img src="https://avatars.githubusercontent.com/u/51191863?v=4?s=100" width="100px;" alt="js110"/><br /><sub><b>js110</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=js110" title="Code">💻</a></td>
|
||||
</tr>
|
||||
@@ -307,8 +307,8 @@ Thanks these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ZangJuxy"><img src="https://avatars.githubusercontent.com/u/71380295?v=4?s=100" width="100px;" alt="ZangJuxy"/><br /><sub><b>ZangJuxy</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ZangJuxy" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/l646505418"><img src="https://avatars.githubusercontent.com/u/50475131?v=4?s=100" width="100px;" alt="l646505418"/><br /><sub><b>l646505418</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=l646505418" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://www.carpewang.com"><img src="https://avatars.githubusercontent.com/u/78642589?v=4?s=100" width="100px;" alt="Carpe-Wang"/><br /><sub><b>Carpe-Wang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Carpe-Wang" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/l646505418"><img src="https://avatars.githubusercontent.com/u/50475131?v=4?s=100" width="100px;" alt="l646505418"/><br /><sub><b>l646505418</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=l646505418" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Al646505418" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://www.carpewang.com"><img src="https://avatars.githubusercontent.com/u/78642589?v=4?s=100" width="100px;" alt="Carpe-Wang"/><br /><sub><b>Carpe-Wang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Carpe-Wang" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3ACarpe-Wang" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/moshu023"><img src="https://avatars.githubusercontent.com/u/48593205?v=4?s=100" width="100px;" alt="莫枢"/><br /><sub><b>莫枢</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=moshu023" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/huangcanda"><img src="https://avatars.githubusercontent.com/u/4470566?v=4?s=100" width="100px;" alt="huangcanda"/><br /><sub><b>huangcanda</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=huangcanda" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.zrkizzy.com"><img src="https://avatars.githubusercontent.com/u/85340613?v=4?s=100" width="100px;" alt="世纪末的架构师"/><br /><sub><b>世纪末的架构师</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Architect-Java" title="Code">💻</a></td>
|
||||
@@ -358,7 +358,7 @@ Thanks these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lisongning"><img src="https://avatars.githubusercontent.com/u/93140178?v=4?s=100" width="100px;" alt="lisongning"/><br /><sub><b>lisongning</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=lisongning" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/YutingNie"><img src="https://avatars.githubusercontent.com/u/104416402?v=4?s=100" width="100px;" alt="YutingNie"/><br /><sub><b>YutingNie</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=YutingNie" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=YutingNie" title="Documentation">📖</a> <a href="#design-YutingNie" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mikezzb"><img src="https://avatars.githubusercontent.com/u/23418428?v=4?s=100" width="100px;" alt="Mike Zhou"/><br /><sub><b>Mike Zhou</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=mikezzb" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=mikezzb" title="Documentation">📖</a> <a href="#design-mikezzb" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/a-little-fool"><img src="https://avatars.githubusercontent.com/u/105542329?v=4?s=100" width="100px;" alt="小笨蛋"/><br /><sub><b>小笨蛋</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=a-little-fool" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/a-little-fool"><img src="https://avatars.githubusercontent.com/u/105542329?v=4?s=100" width="100px;" alt="小笨蛋"/><br /><sub><b>小笨蛋</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=a-little-fool" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=a-little-fool" title="Documentation">📖</a> <a href="#blog-a-little-fool" title="Blogposts">📝</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Aa-little-fool" title="Bug reports">🐛</a> <a href="#design-a-little-fool" title="Design">🎨</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/littlezhongzer"><img src="https://avatars.githubusercontent.com/u/33685289?v=4?s=100" width="100px;" alt="littlezhongzer"/><br /><sub><b>littlezhongzer</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=littlezhongzer" title="Code">💻</a></td>
|
||||
@@ -367,7 +367,7 @@ Thanks these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/XimfengYao"><img src="https://avatars.githubusercontent.com/u/17541537?v=4?s=100" width="100px;" alt="姚贤丰"/><br /><sub><b>姚贤丰</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=XimfengYao" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LINGLUOJUN"><img src="https://avatars.githubusercontent.com/u/16778977?v=4?s=100" width="100px;" alt="lingluojun"/><br /><sub><b>lingluojun</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LINGLUOJUN" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://www.luelueking.com"><img src="https://avatars.githubusercontent.com/u/93204032?v=4?s=100" width="100px;" alt="1ue"/><br /><sub><b>1ue</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=luelueking" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://www.jimmyqiao.top"><img src="https://avatars.githubusercontent.com/u/67301054?v=4?s=100" width="100px;" alt="qyaaaa"/><br /><sub><b>qyaaaa</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=qyaaaa" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://www.jimmyqiao.top"><img src="https://avatars.githubusercontent.com/u/67301054?v=4?s=100" width="100px;" alt="qyaaaa"/><br /><sub><b>qyaaaa</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=qyaaaa" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Aqyaaaa" title="Bug reports">🐛</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://novohit.top"><img src="https://avatars.githubusercontent.com/u/101090395?v=4?s=100" width="100px;" alt="novohit"/><br /><sub><b>novohit</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=novohit" title="Code">💻</a></td>
|
||||
@@ -381,14 +381,14 @@ Thanks these wonderful people, welcome to join us:
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/1036664317"><img src="https://avatars.githubusercontent.com/u/7696697?v=4?s=100" width="100px;" alt="moghn"/><br /><sub><b>moghn</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=1036664317" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/xiaoguolong"><img src="https://avatars.githubusercontent.com/u/33684988?v=4?s=100" width="100px;" alt="xiaoguolong"/><br /><sub><b>xiaoguolong</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=xiaoguolong" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Clownsw"><img src="https://avatars.githubusercontent.com/u/28394742?v=4?s=100" width="100px;" alt="Smliexx"/><br /><sub><b>Smliexx</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Clownsw" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Calvin979"><img src="https://avatars.githubusercontent.com/u/131688897?v=4?s=100" width="100px;" alt="Naruse"/><br /><sub><b>Naruse</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Calvin979" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=Calvin979" title="Code">💻</a> <a href="#design-Calvin979" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Clownsw"><img src="https://avatars.githubusercontent.com/u/28394742?v=4?s=100" width="100px;" alt="Smliexx"/><br /><sub><b>Smliexx</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Clownsw" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3AClownsw" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Calvin979"><img src="https://avatars.githubusercontent.com/u/131688897?v=4?s=100" width="100px;" alt="Naruse"/><br /><sub><b>Naruse</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Calvin979" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=Calvin979" title="Code">💻</a> <a href="#design-Calvin979" title="Design">🎨</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3ACalvin979" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=Calvin979" title="Tests">⚠️</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bbelide2"><img src="https://avatars.githubusercontent.com/u/26840796?v=4?s=100" width="100px;" alt="Bala Sukesh"/><br /><sub><b>Bala Sukesh</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=bbelide2" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jinyaoMa"><img src="https://avatars.githubusercontent.com/u/25066570?v=4?s=100" width="100px;" alt="Jinyao Ma"/><br /><sub><b>Jinyao Ma</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=jinyaoMa" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://linuxsuren.github.io/open-source-best-practice/"><img src="https://avatars.githubusercontent.com/u/1450685?v=4?s=100" width="100px;" alt="Rick"/><br /><sub><b>Rick</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LinuxSuRen" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=LinuxSuRen" title="Tests">⚠️</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ZY945"><img src="https://avatars.githubusercontent.com/u/74083801?v=4?s=100" width="100px;" alt="东风"/><br /><sub><b>东风</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ZY945" title="Code">💻</a> <a href="#design-ZY945" title="Design">🎨</a> <a href="https://github.com/apache/hertzbeat/commits?author=ZY945" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ZY945"><img src="https://avatars.githubusercontent.com/u/74083801?v=4?s=100" width="100px;" alt="东风"/><br /><sub><b>东风</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ZY945" title="Code">💻</a> <a href="#design-ZY945" title="Design">🎨</a> <a href="https://github.com/apache/hertzbeat/commits?author=ZY945" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3AZY945" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/prolevel1"><img src="https://avatars.githubusercontent.com/u/51995525?v=4?s=100" width="100px;" alt="sonam singh"/><br /><sub><b>sonam singh</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=prolevel1" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ZhangZixuan1994"><img src="https://avatars.githubusercontent.com/u/20011653?v=4?s=100" width="100px;" alt="ZhangZixuan1994"/><br /><sub><b>ZhangZixuan1994</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ZhangZixuan1994" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/hurenjie1"><img src="https://avatars.githubusercontent.com/u/40120355?v=4?s=100" width="100px;" alt="SHIG"/><br /><sub><b>SHIG</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=hurenjie1" title="Code">💻</a></td>
|
||||
@@ -398,29 +398,29 @@ Thanks these wonderful people, welcome to join us:
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/avvCode"><img src="https://avatars.githubusercontent.com/u/113538532?v=4?s=100" width="100px;" alt="avv"/><br /><sub><b>avv</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=avvCode" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yqxxgh"><img src="https://avatars.githubusercontent.com/u/42080876?v=4?s=100" width="100px;" alt="yqxxgh"/><br /><sub><b>yqxxgh</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yqxxgh" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yqxxgh"><img src="https://avatars.githubusercontent.com/u/42080876?v=4?s=100" width="100px;" alt="yqxxgh"/><br /><sub><b>yqxxgh</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yqxxgh" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=yqxxgh" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Ayqxxgh" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/CharlieShi46"><img src="https://avatars.githubusercontent.com/u/149798885?v=4?s=100" width="100px;" alt="CharlieShi46"/><br /><sub><b>CharlieShi46</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=CharlieShi46" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Nctllnty"><img src="https://avatars.githubusercontent.com/u/33241818?v=4?s=100" width="100px;" alt="Nctllnty"/><br /><sub><b>Nctllnty</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Nctllnty" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Wang-Yonghao"><img src="https://avatars.githubusercontent.com/u/48146606?v=4?s=100" width="100px;" alt="Wang-Yonghao"/><br /><sub><b>Wang-Yonghao</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Wang-Yonghao" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.yuque.com/dudiao/yy"><img src="https://avatars.githubusercontent.com/u/38355949?v=4?s=100" width="100px;" alt="读钓"/><br /><sub><b>读钓</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=dudiao" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/starmilkxin"><img src="https://avatars.githubusercontent.com/u/55646681?v=4?s=100" width="100px;" alt="Xin"/><br /><sub><b>Xin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=starmilkxin" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/starmilkxin"><img src="https://avatars.githubusercontent.com/u/55646681?v=4?s=100" width="100px;" alt="Xin"/><br /><sub><b>Xin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=starmilkxin" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Astarmilkxin" title="Bug reports">🐛</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/handy-git"><img src="https://avatars.githubusercontent.com/u/32837980?v=4?s=100" width="100px;" alt="handy"/><br /><sub><b>handy</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=handy-git" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LiuTianyou"><img src="https://avatars.githubusercontent.com/u/30208283?v=4?s=100" width="100px;" alt="LiuTianyou"/><br /><sub><b>LiuTianyou</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LiuTianyou" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=LiuTianyou" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LiuTianyou"><img src="https://avatars.githubusercontent.com/u/30208283?v=4?s=100" width="100px;" alt="LiuTianyou"/><br /><sub><b>LiuTianyou</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LiuTianyou" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=LiuTianyou" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3ALiuTianyou" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=LiuTianyou" title="Tests">⚠️</a> <a href="#blog-LiuTianyou" title="Blogposts">📝</a> <a href="#design-LiuTianyou" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/WinterKi1ler"><img src="https://avatars.githubusercontent.com/u/160592092?v=4?s=100" width="100px;" alt="WinterKi1ler"/><br /><sub><b>WinterKi1ler</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=WinterKi1ler" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://sharehoo.cn/"><img src="https://avatars.githubusercontent.com/u/45377370?v=4?s=100" width="100px;" alt="miki"/><br /><sub><b>miki</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=miki-hmt" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://codeflex.substack.com/"><img src="https://avatars.githubusercontent.com/u/85513042?v=4?s=100" width="100px;" alt="Keshav Carpenter"/><br /><sub><b>Keshav Carpenter</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=alpha951" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=alpha951" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/makechoicenow"><img src="https://avatars.githubusercontent.com/u/9911918?v=4?s=100" width="100px;" alt="makechoicenow"/><br /><sub><b>makechoicenow</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=makechoicenow" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gjjjj0101"><img src="https://avatars.githubusercontent.com/u/71874373?v=4?s=100" width="100px;" alt="Gao Jian"/><br /><sub><b>Gao Jian</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=gjjjj0101" title="Code">💻</a> <a href="#design-gjjjj0101" title="Design">🎨</a> <a href="https://github.com/apache/hertzbeat/commits?author=gjjjj0101" title="Tests">⚠️</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gjjjj0101"><img src="https://avatars.githubusercontent.com/u/71874373?v=4?s=100" width="100px;" alt="Gao Jian"/><br /><sub><b>Gao Jian</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=gjjjj0101" title="Tests">⚠️</a> <a href="https://github.com/apache/hertzbeat/commits?author=gjjjj0101" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=gjjjj0101" title="Documentation">📖</a> <a href="#design-gjjjj0101" title="Design">🎨</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Agjjjj0101" title="Bug reports">🐛</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://jangto.tistory.com/"><img src="https://avatars.githubusercontent.com/u/37864182?v=4?s=100" width="100px;" alt="Hyeon Sung"/><br /><sub><b>Hyeon Sung</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=dukbong" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://crossoverjie.top/"><img src="https://avatars.githubusercontent.com/u/15684156?v=4?s=100" width="100px;" alt="crossoverJie"/><br /><sub><b>crossoverJie</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=crossoverJie" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=crossoverJie" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://jangto.tistory.com/"><img src="https://avatars.githubusercontent.com/u/37864182?v=4?s=100" width="100px;" alt="Hyeon Sung"/><br /><sub><b>Hyeon Sung</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=dukbong" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=dukbong" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://crossoverjie.top/"><img src="https://avatars.githubusercontent.com/u/15684156?v=4?s=100" width="100px;" alt="crossoverJie"/><br /><sub><b>crossoverJie</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=crossoverJie" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=crossoverJie" title="Documentation">📖</a> <a href="#blog-crossoverJie" title="Blogposts">📝</a> <a href="https://github.com/apache/hertzbeat/commits?author=crossoverJie" title="Tests">⚠️</a> <a href="#design-crossoverJie" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/PeixyJ"><img src="https://avatars.githubusercontent.com/u/45998593?v=4?s=100" width="100px;" alt="PeixyJ"/><br /><sub><b>PeixyJ</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=PeixyJ" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Hi-Mr-Wind"><img src="https://avatars.githubusercontent.com/u/85803831?v=4?s=100" width="100px;" alt="风如歌"/><br /><sub><b>风如歌</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Hi-Mr-Wind" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MananPoojara"><img src="https://avatars.githubusercontent.com/u/104253184?v=4?s=100" width="100px;" alt="Manan Pujara"/><br /><sub><b>Manan Pujara</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=MananPoojara" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/xuziyang"><img src="https://avatars.githubusercontent.com/u/8465969?v=4?s=100" width="100px;" alt="xuziyang"/><br /><sub><b>xuziyang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=xuziyang" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/xuziyang"><img src="https://avatars.githubusercontent.com/u/8465969?v=4?s=100" width="100px;" alt="xuziyang"/><br /><sub><b>xuziyang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=xuziyang" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=xuziyang" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Axuziyang" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lwqzz"><img src="https://avatars.githubusercontent.com/u/62584513?v=4?s=100" width="100px;" alt="lwqzz"/><br /><sub><b>lwqzz</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=lwqzz" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -428,17 +428,17 @@ Thanks these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tomorrowshipyltm"><img src="https://avatars.githubusercontent.com/u/61336903?v=4?s=100" width="100px;" alt="tomorrowshipyltm"/><br /><sub><b>tomorrowshipyltm</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=tomorrowshipyltm" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/15613060203"><img src="https://avatars.githubusercontent.com/u/41351615?v=4?s=100" width="100px;" alt="栗磊"/><br /><sub><b>栗磊</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=15613060203" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Alanxtl"><img src="https://avatars.githubusercontent.com/u/25652981?v=4?s=100" width="100px;" alt="Alan"/><br /><sub><b>Alan</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Alanxtl" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://www.hadoop.wiki/"><img src="https://avatars.githubusercontent.com/u/29418975?v=4?s=100" width="100px;" alt="Jast"/><br /><sub><b>Jast</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=zhangshenghang" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zuobiao-zhou"><img src="https://avatars.githubusercontent.com/u/61108539?v=4?s=100" width="100px;" alt="Zhang Yuxuan"/><br /><sub><b>Zhang Yuxuan</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=zuobiao-zhou" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://www.hadoop.wiki/"><img src="https://avatars.githubusercontent.com/u/29418975?v=4?s=100" width="100px;" alt="Jast"/><br /><sub><b>Jast</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=zhangshenghang" title="Code">💻</a> <a href="#ideas-zhangshenghang" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/apache/hertzbeat/commits?author=zhangshenghang" title="Documentation">📖</a> <a href="#blog-zhangshenghang" title="Blogposts">📝</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Azhangshenghang" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=zhangshenghang" title="Tests">⚠️</a> <a href="#design-zhangshenghang" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zuobiao-zhou"><img src="https://avatars.githubusercontent.com/u/61108539?v=4?s=100" width="100px;" alt="Zhang Yuxuan"/><br /><sub><b>Zhang Yuxuan</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=zuobiao-zhou" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=zuobiao-zhou" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Azuobiao-zhou" title="Bug reports">🐛</a> <a href="#blog-zuobiao-zhou" title="Blogposts">📝</a> <a href="https://github.com/apache/hertzbeat/commits?author=zuobiao-zhou" title="Tests">⚠️</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Pzz-2021"><img src="https://avatars.githubusercontent.com/u/118056735?v=4?s=100" width="100px;" alt="P.P."/><br /><sub><b>P.P.</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Pzz-2021" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LLP2333"><img src="https://avatars.githubusercontent.com/u/61670545?v=4?s=100" width="100px;" alt="llp2333"/><br /><sub><b>llp2333</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LLP2333" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/HeartLinked"><img src="https://avatars.githubusercontent.com/u/78212101?v=4?s=100" width="100px;" alt="feiyang li"/><br /><sub><b>feiyang li</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=HeartLinked" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Aias00"><img src="https://avatars.githubusercontent.com/u/25810623?v=4?s=100" width="100px;" alt="aias00"/><br /><sub><b>aias00</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Aias00" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Aias00"><img src="https://avatars.githubusercontent.com/u/25810623?v=4?s=100" width="100px;" alt="aias00"/><br /><sub><b>aias00</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Aias00" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=Aias00" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3AAias00" title="Bug reports">🐛</a> <a href="#ideas-Aias00" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/apache/hertzbeat/commits?author=Aias00" title="Tests">⚠️</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/westboy"><img src="https://avatars.githubusercontent.com/u/6385565?v=4?s=100" width="100px;" alt="Jin"/><br /><sub><b>Jin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=westboy" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://blog.csdn.net/qq_52397471"><img src="https://avatars.githubusercontent.com/u/77964041?v=4?s=100" width="100px;" alt="YuLuo"/><br /><sub><b>YuLuo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yuluo-yx" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Yanshuming1"><img src="https://avatars.githubusercontent.com/u/118667222?v=4?s=100" width="100px;" alt="linDong"/><br /><sub><b>linDong</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Yanshuming1" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://blog.csdn.net/qq_52397471"><img src="https://avatars.githubusercontent.com/u/77964041?v=4?s=100" width="100px;" alt="YuLuo"/><br /><sub><b>YuLuo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yuluo-yx" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Ayuluo-yx" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=yuluo-yx" title="Tests">⚠️</a> <a href="#blog-yuluo-yx" title="Blogposts">📝</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Yanshuming1"><img src="https://avatars.githubusercontent.com/u/118667222?v=4?s=100" width="100px;" alt="linDong"/><br /><sub><b>linDong</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Yanshuming1" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=Yanshuming1" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3AYanshuming1" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lwjxy"><img src="https://avatars.githubusercontent.com/u/52726400?v=4?s=100" width="100px;" alt="lwjxy"/><br /><sub><b>lwjxy</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=lwjxy" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -453,17 +453,17 @@ Thanks these wonderful people, welcome to join us:
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ileonli"><img src="https://avatars.githubusercontent.com/u/45332412?v=4?s=100" width="100px;" alt="Leon Li"/><br /><sub><b>Leon Li</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ileonli" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://fnil.net/"><img src="https://avatars.githubusercontent.com/u/14142?v=4?s=100" width="100px;" alt="dennis zhuang"/><br /><sub><b>dennis zhuang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=killme2008" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/kerwin612"><img src="https://avatars.githubusercontent.com/u/3371163?v=4?s=100" width="100px;" alt="Kerwin Bryant"/><br /><sub><b>Kerwin Bryant</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=kerwin612" title="Code">💻</a> <a href="#design-kerwin612" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/kerwin612"><img src="https://avatars.githubusercontent.com/u/3371163?v=4?s=100" width="100px;" alt="Kerwin Bryant"/><br /><sub><b>Kerwin Bryant</b></sub></a><br /><a href="#design-kerwin612" title="Design">🎨</a> <a href="https://github.com/apache/hertzbeat/commits?author=kerwin612" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=kerwin612" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Akerwin612" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ShineDevelopment"><img src="https://avatars.githubusercontent.com/u/59306780?v=4?s=100" width="100px;" alt="daixianglong"/><br /><sub><b>daixianglong</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ShineDevelopment" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mchgood"><img src="https://avatars.githubusercontent.com/u/38482005?v=4?s=100" width="100px;" alt="mchgood"/><br /><sub><b>mchgood</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=mchgood" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/pwallk"><img src="https://avatars.githubusercontent.com/u/69385076?v=4?s=100" width="100px;" alt="kangli"/><br /><sub><b>kangli</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=pwallk" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/pwallk"><img src="https://avatars.githubusercontent.com/u/69385076?v=4?s=100" width="100px;" alt="kangli"/><br /><sub><b>kangli</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=pwallk" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=pwallk" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Apwallk" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/cdphantom"><img src="https://avatars.githubusercontent.com/u/12674795?v=4?s=100" width="100px;" alt="cdphantom"/><br /><sub><b>cdphantom</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=cdphantom" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/asd108908382"><img src="https://avatars.githubusercontent.com/u/77717999?v=4?s=100" width="100px;" alt="jiawei.guo"/><br /><sub><b>jiawei.guo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=asd108908382" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/QBH-insist"><img src="https://avatars.githubusercontent.com/u/39401478?v=4?s=100" width="100px;" alt="QBH-insist"/><br /><sub><b>QBH-insist</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=QBH-insist" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jiangsh-ui"><img src="https://avatars.githubusercontent.com/u/86990361?v=4?s=100" width="100px;" alt="jiangsh"/><br /><sub><b>jiangsh</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=jiangsh-ui" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/keaifafafa"><img src="https://avatars.githubusercontent.com/u/83876361?v=4?s=100" width="100px;" alt="Keaifa"/><br /><sub><b>Keaifa</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=keaifafafa" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/keaifafafa"><img src="https://avatars.githubusercontent.com/u/83876361?v=4?s=100" width="100px;" alt="Keaifa"/><br /><sub><b>Keaifa</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=keaifafafa" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Akeaifafafa" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/loong95"><img src="https://avatars.githubusercontent.com/u/16333958?v=4?s=100" width="100px;" alt="Loong"/><br /><sub><b>Loong</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=loong95" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ceekay47"><img src="https://avatars.githubusercontent.com/u/104664857?v=4?s=100" width="100px;" alt="Chandrakant Vankayalapati"/><br /><sub><b>Chandrakant Vankayalapati</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ceekay47" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MRgenial"><img src="https://avatars.githubusercontent.com/u/49973336?v=4?s=100" width="100px;" alt="b_mountain"/><br /><sub><b>b_mountain</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=MRgenial" title="Code">💻</a></td>
|
||||
@@ -482,13 +482,13 @@ Thanks these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/starryCoder"><img src="https://avatars.githubusercontent.com/u/46510059?v=4?s=100" width="100px;" alt="starryCoder"/><br /><sub><b>starryCoder</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=starryCoder" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/hasimmollah"><img src="https://avatars.githubusercontent.com/u/32538599?v=4?s=100" width="100px;" alt="hasimmollah"/><br /><sub><b>hasimmollah</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=hasimmollah" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ayu-v0"><img src="https://avatars.githubusercontent.com/u/127600988?v=4?s=100" width="100px;" alt="Ayu"/><br /><sub><b>Ayu</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ayu-v0" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Rancho-7"><img src="https://avatars.githubusercontent.com/u/59016860?v=4?s=100" width="100px;" alt="Nick Guo"/><br /><sub><b>Nick Guo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Rancho-7" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Rancho-7"><img src="https://avatars.githubusercontent.com/u/59016860?v=4?s=100" width="100px;" alt="Nick Guo"/><br /><sub><b>Nick Guo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Rancho-7" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=Rancho-7" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3ARancho-7" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/doveLin0818"><img src="https://avatars.githubusercontent.com/u/190927907?v=4?s=100" width="100px;" alt="doveLin"/><br /><sub><b>doveLin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=doveLin0818" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://zzrl.cc/"><img src="https://avatars.githubusercontent.com/u/91836599?v=4?s=100" width="100px;" alt="yunfan24"/><br /><sub><b>yunfan24</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yunfan24" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://zzrl.cc/"><img src="https://avatars.githubusercontent.com/u/91836599?v=4?s=100" width="100px;" alt="yunfan24"/><br /><sub><b>yunfan24</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yunfan24" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=yunfan24" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Ayunfan24" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=yunfan24" title="Tests">⚠️</a> <a href="#blog-yunfan24" title="Blogposts">📝</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lctking"><img src="https://avatars.githubusercontent.com/u/168249998?v=4?s=100" width="100px;" alt="nullwli"/><br /><sub><b>nullwli</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=lctking" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://simonsigre.com/"><img src="https://avatars.githubusercontent.com/u/14932913?v=4?s=100" width="100px;" alt="Simon Sigré"/><br /><sub><b>Simon Sigré</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=simonsigre" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://simonsigre.com/"><img src="https://avatars.githubusercontent.com/u/14932913?v=4?s=100" width="100px;" alt="Simon Sigré"/><br /><sub><b>Simon Sigré</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=simonsigre" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/commits?author=simonsigre" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://www.ponfee.cn/"><img src="https://avatars.githubusercontent.com/u/46117331?v=4?s=100" width="100px;" alt="ponfee"/><br /><sub><b>ponfee</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=ponfee" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Vedant7789"><img src="https://avatars.githubusercontent.com/u/147625492?v=4?s=100" width="100px;" alt="Vedant7789"/><br /><sub><b>Vedant7789</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Vedant7789" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Craaaaazy77"><img src="https://avatars.githubusercontent.com/u/23025522?v=4?s=100" width="100px;" alt="Craaaaazy77"/><br /><sub><b>Craaaaazy77</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Craaaaazy77" title="Documentation">📖</a></td>
|
||||
@@ -497,9 +497,9 @@ Thanks these wonderful people, welcome to join us:
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/helei1030"><img src="https://avatars.githubusercontent.com/u/11839080?v=4?s=100" width="100px;" alt="helei1030"/><br /><sub><b>helei1030</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=helei1030" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://medium.com/@pjfanning"><img src="https://avatars.githubusercontent.com/u/11783444?v=4?s=100" width="100px;" alt="PJ Fanning"/><br /><sub><b>PJ Fanning</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=pjfanning" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://medium.com/@pjfanning"><img src="https://avatars.githubusercontent.com/u/11783444?v=4?s=100" width="100px;" alt="PJ Fanning"/><br /><sub><b>PJ Fanning</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=pjfanning" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Apjfanning" title="Bug reports">🐛</a> <a href="https://github.com/apache/hertzbeat/commits?author=pjfanning" title="Tests">⚠️</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MonsterChenzhuo"><img src="https://avatars.githubusercontent.com/u/60029759?v=4?s=100" width="100px;" alt="monster"/><br /><sub><b>monster</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=MonsterChenzhuo" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MasamiYui"><img src="https://avatars.githubusercontent.com/u/22274133?v=4?s=100" width="100px;" alt="Sherlock Yin"/><br /><sub><b>Sherlock Yin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=MasamiYui" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MasamiYui"><img src="https://avatars.githubusercontent.com/u/22274133?v=4?s=100" width="100px;" alt="Sherlock Yin"/><br /><sub><b>Sherlock Yin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=MasamiYui" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=MasamiYui" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3AMasamiYui" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/wanhao23"><img src="https://avatars.githubusercontent.com/u/29560961?v=4?s=100" width="100px;" alt="wanhao"/><br /><sub><b>wanhao</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=wanhao23" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=wanhao23" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jonasHanhan"><img src="https://avatars.githubusercontent.com/u/130035609?v=4?s=100" width="100px;" alt="jonasHanhan"/><br /><sub><b>jonasHanhan</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=jonasHanhan" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/NikhilMurugesan"><img src="https://avatars.githubusercontent.com/u/49281792?v=4?s=100" width="100px;" alt="NikhilMurugesan"/><br /><sub><b>NikhilMurugesan</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=NikhilMurugesan" title="Code">💻</a></td>
|
||||
@@ -507,18 +507,18 @@ Thanks these wonderful people, welcome to join us:
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/myangle1120"><img src="https://avatars.githubusercontent.com/u/19237013?v=4?s=100" width="100px;" alt="myangle1120"/><br /><sub><b>myangle1120</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=myangle1120" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yasminvo"><img src="https://avatars.githubusercontent.com/u/107528848?v=4?s=100" width="100px;" alt="yasminvo"/><br /><sub><b>yasminvo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yasminvo" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/notbugggg"><img src="https://avatars.githubusercontent.com/u/147966331?v=4?s=100" width="100px;" alt="不关银渐层的事哦"/><br /><sub><b>不关银渐层的事哦</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=notbugggg" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/notbugggg"><img src="https://avatars.githubusercontent.com/u/147966331?v=4?s=100" width="100px;" alt="不关银渐层的事哦"/><br /><sub><b>不关银渐层的事哦</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=notbugggg" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Anotbugggg" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yyahang"><img src="https://avatars.githubusercontent.com/u/90464876?v=4?s=100" width="100px;" alt="yyahang"/><br /><sub><b>yyahang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=yyahang" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/JuJinPark"><img src="https://avatars.githubusercontent.com/u/44892459?v=4?s=100" width="100px;" alt="jujin"/><br /><sub><b>jujin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=JuJinPark" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LL-LIN"><img src="https://avatars.githubusercontent.com/u/43002118?v=4?s=100" width="100px;" alt="LL-LIN"/><br /><sub><b>LL-LIN</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LL-LIN" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://bigcyy.github.io/"><img src="https://avatars.githubusercontent.com/u/73413979?v=4?s=100" width="100px;" alt="Yang Chen"/><br /><sub><b>Yang Chen</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=bigcyy" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/JuJinPark"><img src="https://avatars.githubusercontent.com/u/44892459?v=4?s=100" width="100px;" alt="jujin"/><br /><sub><b>jujin</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=JuJinPark" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=JuJinPark" title="Documentation">📖</a> <a href="#ideas-JuJinPark" title="Ideas, Planning, & Feedback">🤔</a> <a href="#blog-JuJinPark" title="Blogposts">📝</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LL-LIN"><img src="https://avatars.githubusercontent.com/u/43002118?v=4?s=100" width="100px;" alt="LL-LIN"/><br /><sub><b>LL-LIN</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=LL-LIN" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3ALL-LIN" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://bigcyy.github.io/"><img src="https://avatars.githubusercontent.com/u/73413979?v=4?s=100" width="100px;" alt="Yang Chen"/><br /><sub><b>Yang Chen</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=bigcyy" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=bigcyy" title="Documentation">📖</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Abigcyy" title="Bug reports">🐛</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sarthakeash"><img src="https://avatars.githubusercontent.com/u/74091160?v=4?s=100" width="100px;" alt="Sarthak Arora"/><br /><sub><b>Sarthak Arora</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=sarthakeash" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sarthakeash"><img src="https://avatars.githubusercontent.com/u/74091160?v=4?s=100" width="100px;" alt="Sarthak Arora"/><br /><sub><b>Sarthak Arora</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=sarthakeash" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=sarthakeash" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/PengJingzhao"><img src="https://avatars.githubusercontent.com/u/97368949?v=4?s=100" width="100px;" alt="彭镜肇"/><br /><sub><b>彭镜肇</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=PengJingzhao" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gagaradio"><img src="https://avatars.githubusercontent.com/u/18532370?v=4?s=100" width="100px;" alt="Walter Jia"/><br /><sub><b>Walter Jia</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=gagaradio" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/boyucjz"><img src="https://avatars.githubusercontent.com/u/18730041?v=4?s=100" width="100px;" alt="boyucjz"/><br /><sub><b>boyucjz</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=boyucjz" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Cyanty"><img src="https://avatars.githubusercontent.com/u/153884653?v=4?s=100" width="100px;" alt="Cyanty"/><br /><sub><b>Cyanty</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Cyanty" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Cyanty"><img src="https://avatars.githubusercontent.com/u/153884653?v=4?s=100" width="100px;" alt="Cyanty"/><br /><sub><b>Cyanty</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Cyanty" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=Cyanty" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/KevinLLF"><img src="https://avatars.githubusercontent.com/u/85452733?v=4?s=100" width="100px;" alt="Jay丿167"/><br /><sub><b>Jay丿167</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=KevinLLF" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -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.
|
||||
|
||||
# This workflow will build a Java project with Maven
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
|
||||
|
||||
apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJkakNDQVIyZ0F3SUJBZ0lCQURBS0JnZ3Foa2pPUFFRREFqQWpNU0V3SHdZRFZRUUREQmhyTTNNdGMyVnkKZG1WeUxXTmhRREUzTkRVeU5ESTROamd3SGhjTk1qVXdOREl4TVRNME1UQTRXaGNOTXpVd05ERTVNVE0wTVRBNApXakFqTVNFd0h3WURWUVFEREJock0zTXRjMlZ5ZG1WeUxXTmhRREUzTkRVeU5ESTROamd3V1RBVEJnY3Foa2pPClBRSUJCZ2dxaGtqT1BRTUJCd05DQUFUR2U2SW5hQko4RGRiQVdmZTRNM09yS3ZTR1IvWkROS0JJV2lMejZXckcKYmh4Mm5ncHJFQjhRYThEWDZocGRCVkI1RmN3L1hwTXZjRnNCeFh3MThPNkdvMEl3UURBT0JnTlZIUThCQWY4RQpCQU1DQXFRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVWRXOEFySENLNVQ2OFdJeG1BUnlTCkJFcHJ6N1F3Q2dZSUtvWkl6ajBFQXdJRFJ3QXdSQUlnYlhwYXV0c2RlTlJHNm4zRmhBRkkzbncvWUErYTBBdi8KUE9HZzNhYWRCSDBDSUU5NW4vQW5yOXV2Z0tyczVrakpGKzAwci96SWpFdnQxNmhkOGdabFEzb1IKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
|
||||
server: https://172.29.0.11:6443
|
||||
name: default
|
||||
contexts:
|
||||
- context:
|
||||
cluster: default
|
||||
user: default
|
||||
name: default
|
||||
current-context: default
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users:
|
||||
- name: default
|
||||
user:
|
||||
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJrVENDQVRlZ0F3SUJBZ0lJZm5OdzUyby9ybDB3Q2dZSUtvWkl6ajBFQXdJd0l6RWhNQjhHQTFVRUF3d1kKYXpOekxXTnNhV1Z1ZEMxallVQXhOelExTWpReU9EWTRNQjRYRFRJMU1EUXlNVEV6TkRFd09Gb1hEVEkyTURReQpNVEV6TkRFd09Gb3dNREVYTUJVR0ExVUVDaE1PYzNsemRHVnRPbTFoYzNSbGNuTXhGVEFUQmdOVkJBTVRESE41CmMzUmxiVHBoWkcxcGJqQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJHa3UxWDQvRGNMU1h4cXcKSzczdGhMUFd5Y3JCendrL3ZMbzkzMTZ0dXNXRWE4WnNMdytnYk5wWEVOSFNxQk8zdGcrYUFSdGhWakF3OU5GaApZWXNtaTQyalNEQkdNQTRHQTFVZER3RUIvd1FFQXdJRm9EQVRCZ05WSFNVRUREQUtCZ2dyQmdFRkJRY0RBakFmCkJnTlZIU01FR0RBV2dCU1B5dEJrYlFaRmtxSEpOaHlHNjZ1K3ZqbjNhakFLQmdncWhrak9QUVFEQWdOSUFEQkYKQWlFQXZQVC9ENGs2S29tUUxaZjRNMTEvd1psT3QyZkpGTGhIY3EyOHMvUUVLcndDSUZVMWVzTGFFTnRWc2xUeAo0QTFtNkt4eEpUbmdNM0M3UXJ5OUs0Z2hzVDA0Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJkekNDQVIyZ0F3SUJBZ0lCQURBS0JnZ3Foa2pPUFFRREFqQWpNU0V3SHdZRFZRUUREQmhyTTNNdFkyeHAKWlc1MExXTmhRREUzTkRVeU5ESTROamd3SGhjTk1qVXdOREl4TVRNME1UQTRXaGNOTXpVd05ERTVNVE0wTVRBNApXakFqTVNFd0h3WURWUVFEREJock0zTXRZMnhwWlc1MExXTmhRREUzTkRVeU5ESTROamd3V1RBVEJnY3Foa2pPClBRSUJCZ2dxaGtqT1BRTUJCd05DQUFSeENGL2FuNVUvS0xEUk9VUlZxdllWTHFsdWxXaE8rMzJpc1lRU1hlYUgKdnRBWHo3dFE2MkhrazNIdXZudnFmditvQlBKVUlzS3ExTyt6aytLT3JWa2dvMEl3UURBT0JnTlZIUThCQWY4RQpCQU1DQXFRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVWo4clFaRzBHUlpLaHlUWWNodXVyCnZyNDU5Mm93Q2dZSUtvWkl6ajBFQXdJRFNBQXdSUUlnWmxiRkpoZWwzQiszQ2FyZjUrdE5taDhkWkRUOC9yVDAKR01VVXlBNzlWSUVDSVFDSUQ1NkVQbmtOa0xmZExEZXloclBGWks3VWRTc2x2QWZGN2N4czF1MDBxZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
|
||||
client-key-data: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUpMOGJrS1FMbEdZWUE0Ny9Lek9CRm5iZnI0NGQ5VndUWXlPT0dJdnNLOXNvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFYVM3VmZqOE53dEpmR3JBcnZlMkVzOWJKeXNIUENUKzh1ajNmWHEyNnhZUnJ4bXd2RDZCcwoybGNRMGRLb0U3ZTJENW9CRzJGV01ERDAwV0ZoaXlhTGpRPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo=
|
||||
@@ -0,0 +1 @@
|
||||
K10800b7e6b1f6390067e433bd30dff5f925893b60074f8c36f7725bfdce6a45917::server:ca7449eb0dddd8a4bb6e5ca86e2d0cdd
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"detected": true,
|
||||
"monitor": {
|
||||
"intervals": 10,
|
||||
"tags": [],
|
||||
"app": "kubernetes",
|
||||
"host": "172.29.0.11",
|
||||
"name": "Brave_Stingray_55yR"
|
||||
},
|
||||
"collector": "",
|
||||
"params": [
|
||||
{
|
||||
"display": true,
|
||||
"field": "host",
|
||||
"type": 1,
|
||||
"paramValue": "172.29.0.11"
|
||||
},
|
||||
{
|
||||
"display": true,
|
||||
"field": "port",
|
||||
"type": 0,
|
||||
"paramValue": 6443
|
||||
},
|
||||
{
|
||||
"display": true,
|
||||
"field": "authType",
|
||||
"type": 1,
|
||||
"paramValue": "Bearer Token"
|
||||
},
|
||||
{
|
||||
"display": true,
|
||||
"field": "token",
|
||||
"type": 1,
|
||||
"paramValue": "#TOKEN#"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
#!api-testing
|
||||
# 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.
|
||||
|
||||
# yaml-language-server: $schema=https://linuxsuren.github.io/api-testing/api-testing-schema.json
|
||||
name: hertzbeat
|
||||
api: |
|
||||
{{default "http://localhost:1157" (env "SERVER")}}
|
||||
param:
|
||||
monitorHTTP: "{{randAlpha 6}}"
|
||||
monitorSiteMap: "{{randAlpha 6}}"
|
||||
monitorPort: "{{randAlpha 6}}"
|
||||
monitorSSL: "{{randAlpha 6}}"
|
||||
monitorPing: "{{randAlpha 6}}"
|
||||
monitorUDP: "{{randAlpha 6}}"
|
||||
monitorWebsite: "{{randAlpha 6}}"
|
||||
monitorFTP: "{{randAlpha 6}}"
|
||||
tagName: "{{randAlpha 3}}"
|
||||
items:
|
||||
- name: login
|
||||
request:
|
||||
api: /api/account/auth/form
|
||||
method: POST
|
||||
header:
|
||||
Content-type: application/json
|
||||
body: |
|
||||
{
|
||||
"type": 0,
|
||||
"identifier": "admin",
|
||||
"credential": "hertzbeat"
|
||||
}
|
||||
expect:
|
||||
bodyFieldsExpect:
|
||||
code: "0"
|
||||
- name: detectMonitor
|
||||
request:
|
||||
api: /api/monitor/detect
|
||||
method: POST
|
||||
header:
|
||||
Authorization: Bearer {{.login.data.token}}
|
||||
Content-type: application/json
|
||||
bodyFromFile: data/tmp.json
|
||||
expect:
|
||||
statusCode: 200
|
||||
- name: createMonitor
|
||||
request:
|
||||
api: /api/monitor
|
||||
method: POST
|
||||
header:
|
||||
Authorization: Bearer {{.login.data.token}}
|
||||
Content-type: application/json
|
||||
bodyFromFile: data/tmp.json
|
||||
expect:
|
||||
statusCode: 200
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
# 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.
|
||||
|
||||
version: '3.8'
|
||||
services:
|
||||
init:
|
||||
image: bitnami/kubectl:latest
|
||||
privileged: true
|
||||
depends_on:
|
||||
k3s:
|
||||
condition: service_healthy
|
||||
entrypoint: /bin/bash
|
||||
networks:
|
||||
k3s_network:
|
||||
ipv4_address: 172.29.0.10
|
||||
volumes:
|
||||
- ./data/config/k3s.yaml:/.kube/config
|
||||
- ./data:/testcase
|
||||
command:
|
||||
- -c
|
||||
- |
|
||||
# Wait for k3s to start
|
||||
until kubectl cluster-info; do sleep 1; done
|
||||
sleep 5
|
||||
# Create serviceaccount and clusterrolebinding
|
||||
kubectl create serviceaccount -n kube-system cluster-admin-sa
|
||||
kubectl create clusterrolebinding -n kube-system cluster-admin-binding --clusterrole=cluster-admin --serviceaccount=kube-system:cluster-admin-sa
|
||||
cd /testcase
|
||||
kubectl create token cluster-admin-sa -n kube-system --duration=24h
|
||||
echo $(sed "s/#TOKEN#/$(echo $(kubectl create token cluster-admin-sa -n kube-system --duration=24h))/g" create_k8s_monitor_data.json)>tmp.json
|
||||
# install metrics server
|
||||
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
|
||||
sleep 30
|
||||
tail -f /dev/null
|
||||
k3s:
|
||||
image: rancher/k3s:v1.31.0-k3s1
|
||||
container_name: k3s
|
||||
privileged: true
|
||||
ports:
|
||||
- "6443:6443"
|
||||
- "80:80"
|
||||
command: server --bind-address 172.29.0.11
|
||||
networks:
|
||||
k3s_network:
|
||||
ipv4_address: 172.29.0.11
|
||||
healthcheck:
|
||||
test: [ "CMD-SHELL",
|
||||
"kubectl cluster-info && echo $(cat /var/lib/rancher/k3s/server/node-token)>node-token.txt" ]
|
||||
interval: 3s
|
||||
retries: 30
|
||||
start_period: 10s
|
||||
timeout: 10s
|
||||
volumes:
|
||||
- ./data/config/k3s.yaml:/etc/rancher/k3s/k3s.yaml
|
||||
- ./data/config/node-token.txt:/node-token.txt
|
||||
testing:
|
||||
image: ghcr.io/linuxsuren/api-testing:v0.0.17
|
||||
environment:
|
||||
SERVER: http://hertzbeat:1157
|
||||
container_name: e2e-testing
|
||||
volumes:
|
||||
- ./data/:/work/data/
|
||||
- ./data/testsuite.yaml:/work/testsuite.yaml
|
||||
command: atest run -p /work/testsuite.yaml --level debug --thread 3
|
||||
networks:
|
||||
k3s_network:
|
||||
ipv4_address: 172.29.0.12
|
||||
depends_on:
|
||||
checker:
|
||||
condition: service_healthy
|
||||
k3s:
|
||||
condition: service_healthy
|
||||
links:
|
||||
- hertzbeat
|
||||
- checker
|
||||
- k3s
|
||||
checker:
|
||||
image: alpine:latest
|
||||
container_name: e2e-checker
|
||||
command: sh -c "apk add --update curl && tail -f /dev/null"
|
||||
healthcheck:
|
||||
test: [ "CMD-SHELL",
|
||||
"curl -s -X POST -H \"Content-Type: application/json\" -d '{\"type\": 0,\"identifier\": \"admin\",\"credential\": \"hertzbeat\"}' http://hertzbeat:1157/api/account/auth/form | grep -q token"
|
||||
]
|
||||
interval: 3s
|
||||
timeout: 60s
|
||||
retries: 10
|
||||
start_period: 10s
|
||||
networks:
|
||||
k3s_network:
|
||||
ipv4_address: 172.29.0.13
|
||||
depends_on:
|
||||
- init
|
||||
hertzbeat:
|
||||
image: apache/hertzbeat:test
|
||||
container_name: e2e-hertzbeat
|
||||
volumes:
|
||||
- ./logs/:/opt/hertzbeat/logs/
|
||||
ports:
|
||||
- "1157:1157"
|
||||
networks:
|
||||
k3s_network:
|
||||
ipv4_address: 172.29.0.14
|
||||
networks:
|
||||
k3s_network:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.29.0.0/16
|
||||
+14
-18
@@ -29,7 +29,7 @@ param:
|
||||
monitorUDP: "{{randAlpha 6}}"
|
||||
monitorWebsite: "{{randAlpha 6}}"
|
||||
monitorFTP: "{{randAlpha 6}}"
|
||||
tagName: "{{randAlpha 3}}"
|
||||
labelName: "{{randAlpha 3}}"
|
||||
items:
|
||||
- name: login
|
||||
request:
|
||||
@@ -342,39 +342,35 @@ items:
|
||||
header:
|
||||
Authorization: Bearer {{.login.data.token}}
|
||||
|
||||
## Tag
|
||||
- name: createTag
|
||||
## Label
|
||||
- name: createLabel
|
||||
request:
|
||||
api: /api/tag
|
||||
api: /api/label
|
||||
method: POST
|
||||
header:
|
||||
Authorization: Bearer {{.login.data.token}}
|
||||
Content-type: application/json
|
||||
body: |
|
||||
[
|
||||
{
|
||||
"color": "#ff4081",
|
||||
"name": "{{.param.tagName}}"
|
||||
}
|
||||
]
|
||||
- name: listTags
|
||||
{
|
||||
"name": "{{.param.labelName}}"
|
||||
}
|
||||
- name: listLabels
|
||||
request:
|
||||
api: /api/tag?pageIndex=0&pageSize=8&type=1
|
||||
api: /api/label?pageIndex=0&pageSize=8&type=1
|
||||
header:
|
||||
Authorization: Bearer {{.login.data.token}}
|
||||
- name: updateTag
|
||||
- name: updateLabel
|
||||
request:
|
||||
api: /api/tag
|
||||
api: /api/label
|
||||
method: PUT
|
||||
header:
|
||||
Authorization: Bearer {{.login.data.token}}
|
||||
Content-type: application/json
|
||||
body: |
|
||||
{
|
||||
"id": {{(index .listTags.data.content 0).id | int64}},
|
||||
"id": {{(index .listLabels.data.content 0).id | int64}},
|
||||
"name": "{{randAlpha 3}}",
|
||||
"tagValue": "{{randAlpha 3}}",
|
||||
"color": "#ff4081",
|
||||
"type": 1,
|
||||
"creator": "admin",
|
||||
"modifier": "admin"
|
||||
@@ -382,9 +378,9 @@ items:
|
||||
expect:
|
||||
bodyFieldsExpect:
|
||||
code: "0"
|
||||
- name: deleteTag
|
||||
- name: deleteLabel
|
||||
request:
|
||||
api: /api/tag?ids={{(index .listTags.data.content 0).id | int64}}
|
||||
api: /api/label?ids={{(index .listLabels.data.content 0).id | int64}}
|
||||
method: DELETE
|
||||
header:
|
||||
Authorization: Bearer {{.login.data.token}}
|
||||
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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 org.apache.hertzbeat.alert.dao.SingleAlertDao;
|
||||
import org.apache.hertzbeat.alert.util.AlertUtil;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* alert cache manager
|
||||
*/
|
||||
@Component
|
||||
public class AlarmCacheManager {
|
||||
|
||||
/**
|
||||
* The alarm in the process is triggered
|
||||
* key - labels fingerprint
|
||||
*/
|
||||
private final Map<String, SingleAlert> pendingAlertMap;
|
||||
/**
|
||||
* The not recover alert
|
||||
* key - labels fingerprint
|
||||
*/
|
||||
private final Map<String, SingleAlert> firingAlertMap;
|
||||
|
||||
public AlarmCacheManager(SingleAlertDao singleAlertDao) {
|
||||
this.pendingAlertMap = new ConcurrentHashMap<>(8);
|
||||
this.firingAlertMap = new ConcurrentHashMap<>(8);
|
||||
List<SingleAlert> singleAlerts = singleAlertDao.querySingleAlertsByStatus(CommonConstants.ALERT_STATUS_FIRING);
|
||||
for (SingleAlert singleAlert : singleAlerts) {
|
||||
String fingerprint = AlertUtil.calculateFingerprint(singleAlert.getLabels());
|
||||
singleAlert.setId(null);
|
||||
this.firingAlertMap.put(fingerprint, singleAlert);
|
||||
}
|
||||
}
|
||||
|
||||
public void putPending(String fingerPrint, SingleAlert alert) {
|
||||
this.pendingAlertMap.put(fingerPrint, alert);
|
||||
}
|
||||
|
||||
public SingleAlert getPending(String fingerPrint) {
|
||||
return this.pendingAlertMap.get(fingerPrint);
|
||||
}
|
||||
|
||||
public SingleAlert removePending(String fingerPrint) {
|
||||
return this.pendingAlertMap.remove(fingerPrint);
|
||||
}
|
||||
|
||||
public void putFiring(String fingerPrint, SingleAlert alert) {
|
||||
this.firingAlertMap.put(fingerPrint, alert);
|
||||
}
|
||||
|
||||
public SingleAlert getFiring(String fingerPrint) {
|
||||
return this.firingAlertMap.get(fingerPrint);
|
||||
}
|
||||
|
||||
public SingleAlert removeFiring(String fingerPrint) {
|
||||
return this.firingAlertMap.remove(fingerPrint);
|
||||
}
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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 lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.dao.AlertCollectorDao;
|
||||
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
|
||||
import org.apache.hertzbeat.alert.util.AlertUtil;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.apache.hertzbeat.common.entity.manager.Collector;
|
||||
import org.apache.hertzbeat.common.support.event.SystemConfigChangeEvent;
|
||||
import org.apache.hertzbeat.common.util.ResourceBundleUtil;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
/**
|
||||
* handle collector alarm
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class CollectorAlertHandler {
|
||||
|
||||
private static final String KEY_COLLECTOR_NAME = "collectorName";
|
||||
private static final String KEY_COLLECTOR_VERSION = "collectorVersion";
|
||||
private static final String KEY_COLLECTOR_HOST = "collectorHost";
|
||||
|
||||
private final AlertCollectorDao alertCollectorDao;
|
||||
|
||||
private final AlarmCommonReduce alarmCommonReduce;
|
||||
|
||||
private final AlarmCacheManager alarmCacheManager;
|
||||
|
||||
private ResourceBundle bundle;
|
||||
|
||||
|
||||
public CollectorAlertHandler(AlarmCommonReduce alarmCommonReduce, AlertCollectorDao alertCollectorDao,
|
||||
AlarmCacheManager alarmCacheManager) {
|
||||
this.alarmCommonReduce = alarmCommonReduce;
|
||||
this.alertCollectorDao = alertCollectorDao;
|
||||
this.alarmCacheManager = alarmCacheManager;
|
||||
this.bundle = ResourceBundleUtil.getBundle("alerter");
|
||||
}
|
||||
|
||||
/**
|
||||
* handle collector online
|
||||
*
|
||||
* @param identity collector name
|
||||
*/
|
||||
public void online(final String identity) {
|
||||
Collector collector = alertCollectorDao.findCollectorByName(identity);
|
||||
if (collector == null) {
|
||||
return;
|
||||
}
|
||||
Map<String, String> fingerPrints = new HashMap<>(8);
|
||||
fingerPrints.put(KEY_COLLECTOR_NAME, collector.getName());
|
||||
fingerPrints.put(KEY_COLLECTOR_VERSION, collector.getVersion());
|
||||
fingerPrints.put(KEY_COLLECTOR_HOST, collector.getIp());
|
||||
String fingerprint = AlertUtil.calculateFingerprint(fingerPrints);
|
||||
SingleAlert firingAlert = alarmCacheManager.getFiring(fingerprint);
|
||||
if (firingAlert != null) {
|
||||
firingAlert.setTriggerTimes(1);
|
||||
firingAlert.setEndAt(System.currentTimeMillis());
|
||||
firingAlert.setStatus(CommonConstants.ALERT_STATUS_RESOLVED);
|
||||
alarmCommonReduce.reduceAndSendAlarm(firingAlert.clone());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* handle collector offline
|
||||
*
|
||||
* @param identity collector name
|
||||
*/
|
||||
public void offline(final String identity) {
|
||||
Collector collector = alertCollectorDao.findCollectorByName(identity);
|
||||
if (collector == null) {
|
||||
return;
|
||||
}
|
||||
long currentTimeMill = System.currentTimeMillis();
|
||||
Map<String, String> fingerPrints = new HashMap<>(8);
|
||||
fingerPrints.put(KEY_COLLECTOR_NAME, collector.getName());
|
||||
fingerPrints.put(KEY_COLLECTOR_VERSION, collector.getVersion());
|
||||
fingerPrints.put(KEY_COLLECTOR_HOST, collector.getIp());
|
||||
String fingerprint = AlertUtil.calculateFingerprint(fingerPrints);
|
||||
SingleAlert existingAlert = alarmCacheManager.getFiring(fingerprint);
|
||||
if (existingAlert == null) {
|
||||
SingleAlert newAlert = SingleAlert.builder()
|
||||
.labels(fingerPrints)
|
||||
.annotations(fingerPrints)
|
||||
.content(this.bundle.getString("alerter.availability.collector.offline"))
|
||||
.status(CommonConstants.ALERT_STATUS_FIRING)
|
||||
.triggerTimes(1)
|
||||
.startAt(currentTimeMill)
|
||||
.activeAt(currentTimeMill)
|
||||
.build();
|
||||
alarmCacheManager.putFiring(fingerprint, newAlert);
|
||||
alarmCommonReduce.reduceAndSendAlarm(newAlert.clone());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@EventListener(SystemConfigChangeEvent.class)
|
||||
public void onSystemConfigChangeEvent(SystemConfigChangeEvent event) {
|
||||
log.info("calculate alarm receive system config change event: {}.", event.getSource());
|
||||
this.bundle = ResourceBundleUtil.getBundle("alerter");
|
||||
}
|
||||
|
||||
}
|
||||
+15
-32
@@ -17,19 +17,17 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.calculate;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Objects;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
|
||||
import org.apache.hertzbeat.alert.service.DataSourceService;
|
||||
import org.apache.hertzbeat.alert.util.AlertTemplateUtil;
|
||||
import org.apache.hertzbeat.alert.util.AlertUtil;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -47,22 +45,13 @@ public class PeriodicAlertCalculator {
|
||||
|
||||
private final DataSourceService dataSourceService;
|
||||
private final AlarmCommonReduce alarmCommonReduce;
|
||||
/**
|
||||
* The alarm in the process is triggered
|
||||
* key - labels fingerprint
|
||||
*/
|
||||
private final Map<String, SingleAlert> pendingAlertMap;
|
||||
/**
|
||||
* The not recover alert
|
||||
* key - labels fingerprint
|
||||
*/
|
||||
private final Map<String, SingleAlert> firingAlertMap;
|
||||
|
||||
public PeriodicAlertCalculator(DataSourceService dataSourceService, AlarmCommonReduce alarmCommonReduce) {
|
||||
private final AlarmCacheManager alarmCacheManager;
|
||||
|
||||
public PeriodicAlertCalculator(DataSourceService dataSourceService, AlarmCommonReduce alarmCommonReduce,
|
||||
AlarmCacheManager alarmCacheManager) {
|
||||
this.dataSourceService = dataSourceService;
|
||||
this.alarmCommonReduce = alarmCommonReduce;
|
||||
this.pendingAlertMap = new ConcurrentHashMap<>(8);
|
||||
this.firingAlertMap = new ConcurrentHashMap<>(8);
|
||||
this.alarmCacheManager = alarmCacheManager;
|
||||
}
|
||||
|
||||
public void calculate(AlertDefine rule) {
|
||||
@@ -122,8 +111,8 @@ public class PeriodicAlertCalculator {
|
||||
|
||||
private void afterThresholdRuleMatch(long currentTimeMilli, Map<String, String> fingerPrints,
|
||||
Map<String, Object> fieldValueMap, AlertDefine define) {
|
||||
String fingerprint = calculateFingerprint(fingerPrints);
|
||||
SingleAlert existingAlert = pendingAlertMap.get(fingerprint);
|
||||
String fingerprint = AlertUtil.calculateFingerprint(fingerPrints);
|
||||
SingleAlert existingAlert = alarmCacheManager.getPending(fingerprint);
|
||||
Map<String, String> labels = new HashMap<>(8);
|
||||
fieldValueMap.putAll(define.getLabels());
|
||||
labels.putAll(fingerPrints);
|
||||
@@ -144,11 +133,11 @@ public class PeriodicAlertCalculator {
|
||||
// If required trigger times is 1, set to firing status directly
|
||||
if (requiredTimes <= 1) {
|
||||
newAlert.setStatus(CommonConstants.ALERT_STATUS_FIRING);
|
||||
firingAlertMap.put(fingerprint, newAlert);
|
||||
alarmCacheManager.putFiring(fingerprint, newAlert);
|
||||
alarmCommonReduce.reduceAndSendAlarm(newAlert.clone());
|
||||
} else {
|
||||
// Otherwise put into pending queue first
|
||||
pendingAlertMap.put(fingerprint, newAlert);
|
||||
alarmCacheManager.putPending(fingerprint, newAlert);
|
||||
}
|
||||
} else {
|
||||
// Update existing alert
|
||||
@@ -158,17 +147,17 @@ public class PeriodicAlertCalculator {
|
||||
// Check if required trigger times reached
|
||||
if (existingAlert.getStatus().equals(CommonConstants.ALERT_STATUS_PENDING) && existingAlert.getTriggerTimes() >= requiredTimes) {
|
||||
// Reached trigger times threshold, change to firing status
|
||||
pendingAlertMap.remove(fingerprint);
|
||||
alarmCacheManager.removePending(fingerprint);
|
||||
existingAlert.setStatus(CommonConstants.ALERT_STATUS_FIRING);
|
||||
firingAlertMap.put(fingerprint, existingAlert);
|
||||
alarmCacheManager.putFiring(fingerprint, existingAlert);
|
||||
alarmCommonReduce.reduceAndSendAlarm(existingAlert.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleRecoveredAlert(Map<String, String> fingerprints) {
|
||||
String fingerprint = calculateFingerprint(fingerprints);
|
||||
SingleAlert firingAlert = firingAlertMap.remove(fingerprint);
|
||||
String fingerprint = AlertUtil.calculateFingerprint(fingerprints);
|
||||
SingleAlert firingAlert = alarmCacheManager.removeFiring(fingerprint);
|
||||
if (firingAlert != null) {
|
||||
// todo consider multi times to tig for resolved alert
|
||||
firingAlert.setTriggerTimes(1);
|
||||
@@ -176,13 +165,7 @@ public class PeriodicAlertCalculator {
|
||||
firingAlert.setStatus(CommonConstants.ALERT_STATUS_RESOLVED);
|
||||
alarmCommonReduce.reduceAndSendAlarm(firingAlert.clone());
|
||||
}
|
||||
pendingAlertMap.remove(fingerprint);
|
||||
alarmCacheManager.removePending(fingerprint);
|
||||
}
|
||||
|
||||
private String calculateFingerprint(Map<String, String> fingerPrints) {
|
||||
List<String> keyList = fingerPrints.keySet().stream().filter(Objects::nonNull).sorted().toList();
|
||||
List<String> valueList = fingerPrints.values().stream().filter(Objects::nonNull).sorted().toList();
|
||||
return Arrays.hashCode(keyList.toArray(new String[0])) + "-"
|
||||
+ Arrays.hashCode(valueList.toArray(new String[0]));
|
||||
}
|
||||
}
|
||||
|
||||
+37
-41
@@ -18,14 +18,12 @@
|
||||
package org.apache.hertzbeat.alert.calculate;
|
||||
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -38,6 +36,7 @@ import org.apache.hertzbeat.alert.dao.SingleAlertDao;
|
||||
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
|
||||
import org.apache.hertzbeat.alert.service.AlertDefineService;
|
||||
import org.apache.hertzbeat.alert.util.AlertTemplateUtil;
|
||||
import org.apache.hertzbeat.alert.util.AlertUtil;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
@@ -45,6 +44,7 @@ import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.queue.CommonDataQueue;
|
||||
import org.apache.hertzbeat.common.util.CommonUtil;
|
||||
import org.apache.hertzbeat.common.util.JexlExpressionRunner;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
@@ -73,44 +73,47 @@ public class RealTimeAlertCalculator {
|
||||
private static final Pattern APP_PATTERN = Pattern.compile("equals\\(__app__,\"([^\"]+)\"\\)");
|
||||
private static final Pattern AVAILABLE_PATTERN = Pattern.compile("equals\\(__available__,\"([^\"]+)\"\\)");
|
||||
private static final Pattern LABEL_PATTERN = Pattern.compile("contains\\(__labels__,\\s*\"([^\"]+)\"\\)");
|
||||
private static final Pattern INSTANCE_PATTERN = Pattern.compile("equals\\(__instance__,\"(\\d+)\"\\)");
|
||||
private static final Pattern INSTANCE_PATTERN = Pattern.compile("equals\\(__instance__,\\s*\"(\\d+)\"\\)");
|
||||
private static final Pattern METRICS_PATTERN = Pattern.compile("equals\\(__metrics__,\"([^\"]+)\"\\)");
|
||||
|
||||
/**
|
||||
* The alarm in the process is triggered
|
||||
* key - labels fingerprint
|
||||
*/
|
||||
private final Map<String, SingleAlert> pendingAlertMap;
|
||||
/**
|
||||
* The not recover alert
|
||||
* key - labels fingerprint
|
||||
*/
|
||||
private final Map<String, SingleAlert> firingAlertMap;
|
||||
private final AlerterWorkerPool workerPool;
|
||||
private final CommonDataQueue dataQueue;
|
||||
private final AlertDefineService alertDefineService;
|
||||
private final AlarmCommonReduce alarmCommonReduce;
|
||||
private final AlarmCacheManager alarmCacheManager;
|
||||
|
||||
@Autowired
|
||||
public RealTimeAlertCalculator(AlerterWorkerPool workerPool, CommonDataQueue dataQueue,
|
||||
AlertDefineService alertDefineService, SingleAlertDao singleAlertDao,
|
||||
AlarmCommonReduce alarmCommonReduce) {
|
||||
AlarmCommonReduce alarmCommonReduce, AlarmCacheManager alarmCacheManager) {
|
||||
this(workerPool, dataQueue, alertDefineService, singleAlertDao, alarmCommonReduce, alarmCacheManager, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for RealTimeAlertCalculator with a toggle to control whether to start alert calculation threads.
|
||||
*
|
||||
* @param workerPool The worker pool used for concurrent alert calculation.
|
||||
* @param dataQueue The queue from which metric data is pulled and pushed.
|
||||
* @param alertDefineService The service providing alert definition rules.
|
||||
* @param singleAlertDao The DAO for fetching persisted alert states from storage.
|
||||
* @param alarmCommonReduce The component responsible for reducing and sending alerts.
|
||||
* @param start If true, the alert calculation threads will start automatically;
|
||||
* set to false to disable thread start (useful for unit testing).
|
||||
*/
|
||||
public RealTimeAlertCalculator(AlerterWorkerPool workerPool, CommonDataQueue dataQueue,
|
||||
AlertDefineService alertDefineService, SingleAlertDao singleAlertDao,
|
||||
AlarmCommonReduce alarmCommonReduce, AlarmCacheManager alarmCacheManager, boolean start) {
|
||||
this.workerPool = workerPool;
|
||||
this.dataQueue = dataQueue;
|
||||
this.alarmCommonReduce = alarmCommonReduce;
|
||||
this.alertDefineService = alertDefineService;
|
||||
this.pendingAlertMap = new ConcurrentHashMap<>(8);
|
||||
this.firingAlertMap = new ConcurrentHashMap<>(8);
|
||||
// Initialize firing stateAlertMap
|
||||
List<SingleAlert> singleAlerts = singleAlertDao.querySingleAlertsByStatus(CommonConstants.ALERT_STATUS_FIRING);
|
||||
for (SingleAlert singleAlert : singleAlerts) {
|
||||
String fingerprint = calculateFingerprint(singleAlert.getLabels());
|
||||
singleAlert.setId(null);
|
||||
firingAlertMap.put(fingerprint, singleAlert);
|
||||
this.alarmCacheManager = alarmCacheManager;
|
||||
if (start) {
|
||||
startCalculate();
|
||||
}
|
||||
startCalculate();
|
||||
}
|
||||
|
||||
private void startCalculate() {
|
||||
public void startCalculate() {
|
||||
Runnable runnable = () -> {
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
try {
|
||||
@@ -270,7 +273,7 @@ public class RealTimeAlertCalculator {
|
||||
* @param priority Current priority
|
||||
* @return Filtered alert definitions
|
||||
*/
|
||||
private List<AlertDefine> filterThresholdsByAppAndMetrics(List<AlertDefine> thresholds, String app, String metrics, Map<String, String> labels, String instance, int priority) {
|
||||
public List<AlertDefine> filterThresholdsByAppAndMetrics(List<AlertDefine> thresholds, String app, String metrics, Map<String, String> labels, String instance, int priority) {
|
||||
return thresholds.stream()
|
||||
.filter(define -> {
|
||||
if (StringUtils.isBlank(define.getExpr())) {
|
||||
@@ -329,8 +332,8 @@ public class RealTimeAlertCalculator {
|
||||
}
|
||||
|
||||
private void handleRecoveredAlert(Map<String, String> fingerprints) {
|
||||
String fingerprint = calculateFingerprint(fingerprints);
|
||||
SingleAlert firingAlert = firingAlertMap.remove(fingerprint);
|
||||
String fingerprint = AlertUtil.calculateFingerprint(fingerprints);
|
||||
SingleAlert firingAlert = alarmCacheManager.removeFiring(fingerprint);
|
||||
if (firingAlert != null) {
|
||||
// todo consider multi times to tig for resolved alert
|
||||
firingAlert.setTriggerTimes(1);
|
||||
@@ -338,13 +341,13 @@ public class RealTimeAlertCalculator {
|
||||
firingAlert.setStatus(CommonConstants.ALERT_STATUS_RESOLVED);
|
||||
alarmCommonReduce.reduceAndSendAlarm(firingAlert.clone());
|
||||
}
|
||||
pendingAlertMap.remove(fingerprint);
|
||||
alarmCacheManager.removePending(fingerprint);
|
||||
}
|
||||
|
||||
private void afterThresholdRuleMatch(long currentTimeMilli, Map<String, String> fingerPrints,
|
||||
Map<String, Object> fieldValueMap, AlertDefine define, Map<String, String> annotations) {
|
||||
String fingerprint = calculateFingerprint(fingerPrints);
|
||||
SingleAlert existingAlert = pendingAlertMap.get(fingerprint);
|
||||
String fingerprint = AlertUtil.calculateFingerprint(fingerPrints);
|
||||
SingleAlert existingAlert = alarmCacheManager.getPending(fingerprint);
|
||||
fieldValueMap.putAll(define.getLabels());
|
||||
int requiredTimes = define.getTimes() == null ? 1 : define.getTimes();
|
||||
if (existingAlert == null) {
|
||||
@@ -376,11 +379,11 @@ public class RealTimeAlertCalculator {
|
||||
// If required trigger times is 1, set to firing status directly
|
||||
if (requiredTimes <= 1) {
|
||||
newAlert.setStatus(CommonConstants.ALERT_STATUS_FIRING);
|
||||
firingAlertMap.put(fingerprint, newAlert);
|
||||
alarmCacheManager.putFiring(fingerprint, newAlert);
|
||||
alarmCommonReduce.reduceAndSendAlarm(newAlert.clone());
|
||||
} else {
|
||||
// Otherwise put into pending queue first
|
||||
pendingAlertMap.put(fingerprint, newAlert);
|
||||
alarmCacheManager.putPending(fingerprint, newAlert);
|
||||
}
|
||||
} else {
|
||||
// Update existing alert
|
||||
@@ -390,9 +393,9 @@ public class RealTimeAlertCalculator {
|
||||
// Check if required trigger times reached
|
||||
if (existingAlert.getStatus().equals(CommonConstants.ALERT_STATUS_PENDING) && existingAlert.getTriggerTimes() >= requiredTimes) {
|
||||
// Reached trigger times threshold, change to firing status
|
||||
pendingAlertMap.remove(fingerprint);
|
||||
alarmCacheManager.removePending(fingerprint);
|
||||
existingAlert.setStatus(CommonConstants.ALERT_STATUS_FIRING);
|
||||
firingAlertMap.put(fingerprint, existingAlert);
|
||||
alarmCacheManager.putFiring(fingerprint, existingAlert);
|
||||
alarmCommonReduce.reduceAndSendAlarm(existingAlert.clone());
|
||||
}
|
||||
}
|
||||
@@ -426,13 +429,6 @@ public class RealTimeAlertCalculator {
|
||||
}
|
||||
return match != null && match;
|
||||
}
|
||||
|
||||
private String calculateFingerprint(Map<String, String> fingerPrints) {
|
||||
List<String> keyList = fingerPrints.keySet().stream().filter(Objects::nonNull).sorted().toList();
|
||||
List<String> valueList = fingerPrints.values().stream().filter(Objects::nonNull).sorted().toList();
|
||||
return Arrays.hashCode(keyList.toArray(new String[0])) + "-"
|
||||
+ Arrays.hashCode(valueList.toArray(new String[0]));
|
||||
}
|
||||
|
||||
private Set<String> kvLabelsToKvStringSet(Map<String, String> labels) {
|
||||
if (labels == null || labels.isEmpty()) {
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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 org.apache.hertzbeat.common.entity.manager.Collector;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
|
||||
/**
|
||||
* Alert Collector Dao
|
||||
*/
|
||||
public interface AlertCollectorDao extends JpaRepository<Collector, Long>, JpaSpecificationExecutor<Collector> {
|
||||
|
||||
/**
|
||||
* Query collector by name
|
||||
* @param name collector name
|
||||
* @return collector
|
||||
*/
|
||||
Collector findCollectorByName(String name);
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* uptime-kuma alert content entity
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
public class UptimeKumaExternAlert {
|
||||
|
||||
private Heartbeat heartbeat;
|
||||
|
||||
private Monitor monitor;
|
||||
|
||||
private String msg;
|
||||
|
||||
/**
|
||||
* Monitor information
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class Heartbeat {
|
||||
@JsonProperty("monitorID")
|
||||
private int monitorId;
|
||||
private int status;
|
||||
private String time;
|
||||
private String msg;
|
||||
private boolean important;
|
||||
private int duration;
|
||||
private String timezone;
|
||||
private String timezoneOffset;
|
||||
private String localDateTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Monitor information
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class Monitor {
|
||||
private int id;
|
||||
private String name;
|
||||
private String description;
|
||||
private String pathName;
|
||||
private String parent;
|
||||
@JsonProperty("childrenIDs")
|
||||
private List<String> childrenIds;
|
||||
private String url;
|
||||
private String method;
|
||||
private String hostname;
|
||||
private String port;
|
||||
@JsonProperty("maxretries")
|
||||
private int maxRetries;
|
||||
private int weight;
|
||||
private boolean active;
|
||||
private boolean forceInactive;
|
||||
private String type;
|
||||
private int timeout;
|
||||
private int interval;
|
||||
private int retryInterval;
|
||||
private int resendInterval;
|
||||
private String keyword;
|
||||
private boolean invertKeyword;
|
||||
private boolean expiryNotification;
|
||||
private boolean ignoreTls;
|
||||
private boolean upsideDown;
|
||||
private int packetSize;
|
||||
@JsonProperty("maxredirects")
|
||||
private int maxRedirects;
|
||||
@JsonProperty("accepted_statuscodes")
|
||||
private List<String> acceptedStatusCodes;
|
||||
private String dnsResolveType;
|
||||
private String dnsResolveServer;
|
||||
private String dnsLastResult;
|
||||
private String dockerContainer;
|
||||
private String dockerHost;
|
||||
private String proxyId;
|
||||
private Map<String, Object> notificationIDList;
|
||||
private List<String> tags;
|
||||
private boolean maintenance;
|
||||
private String mqttTopic;
|
||||
private String mqttSuccessMessage;
|
||||
private String databaseQuery;
|
||||
private String authMethod;
|
||||
private String grpcUrl;
|
||||
private String grpcProtobuf;
|
||||
private String grpcMethod;
|
||||
private String grpcServiceName;
|
||||
private boolean grpcEnableTls;
|
||||
private String radiusCalledStationId;
|
||||
private String radiusCallingStationId;
|
||||
private String game;
|
||||
private boolean gamedigGivenPortOnly;
|
||||
private String httpBodyEncoding;
|
||||
private String jsonPath;
|
||||
private String expectedValue;
|
||||
private String kafkaProducerTopic;
|
||||
private List<String> kafkaProducerBrokers;
|
||||
private boolean kafkaProducerSsl;
|
||||
private boolean kafkaProducerAllowAutoTopicCreation;
|
||||
private String kafkaProducerMessage;
|
||||
private String screenshot;
|
||||
private boolean includeSensitiveData;
|
||||
}
|
||||
}
|
||||
+64
-66
@@ -52,86 +52,84 @@ final class DbAlertStoreHandlerImpl implements AlertStoreHandler {
|
||||
log.error("The Group Alerts is empty, ignore store");
|
||||
return groupAlert;
|
||||
}
|
||||
// 1. Find existing alert group
|
||||
GroupAlert existGroupAlert = groupAlertDao.findByGroupKey(groupAlert.getGroupKey());
|
||||
|
||||
// 2. Process individual alerts
|
||||
// Process individual alerts
|
||||
Set<String> alertFingerprints = new HashSet<>(8);
|
||||
|
||||
List<SingleAlert> originalAlerts = groupAlert.getAlerts();
|
||||
List<SingleAlert> newAlerts = new ArrayList<>();
|
||||
|
||||
|
||||
for (SingleAlert singleAlert : originalAlerts) {
|
||||
SingleAlert existAlert = singleAlertDao.findByFingerprint(singleAlert.getFingerprint());
|
||||
|
||||
if (existAlert != null) {
|
||||
// Update the existing alert with the ID and creation time from the database
|
||||
singleAlert.setId(existAlert.getId());
|
||||
singleAlert.setGmtCreate(existAlert.getGmtCreate());
|
||||
|
||||
// Status transition logic
|
||||
if (CommonConstants.ALERT_STATUS_FIRING.equals(singleAlert.getStatus())) {
|
||||
// If the alert is firing and the existing alert is not resolved, update the start time and trigger times
|
||||
if (!CommonConstants.ALERT_STATUS_RESOLVED.equals(existAlert.getStatus())) {
|
||||
synchronized (singleAlert.getFingerprint().intern()) {
|
||||
SingleAlert existAlert = singleAlertDao.findByFingerprint(singleAlert.getFingerprint());
|
||||
if (existAlert != null) {
|
||||
// Update the existing alert with the ID and creation time from the database
|
||||
singleAlert.setId(existAlert.getId());
|
||||
singleAlert.setGmtCreate(existAlert.getGmtCreate());
|
||||
// Status transition logic
|
||||
if (CommonConstants.ALERT_STATUS_FIRING.equals(singleAlert.getStatus())) {
|
||||
// If the alert is firing and the existing alert is not resolved, update the start time and trigger times
|
||||
if (!CommonConstants.ALERT_STATUS_RESOLVED.equals(existAlert.getStatus())) {
|
||||
singleAlert.setStartAt(existAlert.getStartAt());
|
||||
int triggerTimes = Optional.ofNullable(existAlert.getTriggerTimes()).orElse(1)
|
||||
+ Optional.ofNullable(singleAlert.getTriggerTimes()).orElse(1);
|
||||
singleAlert.setTriggerTimes(triggerTimes);
|
||||
}
|
||||
} else if (CommonConstants.ALERT_STATUS_RESOLVED.equals(singleAlert.getStatus())) {
|
||||
// If the alert is resolved, set the end time (if not already set) and copy other fields from the existing alert
|
||||
if (singleAlert.getEndAt() == null) {
|
||||
singleAlert.setEndAt(System.currentTimeMillis());
|
||||
}
|
||||
singleAlert.setStartAt(existAlert.getStartAt());
|
||||
int triggerTimes = Optional.ofNullable(existAlert.getTriggerTimes()).orElse(1)
|
||||
+ Optional.ofNullable(singleAlert.getTriggerTimes()).orElse(1);
|
||||
singleAlert.setTriggerTimes(triggerTimes);
|
||||
singleAlert.setActiveAt(existAlert.getActiveAt());
|
||||
singleAlert.setTriggerTimes(existAlert.getTriggerTimes());
|
||||
}
|
||||
} else if (CommonConstants.ALERT_STATUS_RESOLVED.equals(singleAlert.getStatus())) {
|
||||
// If the alert is resolved, set the end time (if not already set) and copy other fields from the existing alert
|
||||
if (singleAlert.getEndAt() == null) {
|
||||
singleAlert.setEndAt(System.currentTimeMillis());
|
||||
}
|
||||
singleAlert.setStartAt(existAlert.getStartAt());
|
||||
singleAlert.setActiveAt(existAlert.getActiveAt());
|
||||
singleAlert.setTriggerTimes(existAlert.getTriggerTimes());
|
||||
}
|
||||
SingleAlert savedSingleAlert = singleAlertDao.save(singleAlert);
|
||||
newAlerts.add(savedSingleAlert);
|
||||
alertFingerprints.add(savedSingleAlert.getFingerprint());
|
||||
}
|
||||
SingleAlert savedSingleAlert = singleAlertDao.save(singleAlert);
|
||||
newAlerts.add(savedSingleAlert);
|
||||
alertFingerprints.add(savedSingleAlert.getFingerprint());
|
||||
}
|
||||
groupAlert.setAlerts(newAlerts);
|
||||
// 3. Process resolved alerts
|
||||
if (existGroupAlert != null) {
|
||||
List<String> existFingerprints = existGroupAlert.getAlertFingerprints();
|
||||
if (existFingerprints != null) {
|
||||
alertFingerprints.addAll(existFingerprints);
|
||||
}
|
||||
// Merge group information
|
||||
groupAlert.setId(existGroupAlert.getId());
|
||||
groupAlert.setGmtCreate(existGroupAlert.getGmtCreate());
|
||||
// Merge other historical information to preserve
|
||||
Map<String, String> existCommonLabels = existGroupAlert.getCommonLabels();
|
||||
if (existCommonLabels != null) {
|
||||
Map<String, String> commonLabels = groupAlert.getCommonLabels();
|
||||
if (commonLabels != null) {
|
||||
// filter common label in commonLabels and existCommonLabels
|
||||
commonLabels = commonLabels.entrySet().stream()
|
||||
.filter(entry -> existCommonLabels.containsKey(entry.getKey()))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
groupAlert.setCommonLabels(commonLabels);
|
||||
// Find existing alert group
|
||||
synchronized (groupAlert.getGroupKey().intern()) {
|
||||
GroupAlert existGroupAlert = groupAlertDao.findByGroupKey(groupAlert.getGroupKey());
|
||||
// Process resolved alerts
|
||||
if (existGroupAlert != null) {
|
||||
List<String> existFingerprints = existGroupAlert.getAlertFingerprints();
|
||||
if (existFingerprints != null) {
|
||||
alertFingerprints.addAll(existFingerprints);
|
||||
}
|
||||
}
|
||||
Map<String, String> existCommonAnnotations = existGroupAlert.getCommonAnnotations();
|
||||
if (existCommonAnnotations != null) {
|
||||
Map<String, String> commonAnnotations = groupAlert.getCommonAnnotations();
|
||||
if (commonAnnotations != null) {
|
||||
// filter common annotation in commonAnnotations and existCommonAnnotations
|
||||
commonAnnotations = commonAnnotations.entrySet().stream()
|
||||
.filter(entry -> existCommonAnnotations.containsKey(entry.getKey()))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
groupAlert.setCommonAnnotations(commonAnnotations);
|
||||
// Merge group information
|
||||
groupAlert.setId(existGroupAlert.getId());
|
||||
groupAlert.setGmtCreate(existGroupAlert.getGmtCreate());
|
||||
// Merge other historical information to preserve
|
||||
Map<String, String> existCommonLabels = existGroupAlert.getCommonLabels();
|
||||
if (existCommonLabels != null) {
|
||||
Map<String, String> commonLabels = groupAlert.getCommonLabels();
|
||||
if (commonLabels != null) {
|
||||
// filter common label in commonLabels and existCommonLabels
|
||||
commonLabels = commonLabels.entrySet().stream()
|
||||
.filter(entry -> existCommonLabels.containsKey(entry.getKey()))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
groupAlert.setCommonLabels(commonLabels);
|
||||
}
|
||||
}
|
||||
Map<String, String> existCommonAnnotations = existGroupAlert.getCommonAnnotations();
|
||||
if (existCommonAnnotations != null) {
|
||||
Map<String, String> commonAnnotations = groupAlert.getCommonAnnotations();
|
||||
if (commonAnnotations != null) {
|
||||
// filter common annotation in commonAnnotations and existCommonAnnotations
|
||||
commonAnnotations = commonAnnotations.entrySet().stream()
|
||||
.filter(entry -> existCommonAnnotations.containsKey(entry.getKey()))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
groupAlert.setCommonAnnotations(commonAnnotations);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Save alert group
|
||||
groupAlert.setAlertFingerprints(alertFingerprints.stream().toList());
|
||||
GroupAlert savedGroupAlert = groupAlertDao.save(groupAlert);
|
||||
savedGroupAlert.setAlerts(groupAlert.getAlerts());
|
||||
return savedGroupAlert;
|
||||
}
|
||||
|
||||
// 4. Save alert group
|
||||
groupAlert.setAlertFingerprints(alertFingerprints.stream().toList());
|
||||
GroupAlert savedGroupAlert = groupAlertDao.save(groupAlert);
|
||||
savedGroupAlert.setAlerts(groupAlert.getAlerts());
|
||||
return savedGroupAlert;
|
||||
}
|
||||
}
|
||||
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.service.impl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.dto.UptimeKumaExternAlert;
|
||||
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
|
||||
import org.apache.hertzbeat.alert.service.ExternAlertService;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* uptime-kuma external alarm service impl
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class UptimeKumaExternAlertServiceImpl implements ExternAlertService {
|
||||
|
||||
@Autowired
|
||||
private AlarmCommonReduce alarmCommonReduce;
|
||||
|
||||
@Override
|
||||
public void addExternAlert(String content) {
|
||||
UptimeKumaExternAlert alert = JsonUtil.fromJson(content, UptimeKumaExternAlert.class);
|
||||
if (alert == null) {
|
||||
log.warn("parse extern alert content failed! content: {}", content);
|
||||
return;
|
||||
}
|
||||
SingleAlert singleAlert = new UptimeKumaAlertConverter().convert(alert);
|
||||
alarmCommonReduce.reduceAndSendAlarm(singleAlert);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converter: UptimeKuma alert to SingleAlert
|
||||
*/
|
||||
public static class UptimeKumaAlertConverter {
|
||||
|
||||
/**
|
||||
* Convert UptimeKuma alert to SingleAlert
|
||||
*/
|
||||
public SingleAlert convert(UptimeKumaExternAlert alert) {
|
||||
// build basic info
|
||||
SingleAlert.SingleAlertBuilder builder = SingleAlert.builder()
|
||||
.status(convertStatus(alert.getHeartbeat().getStatus()))
|
||||
.startAt(parseTime(alert.getHeartbeat().getTime()))
|
||||
.activeAt(parseTime(alert.getHeartbeat().getTime()))
|
||||
.triggerTimes(1);
|
||||
|
||||
// build labels
|
||||
Map<String, String> labels = new HashMap<>();
|
||||
labels.put("__source__", "uptime_kuma");
|
||||
labels.put("monitor_id", String.valueOf(alert.getMonitor().getId()));
|
||||
labels.put("monitor_name", alert.getMonitor().getName());
|
||||
|
||||
// build annotations
|
||||
Map<String, String> annotations = new HashMap<>();
|
||||
annotations.put("description", alert.getMonitor().getDescription());
|
||||
annotations.put("message", alert.getHeartbeat().getMsg());
|
||||
annotations.put("important", String.valueOf(alert.getHeartbeat().isImportant()));
|
||||
|
||||
return builder
|
||||
.labels(labels)
|
||||
.annotations(annotations)
|
||||
.content(buildContent(alert))
|
||||
.build();
|
||||
}
|
||||
|
||||
private String buildContent(UptimeKumaExternAlert alert) {
|
||||
return String.format("Monitor [%s] %s: %s",
|
||||
alert.getMonitor().getName(),
|
||||
alert.getMonitor().getDescription(),
|
||||
alert.getHeartbeat().getMsg());
|
||||
}
|
||||
|
||||
private String convertStatus(int status) {
|
||||
// uptime kuma status: 1-up, 0-down, 2-pending
|
||||
return status == 1 ? CommonConstants.ALERT_STATUS_RESOLVED : CommonConstants.ALERT_STATUS_FIRING;
|
||||
}
|
||||
|
||||
private Long parseTime(String timeStr) {
|
||||
try {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
return sdf.parse(timeStr).getTime();
|
||||
} catch (ParseException e) {
|
||||
log.error("Failed to parse time: {}", timeStr);
|
||||
throw new IllegalArgumentException("Failed to parse time: " + timeStr, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String supportSource() {
|
||||
return "uptime-kuma";
|
||||
}
|
||||
}
|
||||
+5
-4
@@ -21,6 +21,7 @@ import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Alarm template keyword matching replacement engine tool
|
||||
@@ -38,11 +39,11 @@ public final class AlertTemplateUtil {
|
||||
private static final Pattern PATTERN = Pattern.compile("\\$\\{(\\w+)\\}");
|
||||
|
||||
public static String render(String template, Map<String, Object> replaceData) {
|
||||
if (template == null) {
|
||||
return null;
|
||||
if (!StringUtils.hasText(template)) {
|
||||
return template;
|
||||
}
|
||||
if (replaceData == null) {
|
||||
log.warn("The replaceData is null.");
|
||||
log.warn("The render replace data is null.");
|
||||
return template;
|
||||
}
|
||||
try {
|
||||
@@ -50,7 +51,7 @@ public final class AlertTemplateUtil {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
while (matcher.find()) {
|
||||
Object objectValue = replaceData.getOrDefault(matcher.group(1), "NullValue");
|
||||
String value = objectValue.toString();
|
||||
String value = objectValue != null ? objectValue.toString() : "NullValue";
|
||||
matcher.appendReplacement(builder, Matcher.quoteReplacement(value));
|
||||
}
|
||||
matcher.appendTail(builder);
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* alert util
|
||||
*/
|
||||
public class AlertUtil {
|
||||
|
||||
/**
|
||||
* calculate fingerprint
|
||||
* @param fingerPrints finger prints
|
||||
*/
|
||||
public static String calculateFingerprint(Map<String, String> fingerPrints) {
|
||||
List<String> keyList = fingerPrints.keySet().stream().filter(Objects::nonNull).sorted().toList();
|
||||
List<String> valueList = fingerPrints.values().stream().filter(Objects::nonNull).sorted().toList();
|
||||
return Arrays.hashCode(keyList.toArray(new String[0])) + "-"
|
||||
+ Arrays.hashCode(valueList.toArray(new String[0]));
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,8 @@
|
||||
# limitations under the License.
|
||||
|
||||
alerter.availability.recover = Availability Alert Resolved, Monitor Status Normal Now
|
||||
alerter.availability.collector.recover = Collector Availability Alert Resolved, The collector is online
|
||||
alerter.availability.collector.offline = Collector Availability Alert Notify, The collector is offline
|
||||
alerter.alarm.recover = Alert Resolved Notice
|
||||
alerter.notify.title = HertzBeat Alert Notify
|
||||
alerter.notify.target = Monitor Target
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
# limitations under the License.
|
||||
|
||||
alerter.availability.recover = 可用性告警恢复通知, 任务状态已恢复正常
|
||||
alerter.availability.collector.recover = 采集器可用性恢复通知,采集器已上线
|
||||
alerter.availability.collector.offline = 采集器可用性告警通知,采集器已下线
|
||||
alerter.alarm.recover = 告警恢复通知
|
||||
alerter.notify.title = HertzBeat告警通知
|
||||
alerter.notify.target = 告警目标对象
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
|
||||
alerter.availability.recover = 可用性警報已解決,監視狀態現在正常
|
||||
alerter.alarm.recover = 警報解決通知
|
||||
alerter.availability.collector.recover = 采集器可用性恢復通知,采集器已上線
|
||||
alerter.availability.collector.offline = 采集器可用性告警通知,采集器已下線
|
||||
alerter.notify.title = HertzBeat 警報通知
|
||||
alerter.notify.target = 監視目標
|
||||
alerter.notify.monitorId = 監視 ID
|
||||
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.calculate;
|
||||
|
||||
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
|
||||
import org.apache.hertzbeat.alert.service.DataSourceService;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.NullAndEmptySource;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertAll;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Test case for {@link PeriodicAlertCalculator}
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PeriodicAlertCalculatorTest {
|
||||
|
||||
@Mock
|
||||
private DataSourceService dataSourceService;
|
||||
|
||||
@Mock
|
||||
private AlarmCommonReduce alarmCommonReduce;
|
||||
|
||||
@Mock
|
||||
private AlarmCacheManager alarmCacheManager;
|
||||
|
||||
@InjectMocks
|
||||
private PeriodicAlertCalculator periodicAlertCalculator;
|
||||
|
||||
private AlertDefine rule;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
rule = AlertDefine.builder()
|
||||
.id(1L)
|
||||
.name("high_cpu_usage")
|
||||
.type("periodic")
|
||||
.expr("usage>90")
|
||||
.times(1)
|
||||
.labels(Map.of("team", "test-team", "priority", "P1"))
|
||||
.annotations(Map.of("summary", "High CPU usage detected"))
|
||||
.template("High CPU usage (${__value__}%)")
|
||||
.datasource("PROMETHEUS")
|
||||
.enable(true)
|
||||
.period(300)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTriggerAlertWhenMatchThreshold() {
|
||||
reset(alarmCacheManager);
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("__value__", 95.0); // Non-null, matched with threshold
|
||||
result.put("__timestamp__", System.currentTimeMillis());
|
||||
when(dataSourceService.calculate(anyString(), anyString())).thenReturn(List.of(result));
|
||||
when(alarmCacheManager.getPending(anyString())).thenReturn(null);
|
||||
periodicAlertCalculator.calculate(rule);
|
||||
// Verify that putFiring is called
|
||||
ArgumentCaptor<String> idCaptor = ArgumentCaptor.forClass(String.class);
|
||||
ArgumentCaptor<SingleAlert> alertCaptor = ArgumentCaptor.forClass(SingleAlert.class);
|
||||
verify(alarmCacheManager).putFiring(idCaptor.capture(), alertCaptor.capture());
|
||||
// Assertion alarm status and content
|
||||
SingleAlert alert = alertCaptor.getValue();
|
||||
assertAll(() -> assertEquals(CommonConstants.ALERT_STATUS_FIRING, alert.getStatus()),
|
||||
() -> assertEquals("High CPU usage (95.0%)", alert.getContent()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNoTriggerAlertWhenNotMatchThreshold() {
|
||||
reset(alarmCacheManager);
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("__value__", null); // null, not matched with threshold
|
||||
result.put("__timestamp__", System.currentTimeMillis());
|
||||
when(dataSourceService.calculate(anyString(), anyString())).thenReturn(List.of(result));
|
||||
periodicAlertCalculator.calculate(rule);
|
||||
verify(alarmCacheManager, times(0)).putFiring(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testResolveFiringAlert() {
|
||||
reset(alarmCacheManager);
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("__value__", null); // null, not matched with threshold
|
||||
result.put("__timestamp__", System.currentTimeMillis());
|
||||
SingleAlert pendingAlert = SingleAlert.builder()
|
||||
.status(CommonConstants.ALERT_STATUS_FIRING)
|
||||
.triggerTimes(2).startAt(System.currentTimeMillis() - 60000)
|
||||
.activeAt(System.currentTimeMillis() - 30000)
|
||||
.build();
|
||||
when(alarmCacheManager.removeFiring(anyString())).thenReturn(pendingAlert);
|
||||
when(dataSourceService.calculate(anyString(), anyString())).thenReturn(List.of(result));
|
||||
periodicAlertCalculator.calculate(rule);
|
||||
ArgumentCaptor<SingleAlert> resolvedCaptor = ArgumentCaptor.forClass(SingleAlert.class);
|
||||
verify(alarmCommonReduce).reduceAndSendAlarm(resolvedCaptor.capture());
|
||||
SingleAlert resolvedAlert = resolvedCaptor.getValue();
|
||||
assertAll(() -> assertEquals(CommonConstants.ALERT_STATUS_RESOLVED, resolvedAlert.getStatus()),
|
||||
() -> assertNotNull(resolvedAlert.getEndAt()),
|
||||
() -> assertTrue(resolvedAlert.getEndAt() > resolvedAlert.getStartAt()));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@NullAndEmptySource
|
||||
void testSkipWhenDataSourceResultInvalid(List<Map<String, Object>> results) {
|
||||
when(dataSourceService.calculate(anyString(), anyString())).thenReturn(results);
|
||||
periodicAlertCalculator.calculate(rule);
|
||||
verifyNoInteractions(alarmCacheManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHandleDisabledRule() {
|
||||
rule.setEnable(false);
|
||||
periodicAlertCalculator.calculate(rule);
|
||||
verifyNoInteractions(dataSourceService);
|
||||
}
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import org.apache.hertzbeat.alert.AlerterWorkerPool;
|
||||
import org.apache.hertzbeat.alert.dao.SingleAlertDao;
|
||||
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
|
||||
import org.apache.hertzbeat.alert.service.AlertDefineService;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
|
||||
import org.apache.hertzbeat.common.queue.CommonDataQueue;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
class RealTimeAlertCalculatorTest {
|
||||
|
||||
private RealTimeAlertCalculator calculator;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
AlerterWorkerPool mockPool = Mockito.mock(AlerterWorkerPool.class);
|
||||
CommonDataQueue mockQueue = Mockito.mock(CommonDataQueue.class);
|
||||
AlertDefineService mockAlertDefineService = Mockito.mock(AlertDefineService.class);
|
||||
SingleAlertDao mockDao = Mockito.mock(SingleAlertDao.class);
|
||||
AlarmCommonReduce mockReduce = Mockito.mock(AlarmCommonReduce.class);
|
||||
AlarmCacheManager alarmCacheManager = Mockito.mock(AlarmCacheManager.class);
|
||||
|
||||
Mockito.when(mockDao.querySingleAlertsByStatus(Mockito.anyString()))
|
||||
.thenReturn(Collections.emptyList());
|
||||
|
||||
calculator = new RealTimeAlertCalculator(mockPool, mockQueue, mockAlertDefineService, mockDao, mockReduce, alarmCacheManager, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFilterThresholdsByAppAndMetrics_withInstanceExpr_HasSpace() {
|
||||
String app = "redis";
|
||||
String instanceId = "501045327364864";
|
||||
int priority = 0;
|
||||
|
||||
AlertDefine matchDefine = new AlertDefine();
|
||||
matchDefine.setExpr("equals(__app__,\"redis\") && equals(__instance__, \"501045327364864\")");
|
||||
|
||||
AlertDefine unmatchDefine = new AlertDefine();
|
||||
unmatchDefine.setExpr("equals(__app__,\"redis\") && equals(__instance__, \"999999999\")");
|
||||
|
||||
List<AlertDefine> allDefines = Arrays.asList(matchDefine, unmatchDefine);
|
||||
|
||||
List<AlertDefine> filtered = calculator.filterThresholdsByAppAndMetrics(allDefines, app, "", Map.of(), instanceId, priority);
|
||||
|
||||
// It should filter out 999999999.
|
||||
assertEquals(1, filtered.size());
|
||||
assertEquals("equals(__app__,\"redis\") && equals(__instance__, \"501045327364864\")",
|
||||
filtered.get(0).getExpr());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFilterThresholdsByAppAndMetrics_withInstanceExpr_NoSpace() {
|
||||
String app = "redis";
|
||||
String instanceId = "501045327364864";
|
||||
int priority = 0;
|
||||
|
||||
AlertDefine matchDefine = new AlertDefine();
|
||||
matchDefine.setExpr("equals(__app__,\"redis\") && equals(__instance__,\"501045327364864\")");
|
||||
|
||||
AlertDefine unmatchDefine = new AlertDefine();
|
||||
unmatchDefine.setExpr("equals(__app__,\"redis\") && equals(__instance__,\"999999999\")");
|
||||
|
||||
List<AlertDefine> allDefines = Arrays.asList(matchDefine, unmatchDefine);
|
||||
|
||||
List<AlertDefine> filtered = calculator.filterThresholdsByAppAndMetrics(allDefines, app, "", Map.of(), instanceId, priority);
|
||||
|
||||
// It should filter out 999999999.
|
||||
assertEquals(1, filtered.size());
|
||||
assertEquals("equals(__app__,\"redis\") && equals(__instance__,\"501045327364864\")",
|
||||
filtered.get(0).getExpr());
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -57,6 +57,7 @@ class DbAlertStoreHandlerImplTest {
|
||||
public void setUp() {
|
||||
groupAlert = new GroupAlert();
|
||||
singleAlert = new SingleAlert();
|
||||
singleAlert.setFingerprint("test-fingerprint");
|
||||
List<SingleAlert> alerts = new ArrayList<>();
|
||||
alerts.add(singleAlert);
|
||||
groupAlert.setAlerts(alerts);
|
||||
@@ -77,11 +78,13 @@ class DbAlertStoreHandlerImplTest {
|
||||
when(groupAlertDao.findByGroupKey(groupKey)).thenReturn(null);
|
||||
|
||||
SingleAlert savedSingleAlert = new SingleAlert();
|
||||
savedSingleAlert.setFingerprint("test-finger");
|
||||
when(singleAlertDao.save(any(SingleAlert.class))).thenReturn(savedSingleAlert);
|
||||
|
||||
GroupAlert savedGroupAlert = new GroupAlert();
|
||||
savedGroupAlert.setGroupKey(groupKey);
|
||||
when(groupAlertDao.save(any(GroupAlert.class))).thenReturn(savedGroupAlert);
|
||||
|
||||
|
||||
dbAlertStoreHandler.store(groupAlert);
|
||||
|
||||
verify(singleAlertDao).save(any(SingleAlert.class));
|
||||
|
||||
+2
@@ -383,6 +383,8 @@ public class JdbcCommonCollect extends AbstractCollect {
|
||||
case "oracle" -> "jdbc:oracle:thin:@" + host + ":" + port
|
||||
+ "/" + (jdbcProtocol.getDatabase() == null ? "" : jdbcProtocol.getDatabase());
|
||||
case "dm" -> "jdbc:dm://" + host + ":" + port;
|
||||
case "testcontainers" -> "jdbc:tc:" + host + ":" + port
|
||||
+ ":///" + (jdbcProtocol.getDatabase() == null ? "" : jdbcProtocol.getDatabase()) + "?user=root&password=root";
|
||||
default -> throw new IllegalArgumentException("Not support database platform: " + jdbcProtocol.getPlatform());
|
||||
};
|
||||
}
|
||||
|
||||
+2
@@ -156,6 +156,8 @@ public class PrometheusAutoCollectImpl {
|
||||
}
|
||||
|
||||
private List<CollectRep.MetricsData> parseResponseByPrometheusExporter(InputStream inputStream, CollectRep.MetricsData.Builder builder) throws IOException {
|
||||
long endTime = System.currentTimeMillis();
|
||||
builder.setTime(endTime);
|
||||
Map<String, MetricFamily> metricFamilyMap = OnlineParser.parseMetrics(inputStream);
|
||||
List<CollectRep.MetricsData> metricsDataList = new LinkedList<>();
|
||||
if (metricFamilyMap == null) {
|
||||
|
||||
+46
-26
@@ -38,7 +38,10 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
public class OnlineParser {
|
||||
|
||||
private static final Map<Integer, Integer> escapeMap = new HashMap<>(8);
|
||||
|
||||
|
||||
private static final char RIGHT_BRACKET = '}';
|
||||
private static final char LEFT_BRACKET = '{';
|
||||
|
||||
static {
|
||||
escapeMap.put((int) 'n', (int) '\n');
|
||||
escapeMap.put((int) 'b', (int) '\b');
|
||||
@@ -94,14 +97,14 @@ public class OnlineParser {
|
||||
}
|
||||
|
||||
private CharChecker maybeLeftBracket() {
|
||||
if (i == '{') {
|
||||
if (i == LEFT_BRACKET) {
|
||||
satisfied = true;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private CharChecker maybeRightBracket() {
|
||||
if (i == '}') {
|
||||
if (i == RIGHT_BRACKET) {
|
||||
satisfied = true;
|
||||
}
|
||||
return this;
|
||||
@@ -264,35 +267,52 @@ public class OnlineParser {
|
||||
}
|
||||
}
|
||||
|
||||
private static CharChecker parseLabels(InputStream inputStream, StringBuilder stringBuilder, List<MetricFamily.Label> labelList) throws IOException, FormatException {
|
||||
|
||||
/**
|
||||
* parse single label like label_name="label_value"
|
||||
*/
|
||||
private static CharChecker parseLabel(InputStream inputStream, StringBuilder stringBuilder, List<MetricFamily.Label> labelList) throws IOException, FormatException {
|
||||
int i;
|
||||
MetricFamily.Label label = new MetricFamily.Label();
|
||||
i = skipSpaces(inputStream).getInt();
|
||||
if (i == RIGHT_BRACKET) {
|
||||
return new CharChecker(i);
|
||||
}
|
||||
stringBuilder.append((char) i);
|
||||
i = parseLabelName(inputStream, stringBuilder).maybeSpace().maybeEqualsSign().noElse();
|
||||
label.setName(stringBuilder.toString());
|
||||
stringBuilder.delete(0, stringBuilder.length());
|
||||
if (i == ' ') {
|
||||
skipSpaces(inputStream).maybeEqualsSign().noElse();
|
||||
}
|
||||
|
||||
skipSpaces(inputStream).maybeQuotationMark().noElse();
|
||||
parseLabelValue(inputStream, stringBuilder).maybeQuotationMark().noElse();
|
||||
String labelValue = stringBuilder.toString();
|
||||
if (!labelValue.equals(new String(labelValue.getBytes(StandardCharsets.UTF_8)))) {
|
||||
throw new FormatException();
|
||||
}
|
||||
label.setValue(labelValue);
|
||||
stringBuilder.delete(0, stringBuilder.length());
|
||||
labelList.add(label);
|
||||
return new CharChecker(i);
|
||||
}
|
||||
|
||||
|
||||
private static void parseLabels(InputStream inputStream, StringBuilder stringBuilder, List<MetricFamily.Label> labelList) throws IOException, FormatException {
|
||||
int i;
|
||||
while (true) {
|
||||
MetricFamily.Label label = new MetricFamily.Label();
|
||||
i = skipSpaces(inputStream).getInt();
|
||||
stringBuilder.append((char) i);
|
||||
i = parseLabelName(inputStream, stringBuilder).maybeSpace().maybeEqualsSign().noElse();
|
||||
label.setName(stringBuilder.toString());
|
||||
stringBuilder.delete(0, stringBuilder.length());
|
||||
if (i == ' ') {
|
||||
skipSpaces(inputStream).maybeEqualsSign().noElse();
|
||||
// deal with labels like {label1="aaa",label2=bbb",}
|
||||
CharChecker charChecker = parseLabel(inputStream, stringBuilder, labelList);
|
||||
if (charChecker.i == RIGHT_BRACKET) {
|
||||
return;
|
||||
}
|
||||
|
||||
skipSpaces(inputStream).maybeQuotationMark().noElse();
|
||||
parseLabelValue(inputStream, stringBuilder).maybeQuotationMark().noElse();
|
||||
String labelValue = stringBuilder.toString();
|
||||
if (!labelValue.equals(new String(labelValue.getBytes(StandardCharsets.UTF_8)))) {
|
||||
throw new FormatException();
|
||||
}
|
||||
label.setValue(labelValue);
|
||||
stringBuilder.delete(0, stringBuilder.length());
|
||||
|
||||
//deal with labels like {label1="aaa",label2=bbb"}
|
||||
i = skipSpaces(inputStream).maybeSpace().maybeComma().maybeRightBracket().noElse();
|
||||
labelList.add(label);
|
||||
if (i == '}') {
|
||||
break;
|
||||
if (i == RIGHT_BRACKET) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
return new CharChecker(i);
|
||||
}
|
||||
|
||||
private static CharChecker parseMetric(InputStream inputStream, Map<String, MetricFamily> metricFamilyMap, StringBuilder stringBuilder) throws IOException, FormatException {
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ public class HttpSdCollectImpl extends AbstractCollect {
|
||||
@Override
|
||||
public void collect(CollectRep.MetricsData.Builder builder, Metrics metrics) {
|
||||
List<ConnectionConfig> configList = Lists.newArrayList();
|
||||
HttpUriRequest request = RequestBuilder.get().setUri(metrics.getSdProtocol().getSdSource()).build();
|
||||
HttpUriRequest request = RequestBuilder.get().setUri(metrics.getHttp_sd().getUrl()).build();
|
||||
|
||||
try (CloseableHttpResponse response = CommonHttpClient.getHttpClient().execute(request)) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
|
||||
+8
-2
@@ -48,6 +48,13 @@ class OnlineParserTest {
|
||||
String str = """
|
||||
# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.
|
||||
# TYPE go_gc_duration_seconds summary
|
||||
jvm_gc_pause_seconds_count{action="end of major GC",cause="Metadata GC Threshold",} 1.0
|
||||
jvm_gc_pause_seconds_sum{action="end of major GC",cause="Metadata GC Threshold",} 0.139
|
||||
jvm_gc_pause_seconds_count{action="end of minor GC",cause="Metadata GC Threshold",} 1.0
|
||||
jvm_gc_pause_seconds_sum{action="end of minor GC",cause="Metadata GC Threshold",} 0.02
|
||||
jvm_gc_pause_seconds_count{action="end of minor GC",cause="Allocation Failure",} 5.0
|
||||
jvm_gc_pause_seconds_sum{action="end of minor GC",cause="Allocation Failure",} 0.082
|
||||
go_gc_duration_seconds{quantile="0"} 2.0209e-05
|
||||
go_gc_duration_seconds{quantile="0"} 2.0209e-05
|
||||
go_gc_duration_seconds{quantile="0.25"} 6.6917e-05
|
||||
go_gc_duration_seconds{quantile="0.5"} -Inf
|
||||
@@ -87,7 +94,6 @@ class OnlineParserTest {
|
||||
fail("parse failed, different result from two parser.");
|
||||
}
|
||||
MetricFamily metricFamily1 = metricFamilyMap1.get(metricFamilyName);
|
||||
assertEquals(metricFamily1.getName(), metricFamily1.getName());
|
||||
Set<Double> metricValueSet = metricFamily2.getMetricList().stream().map(MetricFamily.Metric::getValue).collect(Collectors.toSet());
|
||||
metricFamily1.getMetricList().forEach(metric -> {
|
||||
// this is for something different between two algorithms above, and both of them is current on this parsing behavior.
|
||||
@@ -99,4 +105,4 @@ class OnlineParserTest {
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -281,7 +281,8 @@ public class CommonDispatcher implements MetricsTaskDispatch, CollectDataDispatc
|
||||
}
|
||||
// If it is an asynchronous periodic cyclic task, directly response the collected data
|
||||
if (job.isSd()) {
|
||||
commonDataQueue.sendServiceDiscoveryData(metricsData);
|
||||
CollectRep.MetricsData sdMetricsData = CollectRep.MetricsData.newBuilder(metricsData).build();
|
||||
commonDataQueue.sendServiceDiscoveryData(sdMetricsData);
|
||||
}
|
||||
commonDataQueue.sendMetricsData(metricsData);
|
||||
} else {
|
||||
|
||||
+1
-1
@@ -126,7 +126,7 @@ public interface DispatchConstants {
|
||||
/**
|
||||
* protocol http sd
|
||||
*/
|
||||
String PROTOCOL_HTTP_SD = "httpsd";
|
||||
String PROTOCOL_HTTP_SD = "http_sd";
|
||||
/**
|
||||
* protocol redfish
|
||||
*/
|
||||
|
||||
+2
-2
@@ -17,10 +17,10 @@
|
||||
|
||||
package org.apache.hertzbeat.collector.dispatch.timer;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -87,7 +87,7 @@ public class TimerDispatcher implements TimerDispatch, DisposableBean {
|
||||
for (Metrics metric : addJob.getMetrics()) {
|
||||
metric.setInterval(0L);
|
||||
}
|
||||
addJob.setIntervals(new LinkedList<>(List.of(0L)));
|
||||
addJob.setIntervals(new ConcurrentLinkedDeque<>(List.of(0L)));
|
||||
Timeout timeout = wheelTimer.newTimeout(timerJob, addJob.getInterval(), TimeUnit.SECONDS);
|
||||
currentTempTaskMap.put(addJob.getId(), timeout);
|
||||
eventListeners.put(addJob.getId(), eventListener);
|
||||
|
||||
+4
-107
@@ -73,14 +73,14 @@ public interface CommonConstants {
|
||||
byte MONITOR_DOWN_CODE = 0x02;
|
||||
|
||||
/**
|
||||
* Monitor bind type, 0: monitor that auto-created by sd
|
||||
* scrape type static
|
||||
*/
|
||||
byte MONITOR_BIND_TYPE_SD_SUB_MONITOR = 0x00;
|
||||
String SCRAPE_STATIC = "static";
|
||||
|
||||
/**
|
||||
* Monitor bind type, 1: the main monitor of sd
|
||||
* scrape type
|
||||
*/
|
||||
byte MONITOR_BIND_TYPE_SD_MAIN_MONITOR = 0x01;
|
||||
String SCRAPE_HTTP_SD = "http_sd";
|
||||
|
||||
/**
|
||||
* label key: instance
|
||||
@@ -192,11 +192,6 @@ public interface CommonConstants {
|
||||
*/
|
||||
String PROM_VALUE = "value";
|
||||
|
||||
/**
|
||||
* Monitor total availability metrics
|
||||
*/
|
||||
String AVAILABILITY = "availability";
|
||||
|
||||
/**
|
||||
* Parameter Type Number
|
||||
*/
|
||||
@@ -222,104 +217,6 @@ public interface CommonConstants {
|
||||
*/
|
||||
byte PARAM_TYPE_ARRAY = 4;
|
||||
|
||||
/**
|
||||
* Authentication type Account password
|
||||
*/
|
||||
byte AUTH_TYPE_PASSWORD = 1;
|
||||
|
||||
/**
|
||||
* Authentication type GitHub three-party login
|
||||
*/
|
||||
byte AUTH_TYPE_GITHUB = 2;
|
||||
|
||||
/**
|
||||
* Authentication type WeChat three-party login
|
||||
*/
|
||||
byte AUTH_TYPE_WEIXIN = 3;
|
||||
|
||||
/**
|
||||
* Authentication type GITEE three-party login
|
||||
*/
|
||||
byte AUTH_TYPE_GITEE = 5;
|
||||
|
||||
/**
|
||||
* inner default label key __instance__
|
||||
*/
|
||||
String LABEL_INNER_KEY_INSTANCE = "__instance__";
|
||||
|
||||
/**
|
||||
* Inside the tag: monitorId Monitor task ID
|
||||
*/
|
||||
String TAG_MONITOR_ID = "monitorId";
|
||||
|
||||
/**
|
||||
* Inside the tag: monitorName Task name
|
||||
*/
|
||||
String TAG_MONITOR_NAME = "monitorName";
|
||||
|
||||
/**
|
||||
* Inside the tag: indicate that this monitor is auto-created by main monitor
|
||||
*/
|
||||
String TAG_AUTO_CREATED = "autoCreated";
|
||||
|
||||
/**
|
||||
* Inside the tag: indicate that this monitor is a main monitor which provides service discovery
|
||||
*/
|
||||
String TAG_SD_MAIN_MONITOR = "sdMainMonitor";
|
||||
|
||||
/**
|
||||
* Inside the tag: monitorHost Task host
|
||||
*/
|
||||
String TAG_MONITOR_HOST = "monitorHost";
|
||||
|
||||
/**
|
||||
* Inside the tag: policyId Alarm threshold rule ID
|
||||
*/
|
||||
String TAG_THRESHOLD_ID = "thresholdId";
|
||||
|
||||
/**
|
||||
* Inside the tag: app Type of monitoring
|
||||
*/
|
||||
String TAG_MONITOR_APP = "app";
|
||||
|
||||
/**
|
||||
* Inside the tag: metrics
|
||||
*/
|
||||
String TAG_METRICS = "metrics";
|
||||
|
||||
/**
|
||||
* Inside the tag: metric
|
||||
*/
|
||||
String TAG_METRIC = "metric";
|
||||
|
||||
/**
|
||||
* Inside the tag: code
|
||||
*/
|
||||
String TAG_CODE = "code";
|
||||
|
||||
/**
|
||||
* Tag Type: Auto-generated
|
||||
*/
|
||||
byte TAG_TYPE_AUTO_GENERATE = 0;
|
||||
/**
|
||||
* Tag Type: User-generated
|
||||
*/
|
||||
byte TAG_TYPE_USER_GENERATE = 1;
|
||||
/**
|
||||
* Tag Type: System preset
|
||||
*/
|
||||
byte TAG_TYPE_SYSTEM_PRESET = 2;
|
||||
|
||||
/**
|
||||
* notice_period type Type field, daily type
|
||||
*/
|
||||
int NOTICE_PERIOD_DAILY = 0;
|
||||
|
||||
/**
|
||||
* key is receiver.id, value is noticePeriod cache key prefix
|
||||
*/
|
||||
String RECEIVER_NOTICE_PERIOD_CACHE_PREFIX = "receiver_notice_period:";
|
||||
|
||||
/**
|
||||
* cache key notice_rule
|
||||
*/
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@ public class GroupAlert {
|
||||
|
||||
@Schema(title = "Alert Fingerprints", example = "[\"dxsdfdsf\"]")
|
||||
@Convert(converter = JsonStringListAttributeConverter.class)
|
||||
@Column(length = 2048)
|
||||
@Column(length = 8192)
|
||||
private List<String> alertFingerprints;
|
||||
|
||||
@Schema(title = "The creator of this record", example = "tom")
|
||||
|
||||
@@ -28,6 +28,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
@@ -114,7 +115,7 @@ public class Job {
|
||||
/**
|
||||
* Refresh time list for one cycle of the job
|
||||
*/
|
||||
private LinkedList<Long> intervals;
|
||||
private ConcurrentLinkedDeque<Long> intervals;
|
||||
/**
|
||||
* Whether it is a recurring periodic task true is yes, false is no
|
||||
*/
|
||||
@@ -328,7 +329,7 @@ public class Job {
|
||||
* @param metricsIntervals A unique list composed of intervals for all metrics
|
||||
* Generate a list of refresh intervals for metric collection
|
||||
*/
|
||||
public void generateMetricsIntervals(List<Long> metricsIntervals) {
|
||||
public synchronized void generateMetricsIntervals(List<Long> metricsIntervals) {
|
||||
// 1. To find the least common multiple (LCM) of all metric refresh intervals
|
||||
long lcm = lcm(metricsIntervals);
|
||||
List<Long> refreshTimes = new LinkedList<>();
|
||||
@@ -348,14 +349,16 @@ public class Job {
|
||||
for (int i = 1; i < refreshTimes.size(); i++) {
|
||||
intervals.add(refreshTimes.get(i) - refreshTimes.get(i - 1));
|
||||
}
|
||||
setIntervals(intervals);
|
||||
setIntervals(new ConcurrentLinkedDeque<>(intervals));
|
||||
}
|
||||
|
||||
public long getInterval() {
|
||||
if (!CollectionUtils.isEmpty(getIntervals())) {
|
||||
long interval = getIntervals().remove();
|
||||
getIntervals().add(interval);
|
||||
return interval;
|
||||
public synchronized long getInterval() {
|
||||
if (!CollectionUtils.isEmpty(this.intervals)) {
|
||||
Long interval = this.intervals.removeFirst();
|
||||
if (interval != null) {
|
||||
this.intervals.addLast(interval);
|
||||
return interval;
|
||||
}
|
||||
}
|
||||
return getDefaultInterval();
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.DnsProtocol;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.FtpProtocol;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.HttpProtocol;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.HttpSdProtocol;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.ModbusProtocol;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.PlcProtocol;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.RegistryProtocol;
|
||||
@@ -63,7 +64,6 @@ import org.apache.hertzbeat.common.entity.job.protocol.TelnetProtocol;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.UdpProtocol;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.WebsocketProtocol;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.entity.sd.ServiceDiscoveryProtocol;
|
||||
|
||||
/**
|
||||
* Details of the monitoring metrics collected
|
||||
@@ -74,6 +74,7 @@ import org.apache.hertzbeat.common.entity.sd.ServiceDiscoveryProtocol;
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
@Slf4j
|
||||
@SuppressWarnings("checkstyle:MemberName")
|
||||
public class Metrics {
|
||||
|
||||
/**
|
||||
@@ -260,9 +261,9 @@ public class Metrics {
|
||||
*/
|
||||
private KafkaProtocol kclient;
|
||||
/**
|
||||
* Collect sd data protocol
|
||||
* http sd protocol
|
||||
*/
|
||||
private ServiceDiscoveryProtocol sdProtocol;
|
||||
private HttpSdProtocol http_sd;
|
||||
/**
|
||||
* Monitoring configuration information using the public plc protocol
|
||||
*/
|
||||
|
||||
+2
-1
@@ -21,7 +21,8 @@ package org.apache.hertzbeat.common.entity.job.protocol;
|
||||
* Define common field method for each protocol in {@link org.apache.hertzbeat.common.entity.job.Metrics}
|
||||
*/
|
||||
public interface CommonRequestProtocol {
|
||||
|
||||
void setHost(String host);
|
||||
|
||||
void setPort(String port);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-8
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.common.entity.manager;
|
||||
package org.apache.hertzbeat.common.entity.job.protocol;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
@@ -23,15 +23,13 @@ import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* sd monitor param
|
||||
* Dns protocol
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class SdMonitorParam {
|
||||
private String detectedHost;
|
||||
private String detectedPort;
|
||||
private Param sdParam;
|
||||
private Long bizId;
|
||||
}
|
||||
public class HttpSdProtocol implements Protocol {
|
||||
|
||||
private String url;
|
||||
}
|
||||
+9
-12
@@ -43,7 +43,7 @@ import org.springframework.data.annotation.LastModifiedDate;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
/**
|
||||
* Tag Entity
|
||||
* Label Entity
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "hzb_tag")
|
||||
@@ -51,30 +51,27 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(description = "Tag Entity")
|
||||
@Schema(description = "Label Entity")
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
public class Tag {
|
||||
public class Label {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Schema(title = "Tag Primary key index ID", example = "87584674384", accessMode = READ_ONLY)
|
||||
@Schema(title = "Primary key index ID", example = "87584674384", accessMode = READ_ONLY)
|
||||
private Long id;
|
||||
|
||||
@Schema(title = "Tag Field", example = "app", accessMode = READ_WRITE)
|
||||
@Schema(title = "Label Field", example = "app", accessMode = READ_WRITE)
|
||||
@NotBlank(message = "name can not null")
|
||||
private String name;
|
||||
|
||||
@Schema(title = "Tag Value", example = "23", accessMode = READ_WRITE)
|
||||
@Schema(title = "Label Value", example = "23", accessMode = READ_WRITE)
|
||||
@Column(length = 2048)
|
||||
private String tagValue;
|
||||
|
||||
@Schema(title = "Tag Color", example = "#ffff", accessMode = READ_WRITE)
|
||||
private String color;
|
||||
|
||||
@Schema(title = "Tag Color", example = "Used for monitoring mysql", accessMode = READ_WRITE)
|
||||
@Schema(title = "Label Description", example = "Used for monitoring mysql", accessMode = READ_WRITE)
|
||||
private String description;
|
||||
|
||||
@Schema(title = "Tag type 0: Auto-generated monitor (monitorId,monitorName) 1: user-generated 2: system preset",
|
||||
@Schema(title = "Tag type 0: Auto-generated 1: user-generated 2: system preset",
|
||||
accessMode = READ_WRITE)
|
||||
@Min(0)
|
||||
@Max(3)
|
||||
@@ -104,7 +101,7 @@ public class Tag {
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Tag tag = (Tag) o;
|
||||
Label tag = (Label) o;
|
||||
return Objects.equals(name, tag.name) && Objects.equals(tagValue, tag.tagValue);
|
||||
}
|
||||
|
||||
@@ -77,6 +77,10 @@ public class Monitor {
|
||||
@Size(max = 100)
|
||||
private String app;
|
||||
|
||||
@Schema(title = "Scrape type: static | http_sd", example = "static", accessMode = READ_WRITE)
|
||||
@Size(max = 100)
|
||||
private String scrape;
|
||||
|
||||
@Schema(title = "peer host: ipv4, ipv6, domain name", example = "192.167.25.11", accessMode = READ_WRITE)
|
||||
@Size(max = 100)
|
||||
@HostValid
|
||||
|
||||
+5
-10
@@ -17,7 +17,6 @@
|
||||
|
||||
package org.apache.hertzbeat.common.entity.manager;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_WRITE;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
@@ -27,8 +26,6 @@ import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Index;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
@@ -60,18 +57,16 @@ public class MonitorBind {
|
||||
@Schema(title = "primary id", example = "23")
|
||||
private Long id;
|
||||
|
||||
@Schema(title = "collector name", example = "87432674384")
|
||||
@Schema(title = "key string: ip:port")
|
||||
private String keyStr;
|
||||
|
||||
@Schema(title = "connect bind id", example = "87432674384")
|
||||
private Long bizId;
|
||||
|
||||
@Schema(title = "monitor ID", example = "87432674336")
|
||||
@Column(name = "monitor_id")
|
||||
private Long monitorId;
|
||||
|
||||
@Schema(title = "Bind type 0: sd", accessMode = READ_WRITE)
|
||||
@Min(0)
|
||||
@Max(3)
|
||||
private byte type;
|
||||
|
||||
@Schema(title = "The creator of this record", example = "tom")
|
||||
@CreatedBy
|
||||
private String creator;
|
||||
@@ -87,4 +82,4 @@ public class MonitorBind {
|
||||
@Schema(title = "Record the latest modification time (timestamp in milliseconds)")
|
||||
@LastModifiedDate
|
||||
private LocalDateTime gmtUpdate;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -454,8 +454,9 @@ public final class CollectRep {
|
||||
return new MetricsData(new ArrowTable(root));
|
||||
} catch (Exception e1) {
|
||||
log.error(e1.getMessage(), e1);
|
||||
root.close();
|
||||
throw e1;
|
||||
} finally {
|
||||
root.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
|
||||
-67
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.common.entity.sd;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* service discovery common field
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ServiceDiscoveryProtocol {
|
||||
private Long id;
|
||||
private List<Long> jobIdList;
|
||||
/**
|
||||
* sd type
|
||||
*/
|
||||
private Type type;
|
||||
/**
|
||||
* Target sd sources by which collector updates SDCache.
|
||||
*/
|
||||
private String sdSource;
|
||||
|
||||
/**
|
||||
* sd Type
|
||||
*/
|
||||
public enum Type {
|
||||
HTTP_SD("httpsd"),
|
||||
;
|
||||
|
||||
private final String protocolName;
|
||||
|
||||
Type(String protocolName) {
|
||||
this.protocolName = protocolName;
|
||||
}
|
||||
|
||||
public static Type getType(String str) {
|
||||
return Arrays.stream(Type.values()).filter(t -> t.toString().equalsIgnoreCase(str)).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
public String getProtocolName() {
|
||||
return protocolName;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -20,7 +20,7 @@ package org.apache.hertzbeat.common.support.event;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* the event for system config change
|
||||
* the event for monitor delete
|
||||
*/
|
||||
public class MonitorDeletedEvent extends ApplicationEvent {
|
||||
|
||||
|
||||
+6
-2
@@ -20,6 +20,7 @@ package org.apache.hertzbeat.common.support.valid;
|
||||
import jakarta.validation.ConstraintValidator;
|
||||
import jakarta.validation.ConstraintValidatorContext;
|
||||
import org.apache.hertzbeat.common.util.IpDomainUtil;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Host Param Validator
|
||||
@@ -33,10 +34,13 @@ public class HostParamValidator implements ConstraintValidator<HostValid, String
|
||||
|
||||
@Override
|
||||
public boolean isValid(String value, ConstraintValidatorContext context) {
|
||||
if (value != null && value.toLowerCase().contains(HTTP)){
|
||||
if (!StringUtils.hasText(value)) {
|
||||
return true;
|
||||
}
|
||||
if (value.toLowerCase().contains(HTTP)){
|
||||
value = value.replaceFirst(PATTERN_HTTP, BLANK);
|
||||
}
|
||||
if (value != null && value.toLowerCase().contains(HTTPS)){
|
||||
if (value.toLowerCase().contains(HTTPS)){
|
||||
value = value.replaceFirst(PATTERN_HTTPS, BLANK);
|
||||
}
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ public final class ArrowUtil {
|
||||
writer.writeBatch();
|
||||
writer.end();
|
||||
writer.close();
|
||||
root.close();
|
||||
}
|
||||
return out.toByteArray();
|
||||
} catch (IOException e) {
|
||||
|
||||
-151
@@ -1,151 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.common.util;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.job.Configmap;
|
||||
import org.apache.hertzbeat.common.entity.job.Job;
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.manager.MonitorBind;
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.SdMonitorParam;
|
||||
import org.apache.hertzbeat.common.entity.manager.Tag;
|
||||
import org.apache.hertzbeat.common.entity.sd.ServiceDiscoveryProtocol;
|
||||
|
||||
/**
|
||||
* Operator for service discovery
|
||||
*/
|
||||
public class SdMonitorOperator {
|
||||
public static Optional<Param> getSdParam(List<Param> params) {
|
||||
return params.stream()
|
||||
.filter(param -> StringUtils.isNoneBlank(param.getField(), param.getParamValue()))
|
||||
.filter(param -> Objects.nonNull(ServiceDiscoveryProtocol.Type.getType(param.getField())))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
public static List<Param> removeSdParam(List<Param> params) {
|
||||
return params.stream()
|
||||
.filter(param -> Objects.isNull(ServiceDiscoveryProtocol.Type.getType(param.getField())))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static List<Param> cloneParamList(List<Param> params) {
|
||||
return params.stream()
|
||||
.map(Param::clone)
|
||||
.peek(param -> param.setId(null))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static MonitorBind buildSdSubMonitorBind(SdMonitorParam sdMonitorParam, long monitorId, Map<String, String> labels) {
|
||||
if (Objects.isNull(sdMonitorParam.getBizId())) {
|
||||
return null;
|
||||
}
|
||||
labels.put(CommonConstants.TAG_AUTO_CREATED, String.valueOf(sdMonitorParam.getBizId()));
|
||||
return MonitorBind.builder()
|
||||
.id(SnowFlakeIdGenerator.generateId())
|
||||
.bizId(sdMonitorParam.getBizId())
|
||||
.monitorId(monitorId)
|
||||
.type(CommonConstants.MONITOR_BIND_TYPE_SD_SUB_MONITOR)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static MonitorBind buildSdMainMonitorBind(SdMonitorParam sdMonitorParam, long monitorId) {
|
||||
if (Objects.isNull(sdMonitorParam.getSdParam())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return MonitorBind.builder()
|
||||
.id(SnowFlakeIdGenerator.generateId())
|
||||
.bizId(sdMonitorParam.getBizId())
|
||||
.monitorId(monitorId)
|
||||
.type(CommonConstants.MONITOR_BIND_TYPE_SD_MAIN_MONITOR)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static Job constructSdJobAndTag(SdMonitorParam sdMonitorParam, Map<String, String> labels, Job appDefine) {
|
||||
if (Objects.isNull(sdMonitorParam.getSdParam())) {
|
||||
return appDefine;
|
||||
}
|
||||
labels.put(CommonConstants.TAG_SD_MAIN_MONITOR, ServiceDiscoveryProtocol.Type.getType(sdMonitorParam.getSdParam().getField()).name());
|
||||
return constructSdJob(appDefine, sdMonitorParam.getSdParam());
|
||||
}
|
||||
|
||||
public static Job constructSdJob(Job appDefine, Param sdParam) {
|
||||
final Job sdJob = appDefine.clone();
|
||||
final ServiceDiscoveryProtocol.Type sdType = ServiceDiscoveryProtocol.Type.getType(sdParam.getField());
|
||||
List<Metrics> metricsList = Lists.newArrayList();
|
||||
Map<String, String> i18n = Maps.newHashMap();
|
||||
i18n.put("zh-CN", "监控目标");
|
||||
i18n.put("en-US", "Monitor Target");
|
||||
List<Metrics.Field> fields = Lists.newArrayList();
|
||||
fields.add(Metrics.Field.builder()
|
||||
.field("host")
|
||||
.type(CommonConstants.TYPE_STRING)
|
||||
.i18n(constructSdFieldI18n("主机", "host"))
|
||||
.build());
|
||||
fields.add(Metrics.Field.builder()
|
||||
.field("port")
|
||||
.type(CommonConstants.TYPE_STRING)
|
||||
.i18n(constructSdFieldI18n("端口", "port"))
|
||||
.build());
|
||||
metricsList.add(Metrics.builder()
|
||||
.name("target")
|
||||
.fields(fields)
|
||||
.i18n(i18n)
|
||||
.protocol(sdType.getProtocolName())
|
||||
.sdProtocol(ServiceDiscoveryProtocol.builder()
|
||||
.sdSource(sdParam.getParamValue())
|
||||
.type(sdType)
|
||||
.build())
|
||||
.build());
|
||||
|
||||
sdJob.setSd(Boolean.TRUE);
|
||||
sdJob.setMetrics(metricsList);
|
||||
sdJob.setConfigmap(Lists.newArrayList(new Configmap(sdParam.getField(), sdParam.getParamValue(), sdParam.getType())));
|
||||
return sdJob;
|
||||
}
|
||||
|
||||
public static List<Tag> addMainMonitorTag(List<Tag> tagList, Param sdParam) {
|
||||
tagList = tagList.stream()
|
||||
.filter(tag -> !StringUtils.equals(tag.getName(), CommonConstants.TAG_SD_MAIN_MONITOR))
|
||||
.filter(tag -> !StringUtils.equals(tag.getName(), CommonConstants.TAG_AUTO_CREATED))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
tagList.add(Tag.builder().name(CommonConstants.TAG_SD_MAIN_MONITOR)
|
||||
.tagValue(ServiceDiscoveryProtocol.Type.getType(sdParam.getField()).name())
|
||||
.type(CommonConstants.TAG_TYPE_AUTO_GENERATE)
|
||||
.build());
|
||||
|
||||
return tagList;
|
||||
}
|
||||
|
||||
private static Map<String, String> constructSdFieldI18n(String zh, String en) {
|
||||
Map<String, String> i18n = Maps.newHashMap();
|
||||
i18n.put("zh-CN", zh);
|
||||
i18n.put("en-US", en);
|
||||
return i18n;
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -53,13 +53,13 @@ class HostParamValidatorTest {
|
||||
@Test
|
||||
public void testIsValid() {
|
||||
boolean result = hostParamValidator.isValid(null, context);
|
||||
assertFalse(result);
|
||||
assertTrue(result);
|
||||
|
||||
result = hostParamValidator.isValid("", context);
|
||||
assertFalse(result);
|
||||
assertTrue(result);
|
||||
|
||||
result = hostParamValidator.isValid(" ", context);
|
||||
assertFalse(result);
|
||||
assertTrue(result);
|
||||
|
||||
result = hostParamValidator.isValid("192.168.1.1", context);
|
||||
assertTrue(result);
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<mysql.version>8.0.30</mysql.version>
|
||||
<postgresql.version>42.5.5</postgresql.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -53,10 +55,46 @@
|
||||
<version>${hertzbeat.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Testcontainers Jupiter / JUnit 5 -->
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>testcontainers</artifactId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- Testcontainers JDBC Support -->
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>jdbc</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- Testcontainers Database Modules -->
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>mysql</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- JDBC Drivers -->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>${mysql.version}</version>
|
||||
<scope>test</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>${postgresql.version}</version>
|
||||
<scope>test</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</project>
|
||||
</project>
|
||||
|
||||
+72
@@ -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.collector.collect.basic.database;
|
||||
|
||||
/**
|
||||
* Database type (image mame) and image tag enumeration class
|
||||
*/
|
||||
public enum DatabaseImagesEnum {
|
||||
MYSQL("mysql", "8.0.36"),
|
||||
POSTGRESQL("postgresql", "15");
|
||||
|
||||
private final String imageName;
|
||||
private final String defaultTag;
|
||||
|
||||
DatabaseImagesEnum(String imageName, String defaultTag) {
|
||||
this.imageName = imageName;
|
||||
this.defaultTag = defaultTag;
|
||||
}
|
||||
|
||||
public String getImageName() {
|
||||
return imageName;
|
||||
}
|
||||
|
||||
public String getDefaultTag() {
|
||||
return defaultTag;
|
||||
}
|
||||
|
||||
public String getFullImageName() {
|
||||
return imageName + ":" + defaultTag;
|
||||
}
|
||||
|
||||
public static DatabaseImagesEnum fromImageName(String imageName) {
|
||||
for (DatabaseImagesEnum value : values()) {
|
||||
if (value.getImageName().equalsIgnoreCase(imageName)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown database image name: " + imageName);
|
||||
}
|
||||
|
||||
public static DatabaseImagesEnum fromFullImageName(String fullImageName) {
|
||||
for (DatabaseImagesEnum value : values()) {
|
||||
if (value.getFullImageName().equalsIgnoreCase(fullImageName)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown full database image name: " + fullImageName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DatabaseImagesEnum{"
|
||||
+ "imageName='" + imageName + '\''
|
||||
+ ", defaultTag='" + defaultTag + '\''
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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.basic.database;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.collector.collect.AbstractCollectE2eTest;
|
||||
import org.apache.hertzbeat.collector.collect.database.JdbcCommonCollect;
|
||||
import org.apache.hertzbeat.collector.util.CollectUtil;
|
||||
import org.apache.hertzbeat.common.entity.job.Configmap;
|
||||
import org.apache.hertzbeat.common.entity.job.Job;
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.JdbcProtocol;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.Protocol;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* E2e test monitored by Jdbc Common
|
||||
*/
|
||||
@Slf4j
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class JdbcCommonCollectE2eTest extends AbstractCollectE2eTest {
|
||||
private static final DatabaseImagesEnum databaseImage = DatabaseImagesEnum.fromImageName("postgresql");
|
||||
|
||||
private static final String DATABASE_IMAGE_NAME = databaseImage.getImageName();
|
||||
|
||||
private static final String DATABASE_IMAGE_TAG = databaseImage.getDefaultTag();
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
collect = new JdbcCommonCollect();
|
||||
metrics = new Metrics();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CollectRep.MetricsData.Builder collectMetrics(Metrics metricsDef) {
|
||||
JdbcProtocol jdbcProtocol = (JdbcProtocol) buildProtocol(metricsDef);
|
||||
metrics.setJdbc(jdbcProtocol);
|
||||
CollectRep.MetricsData.Builder metricsData = CollectRep.MetricsData.newBuilder();
|
||||
metricsData.setApp(DATABASE_IMAGE_NAME);
|
||||
metrics.setAliasFields(metricsDef.getAliasFields());
|
||||
return collectMetricsData(metrics, metricsDef, metricsData);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Protocol buildProtocol(Metrics metricsDef) {
|
||||
JdbcProtocol jdbcProtocol = metricsDef.getJdbc();
|
||||
jdbcProtocol.setHost(DATABASE_IMAGE_NAME);
|
||||
jdbcProtocol.setPort(DATABASE_IMAGE_TAG);
|
||||
jdbcProtocol.setDatabase("test");
|
||||
jdbcProtocol.setPlatform("testcontainers");
|
||||
return jdbcProtocol;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithJdbcTcUrl() {
|
||||
Job dockerJob = appService.getAppDefine(DATABASE_IMAGE_NAME);
|
||||
List<Map<String, Configmap>> configmapFromPreCollectData = new LinkedList<>();
|
||||
for (Metrics metricsDef : dockerJob.getMetrics()) {
|
||||
metricsDef = CollectUtil.replaceCryPlaceholderToMetrics(metricsDef, configmapFromPreCollectData.size() > 0 ? configmapFromPreCollectData.get(0) : new HashMap<>());
|
||||
String metricName = metricsDef.getName();
|
||||
if ("slow_sql".equals(metricName)) {
|
||||
Metrics finalMetricsDef = metricsDef;
|
||||
assertDoesNotThrow(() -> collectMetrics(finalMetricsDef),
|
||||
String.format("%s failed to collect metrics)", metricName));
|
||||
log.info("{} metrics validation passed", metricName);
|
||||
continue; // skip slow_sql, extra extensions
|
||||
}
|
||||
CollectRep.MetricsData metricsData = validateMetricsCollection(metricsDef, metricName, true);
|
||||
CollectUtil.getConfigmapFromPreCollectData(metricsData);
|
||||
}
|
||||
}
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 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.basic.redis;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.collector.collect.AbstractCollectE2eTest;
|
||||
import org.apache.hertzbeat.collector.collect.redis.RedisCommonCollectImpl;
|
||||
import org.apache.hertzbeat.common.entity.job.Job;
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.Protocol;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.RedisProtocol;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.Network;
|
||||
import org.testcontainers.containers.wait.strategy.Wait;
|
||||
import org.testcontainers.lifecycle.Startables;
|
||||
import org.testcontainers.utility.DockerImageName;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* End-to-end test class for redis collector
|
||||
*/
|
||||
@Slf4j
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class RedisCommonCollectE2eTest extends AbstractCollectE2eTest {
|
||||
private static final String REDIS_IMAGE = "redis:7.4.2";
|
||||
private static final String HOST = "127.0.0.1";
|
||||
private static final int REDIS_PORT = 6379;
|
||||
private static final String REDIS_PATTERN = "1";
|
||||
private static final List<String> ALLOW_EMPTY_WHITE_LIST = Arrays.asList("server", "errorstats", "commandstats", "keyspace");
|
||||
|
||||
private static GenericContainer<?> redisContainer;
|
||||
|
||||
private Integer mappedPort;
|
||||
|
||||
@AfterAll
|
||||
public static void tearDown() throws InterruptedException {
|
||||
if (redisContainer != null) {
|
||||
redisContainer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
collect = new RedisCommonCollectImpl();
|
||||
|
||||
// Set up and start container
|
||||
setupAndStartContainer();
|
||||
|
||||
mappedPort = redisContainer.getMappedPort(REDIS_PORT);
|
||||
log.info("Container started successfully with mapped port: {}", mappedPort);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRedisCollect() throws ExecutionException, InterruptedException, TimeoutException {
|
||||
Assertions.assertTrue(redisContainer.isRunning(), "redis container should be running");
|
||||
|
||||
Job redisJob = appService.getAppDefine("redis");
|
||||
redisJob.getMetrics().forEach(metricsDef -> {
|
||||
if (ALLOW_EMPTY_WHITE_LIST.contains(metricsDef.getName())) {
|
||||
validateMetricsCollection(metricsDef, metricsDef.getName(), true);
|
||||
} else {
|
||||
validateMetricsCollection(metricsDef, metricsDef.getName());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CollectRep.MetricsData.Builder collectMetrics(Metrics metricsDef) {
|
||||
//Build redis protocol configuration
|
||||
RedisProtocol redisProtocol = (RedisProtocol) buildProtocol(metricsDef);
|
||||
metrics.setRedis(redisProtocol);
|
||||
metrics.setName(metricsDef.getName());
|
||||
metrics.setFields(metricsDef.getFields());
|
||||
return collectMetricsData(metrics, metricsDef);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Protocol buildProtocol(Metrics metricsDef) {
|
||||
RedisProtocol redisProtocol = new RedisProtocol();
|
||||
redisProtocol.setHost(HOST);
|
||||
redisProtocol.setPort(mappedPort.toString());
|
||||
redisProtocol.setPattern(REDIS_PATTERN);
|
||||
return redisProtocol;
|
||||
}
|
||||
|
||||
public void setupAndStartContainer() {
|
||||
Network network = Network.builder().build();
|
||||
redisContainer = new GenericContainer<>(DockerImageName.parse(REDIS_IMAGE))
|
||||
.withExposedPorts(REDIS_PORT)
|
||||
.withNetwork(network)
|
||||
.withNetworkAliases("redis")
|
||||
.waitingFor(Wait.forListeningPort())
|
||||
.withStartupTimeout(Duration.ofSeconds(30));
|
||||
|
||||
Startables.deepStart(Stream.of(redisContainer)).join();
|
||||
}
|
||||
}
|
||||
+6
@@ -27,6 +27,7 @@ import org.apache.hertzbeat.common.entity.job.Job;
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.Protocol;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.manager.dao.DefineDao;
|
||||
import org.apache.hertzbeat.manager.service.impl.AppServiceImpl;
|
||||
import org.apache.hertzbeat.manager.service.impl.ObjectStoreConfigServiceImpl;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
@@ -34,6 +35,7 @@ import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -63,10 +65,13 @@ public abstract class AbstractCollectE2eTest {
|
||||
private Timeout timeout;
|
||||
@Mock
|
||||
private Job job;
|
||||
@Mock
|
||||
private DefineDao defineDao;
|
||||
|
||||
public void setUp() throws Exception {
|
||||
// Initialize mocks
|
||||
MockitoAnnotations.openMocks(this);
|
||||
when(defineDao.findAll()).thenReturn(new ArrayList<>());
|
||||
when(timeout.task()).thenReturn(timerJob);
|
||||
when(timerJob.getJob()).thenReturn(job);
|
||||
metricsCollect = new MetricsCollect(mock(Metrics.class), timeout, mock(CollectDataDispatch.class), null, List.of());
|
||||
@@ -78,6 +83,7 @@ public abstract class AbstractCollectE2eTest {
|
||||
|
||||
/**
|
||||
* Validate metrics collection, check if the metrics values are not empty <br/>
|
||||
*
|
||||
* @param metricsDef metrics definition
|
||||
* @param metricName metric name
|
||||
* @return metrics data
|
||||
|
||||
+14
-1
@@ -74,4 +74,17 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>testcontainers-bom</artifactId>
|
||||
<version>${testcontainers.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
</project>
|
||||
|
||||
+58
-60
@@ -18,25 +18,22 @@
|
||||
package org.apache.hertzbeat.manager.component.sd;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import java.util.HashSet;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.LinkedList;
|
||||
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.commons.lang3.StringUtils;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.arrow.RowWrapper;
|
||||
import org.apache.hertzbeat.common.entity.manager.CollectorMonitorBind;
|
||||
import org.apache.hertzbeat.common.entity.manager.Monitor;
|
||||
import org.apache.hertzbeat.common.entity.manager.MonitorBind;
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.SdMonitorParam;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.queue.CommonDataQueue;
|
||||
import org.apache.hertzbeat.common.util.SdMonitorOperator;
|
||||
import org.apache.hertzbeat.manager.dao.CollectorMonitorBindDao;
|
||||
import org.apache.hertzbeat.manager.dao.MonitorBindDao;
|
||||
import org.apache.hertzbeat.manager.dao.MonitorDao;
|
||||
@@ -45,7 +42,6 @@ import org.apache.hertzbeat.manager.scheduler.ManagerWorkerPool;
|
||||
import org.apache.hertzbeat.manager.service.MonitorService;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Service Discovery Worker
|
||||
@@ -53,6 +49,9 @@ import org.springframework.util.CollectionUtils;
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ServiceDiscoveryWorker implements InitializingBean {
|
||||
|
||||
private static final String FILED_HOST = "host";
|
||||
private static final String FILED_PORT = "port";
|
||||
private final MonitorService monitorService;
|
||||
private final ParamDao paramDao;
|
||||
private final MonitorDao monitorDao;
|
||||
@@ -83,74 +82,73 @@ public class ServiceDiscoveryWorker implements InitializingBean {
|
||||
public void run() {
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
try (final CollectRep.MetricsData metricsData = dataQueue.pollServiceDiscoveryData()) {
|
||||
|
||||
Long monitorId = metricsData.getId();
|
||||
final Monitor mainMonitor = monitorDao.findMonitorsByIdIn(Sets.newHashSet(monitorId)).get(0);
|
||||
mainMonitor.getLabels().remove(CommonConstants.TAG_SD_MAIN_MONITOR);
|
||||
final Monitor mainMonitor = monitorDao.findById(monitorId).orElse(null);
|
||||
if (mainMonitor == null) {
|
||||
log.warn("No monitor found for id {}", monitorId);
|
||||
continue;
|
||||
}
|
||||
// collector
|
||||
final Optional<CollectorMonitorBind> collectorBind = collectorMonitorBindDao.findCollectorMonitorBindByMonitorId(mainMonitor.getId());
|
||||
final Optional<CollectorMonitorBind> collectorBind = collectorMonitorBindDao.findCollectorMonitorBindByMonitorId(monitorId);
|
||||
String collector = collectorBind.map(CollectorMonitorBind::getCollector).orElse(null);
|
||||
// param
|
||||
List<Param> mainMonitorParamList = paramDao.findParamsByMonitorId(mainMonitor.getId());
|
||||
mainMonitorParamList = SdMonitorOperator.removeSdParam(mainMonitorParamList);
|
||||
|
||||
final Set<Long> subMonitorIdSet = monitorBindDao.findMonitorBindByBizIdAndType(monitorId, CommonConstants.MONITOR_BIND_TYPE_SD_SUB_MONITOR)
|
||||
.stream()
|
||||
.map(MonitorBind::getMonitorId)
|
||||
.collect(Collectors.toSet());
|
||||
final Map<String, List<Monitor>> hostMonitorMap = CollectionUtils.isEmpty(subMonitorIdSet)
|
||||
? Maps.newHashMap()
|
||||
: monitorDao.findMonitorsByIdIn(subMonitorIdSet).stream().collect(Collectors.groupingBy(Monitor::getHost));
|
||||
|
||||
// params
|
||||
List<Param> mainMonitorParams = paramDao.findParamsByMonitorId(monitorId);
|
||||
final Map<String, MonitorBind> subMonitorBindMap = monitorBindDao.findMonitorBindsByBizId(monitorId)
|
||||
.stream().collect(Collectors.toMap(MonitorBind::getKeyStr, item -> item));
|
||||
RowWrapper rowWrapper = metricsData.readRow();
|
||||
|
||||
Map<String, String> fieldsValue = Maps.newHashMapWithExpectedSize(8);
|
||||
while (rowWrapper.hasNextRow()) {
|
||||
rowWrapper = rowWrapper.nextRow();
|
||||
|
||||
|
||||
final String host = rowWrapper.nextCell().getValue();
|
||||
final String port = rowWrapper.nextCell().getValue();
|
||||
final List<Monitor> monitorList = hostMonitorMap.get(host);
|
||||
if (CollectionUtils.isEmpty(monitorList)) {
|
||||
monitorService.addAndSaveMonitorJob(mainMonitor.clone(), SdMonitorOperator.cloneParamList(mainMonitorParamList), collector,
|
||||
SdMonitorParam.builder()
|
||||
.detectedHost(host)
|
||||
.detectedPort(port)
|
||||
.bizId(mainMonitor.getId())
|
||||
.build(), null);
|
||||
return;
|
||||
fieldsValue.clear();
|
||||
rowWrapper.cellStream().forEach(cell -> {
|
||||
String value = cell.getValue();
|
||||
fieldsValue.put(cell.getField().getName(), value);
|
||||
});
|
||||
final String host = fieldsValue.get(FILED_HOST);
|
||||
final String port = fieldsValue.get(FILED_PORT);
|
||||
final String keyStr = host + ":" + port;
|
||||
if (subMonitorBindMap.containsKey(keyStr)) {
|
||||
subMonitorBindMap.remove(keyStr);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Monitor monitor : monitorList) {
|
||||
// make sure monitor that has the same host and port is not existed.
|
||||
final Optional<Param> samePortParam = paramDao.findParamsByMonitorId(monitor.getId()).stream()
|
||||
.filter(param -> StringUtils.equals(param.getField(), "port"))
|
||||
.filter(param -> StringUtils.equals(param.getParamValue(), port))
|
||||
.findFirst();
|
||||
if (samePortParam.isEmpty()) {
|
||||
monitorService.addAndSaveMonitorJob(mainMonitor.clone(), SdMonitorOperator.cloneParamList(mainMonitorParamList), collector,
|
||||
SdMonitorParam.builder()
|
||||
.detectedHost(host)
|
||||
.detectedPort(port)
|
||||
.bizId(mainMonitor.getId())
|
||||
.build(), null);
|
||||
} else {
|
||||
monitorService.enableManageMonitors(Sets.newHashSet(monitor.getId()));
|
||||
Monitor newMonitor = mainMonitor.clone();
|
||||
newMonitor.setId(null);
|
||||
newMonitor.setHost(host);
|
||||
newMonitor.setName(newMonitor.getName() + "-" + host + ":" + port);
|
||||
newMonitor.setScrape(CommonConstants.SCRAPE_STATIC);
|
||||
newMonitor.setGmtCreate(LocalDateTime.now());
|
||||
newMonitor.setGmtUpdate(LocalDateTime.now());
|
||||
// replace host port
|
||||
List<Param> newParams = new LinkedList<>();
|
||||
for (Param param : mainMonitorParams) {
|
||||
Param newParam = param.clone();
|
||||
newParam.setId(null);
|
||||
newParam.setGmtUpdate(null);
|
||||
newParam.setGmtCreate(null);
|
||||
if (FILED_HOST.equals(newParam.getField())) {
|
||||
newParam.setParamValue(host);
|
||||
} else if (FILED_PORT.equals(newParam.getField())) {
|
||||
newParam.setParamValue(port);
|
||||
}
|
||||
newParams.add(newParam);
|
||||
}
|
||||
|
||||
// make sure hostMonitorMap contains monitors that have not judged yet.
|
||||
hostMonitorMap.remove(host);
|
||||
monitorService.addMonitor(newMonitor, newParams, collector, null);
|
||||
MonitorBind monitorBind = MonitorBind.builder()
|
||||
.bizId(monitorId)
|
||||
.monitorId(newMonitor.getId())
|
||||
.keyStr(keyStr)
|
||||
.build();
|
||||
monitorBindDao.save(monitorBind);
|
||||
}
|
||||
|
||||
// hostMonitorMap only contains monitors which are already existed but not in service discovery data
|
||||
// due to monitors that coincide with service discovery data are removed.
|
||||
// Thus, all monitors still in hostMonitorMap need to be cancelled.
|
||||
final HashSet<Long> needCancelMonitorIdSet = Sets.newHashSet();
|
||||
hostMonitorMap.forEach((key, value) -> needCancelMonitorIdSet.addAll(value.stream()
|
||||
.map(Monitor::getId)
|
||||
.collect(Collectors.toSet())));
|
||||
final Set<Long> needCancelMonitorIdSet = subMonitorBindMap.values().stream()
|
||||
.map(MonitorBind::getMonitorId).collect(Collectors.toSet());
|
||||
monitorService.cancelManageMonitors(needCancelMonitorIdSet);
|
||||
for (Long id : needCancelMonitorIdSet) {
|
||||
monitorBindDao.deleteMonitorBindByBizIdAndMonitorId(monitorId, id);
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
log.error(exception.getMessage(), exception);
|
||||
}
|
||||
|
||||
+25
-24
@@ -20,12 +20,13 @@ package org.apache.hertzbeat.manager.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.validation.Valid;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import org.apache.hertzbeat.common.entity.dto.Message;
|
||||
import org.apache.hertzbeat.common.entity.manager.Tag;
|
||||
import org.apache.hertzbeat.manager.service.TagService;
|
||||
import org.apache.hertzbeat.common.entity.manager.Label;
|
||||
import org.apache.hertzbeat.manager.service.LabelService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -39,48 +40,48 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Tags management API
|
||||
* Label management API
|
||||
*/
|
||||
@io.swagger.v3.oas.annotations.tags.Tag(name = "Tag Manage API")
|
||||
@Tag(name = "Label Manage API")
|
||||
@RestController
|
||||
@RequestMapping(path = "/api/tag", produces = {APPLICATION_JSON_VALUE})
|
||||
public class TagController {
|
||||
@RequestMapping(path = "/api/label", produces = {APPLICATION_JSON_VALUE})
|
||||
public class LabelController {
|
||||
|
||||
@Autowired
|
||||
private TagService tagService;
|
||||
private LabelService labelService;
|
||||
|
||||
@PostMapping
|
||||
@Operation(summary = "Add Tag", description = "Add Tag")
|
||||
public ResponseEntity<Message<Void>> addNewTags(@Valid @RequestBody List<Tag> tags) {
|
||||
tagService.addTags(tags);
|
||||
@Operation(summary = "Add Label", description = "Add Label")
|
||||
public ResponseEntity<Message<Void>> addNewLabels(@Valid @RequestBody Label label) {
|
||||
labelService.addLabel(label);
|
||||
return ResponseEntity.ok(Message.success("Add success"));
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
@Operation(summary = "Modify an existing tag", description = "Modify an existing tag")
|
||||
public ResponseEntity<Message<Void>> modifyMonitor(@Valid @RequestBody Tag tag) {
|
||||
// Verify request data
|
||||
if (tag.getId() == null) {
|
||||
@Operation(summary = "Modify an existing Label", description = "Modify an existing Label")
|
||||
public ResponseEntity<Message<Void>> modifyLabel(@Valid @RequestBody Label label) {
|
||||
if (label.getId() == null) {
|
||||
throw new IllegalArgumentException("ID cannot be null.");
|
||||
}
|
||||
tagService.modifyTag(tag);
|
||||
labelService.modifyLabel(label);
|
||||
return ResponseEntity.ok(Message.success("Modify success"));
|
||||
}
|
||||
|
||||
@GetMapping()
|
||||
@Operation(summary = "Get tags information", description = "Obtain label information based on conditions")
|
||||
public ResponseEntity<Message<Page<Tag>>> getTags(
|
||||
@Parameter(description = "Tag content search", example = "status") @RequestParam(required = false) String search,
|
||||
@Parameter(description = "Tag type", example = "0") @RequestParam(required = false) Byte type,
|
||||
@Operation(summary = "Get labels information", description = "Obtain label information based on conditions")
|
||||
public ResponseEntity<Message<Page<Label>>> getLabels(
|
||||
@Parameter(description = "Label content search", example = "status") @RequestParam(required = false) String search,
|
||||
@Parameter(description = "Label type", example = "0") @RequestParam(required = false) Byte type,
|
||||
@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) {
|
||||
return ResponseEntity.ok(Message.success(tagService.getTags(search, type, pageIndex, pageSize)));
|
||||
return ResponseEntity.ok(Message.success(labelService.getLabels(search, type, pageIndex, pageSize)));
|
||||
}
|
||||
|
||||
@DeleteMapping()
|
||||
@Operation(summary = "Delete tags based on ID", description = "Delete tags based on ID")
|
||||
public ResponseEntity<Message<Void>> deleteTags(
|
||||
@Parameter(description = "TAG IDs ", example = "6565463543") @RequestParam(required = false) List<Long> ids) {
|
||||
tagService.deleteTags(new HashSet<>(ids));
|
||||
@Operation(summary = "Delete Label based on ID", description = "Delete Label based on ID")
|
||||
public ResponseEntity<Message<Void>> deleteLabels(
|
||||
@Parameter(description = "Label id ", example = "6565463543") @RequestParam(required = false) List<Long> ids) {
|
||||
labelService.deleteLabels(new HashSet<>(ids));
|
||||
return ResponseEntity.ok(Message.success("Delete success"));
|
||||
}
|
||||
}
|
||||
-12
@@ -127,16 +127,4 @@ public class MonitorsController {
|
||||
monitorService.importConfig(file);
|
||||
return ResponseEntity.ok(Message.success("Import success"));
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/copy")
|
||||
@Operation(summary = "copy monitors by ids", description = "copy monitors by ids")
|
||||
public ResponseEntity<Message<Void>> duplicateMonitors(
|
||||
@Parameter(description = "Monitor ID List", example = "6565463543") @RequestParam List<Long> ids
|
||||
) {
|
||||
if (ids != null && !ids.isEmpty()) {
|
||||
monitorService.copyMonitors(ids);
|
||||
}
|
||||
return ResponseEntity.ok(Message.success("copy success"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
package org.apache.hertzbeat.manager.dao;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.apache.hertzbeat.common.entity.manager.Collector;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
@@ -34,7 +35,14 @@ public interface CollectorDao extends JpaRepository<Collector, Long>, JpaSpecifi
|
||||
* @return collector
|
||||
*/
|
||||
Optional<Collector> findCollectorByName(String name);
|
||||
|
||||
|
||||
/**
|
||||
* find collectors by names
|
||||
* @param names collector name list
|
||||
* @return collector list
|
||||
*/
|
||||
List<Collector> findCollectorsByNameIn(List<String> names);
|
||||
|
||||
/**
|
||||
* delete collector by name
|
||||
* @param collector collector name
|
||||
|
||||
+10
-18
@@ -17,37 +17,29 @@
|
||||
|
||||
package org.apache.hertzbeat.manager.dao;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import org.apache.hertzbeat.common.entity.manager.Tag;
|
||||
import org.apache.hertzbeat.common.entity.manager.Label;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
|
||||
/**
|
||||
* tag repository
|
||||
* Label repository
|
||||
*/
|
||||
public interface TagDao extends JpaRepository<Tag, Long>, JpaSpecificationExecutor<Tag> {
|
||||
public interface LabelDao extends JpaRepository<Label, Long>, JpaSpecificationExecutor<Label> {
|
||||
|
||||
/**
|
||||
* delete tags by tag id
|
||||
* delete Label by Label id
|
||||
* @param ids id list
|
||||
*/
|
||||
void deleteTagsByIdIn(Set<Long> ids);
|
||||
|
||||
/**
|
||||
* find tags by tag id
|
||||
* @param ids id list
|
||||
* @return tag list
|
||||
*/
|
||||
List<Tag> findByIdIn(Set<Long> ids);
|
||||
void deleteLabelsByIdIn(Set<Long> ids);
|
||||
|
||||
/**
|
||||
* find tag by name and value
|
||||
* @param name tag name
|
||||
* @param value tag value
|
||||
* @return tag
|
||||
* find Label by name and value
|
||||
* @param name Label name
|
||||
* @param value Label value
|
||||
* @return Label
|
||||
*/
|
||||
Optional<Tag> findTagByNameAndTagValue(String name, String value);
|
||||
Optional<Label> findLabelByNameAndTagValue(String name, String value);
|
||||
|
||||
}
|
||||
+7
-9
@@ -18,22 +18,20 @@
|
||||
package org.apache.hertzbeat.manager.dao;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.apache.hertzbeat.common.entity.manager.MonitorBind;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
|
||||
/**
|
||||
* AuthResources database operation
|
||||
* MonitorBind database operation
|
||||
*/
|
||||
public interface MonitorBindDao extends JpaRepository<MonitorBind, Long>, JpaSpecificationExecutor<MonitorBind> {
|
||||
List<MonitorBind> findAllByBizIdInAndType(Set<Long> bizIdList, byte type);
|
||||
|
||||
List<MonitorBind> findMonitorBindByBizIdAndType(Long bizId, byte type);
|
||||
|
||||
MonitorBind findMonitorBindByBizIdAndMonitorIdAndType(Long bizId, Long monitorId, byte type);
|
||||
|
||||
List<MonitorBind> findAllByType(byte type);
|
||||
List<MonitorBind> findMonitorBindsByBizId(Long bizId);
|
||||
|
||||
void deleteByMonitorId(Long monitorId);
|
||||
}
|
||||
|
||||
@Modifying
|
||||
void deleteMonitorBindByBizIdAndMonitorId(Long bizId, Long monitorId);
|
||||
}
|
||||
|
||||
+2
-2
@@ -36,12 +36,12 @@ import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
@Schema(description = "Monitoring information entities")
|
||||
public class MonitorDto {
|
||||
|
||||
@Schema(description = "Monitor Content", accessMode = READ_WRITE)
|
||||
@Schema(description = "monitor content", accessMode = READ_WRITE)
|
||||
@NotNull
|
||||
@Valid
|
||||
private Monitor monitor;
|
||||
|
||||
@Schema(description = "Monitor Params", accessMode = READ_WRITE)
|
||||
@Schema(description = "monitor params", accessMode = READ_WRITE)
|
||||
@NotEmpty
|
||||
@Valid
|
||||
private List<Param> params;
|
||||
|
||||
+8
-17
@@ -19,10 +19,8 @@ package org.apache.hertzbeat.manager.scheduler;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.dto.CollectorInfo;
|
||||
import org.apache.hertzbeat.common.entity.job.Configmap;
|
||||
@@ -30,13 +28,10 @@ import org.apache.hertzbeat.common.entity.job.Job;
|
||||
import org.apache.hertzbeat.common.entity.manager.Collector;
|
||||
import org.apache.hertzbeat.common.entity.manager.CollectorMonitorBind;
|
||||
import org.apache.hertzbeat.common.entity.manager.Monitor;
|
||||
import org.apache.hertzbeat.common.entity.manager.MonitorBind;
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.apache.hertzbeat.common.util.SdMonitorOperator;
|
||||
import org.apache.hertzbeat.manager.dao.CollectorDao;
|
||||
import org.apache.hertzbeat.manager.dao.CollectorMonitorBindDao;
|
||||
import org.apache.hertzbeat.manager.dao.MonitorBindDao;
|
||||
import org.apache.hertzbeat.manager.dao.MonitorDao;
|
||||
import org.apache.hertzbeat.manager.dao.ParamDao;
|
||||
import org.apache.hertzbeat.manager.service.AppService;
|
||||
@@ -45,6 +40,7 @@ import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* scheduler init
|
||||
@@ -77,9 +73,6 @@ public class SchedulerInit implements CommandLineRunner {
|
||||
|
||||
@Autowired
|
||||
private CollectorMonitorBindDao collectorMonitorBindDao;
|
||||
|
||||
@Autowired
|
||||
private MonitorBindDao monitorBindDao;
|
||||
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
@@ -98,20 +91,18 @@ public class SchedulerInit implements CommandLineRunner {
|
||||
// init jobs
|
||||
List<Monitor> monitors = monitorDao.findMonitorsByStatusNotInAndJobIdNotNull(List.of(CommonConstants.MONITOR_PAUSED_CODE));
|
||||
List<CollectorMonitorBind> monitorBinds = collectorMonitorBindDao.findAll();
|
||||
final Set<Long> sdMonitorIds = monitorBindDao.findAllByType(CommonConstants.MONITOR_BIND_TYPE_SD_SUB_MONITOR).stream()
|
||||
.map(MonitorBind::getBizId)
|
||||
.collect(Collectors.toSet());
|
||||
Map<Long, String> monitorIdCollectorMap = monitorBinds.stream().collect(
|
||||
Collectors.toMap(CollectorMonitorBind::getMonitorId, CollectorMonitorBind::getCollector));
|
||||
for (Monitor monitor : monitors) {
|
||||
try {
|
||||
// build collect job entity
|
||||
Job appDefine = appService.getAppDefine(monitor.getApp());
|
||||
List<Param> params = paramDao.findParamsByMonitorId(monitor.getId());
|
||||
if (sdMonitorIds.contains(monitor.getId())) {
|
||||
appDefine = SdMonitorOperator.constructSdJob(appDefine, SdMonitorOperator.getSdParam(params).get());
|
||||
boolean isStatic = CommonConstants.SCRAPE_STATIC.equals(monitor.getScrape()) || !StringUtils.hasText(monitor.getScrape());
|
||||
String app = isStatic ? monitor.getApp() : monitor.getScrape();
|
||||
Job appDefine = appService.getAppDefine(app);
|
||||
if (!isStatic) {
|
||||
appDefine.setSd(true);
|
||||
}
|
||||
|
||||
List<Param> params = paramDao.findParamsByMonitorId(monitor.getId());
|
||||
if (CommonConstants.PROMETHEUS.equals(monitor.getApp())) {
|
||||
appDefine.setApp(CommonConstants.PROMETHEUS_APP_PREFIX + monitor.getName());
|
||||
}
|
||||
@@ -129,7 +120,7 @@ public class SchedulerInit implements CommandLineRunner {
|
||||
.map(param -> new Configmap(param.getField(), param.getParamValue(),
|
||||
param.getType())).collect(Collectors.toList());
|
||||
List<ParamDefine> paramDefaultValue = appDefine.getParams().stream()
|
||||
.filter(item -> StringUtils.isNotBlank(item.getDefaultValue()))
|
||||
.filter(item -> StringUtils.hasText(item.getDefaultValue()))
|
||||
.toList();
|
||||
paramDefaultValue.forEach(defaultVar -> {
|
||||
if (configmaps.stream().noneMatch(item -> item.getKey().equals(defaultVar.getField()))) {
|
||||
|
||||
+8
-1
@@ -24,6 +24,7 @@ import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.calculate.CollectorAlertHandler;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
|
||||
import org.apache.hertzbeat.common.support.CommonThreadPool;
|
||||
import org.apache.hertzbeat.manager.scheduler.CollectorJobScheduler;
|
||||
@@ -56,6 +57,8 @@ public class ManageServer implements CommandLineRunner {
|
||||
|
||||
private final CollectorJobScheduler collectorJobScheduler;
|
||||
|
||||
private final CollectorAlertHandler collectorAlertHandler;
|
||||
|
||||
private ScheduledExecutorService channelSchedule;
|
||||
|
||||
private RemotingServer remotingServer;
|
||||
@@ -64,9 +67,11 @@ public class ManageServer implements CommandLineRunner {
|
||||
|
||||
public ManageServer(final SchedulerProperties schedulerProperties,
|
||||
final CollectorJobScheduler collectorJobScheduler,
|
||||
final CommonThreadPool threadPool) {
|
||||
final CommonThreadPool threadPool,
|
||||
final CollectorAlertHandler collectorAlertHandler) {
|
||||
this.collectorJobScheduler = collectorJobScheduler;
|
||||
this.collectorJobScheduler.setManageServer(this);
|
||||
this.collectorAlertHandler = collectorAlertHandler;
|
||||
this.init(schedulerProperties, threadPool);
|
||||
}
|
||||
|
||||
@@ -98,6 +103,7 @@ public class ManageServer implements CommandLineRunner {
|
||||
channel.closeFuture();
|
||||
this.clientChannelTable.remove(collector);
|
||||
this.collectorJobScheduler.collectorGoOffline(collector);
|
||||
this.collectorAlertHandler.offline(collector);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
@@ -131,6 +137,7 @@ public class ManageServer implements CommandLineRunner {
|
||||
preChannel.close();
|
||||
}
|
||||
this.clientChannelTable.put(identity, channel);
|
||||
this.collectorAlertHandler.online(identity);
|
||||
}
|
||||
|
||||
public void closeChannel(final String identity) {
|
||||
|
||||
+17
-18
@@ -18,40 +18,39 @@
|
||||
package org.apache.hertzbeat.manager.service;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import org.apache.hertzbeat.common.entity.manager.Tag;
|
||||
import org.apache.hertzbeat.common.entity.manager.Label;
|
||||
import org.springframework.data.domain.Page;
|
||||
|
||||
/**
|
||||
* tag service
|
||||
* Label service
|
||||
*/
|
||||
public interface TagService {
|
||||
public interface LabelService {
|
||||
|
||||
/**
|
||||
* new tags
|
||||
* @param tags tag
|
||||
* new Label
|
||||
* @param label label
|
||||
*/
|
||||
void addTags(List<Tag> tags);
|
||||
void addLabel(Label label);
|
||||
|
||||
/**
|
||||
* update tag
|
||||
* @param tag Tag
|
||||
* update label
|
||||
* @param label label
|
||||
*/
|
||||
void modifyTag(Tag tag);
|
||||
void modifyLabel(Label label);
|
||||
|
||||
/**
|
||||
* get tag page list
|
||||
* @param search Tag content search
|
||||
* @param type Tag type
|
||||
* get label page list
|
||||
* @param search label content search
|
||||
* @param type label type
|
||||
* @param pageIndex List current page
|
||||
* @param pageSize Number of list pagination
|
||||
* @return Tags
|
||||
* @return label
|
||||
*/
|
||||
Page<Tag> getTags(String search, Byte type, int pageIndex, int pageSize);
|
||||
Page<Label> getLabels(String search, Byte type, int pageIndex, int pageSize);
|
||||
|
||||
/**
|
||||
* delete tags
|
||||
* @param ids tag id list
|
||||
* delete labels
|
||||
* @param ids label id list
|
||||
*/
|
||||
void deleteTags(HashSet<Long> ids);
|
||||
void deleteLabels(HashSet<Long> ids);
|
||||
}
|
||||
+3
-14
@@ -22,14 +22,12 @@ import org.apache.hertzbeat.common.entity.grafana.GrafanaDashboard;
|
||||
import org.apache.hertzbeat.common.entity.job.Job;
|
||||
import org.apache.hertzbeat.common.entity.manager.Monitor;
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.SdMonitorParam;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.AppCount;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.MonitorDto;
|
||||
import org.apache.hertzbeat.manager.support.exception.MonitorDetectException;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -125,14 +123,14 @@ public interface MonitorService {
|
||||
*
|
||||
* @param ids Monitoring ID List
|
||||
*/
|
||||
void cancelManageMonitors(HashSet<Long> ids);
|
||||
void cancelManageMonitors(Set<Long> ids);
|
||||
|
||||
/**
|
||||
* Start the managed monitoring items in batches according to the monitoring ID list
|
||||
*
|
||||
* @param ids Monitoring ID List
|
||||
*/
|
||||
void enableManageMonitors(HashSet<Long> ids);
|
||||
void enableManageMonitors(Set<Long> ids);
|
||||
|
||||
/**
|
||||
* Query the monitoring category and its corresponding monitoring quantity
|
||||
@@ -183,22 +181,13 @@ public interface MonitorService {
|
||||
*/
|
||||
void importConfig(MultipartFile file) throws Exception;
|
||||
|
||||
/**
|
||||
* Copy monitor in batches based on the id
|
||||
*
|
||||
* @param ids monitor id
|
||||
*/
|
||||
void copyMonitors(List<Long> ids);
|
||||
|
||||
/**
|
||||
* update app collect job by app
|
||||
*
|
||||
* @param job job content
|
||||
*/
|
||||
void updateAppCollectJob(Job job);
|
||||
|
||||
void addAndSaveMonitorJob(Monitor monitor, List<Param> params, String collector, SdMonitorParam sdMonitorParam, GrafanaDashboard grafanaDashboard);
|
||||
|
||||
|
||||
/**
|
||||
* Copy monitor by id
|
||||
*
|
||||
|
||||
+2
-2
@@ -31,7 +31,7 @@ import org.apache.hertzbeat.manager.config.ManagerSseManager;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.MonitorDto;
|
||||
import org.apache.hertzbeat.manager.service.ImExportService;
|
||||
import org.apache.hertzbeat.manager.service.MonitorService;
|
||||
import org.apache.hertzbeat.manager.service.TagService;
|
||||
import org.apache.hertzbeat.manager.service.LabelService;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
@@ -55,7 +55,7 @@ public abstract class AbstractImExportServiceImpl implements ImExportService {
|
||||
private MonitorService monitorService;
|
||||
|
||||
@Resource
|
||||
private TagService tagService;
|
||||
private LabelService tagService;
|
||||
|
||||
@Resource
|
||||
private ManagerSseManager managerSseManager;
|
||||
|
||||
+1
-5
@@ -270,10 +270,6 @@ public class AppServiceImpl implements AppService, InitializingBean {
|
||||
public List<Hierarchy> getAllAppHierarchy(String lang) {
|
||||
LinkedList<Hierarchy> hierarchies = new LinkedList<>();
|
||||
for (var job : appDefines.values()) {
|
||||
// TODO temporarily filter out push to solve the front-end problem, and open it after the subsequent design optimization
|
||||
if (DispatchConstants.PROTOCOL_PUSH.equalsIgnoreCase(job.getApp())) {
|
||||
continue;
|
||||
}
|
||||
queryAppHierarchy(lang, hierarchies, job);
|
||||
}
|
||||
return hierarchies;
|
||||
@@ -518,7 +514,7 @@ public class AppServiceImpl implements AppService, InitializingBean {
|
||||
*/
|
||||
private void refreshStore(ObjectStoreDTO<?> objectStoreConfig) {
|
||||
if (objectStoreConfig == null) {
|
||||
appDefineStore = new LocalFileAppDefineStoreImpl();
|
||||
appDefineStore = new DatabaseAppDefineStoreImpl();
|
||||
} else {
|
||||
if (objectStoreConfig.getType() == ObjectStoreDTO.Type.OBS) {
|
||||
appDefineStore = new ObjectStoreAppDefineStoreImpl();
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ public class CollectorServiceImpl implements CollectorService {
|
||||
|
||||
@Autowired(required = false)
|
||||
private ManageServer manageServer;
|
||||
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Page<CollectorSummary> getCollectors(String name, int pageIndex, Integer pageSize) {
|
||||
|
||||
+28
-32
@@ -23,13 +23,12 @@ import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.hertzbeat.common.entity.manager.Tag;
|
||||
import org.apache.hertzbeat.manager.dao.TagDao;
|
||||
import org.apache.hertzbeat.manager.service.TagService;
|
||||
import org.apache.hertzbeat.common.entity.manager.Label;
|
||||
import org.apache.hertzbeat.manager.dao.LabelDao;
|
||||
import org.apache.hertzbeat.manager.service.LabelService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
@@ -38,50 +37,47 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Tag service implementation.
|
||||
* Label service implementation.
|
||||
*/
|
||||
@Service
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Slf4j
|
||||
public class TagServiceImpl implements TagService {
|
||||
public class LabelServiceImpl implements LabelService {
|
||||
|
||||
@Autowired
|
||||
private TagDao tagDao;
|
||||
private LabelDao labelDao;
|
||||
|
||||
@Override
|
||||
public void addTags(List<Tag> tags) {
|
||||
public void addLabel(Label label) {
|
||||
// Verify request data
|
||||
tags = tags.stream().peek(tag -> {
|
||||
Optional<Tag> tagOptional = tagDao.findTagByNameAndTagValue(tag.getName(), tag.getTagValue());
|
||||
if (tagOptional.isPresent()) {
|
||||
throw new IllegalArgumentException("The tag already exists.");
|
||||
}
|
||||
tag.setType((byte) 1);
|
||||
tag.setId(null);
|
||||
}).distinct().collect(Collectors.toList());
|
||||
tagDao.saveAll(tags);
|
||||
Optional<Label> optional = labelDao.findLabelByNameAndTagValue(label.getName(), label.getTagValue());
|
||||
if (optional.isPresent()) {
|
||||
throw new IllegalArgumentException("The label already exists.");
|
||||
}
|
||||
label.setType((byte) 1);
|
||||
label.setId(null);
|
||||
labelDao.save(label);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modifyTag(Tag tag) {
|
||||
Optional<Tag> tagOptional = tagDao.findById(tag.getId());
|
||||
if (tagOptional.isPresent()) {
|
||||
public void modifyLabel(Label label) {
|
||||
Optional<Label> optional = labelDao.findById(label.getId());
|
||||
if (optional.isPresent()) {
|
||||
|
||||
Optional<Tag> tagExistOptional = tagDao.findTagByNameAndTagValue(tag.getName(), tag.getTagValue());
|
||||
if (tagExistOptional.isPresent() && !tagExistOptional.get().getId().equals(tag.getId())) {
|
||||
throw new IllegalArgumentException("The tag with same key and value already exists.");
|
||||
Optional<Label> existOptional = labelDao.findLabelByNameAndTagValue(label.getName(), label.getTagValue());
|
||||
if (existOptional.isPresent() && !existOptional.get().getId().equals(label.getId())) {
|
||||
throw new IllegalArgumentException("The label with same key and value already exists.");
|
||||
}
|
||||
tag.setTagValue(StringUtils.isEmpty(tag.getTagValue()) ? null : tag.getTagValue());
|
||||
tagDao.save(tag);
|
||||
label.setTagValue(StringUtils.isEmpty(label.getTagValue()) ? null : label.getTagValue());
|
||||
labelDao.save(label);
|
||||
} else {
|
||||
throw new IllegalArgumentException("The tag is not existed");
|
||||
throw new IllegalArgumentException("The label is not existed");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Tag> getTags(String search, Byte type, int pageIndex, int pageSize) {
|
||||
// Get tag information
|
||||
Specification<Tag> specification = (root, query, criteriaBuilder) -> {
|
||||
public Page<Label> getLabels(String search, Byte type, int pageIndex, int pageSize) {
|
||||
Specification<Label> specification = (root, query, criteriaBuilder) -> {
|
||||
List<Predicate> andList = new ArrayList<>();
|
||||
if (type != null) {
|
||||
Predicate predicateApp = criteriaBuilder.equal(root.get("type"), type);
|
||||
@@ -111,14 +107,14 @@ public class TagServiceImpl implements TagService {
|
||||
}
|
||||
};
|
||||
PageRequest pageRequest = PageRequest.of(pageIndex, pageSize);
|
||||
return tagDao.findAll(specification, pageRequest);
|
||||
return labelDao.findAll(specification, pageRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteTags(HashSet<Long> ids) {
|
||||
public void deleteLabels(HashSet<Long> ids) {
|
||||
if (CollectionUtils.isEmpty(ids)){
|
||||
return;
|
||||
}
|
||||
tagDao.deleteTagsByIdIn(ids);
|
||||
labelDao.deleteLabelsByIdIn(ids);
|
||||
}
|
||||
}
|
||||
+128
-327
@@ -23,7 +23,6 @@ import jakarta.persistence.criteria.CriteriaBuilder;
|
||||
import jakarta.persistence.criteria.Predicate;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.hertzbeat.alert.dao.AlertDefineBindDao;
|
||||
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
@@ -34,23 +33,19 @@ import org.apache.hertzbeat.common.entity.grafana.GrafanaDashboard;
|
||||
import org.apache.hertzbeat.common.entity.job.Configmap;
|
||||
import org.apache.hertzbeat.common.entity.job.Job;
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.CommonRequestProtocol;
|
||||
import org.apache.hertzbeat.common.entity.manager.Collector;
|
||||
import org.apache.hertzbeat.common.entity.manager.CollectorMonitorBind;
|
||||
import org.apache.hertzbeat.common.entity.manager.Monitor;
|
||||
import org.apache.hertzbeat.common.entity.manager.MonitorBind;
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.apache.hertzbeat.common.entity.manager.SdMonitorParam;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.entity.sd.ServiceDiscoveryProtocol;
|
||||
import org.apache.hertzbeat.common.support.event.MonitorDeletedEvent;
|
||||
import org.apache.hertzbeat.common.util.AesUtil;
|
||||
import org.apache.hertzbeat.common.util.CommonUtil;
|
||||
import org.apache.hertzbeat.common.util.FileUtil;
|
||||
import org.apache.hertzbeat.common.util.IntervalExpressionUtil;
|
||||
import org.apache.hertzbeat.common.util.IpDomainUtil;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.apache.hertzbeat.common.util.SdMonitorOperator;
|
||||
import org.apache.hertzbeat.common.util.SnowFlakeIdGenerator;
|
||||
import org.apache.hertzbeat.grafana.service.DashboardService;
|
||||
import org.apache.hertzbeat.manager.config.ManagerSseManager;
|
||||
@@ -65,7 +60,6 @@ import org.apache.hertzbeat.manager.scheduler.CollectJobScheduling;
|
||||
import org.apache.hertzbeat.manager.service.AppService;
|
||||
import org.apache.hertzbeat.manager.service.ImExportService;
|
||||
import org.apache.hertzbeat.manager.service.MonitorService;
|
||||
import org.apache.hertzbeat.manager.service.TagService;
|
||||
import org.apache.hertzbeat.manager.support.exception.MonitorDatabaseException;
|
||||
import org.apache.hertzbeat.manager.support.exception.MonitorDetectException;
|
||||
import org.apache.hertzbeat.warehouse.service.WarehouseService;
|
||||
@@ -81,15 +75,14 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -109,15 +102,11 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
private static final Long MONITOR_ID_TMP = 1000000000L;
|
||||
private static final byte ALL_MONITOR_STATUS = 9;
|
||||
|
||||
private static final int TAG_LENGTH = 2;
|
||||
|
||||
private static final String CONTENT_VALUE = MediaType.APPLICATION_OCTET_STREAM_VALUE + SignConstants.SINGLE_MARK + "charset=" + StandardCharsets.UTF_8;
|
||||
private final Map<String, ImExportService> imExportServiceMap = new HashMap<>();
|
||||
@Autowired
|
||||
private AppService appService;
|
||||
@Autowired
|
||||
private TagService tagService;
|
||||
@Autowired
|
||||
private CollectJobScheduling collectJobScheduling;
|
||||
@Autowired
|
||||
private MonitorDao monitorDao;
|
||||
@@ -147,25 +136,74 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public void detectMonitor(Monitor monitor, List<Param> params, String collector) throws MonitorDetectException {
|
||||
final Optional<Param> sdParam = SdMonitorOperator.getSdParam(params);
|
||||
|
||||
if (sdParam.isPresent()) {
|
||||
collectOneTimeSdData(monitor, collector, sdParam.get());
|
||||
} else {
|
||||
if (CommonConstants.SCRAPE_STATIC.equals(monitor.getScrape()) || !StringUtils.hasText(monitor.getScrape())) {
|
||||
detectMonitorDirectly(monitor, params, collector);
|
||||
} else {
|
||||
detectSdMonitor(monitor, params, collector);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void addMonitor(Monitor monitor, List<Param> params, String collector, GrafanaDashboard grafanaDashboard) throws RuntimeException {
|
||||
final Optional<Param> sdParam = SdMonitorOperator.getSdParam(params);
|
||||
if (sdParam.isPresent()) {
|
||||
addAndSaveMonitorJob(monitor.clone(), params, collector, SdMonitorParam.builder().sdParam(sdParam.get()).build(), grafanaDashboard);
|
||||
return;
|
||||
// Apply for monitor id
|
||||
long monitorId = SnowFlakeIdGenerator.generateId();
|
||||
Map<String, String> labels = monitor.getLabels();
|
||||
if (labels == null) {
|
||||
labels = new HashMap<>(8);
|
||||
monitor.setLabels(labels);
|
||||
}
|
||||
// Construct the collection task Job entity
|
||||
boolean isStatic = CommonConstants.SCRAPE_STATIC.equals(monitor.getScrape()) || !StringUtils.hasText(monitor.getScrape());
|
||||
String app = isStatic ? monitor.getApp() : monitor.getScrape();
|
||||
Job appDefine = appService.getAppDefine(app);
|
||||
if (!isStatic) {
|
||||
appDefine.setSd(true);
|
||||
}
|
||||
if (CommonConstants.PROMETHEUS.equals(monitor.getApp())) {
|
||||
appDefine.setApp(CommonConstants.PROMETHEUS_APP_PREFIX + monitor.getName());
|
||||
}
|
||||
appDefine.setMonitorId(monitorId);
|
||||
appDefine.setDefaultInterval(monitor.getIntervals());
|
||||
appDefine.setCyclic(true);
|
||||
appDefine.setTimestamp(System.currentTimeMillis());
|
||||
Map<String, String> metadata = Map.of(CommonConstants.LABEL_INSTANCE_NAME, monitor.getName(),
|
||||
CommonConstants.LABEL_INSTANCE_HOST, monitor.getHost());
|
||||
appDefine.setMetadata(metadata);
|
||||
appDefine.setLabels(monitor.getLabels());
|
||||
appDefine.setAnnotations(monitor.getAnnotations());
|
||||
List<Configmap> configmaps = params.stream().map(param -> {
|
||||
param.setMonitorId(monitorId);
|
||||
return new Configmap(param.getField(), param.getParamValue(), param.getType());
|
||||
}).collect(Collectors.toList());
|
||||
appDefine.setConfigmap(configmaps);
|
||||
long jobId = collector == null ? collectJobScheduling.addAsyncCollectJob(appDefine, null) :
|
||||
collectJobScheduling.addAsyncCollectJob(appDefine, collector);
|
||||
try {
|
||||
detectMonitor(monitor, params, collector);
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
addAndSaveMonitorJob(monitor, params, collector, null, grafanaDashboard);
|
||||
try {
|
||||
if (collector != null) {
|
||||
CollectorMonitorBind collectorMonitorBind = CollectorMonitorBind.builder()
|
||||
.collector(collector)
|
||||
.monitorId(monitorId)
|
||||
.build();
|
||||
collectorMonitorBindDao.save(collectorMonitorBind);
|
||||
}
|
||||
monitor.setId(monitorId);
|
||||
monitor.setJobId(jobId);
|
||||
// create grafana dashboard
|
||||
if (monitor.getApp().equals(CommonConstants.PROMETHEUS) && grafanaDashboard != null && grafanaDashboard.isEnabled()) {
|
||||
dashboardService.createOrUpdateDashboard(grafanaDashboard.getTemplate(), monitorId);
|
||||
}
|
||||
monitorDao.save(monitor);
|
||||
paramDao.saveAll(params);
|
||||
} catch (Exception e) {
|
||||
log.error("Error while adding monitor: {}", e.getMessage(), e);
|
||||
collectJobScheduling.cancelAsyncCollectJob(jobId);
|
||||
throw new MonitorDatabaseException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -233,7 +271,7 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
}
|
||||
}
|
||||
// the dispatch collector must exist if pin
|
||||
if (StringUtils.isNotBlank(monitorDto.getCollector())) {
|
||||
if (StringUtils.hasText(monitorDto.getCollector())) {
|
||||
Optional<Collector> optionalCollector = collectorDao.findCollectorByName(monitorDto.getCollector());
|
||||
if (optionalCollector.isEmpty()) {
|
||||
throw new IllegalArgumentException("The pinned collector does not exist.");
|
||||
@@ -250,7 +288,7 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
if (paramDefine.isRequired() && (param == null || param.getParamValue() == null)) {
|
||||
throw new IllegalArgumentException("Params field " + field + " is required.");
|
||||
}
|
||||
if (param != null && param.getParamValue() != null && StringUtils.isNotBlank(param.getParamValue())) {
|
||||
if (param != null && StringUtils.hasText(param.getParamValue())) {
|
||||
switch (paramDefine.getType()) {
|
||||
case "number":
|
||||
double doubleValue;
|
||||
@@ -376,7 +414,6 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void modifyMonitor(Monitor monitor, List<Param> params, String collector, GrafanaDashboard grafanaDashboard) throws RuntimeException {
|
||||
final Optional<Param> sdParam = SdMonitorOperator.getSdParam(params);
|
||||
long monitorId = monitor.getId();
|
||||
// Check to determine whether the monitor corresponding to the monitor id exists
|
||||
Optional<Monitor> queryOption = monitorDao.findById(monitorId);
|
||||
@@ -393,36 +430,14 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
labels = new HashMap<>(8);
|
||||
monitor.setLabels(labels);
|
||||
}
|
||||
|
||||
boolean isStatic = CommonConstants.SCRAPE_STATIC.equals(monitor.getScrape()) || !StringUtils.hasText(monitor.getScrape());
|
||||
if (preMonitor.getStatus() != CommonConstants.MONITOR_PAUSED_CODE) {
|
||||
// Construct the collection task Job entity
|
||||
Job appDefine = appService.getAppDefine(monitor.getApp());
|
||||
if (sdParam.isPresent()) {
|
||||
// not allow to modify a sub monitor to main monitor
|
||||
labels.entrySet()
|
||||
.stream()
|
||||
.filter(label -> org.apache.commons.lang3.StringUtils.equals(label.getKey(), CommonConstants.TAG_AUTO_CREATED))
|
||||
.findFirst()
|
||||
.ifPresent(label -> {
|
||||
final MonitorBind isSubMonitorBefore = monitorBindDao.findMonitorBindByBizIdAndMonitorIdAndType(Long.valueOf(label.getValue()),
|
||||
monitorId, CommonConstants.MONITOR_BIND_TYPE_SD_SUB_MONITOR);
|
||||
if (Objects.nonNull(isSubMonitorBefore)) {
|
||||
throw new UnsupportedOperationException("Can not change a auto-created monitor to sd! ");
|
||||
}
|
||||
});
|
||||
// create main monitor bind
|
||||
final List<MonitorBind> mainMonitorBind = monitorBindDao.findMonitorBindByBizIdAndType(monitorId, CommonConstants.MONITOR_BIND_TYPE_SD_MAIN_MONITOR);
|
||||
if (CollectionUtils.isEmpty(mainMonitorBind)) {
|
||||
monitorBindDao.save(Objects.requireNonNull(
|
||||
SdMonitorOperator.buildSdMainMonitorBind(
|
||||
SdMonitorParam.builder().sdParam(sdParam.get()).build(), monitorId
|
||||
)));
|
||||
}
|
||||
|
||||
appDefine = SdMonitorOperator.constructSdJob(appDefine, sdParam.get());
|
||||
labels.put(CommonConstants.TAG_SD_MAIN_MONITOR, ServiceDiscoveryProtocol.Type.getType(sdParam.get().getField()).name());
|
||||
String app = isStatic ? monitor.getApp() : monitor.getScrape();
|
||||
Job appDefine = appService.getAppDefine(app);
|
||||
if (!isStatic) {
|
||||
appDefine.setSd(true);
|
||||
}
|
||||
|
||||
if (CommonConstants.PROMETHEUS.equals(monitor.getApp())) {
|
||||
appDefine.setApp(CommonConstants.PROMETHEUS_APP_PREFIX + monitor.getName());
|
||||
}
|
||||
@@ -450,8 +465,7 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
// execute only in non paused status
|
||||
try {
|
||||
detectMonitor(monitor, params, collector);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
// After the update is successfully released, refresh the database
|
||||
@@ -474,12 +488,6 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
}
|
||||
}
|
||||
monitorDao.save(monitor);
|
||||
|
||||
// delete all sub monitor if sd param is removed
|
||||
final List<MonitorBind> subMonitorBindList = monitorBindDao.findMonitorBindByBizIdAndType(monitorId, CommonConstants.MONITOR_BIND_TYPE_SD_SUB_MONITOR);
|
||||
if (!CollectionUtils.isEmpty(subMonitorBindList) && sdParam.isEmpty()) {
|
||||
deleteMonitors(subMonitorBindList.stream().map(MonitorBind::getMonitorId).collect(Collectors.toSet()));
|
||||
}
|
||||
paramDao.saveAll(params);
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
@@ -501,12 +509,6 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
if (CollectionUtils.isEmpty(ids)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<MonitorBind> allSubMonitorBind = monitorBindDao.findAllByBizIdInAndType(ids, CommonConstants.MONITOR_BIND_TYPE_SD_SUB_MONITOR);
|
||||
if (!CollectionUtils.isEmpty(allSubMonitorBind)) {
|
||||
ids.addAll(allSubMonitorBind.stream().map(MonitorBind::getMonitorId).collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
List<Monitor> monitors = monitorDao.findMonitorsByIdIn(ids);
|
||||
if (!monitors.isEmpty()) {
|
||||
monitorDao.deleteAll(monitors);
|
||||
@@ -537,7 +539,9 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
monitorDto.setMetrics(metrics);
|
||||
monitorDto.setGrafanaDashboard(dashboardService.getDashboardByMonitorId(id));
|
||||
} else {
|
||||
Job job = appService.getAppDefine(monitor.getApp());
|
||||
boolean isStatic = CommonConstants.SCRAPE_STATIC.equals(monitor.getScrape()) || !StringUtils.hasText(monitor.getScrape());
|
||||
String type = isStatic ? monitor.getApp() : monitor.getScrape();
|
||||
Job job = appService.getAppDefine(type);
|
||||
List<String> metrics = job.getMetrics().stream()
|
||||
.filter(Metrics::isVisible)
|
||||
.map(Metrics::getName).collect(Collectors.toList());
|
||||
@@ -563,7 +567,7 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
}
|
||||
andList.add(inPredicate);
|
||||
}
|
||||
if (StringUtils.isNotBlank(app)) {
|
||||
if (StringUtils.hasText(app)) {
|
||||
Predicate predicateApp = criteriaBuilder.equal(root.get("app"), app);
|
||||
andList.add(predicateApp);
|
||||
}
|
||||
@@ -575,17 +579,17 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
Predicate andPredicate = criteriaBuilder.and(andList.toArray(andPredicates));
|
||||
|
||||
List<Predicate> orList = new ArrayList<>();
|
||||
if (StringUtils.isNotBlank(search)) {
|
||||
if (StringUtils.hasText(search)) {
|
||||
Predicate predicateHost = criteriaBuilder.like(root.get("host"), "%" + search + "%");
|
||||
Predicate predicateName = criteriaBuilder.like(criteriaBuilder.lower(root.get("name")), "%" + search.toLowerCase() + "%");
|
||||
if (StringUtils.isNumeric(search)){
|
||||
if (CommonUtil.isNumeric(search)){
|
||||
Predicate predicateId = criteriaBuilder.equal(root.get("id"), Long.parseLong(search));
|
||||
orList.add(predicateId);
|
||||
}
|
||||
orList.add(predicateHost);
|
||||
orList.add(predicateName);
|
||||
}
|
||||
if (StringUtils.isNotBlank(labels)) {
|
||||
if (StringUtils.hasText(labels)) {
|
||||
String[] labelAres = labels.split(",");
|
||||
for (String label : labelAres) {
|
||||
String[] labelArr = label.split(":");
|
||||
@@ -620,18 +624,10 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelManageMonitors(HashSet<Long> ids) {
|
||||
public void cancelManageMonitors(Set<Long> ids) {
|
||||
if (CollectionUtils.isEmpty(ids)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Set<Long> subMonitorIds = monitorBindDao.findAllByBizIdInAndType(ids, CommonConstants.MONITOR_BIND_TYPE_SD_SUB_MONITOR).stream()
|
||||
.map(MonitorBind::getMonitorId)
|
||||
.collect(Collectors.toSet());
|
||||
if (!CollectionUtils.isEmpty(subMonitorIds)) {
|
||||
ids.addAll(subMonitorIds);
|
||||
}
|
||||
|
||||
// Update monitoring status Delete corresponding monitoring periodic task
|
||||
// The jobId is not deleted, and the jobId is reused again after the management is started.
|
||||
List<Monitor> managedMonitors = monitorDao.findMonitorsByIdIn(ids)
|
||||
@@ -648,7 +644,7 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableManageMonitors(HashSet<Long> ids) {
|
||||
public void enableManageMonitors(Set<Long> ids) {
|
||||
// Update monitoring status Add corresponding monitoring periodic task
|
||||
List<Monitor> unManagedMonitors = monitorDao.findMonitorsByIdIn(ids)
|
||||
.stream().filter(monitor ->
|
||||
@@ -662,20 +658,12 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
for (Monitor monitor : unManagedMonitors) {
|
||||
// Construct the collection task Job entity
|
||||
List<Param> params = paramDao.findParamsByMonitorId(monitor.getId());
|
||||
final Optional<Param> sdParam = SdMonitorOperator.getSdParam(params);
|
||||
Job appDefine = appService.getAppDefine(monitor.getApp());
|
||||
if (sdParam.isPresent()) {
|
||||
final List<MonitorBind> mainMonitorBind = monitorBindDao.findMonitorBindByBizIdAndType(monitor.getId(), CommonConstants.MONITOR_BIND_TYPE_SD_MAIN_MONITOR);
|
||||
if (CollectionUtils.isEmpty(mainMonitorBind)) {
|
||||
monitorBindDao.save(Objects.requireNonNull(
|
||||
SdMonitorOperator.buildSdMainMonitorBind(
|
||||
SdMonitorParam.builder().sdParam(sdParam.get()).build(), monitor.getId()
|
||||
)));
|
||||
}
|
||||
|
||||
appDefine = SdMonitorOperator.constructSdJob(appDefine, sdParam.get());
|
||||
boolean isStatic = CommonConstants.SCRAPE_STATIC.equals(monitor.getScrape()) || !StringUtils.hasText(monitor.getScrape());
|
||||
String app = isStatic ? monitor.getApp() : monitor.getScrape();
|
||||
Job appDefine = appService.getAppDefine(app);
|
||||
if (!isStatic) {
|
||||
appDefine.setSd(true);
|
||||
}
|
||||
|
||||
if (CommonConstants.PROMETHEUS.equals(monitor.getApp())) {
|
||||
appDefine.setApp(CommonConstants.PROMETHEUS_APP_PREFIX + monitor.getName());
|
||||
}
|
||||
@@ -691,7 +679,7 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
List<Configmap> configmaps = params.stream().map(param ->
|
||||
new Configmap(param.getField(), param.getParamValue(), param.getType())).collect(Collectors.toList());
|
||||
List<ParamDefine> paramDefaultValue = appDefine.getParams().stream()
|
||||
.filter(item -> StringUtils.isNotBlank(item.getDefaultValue()))
|
||||
.filter(item -> StringUtils.hasText(item.getDefaultValue()))
|
||||
.toList();
|
||||
paramDefaultValue.forEach(defaultVar -> {
|
||||
if (configmaps.stream().noneMatch(item -> item.getKey().equals(defaultVar.getField()))) {
|
||||
@@ -752,25 +740,6 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
}).filter(Objects::nonNull).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void copyMonitors(List<Long> ids) {
|
||||
|
||||
ids.stream().parallel().forEach(id -> {
|
||||
// get monitor and Params according id
|
||||
Optional<Monitor> monitorOpt = monitorDao.findById(id);
|
||||
List<Param> params = paramDao.findParamsByMonitorId(id);
|
||||
|
||||
monitorOpt.ifPresentOrElse(monitor -> {
|
||||
// deep copy original monitor to achieve persist in JPA
|
||||
Monitor newMonitor = JsonUtil.fromJson(JsonUtil.toJson(monitor), Monitor.class);
|
||||
if (newMonitor != null) {
|
||||
copyMonitor(newMonitor, params);
|
||||
}
|
||||
}, () -> log.warn("can not find the monitor for id :{}", id));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAppCollectJob(Job job) {
|
||||
List<Monitor> monitors = monitorDao.findMonitorsByAppEquals(job.getApp())
|
||||
@@ -807,7 +776,7 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
List<Configmap> configmaps = params.stream().map(param -> new Configmap(param.getField(),
|
||||
param.getParamValue(), param.getType())).collect(Collectors.toList());
|
||||
List<ParamDefine> paramDefaultValue = appDefine.getParams().stream()
|
||||
.filter(item -> StringUtils.isNotBlank(item.getDefaultValue()))
|
||||
.filter(item -> StringUtils.hasText(item.getDefaultValue()))
|
||||
.toList();
|
||||
paramDefaultValue.forEach(defaultVar -> {
|
||||
if (configmaps.stream().noneMatch(item -> item.getKey().equals(defaultVar.getField()))) {
|
||||
@@ -827,80 +796,7 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAndSaveMonitorJob(Monitor monitor, List<Param> params, String collector, SdMonitorParam sdMonitorParam, GrafanaDashboard grafanaDashboard) {
|
||||
// Apply for monitor id
|
||||
long monitorId = SnowFlakeIdGenerator.generateId();
|
||||
Map<String, String> labels = monitor.getLabels();
|
||||
if (labels == null) {
|
||||
labels = new HashMap<>(8);
|
||||
monitor.setLabels(labels);
|
||||
}
|
||||
MonitorBind monitorBind = null;
|
||||
// Construct the collection task Job entity
|
||||
Job appDefine = appService.getAppDefine(monitor.getApp());
|
||||
if (Objects.nonNull(sdMonitorParam)) {
|
||||
monitorBind = Objects.isNull(sdMonitorParam.getSdParam())
|
||||
? SdMonitorOperator.buildSdSubMonitorBind(sdMonitorParam, monitorId, labels)
|
||||
: SdMonitorOperator.buildSdMainMonitorBind(sdMonitorParam, monitorId);
|
||||
|
||||
appDefine = SdMonitorOperator.constructSdJobAndTag(sdMonitorParam, labels, appDefine);
|
||||
}
|
||||
if (CommonConstants.PROMETHEUS.equals(monitor.getApp())) {
|
||||
appDefine.setApp(CommonConstants.PROMETHEUS_APP_PREFIX + monitor.getName());
|
||||
}
|
||||
appDefine.setMonitorId(monitorId);
|
||||
appDefine.setDefaultInterval(monitor.getIntervals());
|
||||
appDefine.setCyclic(true);
|
||||
appDefine.setTimestamp(System.currentTimeMillis());
|
||||
Map<String, String> metadata = Map.of(CommonConstants.LABEL_INSTANCE_NAME, monitor.getName(),
|
||||
CommonConstants.LABEL_INSTANCE_HOST, monitor.getHost());
|
||||
appDefine.setMetadata(metadata);
|
||||
appDefine.setLabels(monitor.getLabels());
|
||||
appDefine.setAnnotations(monitor.getAnnotations());
|
||||
resetMetricsCommonField(monitor, appDefine, sdMonitorParam);
|
||||
|
||||
List<Configmap> configmaps = params.stream().map(param -> {
|
||||
param.setMonitorId(monitorId);
|
||||
return new Configmap(param.getField(), param.getParamValue(), param.getType());
|
||||
}).collect(Collectors.toList());
|
||||
appDefine.setConfigmap(configmaps);
|
||||
|
||||
long jobId = collector == null ? collectJobScheduling.addAsyncCollectJob(appDefine, null) :
|
||||
collectJobScheduling.addAsyncCollectJob(appDefine, collector);
|
||||
|
||||
try {
|
||||
detectMonitor(monitor, params, collector);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
try {
|
||||
if (collector != null) {
|
||||
CollectorMonitorBind collectorMonitorBind = CollectorMonitorBind.builder()
|
||||
.collector(collector)
|
||||
.monitorId(monitorId)
|
||||
.build();
|
||||
collectorMonitorBindDao.save(collectorMonitorBind);
|
||||
}
|
||||
monitor.setId(monitorId);
|
||||
monitor.setJobId(jobId);
|
||||
// create grafana dashboard
|
||||
if (monitor.getApp().equals(CommonConstants.PROMETHEUS) && grafanaDashboard != null && grafanaDashboard.isEnabled()) {
|
||||
dashboardService.createOrUpdateDashboard(grafanaDashboard.getTemplate(), monitorId);
|
||||
}
|
||||
monitorDao.save(monitor);
|
||||
if (Objects.nonNull(monitorBind)) {
|
||||
monitorBindDao.save(monitorBind);
|
||||
}
|
||||
paramDao.saveAll(params);
|
||||
} catch (Exception e) {
|
||||
log.error("Error while adding monitor: {}", e.getMessage(), e);
|
||||
collectJobScheduling.cancelAsyncCollectJob(jobId);
|
||||
throw new MonitorDatabaseException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Monitor getMonitor(Long monitorId) {
|
||||
return monitorDao.findById(monitorId).orElse(null);
|
||||
@@ -916,73 +812,69 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
return monitorDao.findMonitorsByAppEquals(app);
|
||||
}
|
||||
|
||||
private void resetMetricsCommonField(Monitor monitor, Job appDefine, SdMonitorParam sdMonitorParam) {
|
||||
if (Objects.isNull(sdMonitorParam)
|
||||
|| org.apache.commons.lang3.StringUtils.isAnyBlank(sdMonitorParam.getDetectedHost(), sdMonitorParam.getDetectedPort())) {
|
||||
return;
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void copyMonitor(Long id) {
|
||||
// Get the source monitor information
|
||||
Optional<Monitor> monitorOptional = monitorDao.findById(id);
|
||||
if (monitorOptional.isEmpty()) {
|
||||
throw new IllegalArgumentException("Monitor not found: " + id);
|
||||
}
|
||||
|
||||
for (Metrics metric : appDefine.getMetrics()) {
|
||||
Arrays.stream(metric.getClass().getDeclaredFields())
|
||||
.filter(field -> metric.getProtocol().equalsIgnoreCase(field.getName()))
|
||||
.findFirst()
|
||||
.ifPresent(field -> {
|
||||
field.setAccessible(true);
|
||||
final Object obj;
|
||||
try {
|
||||
obj = field.get(metric);
|
||||
if (obj instanceof CommonRequestProtocol protocol) {
|
||||
protocol.setHost(sdMonitorParam.getDetectedHost());
|
||||
protocol.setPort(sdMonitorParam.getDetectedPort());
|
||||
}
|
||||
} catch (IllegalAccessException exception) {
|
||||
log.warn("Not such field {}", field.getName());
|
||||
}
|
||||
});
|
||||
Monitor sourceMonitor = monitorOptional.get();
|
||||
// Get the parameters of source monitor
|
||||
List<Param> sourceParams = paramDao.findParamsByMonitorId(id);
|
||||
// Create new monitor object
|
||||
Monitor newMonitor = new Monitor();
|
||||
// Copy basic properties, exclude ID, jobId and status
|
||||
BeanUtils.copyProperties(sourceMonitor, newMonitor, "id", "jobId", "status");
|
||||
// Set new name
|
||||
newMonitor.setName(sourceMonitor.getName() + "_copy");
|
||||
// Set initial status
|
||||
newMonitor.setStatus(CommonConstants.MONITOR_UP_CODE);
|
||||
// Set create and update time
|
||||
newMonitor.setGmtCreate(LocalDateTime.now());
|
||||
newMonitor.setGmtUpdate(LocalDateTime.now());
|
||||
// Copy parameters
|
||||
List<Param> newParams = new ArrayList<>();
|
||||
if (!sourceParams.isEmpty()) {
|
||||
for (Param sourceParam : sourceParams) {
|
||||
Param newParam = new Param();
|
||||
BeanUtils.copyProperties(sourceParam, newParam, "id");
|
||||
newParam.setMonitorId(newMonitor.getId());
|
||||
newParams.add(newParam);
|
||||
}
|
||||
}
|
||||
|
||||
monitor.setHost(sdMonitorParam.getDetectedHost());
|
||||
Map<String, String> labels = monitor.getLabels();
|
||||
if (labels == null) {
|
||||
labels = new HashMap<>(8);
|
||||
monitor.setLabels(labels);
|
||||
}
|
||||
labels.put(CommonConstants.TAG_MONITOR_HOST, sdMonitorParam.getDetectedHost());
|
||||
monitor.setName(monitor.getApp().toUpperCase() + CommonConstants.LOCALE_SEPARATOR + sdMonitorParam.getDetectedHost()
|
||||
+ CommonConstants.LOCALE_SEPARATOR + SnowFlakeIdGenerator.generateId());
|
||||
addMonitor(newMonitor, newParams, null, null);
|
||||
}
|
||||
|
||||
private void copyMonitor(Monitor monitor, List<Param> params) {
|
||||
monitor.setName(String.format("%s - copy", monitor.getName()));
|
||||
addMonitor(monitor, params, null, null);
|
||||
}
|
||||
|
||||
private void collectOneTimeSdData(Monitor monitor, String collector, Param sdParam) {
|
||||
private void detectSdMonitor(Monitor monitor, List<Param> params, String collector) {
|
||||
Long monitorId = monitor.getId();
|
||||
if (monitorId == null || monitorId == 0) {
|
||||
monitorId = MONITOR_ID_TMP;
|
||||
}
|
||||
Job appDefine = appService.getAppDefine(monitor.getApp());
|
||||
if (CommonConstants.PROMETHEUS.equals(monitor.getApp())) {
|
||||
appDefine.setApp(CommonConstants.PROMETHEUS_APP_PREFIX + monitor.getName());
|
||||
if (monitor.getScrape() == null || params == null || params.isEmpty()) {
|
||||
throw new IllegalArgumentException("scrape params is null or empty!");
|
||||
}
|
||||
Job appDefine = appService.getAppDefine(monitor.getScrape());
|
||||
appDefine.setMonitorId(monitorId);
|
||||
appDefine.setCyclic(false);
|
||||
appDefine.setTimestamp(System.currentTimeMillis());
|
||||
Map<String, String> metadata = Map.of(CommonConstants.LABEL_INSTANCE_NAME, monitor.getName(),
|
||||
CommonConstants.LABEL_INSTANCE_HOST, monitor.getHost());
|
||||
Map<String, String> metadata = Map.of(CommonConstants.LABEL_INSTANCE_NAME, monitor.getName());
|
||||
appDefine.setMetadata(metadata);
|
||||
appDefine.setLabels(monitor.getLabels());
|
||||
appDefine.setAnnotations(monitor.getAnnotations());
|
||||
final Job sdJob = SdMonitorOperator.constructSdJob(appDefine, sdParam);
|
||||
List<Configmap> configmaps = params.stream().map(param ->
|
||||
new Configmap(param.getField(), param.getParamValue(), param.getType())).collect(Collectors.toList());
|
||||
appDefine.setConfigmap(configmaps);
|
||||
appDefine.setSd(true);
|
||||
List<CollectRep.MetricsData> collectRep;
|
||||
if (collector != null) {
|
||||
collectRep = collectJobScheduling.collectSyncJobData(sdJob, collector);
|
||||
collectRep = collectJobScheduling.collectSyncJobData(appDefine, collector);
|
||||
} else {
|
||||
collectRep = collectJobScheduling.collectSyncJobData(sdJob);
|
||||
collectRep = collectJobScheduling.collectSyncJobData(appDefine);
|
||||
}
|
||||
monitor.setStatus(CommonConstants.MONITOR_UP_CODE);
|
||||
|
||||
// If the detection result fails, a detection exception is thrown
|
||||
if (collectRep == null || collectRep.isEmpty()) {
|
||||
monitor.setStatus(CommonConstants.MONITOR_DOWN_CODE);
|
||||
@@ -1038,95 +930,4 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
}
|
||||
collectRep.forEach(CollectRep.MetricsData::close);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void copyMonitor(Long id) {
|
||||
// Get the source monitor information
|
||||
Optional<Monitor> monitorOptional = monitorDao.findById(id);
|
||||
if (monitorOptional.isEmpty()) {
|
||||
throw new IllegalArgumentException("Monitor not found: " + id);
|
||||
}
|
||||
Monitor sourceMonitor = monitorOptional.get();
|
||||
|
||||
// Get the parameters of source monitor
|
||||
List<Param> sourceParams = paramDao.findParamsByMonitorId(id);
|
||||
|
||||
// Create new monitor object
|
||||
Monitor newMonitor = new Monitor();
|
||||
// Copy basic properties, exclude ID, jobId and status
|
||||
BeanUtils.copyProperties(sourceMonitor, newMonitor, "id", "jobId", "status");
|
||||
// Set new name
|
||||
newMonitor.setName(sourceMonitor.getName() + "_copy");
|
||||
// Set initial status
|
||||
newMonitor.setStatus(CommonConstants.MONITOR_UP_CODE);
|
||||
// Set create and update time
|
||||
newMonitor.setGmtCreate(LocalDateTime.now());
|
||||
newMonitor.setGmtUpdate(LocalDateTime.now());
|
||||
// Generate new ID using snowflake algorithm
|
||||
newMonitor.setId(SnowFlakeIdGenerator.generateId());
|
||||
// Save new monitor
|
||||
newMonitor = monitorDao.save(newMonitor);
|
||||
|
||||
// Ensure ID is set
|
||||
if (newMonitor.getId() == null) {
|
||||
throw new RuntimeException("Failed to generate monitor ID");
|
||||
}
|
||||
|
||||
// Copy parameters
|
||||
if (!sourceParams.isEmpty()) {
|
||||
List<Param> newParams = new ArrayList<>();
|
||||
for (Param sourceParam : sourceParams) {
|
||||
Param newParam = new Param();
|
||||
BeanUtils.copyProperties(sourceParam, newParam, "id");
|
||||
newParam.setMonitorId(newMonitor.getId());
|
||||
newParams.add(newParam);
|
||||
}
|
||||
paramDao.saveAll(newParams);
|
||||
}
|
||||
|
||||
try {
|
||||
// Build collect job
|
||||
Job appDefine = appService.getAppDefine(newMonitor.getApp());
|
||||
if (CommonConstants.PROMETHEUS.equals(newMonitor.getApp())) {
|
||||
appDefine.setApp(CommonConstants.PROMETHEUS_APP_PREFIX + newMonitor.getName());
|
||||
}
|
||||
// Ensure using correct monitor ID
|
||||
appDefine.setMonitorId(newMonitor.getId());
|
||||
appDefine.setDefaultInterval(newMonitor.getIntervals());
|
||||
appDefine.setCyclic(true);
|
||||
appDefine.setTimestamp(System.currentTimeMillis());
|
||||
Map<String, String> metadata = Map.of(CommonConstants.LABEL_INSTANCE_NAME, newMonitor.getName(),
|
||||
CommonConstants.LABEL_INSTANCE_HOST, newMonitor.getHost());
|
||||
appDefine.setMetadata(metadata);
|
||||
appDefine.setLabels(newMonitor.getLabels());
|
||||
appDefine.setAnnotations(newMonitor.getAnnotations());
|
||||
List<Configmap> configmaps = sourceParams.stream()
|
||||
.map(param -> new Configmap(param.getField(), param.getParamValue(), param.getType()))
|
||||
.collect(Collectors.toList());
|
||||
appDefine.setConfigmap(configmaps);
|
||||
|
||||
// Get collector configuration from source monitor
|
||||
Optional<CollectorMonitorBind> bindOptional =
|
||||
collectorMonitorBindDao.findCollectorMonitorBindByMonitorId(sourceMonitor.getId());
|
||||
String collector = bindOptional.map(CollectorMonitorBind::getCollector).orElse(null);
|
||||
|
||||
// Dispatch collect job
|
||||
long jobId = collectJobScheduling.addAsyncCollectJob(appDefine, collector);
|
||||
newMonitor.setJobId(jobId);
|
||||
monitorDao.save(newMonitor);
|
||||
|
||||
// Copy collector binding if exists
|
||||
if (collector != null) {
|
||||
CollectorMonitorBind newBind = CollectorMonitorBind.builder()
|
||||
.collector(collector)
|
||||
.monitorId(newMonitor.getId())
|
||||
.build();
|
||||
collectorMonitorBindDao.save(newBind);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Create collect job error: {}", e.getMessage(), e);
|
||||
throw new RuntimeException("Create collect job failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,10 +132,21 @@ warehouse:
|
||||
# (please set this configuration reasonably as history records can affect performance when it is large)
|
||||
max-history-record-num: 6000
|
||||
victoria-metrics:
|
||||
# Standalone mode toggle — must be set to false when using cluster mode
|
||||
enabled: false
|
||||
url: http://localhost:8428
|
||||
username: root
|
||||
password: root
|
||||
cluster:
|
||||
enabled: false
|
||||
select:
|
||||
url: http://localhost:8481
|
||||
username: root
|
||||
password: root
|
||||
insert:
|
||||
url: http://localhost:8480
|
||||
username: root
|
||||
password: root
|
||||
td-engine:
|
||||
enabled: false
|
||||
driver-class-name: com.taosdata.jdbc.rs.RestfulDriver
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# The monitoring type category:service-application service monitoring db-database monitoring custom-custom monitoring os-operating system monitoring
|
||||
category: __system__
|
||||
# Monitoring application type name (consistent with file name) eg: linux windows tomcat mysql aws...
|
||||
app: http_sd
|
||||
# The app api i18n name
|
||||
name:
|
||||
zh-CN: Http Service Discovery
|
||||
en-US: Http Service Discovery
|
||||
# Input params define for app api(render web ui by the definition)
|
||||
params:
|
||||
# field-param field key
|
||||
- field: __sd_url__
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 服务发现地址
|
||||
en-US: Service Discovery Url
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: text
|
||||
# required-true or false
|
||||
required: true
|
||||
|
||||
metrics:
|
||||
- name: target
|
||||
i18n:
|
||||
zh-CN: 监控目标
|
||||
en-US: Monitor Target
|
||||
# metrics scheduling priority(0->127)->(high->low), metrics with the same priority will be scheduled in parallel
|
||||
# priority 0's metrics is availability metrics, it will be scheduled first, only availability metrics collect success will the scheduling continue
|
||||
priority: 0
|
||||
# collect metrics content
|
||||
fields:
|
||||
# field-metric name, type-metric type(0-number,1-string), unit-metric unit('%','ms','MB'), label-whether it is a metrics label field
|
||||
- field: host
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: Host
|
||||
en-US: Host
|
||||
- field: port
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: Port
|
||||
en-US: Port
|
||||
# the protocol used for monitoring, eg: sql, ssh, http, telnet, wmi, snmp, sdk
|
||||
protocol: http_sd
|
||||
# the config content when protocol is http_sd
|
||||
http_sd:
|
||||
url: ^_^__sd_url__^_^
|
||||
@@ -14,7 +14,7 @@
|
||||
# limitations under the License.
|
||||
|
||||
# The monitoring type category:service-application service monitoring db-database monitoring custom-custom monitoring os-operating system monitoring
|
||||
category: service
|
||||
category: __system__
|
||||
# Monitoring application type name (consistent with file name) eg: linux windows tomcat mysql aws...
|
||||
app: push
|
||||
# The app api i18n name
|
||||
|
||||
@@ -26,13 +26,13 @@
|
||||
</#if>
|
||||
"triggerTimes": ${alert.triggerTimes!0},
|
||||
"startAt": "${((alert.startAt!0)?number_to_datetime)?string('yyyy-MM-dd HH:mm:ss')}",
|
||||
<#if alert.activeAt?? && alert.activeAt gt 0>,
|
||||
"activeAt": "${((alert.activeAt!0)?number_to_datetime)?string('yyyy-MM-dd HH:mm:ss')}"
|
||||
<#if alert.activeAt?? && alert.activeAt gt 0>
|
||||
"activeAt": "${((alert.activeAt!0)?number_to_datetime)?string('yyyy-MM-dd HH:mm:ss')}",
|
||||
</#if>
|
||||
<#if alert.endAt?? && alert.endAt gt 0>,
|
||||
"endAt": "${(alert.endAt?number_to_datetime)?string('yyyy-MM-dd HH:mm:ss')}"
|
||||
<#if alert.endAt?? && alert.endAt gt 0>
|
||||
"endAt": "${(alert.endAt?number_to_datetime)?string('yyyy-MM-dd HH:mm:ss')}"<#if alert.annotations?? && alert.annotations?size gt 0>,</#if>
|
||||
</#if>
|
||||
<#if alert.annotations?? && alert.annotations?size gt 0>,
|
||||
<#if alert.annotations?? && alert.annotations?size gt 0>
|
||||
"annotations": {
|
||||
<#list alert.annotations?keys as key>
|
||||
"${key}": "${alert.annotations[key]?json_string}"<#if key?has_next>,</#if>
|
||||
|
||||
+24
-31
@@ -19,12 +19,10 @@ package org.apache.hertzbeat.manager.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.List;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.manager.Tag;
|
||||
import org.apache.hertzbeat.common.entity.manager.Label;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.apache.hertzbeat.manager.service.impl.TagServiceImpl;
|
||||
import org.apache.hertzbeat.manager.service.impl.LabelServiceImpl;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -37,42 +35,38 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
|
||||
/**
|
||||
* Test case for {@link TagController}
|
||||
* Test case for {@link LabelController}
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class TagControllerTest {
|
||||
class LabelControllerTest {
|
||||
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Mock
|
||||
private TagServiceImpl tagService;
|
||||
private LabelServiceImpl labelService;
|
||||
|
||||
@InjectMocks
|
||||
private TagController tagController;
|
||||
private LabelController labelController;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
this.mockMvc = MockMvcBuilders.standaloneSetup(tagController).build();
|
||||
this.mockMvc = MockMvcBuilders.standaloneSetup(labelController).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void addNewTags() throws Exception {
|
||||
List<Tag> tags = new ArrayList<>();
|
||||
Tag tag = new Tag();
|
||||
tag.setId(87584674384L);
|
||||
tag.setName("app");
|
||||
tag.setTagValue("23");
|
||||
tag.setColor("#ffff");
|
||||
tag.setType((byte) 1);
|
||||
tag.setCreator("tom");
|
||||
tag.setModifier("tom");
|
||||
|
||||
tags.add(tag);
|
||||
void addNewLabel() throws Exception {
|
||||
Label label = new Label();
|
||||
label.setId(87584674384L);
|
||||
label.setName("app");
|
||||
label.setTagValue("23");
|
||||
label.setType((byte) 1);
|
||||
label.setCreator("tom");
|
||||
label.setModifier("tom");
|
||||
|
||||
|
||||
this.mockMvc.perform(MockMvcRequestBuilders.post("/api/tag")
|
||||
this.mockMvc.perform(MockMvcRequestBuilders.post("/api/label")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(JsonUtil.toJson(tags)))
|
||||
.content(JsonUtil.toJson(label)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
|
||||
.andExpect(jsonPath("$.msg").value("Add success"))
|
||||
@@ -80,17 +74,16 @@ class TagControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void modifyMonitor() throws Exception {
|
||||
Tag tag = new Tag();
|
||||
void modifyLabel() throws Exception {
|
||||
Label tag = new Label();
|
||||
tag.setId(87584674384L);
|
||||
tag.setName("app");
|
||||
tag.setTagValue("23");
|
||||
tag.setColor("#ffff");
|
||||
tag.setType((byte) 1);
|
||||
tag.setCreator("tom");
|
||||
tag.setModifier("tom");
|
||||
|
||||
this.mockMvc.perform(MockMvcRequestBuilders.put("/api/tag")
|
||||
this.mockMvc.perform(MockMvcRequestBuilders.put("/api/label")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(JsonUtil.toJson(tag)))
|
||||
.andExpect(status().isOk())
|
||||
@@ -100,8 +93,8 @@ class TagControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTags() throws Exception {
|
||||
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/tag?type={type}&search={search}",
|
||||
void getLabels() throws Exception {
|
||||
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/label?type={type}&search={search}",
|
||||
(byte) 1, "status"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
|
||||
@@ -109,8 +102,8 @@ class TagControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteTags() throws Exception {
|
||||
this.mockMvc.perform(MockMvcRequestBuilders.delete("/api/tag")
|
||||
void deleteLabels() throws Exception {
|
||||
this.mockMvc.perform(MockMvcRequestBuilders.delete("/api/label")
|
||||
.contentType(MediaType.MULTIPART_FORM_DATA)
|
||||
.param("ids", "6565463543"))
|
||||
.andExpect(status().isOk())
|
||||
-11
@@ -145,15 +145,4 @@ class MonitorsControllerTest {
|
||||
.andExpect(jsonPath("$.code").value("0"))
|
||||
.andExpect(jsonPath("$.msg").value("Import success"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void duplicateMonitors() throws Exception {
|
||||
// Mock the behavior of monitorService.copyMonitors
|
||||
doNothing().when(monitorService).copyMonitors(List.of(6565463543L));
|
||||
|
||||
// Perform the POST request and verify the response
|
||||
this.mockMvc.perform(MockMvcRequestBuilders.post("/api/monitors/copy")
|
||||
.param("ids", "6565463543"))
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
}
|
||||
|
||||
+7
-1
@@ -19,6 +19,7 @@ package org.apache.hertzbeat.manager.dao;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import javax.annotation.Resource;
|
||||
import org.apache.hertzbeat.common.entity.manager.Collector;
|
||||
import org.apache.hertzbeat.manager.AbstractSpringIntegrationTest;
|
||||
@@ -27,6 +28,8 @@ import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Test case for {@link CollectorDao}
|
||||
*/
|
||||
@@ -66,5 +69,8 @@ public class CollectorDaoTest extends AbstractSpringIntegrationTest {
|
||||
assertTrue(collectorDao.findCollectorByName("test").isPresent());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void findCollectorsByNameIn(){
|
||||
assertFalse(collectorDao.findCollectorsByNameIn(List.of("test")).isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
+11
-12
@@ -26,7 +26,7 @@ import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.hertzbeat.common.entity.manager.Tag;
|
||||
import org.apache.hertzbeat.common.entity.manager.Label;
|
||||
import org.apache.hertzbeat.manager.AbstractSpringIntegrationTest;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -34,20 +34,19 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Test case for {@link TagDao}
|
||||
* Test case for {@link LabelDao}
|
||||
*/
|
||||
@Transactional
|
||||
class TagDaoTest extends AbstractSpringIntegrationTest {
|
||||
class LabelDaoTest extends AbstractSpringIntegrationTest {
|
||||
|
||||
@Resource
|
||||
private TagDao tagDao;
|
||||
private LabelDao labelDao;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
Tag tag = Tag.builder()
|
||||
Label tag = Label.builder()
|
||||
.name("mock tag")
|
||||
.tagValue("mock value")
|
||||
.color("mock color")
|
||||
.type((byte) 1)
|
||||
.creator("mock creator")
|
||||
.modifier("mock modifier")
|
||||
@@ -55,26 +54,26 @@ class TagDaoTest extends AbstractSpringIntegrationTest {
|
||||
.gmtUpdate(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
tag = tagDao.saveAndFlush(tag);
|
||||
tag = labelDao.saveAndFlush(tag);
|
||||
assertNotNull(tag);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
tagDao.deleteAll();
|
||||
labelDao.deleteAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteTagsByIdIn() {
|
||||
List<Tag> tagList = tagDao.findAll();
|
||||
List<Label> tagList = labelDao.findAll();
|
||||
|
||||
assertNotNull(tagList);
|
||||
assertFalse(tagList.isEmpty());
|
||||
|
||||
Set<Long> ids = tagList.stream().map(Tag::getId).collect(Collectors.toSet());
|
||||
assertDoesNotThrow(() -> tagDao.deleteTagsByIdIn(ids));
|
||||
Set<Long> ids = tagList.stream().map(Label::getId).collect(Collectors.toSet());
|
||||
assertDoesNotThrow(() -> labelDao.deleteLabelsByIdIn(ids));
|
||||
|
||||
tagList = tagDao.findAll();
|
||||
tagList = labelDao.findAll();
|
||||
assertNotNull(tagList);
|
||||
assertTrue(tagList.isEmpty());
|
||||
}
|
||||
+14
-6
@@ -17,13 +17,8 @@
|
||||
|
||||
package org.apache.hertzbeat.manager.service;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.when;
|
||||
import java.util.Collections;
|
||||
import org.apache.hertzbeat.common.entity.manager.Monitor;
|
||||
import org.apache.hertzbeat.manager.dao.DefineDao;
|
||||
import org.apache.hertzbeat.manager.dao.MonitorDao;
|
||||
import org.apache.hertzbeat.manager.service.impl.AppServiceImpl;
|
||||
import org.apache.hertzbeat.manager.service.impl.ObjectStoreConfigServiceImpl;
|
||||
@@ -35,6 +30,15 @@ import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Test case for {@link AppService}
|
||||
*/
|
||||
@@ -47,6 +51,9 @@ class AppServiceTest {
|
||||
@Mock
|
||||
private MonitorDao monitorDao;
|
||||
|
||||
@Mock
|
||||
private DefineDao defineDao;
|
||||
|
||||
@Mock
|
||||
private WarehouseService warehouseService;
|
||||
|
||||
@@ -55,6 +62,7 @@ class AppServiceTest {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
when(defineDao.findAll()).thenReturn(new ArrayList<>());
|
||||
appService.afterPropertiesSet();
|
||||
}
|
||||
|
||||
|
||||
+4
@@ -41,6 +41,7 @@ import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
@@ -67,6 +68,9 @@ public class CollectorServiceTest {
|
||||
@Mock
|
||||
private ManageServer manageServer;
|
||||
|
||||
@Mock
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
|
||||
@Test
|
||||
public void getCollectors() {
|
||||
|
||||
+24
-28
@@ -26,14 +26,12 @@ import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.manager.Tag;
|
||||
import org.apache.hertzbeat.manager.dao.TagDao;
|
||||
import org.apache.hertzbeat.manager.service.impl.TagServiceImpl;
|
||||
import org.apache.hertzbeat.common.entity.manager.Label;
|
||||
import org.apache.hertzbeat.manager.dao.LabelDao;
|
||||
import org.apache.hertzbeat.manager.service.impl.LabelServiceImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
@@ -44,50 +42,48 @@ import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
|
||||
/**
|
||||
* Test case for {@link TagService}
|
||||
* Test case for {@link LabelService}
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class TagServiceTest {
|
||||
class LabelServiceTest {
|
||||
|
||||
@InjectMocks
|
||||
private TagServiceImpl tagService;
|
||||
private LabelServiceImpl labelService;
|
||||
|
||||
@Mock
|
||||
private TagDao tagDao;
|
||||
private LabelDao labelDao;
|
||||
|
||||
@Test
|
||||
void addTags() {
|
||||
void addLabel() {
|
||||
// Prepare test data
|
||||
List<Tag> tags = Collections.singletonList(
|
||||
Tag.builder().id(1L).name("tagname").tagValue("tagvalue").build()
|
||||
);
|
||||
when(tagDao.findTagByNameAndTagValue(anyString(), anyString())).thenReturn(Optional.empty());
|
||||
Label label = Label.builder().id(1L).name("tagname").tagValue("tagvalue").build();
|
||||
when(labelDao.findLabelByNameAndTagValue(anyString(), anyString())).thenReturn(Optional.empty());
|
||||
|
||||
tagService.addTags(tags);
|
||||
labelService.addLabel(label);
|
||||
|
||||
verify(tagDao).saveAll(tags);
|
||||
verify(labelDao).save(label);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void modifyTag() {
|
||||
Tag tag = Tag.builder().id(1L).build();
|
||||
when(tagDao.findById(1L)).thenReturn(Optional.of(tag));
|
||||
when(tagDao.save(tag)).thenReturn(tag);
|
||||
assertDoesNotThrow(() -> tagService.modifyTag(tag));
|
||||
void modifyLabel() {
|
||||
Label tag = Label.builder().id(1L).build();
|
||||
when(labelDao.findById(1L)).thenReturn(Optional.of(tag));
|
||||
when(labelDao.save(tag)).thenReturn(tag);
|
||||
assertDoesNotThrow(() -> labelService.modifyLabel(tag));
|
||||
reset();
|
||||
when(tagDao.findById(1L)).thenReturn(Optional.empty());
|
||||
assertThrows(IllegalArgumentException.class, () -> tagService.modifyTag(tag));
|
||||
when(labelDao.findById(1L)).thenReturn(Optional.empty());
|
||||
assertThrows(IllegalArgumentException.class, () -> labelService.modifyLabel(tag));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTags() {
|
||||
when(tagDao.findAll(any(Specification.class), any(PageRequest.class))).thenReturn(Page.empty());
|
||||
assertNotNull(tagService.getTags(null, null, 1, 10));
|
||||
void getLabels() {
|
||||
when(labelDao.findAll(any(Specification.class), any(PageRequest.class))).thenReturn(Page.empty());
|
||||
assertNotNull(labelService.getLabels(null, null, 1, 10));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteTags() {
|
||||
assertDoesNotThrow(() -> tagService.deleteTags(new HashSet<>(1)));
|
||||
void deleteLabels() {
|
||||
assertDoesNotThrow(() -> labelService.deleteLabels(new HashSet<>(1)));
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -83,7 +83,7 @@ import org.springframework.data.jpa.domain.Specification;
|
||||
* clickhouse client -h 127.0.0.1 -d default -m -u default --password 123456
|
||||
* Test case for {@link MonitorService}
|
||||
*
|
||||
* @see TagServiceTest
|
||||
* @see LabelServiceTest
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class MonitorServiceTest {
|
||||
@@ -101,7 +101,7 @@ class MonitorServiceTest {
|
||||
private AppService appService;
|
||||
|
||||
@Mock
|
||||
private TagService tagService;
|
||||
private LabelService tagService;
|
||||
|
||||
@Mock
|
||||
private CollectJobScheduling collectJobScheduling;
|
||||
@@ -744,7 +744,7 @@ class MonitorServiceTest {
|
||||
List<Param> params = Collections.singletonList(new Param());
|
||||
when(monitorDao.findById(1L)).thenReturn(Optional.of(monitor));
|
||||
when(paramDao.findParamsByMonitorId(1L)).thenReturn(params);
|
||||
assertDoesNotThrow(() -> monitorService.copyMonitors(List.of(1L)));
|
||||
assertDoesNotThrow(() -> monitorService.copyMonitor(1L));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,8 @@ public class LogService {
|
||||
- Get latest 10 logs: SELECT * FROM hzb_log ORDER BY timestamp DESC LIMIT 10
|
||||
- Query ERROR level logs: SELECT * FROM hzb_log WHERE severity_number=17
|
||||
- Query specific time range: SELECT * FROM hzb_log WHERE timestamp > '2024-01-01 00:00:00'
|
||||
- Query last ten minutes: SELECT * FROM hzb_log WHERE timestamp >= now() - Interval '10m' AND timestamp < now()
|
||||
- Query logs at or above the INFO level: SELECT * FROM hzb_log WHERE severity_number >= 9
|
||||
|
||||
Field descriptions:
|
||||
1. severity_number (log level):
|
||||
|
||||
+3
@@ -201,6 +201,9 @@ public class JpaDatabaseDataStorage extends AbstractHistoryDataStorage {
|
||||
List<Predicate> andList = new ArrayList<>();
|
||||
Predicate predicateMonitorId = criteriaBuilder.equal(root.get("monitorId"), monitorId);
|
||||
Predicate predicateMonitorType = criteriaBuilder.equal(root.get("app"), app);
|
||||
if (CommonConstants.PROMETHEUS.equals(app)) {
|
||||
predicateMonitorType = criteriaBuilder.like(root.get("app"), CommonConstants.PROMETHEUS_APP_PREFIX + "%");
|
||||
}
|
||||
Predicate predicateMonitorMetrics = criteriaBuilder.equal(root.get("metrics"), metrics);
|
||||
Predicate predicateMonitorMetric = criteriaBuilder.equal(root.get("metric"), metric);
|
||||
andList.add(predicateMonitorId);
|
||||
|
||||
+75
-33
@@ -25,6 +25,7 @@ import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.TemporalAmount;
|
||||
import java.util.Arrays;
|
||||
@@ -33,6 +34,9 @@ import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -76,11 +80,13 @@ import static org.apache.hertzbeat.common.constants.ConfigConstants.FunctionModu
|
||||
@Slf4j
|
||||
public class VictoriaMetricsClusterDataStorage extends AbstractHistoryDataStorage {
|
||||
|
||||
private static final String IMPORT_PATH = "/api/v1/import";
|
||||
private static final String EXPORT_PATH = "/api/v1/export";
|
||||
private static final String STATUS_PATH = "/api/v1/status/tsdb";
|
||||
private static final String VM_INSERT_BASE_PATH = "/insert/%s/%s";
|
||||
private static final String VM_SELECT_BASE_PATH = "/select/%s/%s";
|
||||
private static final String IMPORT_PATH = "prometheus/api/v1/import";
|
||||
private static final String EXPORT_PATH = "prometheus/api/v1/export";
|
||||
private static final String STATUS_PATH = "prometheus/api/v1/status/tsdb";
|
||||
private static final String STATUS_SUCCESS = "success";
|
||||
private static final String QUERY_RANGE_PATH = "/api/v1/query_range";
|
||||
private static final String QUERY_RANGE_PATH = "prometheus/api/v1/query_range";
|
||||
private static final String LABEL_KEY_NAME = "__name__";
|
||||
private static final String LABEL_KEY_JOB = "job";
|
||||
private static final String LABEL_KEY_INSTANCE = "instance";
|
||||
@@ -88,6 +94,7 @@ public class VictoriaMetricsClusterDataStorage extends AbstractHistoryDataStorag
|
||||
private static final String MONITOR_METRICS_KEY = "__metrics__";
|
||||
private static final String MONITOR_METRIC_KEY = "__metric__";
|
||||
|
||||
private final VictoriaMetricsClusterProperties vmClusterProps;
|
||||
private final VictoriaMetricsInsertProperties vmInsertProps;
|
||||
private final VictoriaMetricsSelectProperties vmSelectProps;
|
||||
|
||||
@@ -100,6 +107,7 @@ public class VictoriaMetricsClusterDataStorage extends AbstractHistoryDataStorag
|
||||
throw new IllegalArgumentException("please config Warehouse victoriaMetrics cluster props");
|
||||
}
|
||||
this.restTemplate = restTemplate;
|
||||
this.vmClusterProps = vmClusterProps;
|
||||
this.vmInsertProps = vmClusterProps.insert();
|
||||
this.vmSelectProps = vmClusterProps.select();
|
||||
serverAvailable = checkVictoriaMetricsDatasourceAvailable();
|
||||
@@ -108,7 +116,23 @@ public class VictoriaMetricsClusterDataStorage extends AbstractHistoryDataStorag
|
||||
private boolean checkVictoriaMetricsDatasourceAvailable() {
|
||||
// check server status
|
||||
try {
|
||||
String result = restTemplate.getForObject(vmSelectProps.url() + STATUS_PATH, String.class);
|
||||
String selectNodeStatusUrl = vmClusterProps.select().url() + VM_SELECT_BASE_PATH.formatted(vmClusterProps.accountID(), STATUS_PATH);
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
if (StringUtils.hasText(vmInsertProps.username())
|
||||
&& StringUtils.hasText(vmInsertProps.password())) {
|
||||
String authStr = vmInsertProps.username() + ":" + vmInsertProps.password();
|
||||
String encodedAuth = Base64Util.encode(authStr);
|
||||
headers.add(HttpHeaders.AUTHORIZATION, NetworkConstants.BASIC + SignConstants.BLANK + encodedAuth);
|
||||
}
|
||||
HttpEntity<Void> requestEntity = new HttpEntity<>(headers);
|
||||
ResponseEntity<String> responseEntity = restTemplate.exchange(
|
||||
selectNodeStatusUrl,
|
||||
HttpMethod.GET,
|
||||
requestEntity,
|
||||
String.class
|
||||
);
|
||||
|
||||
String result = responseEntity.getBody();
|
||||
|
||||
JsonNode jsonNode = JsonUtil.fromJson(result);
|
||||
if (jsonNode != null && STATUS_SUCCESS.equalsIgnoreCase(jsonNode.get(STATUS).asText())) {
|
||||
@@ -225,7 +249,8 @@ public class VictoriaMetricsClusterDataStorage extends AbstractHistoryDataStorag
|
||||
stringBuilder.append(JsonUtil.toJson(content)).append("\n");
|
||||
}
|
||||
HttpEntity<String> httpEntity = new HttpEntity<>(stringBuilder.toString(), headers);
|
||||
ResponseEntity<String> responseEntity = restTemplate.postForEntity(vmInsertProps.url() + IMPORT_PATH,
|
||||
String importUrl = vmClusterProps.insert().url() + VM_INSERT_BASE_PATH.formatted(vmClusterProps.accountID(), IMPORT_PATH);
|
||||
ResponseEntity<String> responseEntity = restTemplate.postForEntity(importUrl,
|
||||
httpEntity, String.class);
|
||||
if (responseEntity.getStatusCode().is2xxSuccessful()) {
|
||||
log.debug("insert metrics data to victoria-metrics success.");
|
||||
@@ -252,9 +277,11 @@ public class VictoriaMetricsClusterDataStorage extends AbstractHistoryDataStorag
|
||||
if (CommonConstants.PROMETHEUS.equals(app)) {
|
||||
labelName = metrics;
|
||||
}
|
||||
String timeSeriesSelector =
|
||||
LABEL_KEY_NAME + "=\"" + labelName + "\"" + "," + LABEL_KEY_INSTANCE + "=\"" + monitorId + "\"" + ","
|
||||
+ (CommonConstants.PROMETHEUS.equals(app) ? "" : "," + MONITOR_METRIC_KEY + "=\"" + metric + "\"");
|
||||
String timeSeriesSelector = Stream.of(
|
||||
LABEL_KEY_NAME + "=\"" + labelName + "\"",
|
||||
LABEL_KEY_INSTANCE + "=\"" + monitorId + "\"",
|
||||
CommonConstants.PROMETHEUS.equals(app) ? null : MONITOR_METRIC_KEY + "=\"" + metric + "\""
|
||||
).filter(Objects::nonNull).collect(Collectors.joining(","));
|
||||
Map<String, List<Value>> instanceValuesMap = new HashMap<>(8);
|
||||
try {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
@@ -267,11 +294,15 @@ public class VictoriaMetricsClusterDataStorage extends AbstractHistoryDataStorag
|
||||
+ SignConstants.BLANK + encodedAuth);
|
||||
}
|
||||
HttpEntity<Void> httpEntity = new HttpEntity<>(headers);
|
||||
URI uri = UriComponentsBuilder.fromHttpUrl(vmSelectProps.url() + EXPORT_PATH)
|
||||
.queryParam(URLEncoder.encode("match[]", StandardCharsets.UTF_8),
|
||||
URLEncoder.encode("{" + timeSeriesSelector + "}", StandardCharsets.UTF_8))
|
||||
.queryParam("start", URLEncoder.encode("now-" + history, StandardCharsets.UTF_8))
|
||||
.queryParam("end", "now").build(true).toUri();
|
||||
Instant end = Instant.now();
|
||||
Duration duration = Duration.ofHours(Long.parseLong(history.replace("h", "")));
|
||||
Instant start = end.minus(duration);
|
||||
String exportUrl = vmClusterProps.select().url() + VM_SELECT_BASE_PATH.formatted(vmClusterProps.accountID(), EXPORT_PATH);
|
||||
URI uri = UriComponentsBuilder.fromHttpUrl(exportUrl)
|
||||
.queryParam("match", URLEncoder.encode("{" + timeSeriesSelector + "}", StandardCharsets.UTF_8))
|
||||
.queryParam("start", String.valueOf(start.getEpochSecond()))
|
||||
.queryParam("end", String.valueOf(end.getEpochSecond()))
|
||||
.build(true).toUri();
|
||||
ResponseEntity<String> responseEntity = restTemplate.exchange(uri, HttpMethod.GET, httpEntity,
|
||||
String.class);
|
||||
if (responseEntity.getStatusCode().is2xxSuccessful()) {
|
||||
@@ -347,9 +378,11 @@ public class VictoriaMetricsClusterDataStorage extends AbstractHistoryDataStorag
|
||||
if (CommonConstants.PROMETHEUS.equals(app)) {
|
||||
labelName = metrics;
|
||||
}
|
||||
String timeSeriesSelector =
|
||||
LABEL_KEY_NAME + "=\"" + labelName + "\"" + "," + LABEL_KEY_INSTANCE + "=\"" + monitorId + "\"" + ","
|
||||
+ (CommonConstants.PROMETHEUS.equals(app) ? "" : "," + MONITOR_METRIC_KEY + "=\"" + metric + "\"");
|
||||
String timeSeriesSelector = Stream.of(
|
||||
LABEL_KEY_NAME + "=\"" + labelName + "\"",
|
||||
LABEL_KEY_INSTANCE + "=\"" + monitorId + "\"",
|
||||
CommonConstants.PROMETHEUS.equals(app) ? null : MONITOR_METRIC_KEY + "=\"" + metric + "\""
|
||||
).filter(Objects::nonNull).collect(Collectors.joining(","));
|
||||
Map<String, List<Value>> instanceValuesMap = new HashMap<>(8);
|
||||
try {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
@@ -362,10 +395,13 @@ public class VictoriaMetricsClusterDataStorage extends AbstractHistoryDataStorag
|
||||
+ SignConstants.BLANK + encodedAuth);
|
||||
}
|
||||
HttpEntity<Void> httpEntity = new HttpEntity<>(headers);
|
||||
URI uri = UriComponentsBuilder.fromHttpUrl(vmSelectProps.url() + QUERY_RANGE_PATH)
|
||||
.queryParam(URLEncoder.encode("query", StandardCharsets.UTF_8),
|
||||
URLEncoder.encode("{" + timeSeriesSelector + "}", StandardCharsets.UTF_8))
|
||||
.queryParam("step", "4h").queryParam("start", startTime).queryParam("end", endTime).build(true)
|
||||
String rangeUrl = VM_SELECT_BASE_PATH.formatted(vmClusterProps.accountID(), QUERY_RANGE_PATH);
|
||||
URI uri = UriComponentsBuilder.fromHttpUrl(rangeUrl)
|
||||
.queryParam("query", URLEncoder.encode("{" + timeSeriesSelector + "}", StandardCharsets.UTF_8))
|
||||
.queryParam("step", "4h")
|
||||
.queryParam("start", startTime)
|
||||
.queryParam("end", endTime)
|
||||
.build(true)
|
||||
.toUri();
|
||||
ResponseEntity<PromQlQueryContent> responseEntity = restTemplate.exchange(uri, HttpMethod.GET,
|
||||
httpEntity, PromQlQueryContent.class);
|
||||
@@ -400,10 +436,12 @@ public class VictoriaMetricsClusterDataStorage extends AbstractHistoryDataStorag
|
||||
log.error("query metrics data from victoria-metrics failed. {}", responseEntity);
|
||||
}
|
||||
// max
|
||||
uri = UriComponentsBuilder.fromHttpUrl(vmSelectProps.url() + QUERY_RANGE_PATH)
|
||||
.queryParam(URLEncoder.encode("query", StandardCharsets.UTF_8),
|
||||
URLEncoder.encode("max_over_time({" + timeSeriesSelector + "})", StandardCharsets.UTF_8))
|
||||
.queryParam("step", "4h").queryParam("start", startTime).queryParam("end", endTime).build(true)
|
||||
uri = UriComponentsBuilder.fromHttpUrl(rangeUrl)
|
||||
.queryParam("query", URLEncoder.encode("max_over_time({" + timeSeriesSelector + "})", StandardCharsets.UTF_8))
|
||||
.queryParam("step", "4h")
|
||||
.queryParam("start", startTime)
|
||||
.queryParam("end", endTime)
|
||||
.build(true)
|
||||
.toUri();
|
||||
responseEntity = restTemplate.exchange(uri, HttpMethod.GET, httpEntity, PromQlQueryContent.class);
|
||||
if (responseEntity.getStatusCode().is2xxSuccessful()) {
|
||||
@@ -436,10 +474,12 @@ public class VictoriaMetricsClusterDataStorage extends AbstractHistoryDataStorag
|
||||
}
|
||||
}
|
||||
// min
|
||||
uri = UriComponentsBuilder.fromHttpUrl(vmSelectProps.url() + QUERY_RANGE_PATH)
|
||||
.queryParam(URLEncoder.encode("query", StandardCharsets.UTF_8),
|
||||
URLEncoder.encode("min_over_time({" + timeSeriesSelector + "})", StandardCharsets.UTF_8))
|
||||
.queryParam("step", "4h").queryParam("start", startTime).queryParam("end", endTime).build(true)
|
||||
uri = UriComponentsBuilder.fromHttpUrl(rangeUrl)
|
||||
.queryParam("query", URLEncoder.encode("min_over_time({" + timeSeriesSelector + "})", StandardCharsets.UTF_8))
|
||||
.queryParam("step", "4h")
|
||||
.queryParam("start", startTime)
|
||||
.queryParam("end", endTime)
|
||||
.build(true)
|
||||
.toUri();
|
||||
responseEntity = restTemplate.exchange(uri, HttpMethod.GET, httpEntity, PromQlQueryContent.class);
|
||||
if (responseEntity.getStatusCode().is2xxSuccessful()) {
|
||||
@@ -472,10 +512,12 @@ public class VictoriaMetricsClusterDataStorage extends AbstractHistoryDataStorag
|
||||
}
|
||||
}
|
||||
// avg
|
||||
uri = UriComponentsBuilder.fromHttpUrl(vmSelectProps.url() + QUERY_RANGE_PATH)
|
||||
.queryParam(URLEncoder.encode("query", StandardCharsets.UTF_8),
|
||||
URLEncoder.encode("avg_over_time({" + timeSeriesSelector + "})", StandardCharsets.UTF_8))
|
||||
.queryParam("step", "4h").queryParam("start", startTime).queryParam("end", endTime).build(true)
|
||||
uri = UriComponentsBuilder.fromHttpUrl(rangeUrl)
|
||||
.queryParam("query", URLEncoder.encode("avg_over_time({" + timeSeriesSelector + "})", StandardCharsets.UTF_8))
|
||||
.queryParam("step", "4h")
|
||||
.queryParam("start", startTime)
|
||||
.queryParam("end", endTime)
|
||||
.build(true)
|
||||
.toUri();
|
||||
responseEntity = restTemplate.exchange(uri, HttpMethod.GET, httpEntity, PromQlQueryContent.class);
|
||||
if (responseEntity.getStatusCode().is2xxSuccessful()) {
|
||||
|
||||
+3
@@ -21,6 +21,7 @@ import org.apache.hertzbeat.common.constants.ConfigConstants;
|
||||
import org.apache.hertzbeat.common.constants.SignConstants;
|
||||
import org.apache.hertzbeat.warehouse.constants.WarehouseConstants;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.bind.DefaultValue;
|
||||
|
||||
/**
|
||||
* Victoriametrics configuration information
|
||||
@@ -32,6 +33,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
+ SignConstants.DOT
|
||||
+ WarehouseConstants.HistoryName.VM_CLUSTER)
|
||||
public record VictoriaMetricsClusterProperties(
|
||||
@DefaultValue("false") boolean enabled,
|
||||
@DefaultValue("0") String accountID,
|
||||
VictoriaMetricsInsertProperties insert,
|
||||
VictoriaMetricsSelectProperties select
|
||||
) {
|
||||
|
||||
@@ -53,18 +53,10 @@ Bug fix.
|
||||
5. [[collector]bugfix: fix warehouse data queue consume error #153](https://github.com/apache/hertzbeat/pull/153). issue by @daqianxiaoyao
|
||||
6. [[web-app]bugfix:fix input blocking when input error in dark theme #157](https://github.com/apache/hertzbeat/pull/157). issue by @ConradWen
|
||||
|
||||
**Full Changelog**: <https://github.com/apache/hertzbeat/compare/v1.0-beta.8...v1.0>
|
||||
|
||||
Online <https://console.tancloud.cn>.
|
||||
|
||||
-----------------------
|
||||
|
||||
Redis monitor is coming:
|
||||
|
||||
<img width="1910" alt="2022-05-29 20 23 58" src="https://user-images.githubusercontent.com/24788200/170868079-325ccc08-165f-4d0e-9ebb-18b0b5c9db3f.png"/>
|
||||
|
||||
<img width="959" alt="2022-05-29 20 24 21" src="https://user-images.githubusercontent.com/24788200/170868094-3c4de70f-934a-4a13-ae1a-0744c30f14f3.png"/>
|
||||
|
||||
> [HertzBeat](https://github.com/apache/hertzbeat), incubated by [Dromara](https://dromara.org) and open-sourced by [TanCloud](https://tancloud.cn), is an open-source monitoring and alerting project with a user-friendly visual interface that supports monitoring types such as websites, APIs, PING, ports, databases, operating systems, and more.
|
||||
> Of course, we also provide a corresponding [SaaS cloud monitoring version](https://console.tancloud.cn), so small and medium-sized teams and individuals no longer need to deploy a cumbersome monitoring system to monitor their website resources; you can [log in](https://console.tancloud.cn) to start monitoring for free.
|
||||
>
|
||||
|
||||
@@ -36,9 +36,6 @@ Bugfixes:
|
||||
Online at <https://console.tancloud.cn>.
|
||||
|
||||
---
|
||||
Windows Monitor coming:
|
||||
|
||||

|
||||
|
||||
⚠️ ⚠️⚠️⚠️Please note that upgrading to v1.1.0 from other versions requires running the following SQL script. Now, our table names have a unified prefix "hzb_prefix".
|
||||
|
||||
@@ -60,62 +57,6 @@ Have Fun!
|
||||
|
||||
---
|
||||
|
||||
## V1.1.0
|
||||
|
||||
Home: hertzbeat.com | tancloud.cn
|
||||
|
||||
Hi guys! HertzBeat v1.1.0 is coming. This version we support snmp protocol and use snmp to collect windows metrics.
|
||||
Another major change is that we use the H2 database by default to replace the MYSQL database as storage to facilitate the installation and deployment of users. Now only one docker command is needed to install and experience hertzbeat: `docker run -d -p 1157:1157 --name hertzbeat apache/hertzbeat`
|
||||
Let's Try It!
|
||||
|
||||
Thanks to the contributors! 👍👍
|
||||
|
||||
Feature:
|
||||
|
||||
1. [[monitor]feature: support snmp collect protocol and windows monitor type #192](https://github.com/apache/hertzbeat/pull/192). contribute by @ChineseTony
|
||||
2. [[monitor]change default database mysql to h2 #191](https://github.com/apache/hertzbeat/pull/191)
|
||||
3. [[manager]support monitor params name i18n #184](https://github.com/apache/hertzbeat/pull/184).
|
||||
4. [[script]build multi cpu arch hertzbeat docker version #189](https://github.com/apache/hertzbeat/pull/189).
|
||||
5. [[monitor]feature: support oracle multi tablespaces #163](https://github.com/apache/hertzbeat/pull/163) contribute by @brave4Time
|
||||
6. [[monitor]database tables append prefix hzb_ #193](https://github.com/apache/hertzbeat/pull/193) issue from @shimingxy
|
||||
|
||||
Bugfix.
|
||||
|
||||
1. [[monitor]fix can not collect cpu metrics in tencent centos #164](https://github.com/apache/hertzbeat/pull/164) contribute by @wyt199905 .
|
||||
2. [[manager]fix oracle config yml percentage error #168](https://github.com/apache/hertzbeat/pull/168)
|
||||
3. [[monitor] bugfix: fix elasticsearch collect error when need basic auth #174](https://github.com/apache/hertzbeat/pull/174) contribute by @weifuqing
|
||||
4. [Change the Oracle database name to the service name to reduce ambiguity #182](https://github.com/apache/hertzbeat/pull/182) @zklmcookle
|
||||
|
||||
Online <https://console.tancloud.cn>.
|
||||
|
||||
---
|
||||
|
||||
Windows Monitor coming:
|
||||
|
||||
<img width="1444" alt="2022-06-19 11 30 57" src="https://user-images.githubusercontent.com/24788200/174481159-b8a73c87-aff5-4c4c-befb-bd0d26685d71.png"/>
|
||||
|
||||
⚠️ ⚠️⚠️⚠️Attention other version upgrade to v1.1.0 need run sql script. Now the tables name has hzb_ prefix.
|
||||
|
||||
```properties
|
||||
ALTER TABLE alert RENAME TO hzb_alert;
|
||||
ALTER TABLE alert_define RENAME TO hzb_alert_define;
|
||||
ALTER TABLE alert_define_monitor_bind RENAME TO hzb_alert_define_monitor_bind;
|
||||
ALTER TABLE monitor RENAME TO hzb_monitor;
|
||||
ALTER TABLE notice_receiver RENAME TO hzb_notice_receiver;
|
||||
ALTER TABLE notice_rule RENAME TO hzb_notice_rule;
|
||||
ALTER TABLE param RENAME TO hzb_param;
|
||||
ALTER TABLE param_define RENAME TO hzb_param_define;
|
||||
ALTER TABLE tag RENAME TO hzb_tag;
|
||||
ALTER TABLE tag_monitor_bind RENAME TO hzb_tag_monitor_bind;
|
||||
commit;
|
||||
```
|
||||
|
||||
Have Fun!
|
||||
|
||||
Have Fun!
|
||||
|
||||
---
|
||||
|
||||
> [HertzBeat](https://github.com/apache/hertzbeat), incubated by [Dromara](https://dromara.org) and open-sourced by [TanCloud](https://tancloud.cn), is an open-source project supporting monitoring for websites, APIs, PING, ports, databases, operating systems, and more, with a user-friendly visual interface.
|
||||
> We also offer a [SAAS cloud monitoring version](https://console.tancloud.cn), allowing small and medium teams and individuals to start monitoring their web resources without deploying a complicated monitoring system, simply [log in to start](https://console.tancloud.cn) your monitoring journey for free.
|
||||
> HertzBeat supports custom monitoring; you can customize the monitoring types and metrics you need through configuration of the YML file to meet common personalized needs.
|
||||
|
||||
@@ -36,10 +36,6 @@ Bugfixes:
|
||||
Online at [https://console.tancloud.cn](https://console.tancloud.cn).
|
||||
|
||||
---
|
||||
Windows Monitor is coming:
|
||||
|
||||

|
||||
|
||||
⚠️ ⚠️⚠️⚠️Please note that upgrading to v1.1.0 from other versions requires running the following SQL script. Now, our table names have a unified prefix hzb_prefix.
|
||||
|
||||
```properties
|
||||
@@ -60,60 +56,6 @@ Have Fun!
|
||||
|
||||
---
|
||||
|
||||
## V1.1.0
|
||||
|
||||
Home: hertzbeat.com | tancloud.cn
|
||||
|
||||
Hi guys! HertzBeat v1.1.0 is coming. This version we support snmp protocol and use snmp to collect windows metrics.
|
||||
Another major change is that we use the H2 database by default to replace the MYSQL database as storage to facilitate the installation and deployment of users. Now only one docker command is needed to install and experience hertzbeat: `docker run -d -p 1157:1157 --name hertzbeat apache/hertzbeat`
|
||||
Let's Try It!
|
||||
|
||||
Thanks to the contributors! 👍👍
|
||||
|
||||
Feature:
|
||||
|
||||
1. [[monitor]feature: support snmp collect protocol and windows monitor type #192](https://github.com/apache/hertzbeat/pull/192). contribute by @ChineseTony
|
||||
2. [[monitor]change default database mysql to h2 #191](https://github.com/apache/hertzbeat/pull/191)
|
||||
3. [[manager]support monitor params name i18n #184](https://github.com/apache/hertzbeat/pull/184).
|
||||
4. [[script]build multi cpu arch hertzbeat docker version #189](https://github.com/apache/hertzbeat/pull/189).
|
||||
5. [[monitor]feature: support oracle multi tablespaces #163](https://github.com/apache/hertzbeat/pull/163) contribute by @brave4Time
|
||||
6. [[monitor]database tables append prefix hzb_ #193](https://github.com/apache/hertzbeat/pull/193) issue from @shimingxy
|
||||
|
||||
Bugfix.
|
||||
|
||||
1. [[monitor]fix can not collect cpu metrics in tencent centos #164](https://github.com/apache/hertzbeat/pull/164) contribute by @wyt199905 .
|
||||
2. [[manager]fix oracle config yml percentage error #168](https://github.com/apache/hertzbeat/pull/168)
|
||||
3. [[monitor] bugfix: fix elasticsearch collect error when need basic auth #174](https://github.com/apache/hertzbeat/pull/174) contribute by @weifuqing
|
||||
4. [Change the Oracle database name to the service name to reduce ambiguity #182](https://github.com/apache/hertzbeat/pull/182) @zklmcookle
|
||||
|
||||
Online <https://console.tancloud.cn>.
|
||||
|
||||
---
|
||||
|
||||
Windows Monitor coming:
|
||||
|
||||
<img width="1444" alt="2022-06-19 11 30 57" src="https://user-images.githubusercontent.com/24788200/174481159-b8a73c87-aff5-4c4c-befb-bd0d26685d71.png"/>
|
||||
|
||||
⚠️ ⚠️⚠️⚠️Attention other version upgrade to v1.1.0 need run sql script. Now the tables name has hzb_ prefix.
|
||||
|
||||
```properties
|
||||
ALTER TABLE alert RENAME TO hzb_alert;
|
||||
ALTER TABLE alert_define RENAME TO hzb_alert_define;
|
||||
ALTER TABLE alert_define_monitor_bind RENAME TO hzb_alert_define_monitor_bind;
|
||||
ALTER TABLE monitor RENAME TO hzb_monitor;
|
||||
ALTER TABLE notice_receiver RENAME TO hzb_notice_receiver;
|
||||
ALTER TABLE notice_rule RENAME TO hzb_notice_rule;
|
||||
ALTER TABLE param RENAME TO hzb_param;
|
||||
ALTER TABLE param_define RENAME TO hzb_param_define;
|
||||
ALTER TABLE tag RENAME TO hzb_tag;
|
||||
ALTER TABLE tag_monitor_bind RENAME TO hzb_tag_monitor_bind;
|
||||
commit;
|
||||
```
|
||||
|
||||
Have Fun!
|
||||
|
||||
---
|
||||
|
||||
> [HertzBeat](https://github.com/apache/hertzbeat), incubated by [Dromara](https://dromara.org) and open-sourced by [TanCloud](https://tancloud.cn), is an open-source monitoring and alerting project that supports monitoring types such as websites, APIs, PING, ports, databases, operating systems, etc., with a user-friendly and easy-to-use visual interface.
|
||||
> We also offer a [SaaS cloud monitoring version](https://console.tancloud.cn) for small and medium teams and individuals, eliminating the need to deploy a complicated monitoring system to monitor their web resources. [Sign up to start](https://console.tancloud.cn) your monitoring journey for free.
|
||||
> HertzBeat supports custom monitoring; by configuring the YML file, we can customize the required monitoring types and metrics to meet common personalized needs.
|
||||
|
||||
@@ -7,7 +7,7 @@ author_image_url: https://avatars.githubusercontent.com/u/24788200?s=400&v=4
|
||||
tags: [opensource]
|
||||
---
|
||||
|
||||
> Friendly Cloud Monitoring Tool. | 易用友好的实时监控工具, 无需Agent, 强大自定义监控能力.
|
||||
> Friendly Cloud Monitoring Tool.
|
||||
|
||||
**Home: [hertzbeat.com](https://hertzbeat.com)**
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ Have Fun!
|
||||
Need Convert `application.yml`.
|
||||
|
||||
```yaml
|
||||
spring.
|
||||
spring:
|
||||
resources: static-locations.
|
||||
static-locations.
|
||||
- classpath:/dist/
|
||||
@@ -70,8 +70,8 @@ spring.
|
||||
To
|
||||
|
||||
```yaml
|
||||
spring.
|
||||
web.
|
||||
spring:
|
||||
web:
|
||||
resources: static-locations.
|
||||
static-locations.
|
||||
- classpath:/dist/
|
||||
|
||||
@@ -54,50 +54,3 @@ Bugfix.
|
||||
Online <https://console.tancloud.cn>.
|
||||
|
||||
Have Fun!
|
||||
|
||||
----
|
||||
|
||||
## V1.2.2
|
||||
|
||||
官网: hertzbeat.com | tancloud.cn
|
||||
|
||||
大家好,HertzBeat v1.2.2发布啦!这个版本带来个超多重大更新,我们支持了对云原生kubernets, docker的监控,支持了对springboot应用, nacos注册发现中心,达梦数据库,opengauss数据库等的指标监控。我们也引入了一个实验性特性,用户可以使用promethues promql 从promethues server拿取指标数据作为hertzbeat自定义监控指标数据。当然我们也新增了多个测试用户覆盖,修复了多个BUG。还有个很多用户想要的更新,我们新增了对linux监控的top10 cpu 内存利用率的进程监控指标。有个这个指标,我们就可以干很多事情。比如监控某个进程CPU异常,内存爆满啥的。快来试试吧!
|
||||
|
||||
只需要一条docker命令即可安装体验heartbeat :
|
||||
`docker run -d -p 1157:1157 --name hertzbeat apache/hertzbeat`
|
||||
|
||||
感谢hertzbeat贡献者们的贡献!👍👍
|
||||
|
||||
我们急需对测试用例,新增应用监控,文档等各方面的贡献者,非常欢迎您的加入。快来吧,HertzBeat上手非常简单!
|
||||
|
||||
Feature:
|
||||
|
||||
1. [[manager,collector] support dm database monitor #410](https://github.com/apache/hertzbeat/pull/410) @TJxiaobao
|
||||
2. [[home] add DM db document supplement #411](https://github.com/apache/hertzbeat/pull/411) @TJxiaobao
|
||||
3. [[home] support algolia search #416](https://github.com/apache/hertzbeat/pull/416)
|
||||
4. [[collector] support trigger and grading multiple subtasks through -_- placeholder expression #418](https://github.com/apache/hertzbeat/pull/418)
|
||||
5. [WIP:feature support k8s monitor, http monitor nacos, service&http_micro monitor msa #421](https://github.com/apache/hertzbeat/pull/421) @cuipiheqiuqiu
|
||||
6. [[manager] support opengauss database monitor #422](https://github.com/apache/hertzbeat/pull/422)
|
||||
7. [[#406]\[warehose\] Add unit test MetricsDataControllerTest.java #426](https://github.com/apache/hertzbeat/pull/426) @haibo-duan
|
||||
8. [[#358]\[manager\] Add unit test manager/service/NoticeConfigServiceTest.java #427](https://github.com/apache/hertzbeat/pull/427) @haibo-duan
|
||||
9. [[#356]\[manager\] unit test case of manager/service/MailServiceTest.java #432](https://github.com/apache/hertzbeat/pull/432) @csyshu
|
||||
10. [[manager,collector] support docker metrics monitor #438](https://github.com/apache/hertzbeat/pull/438) @TJxiaobao
|
||||
11. [[alerter] implement AlertDefineControllerTest unit case #448](https://github.com/apache/hertzbeat/pull/448) @Ceilzcx
|
||||
12. [[collector] support spi load AbstractCollect Impl instance #449](https://github.com/apache/hertzbeat/pull/449)
|
||||
13. [[manager] support linux process top10 cpu_usage mem_usage #451](https://github.com/apache/hertzbeat/pull/451)
|
||||
14. [[hertzbeat] support springboot2.0 metrics monitor #453](https://github.com/apache/hertzbeat/pull/453)
|
||||
15. [[manager-monitors](增强)应用服务检测-网站检测-分页:添加默认name升序 (enhancement)manager-… #455](https://github.com/apache/hertzbeat/pull/455) @luxx-lq
|
||||
16. [[hertzbeat] update use PromQL to collect metrics from prometheus server #456](https://github.com/apache/hertzbeat/pull/456)
|
||||
17. [[manager] support custom monitor api response data code #460](https://github.com/apache/hertzbeat/pull/460)
|
||||
|
||||
Bugfix.
|
||||
|
||||
1. [【bugfix#408】if logs dir not exist, create logs dir #409](https://github.com/apache/hertzbeat/pull/409) @Ceilzcx
|
||||
2. [[warehouse] bugfix RealTimeRedisDataStorage wrong extend from #413](https://github.com/apache/hertzbeat/pull/413)
|
||||
3. [end The query closed the dataSet #414](https://github.com/apache/hertzbeat/pull/414) @Ceilzcx
|
||||
4. [[alerter] bugfix monitor status not change when alert #415](https://github.com/apache/hertzbeat/pull/415)
|
||||
5. [[OS Monitor]bugfix:Fix cpu cores and interrupt acquisition under Orac… #424](https://github.com/apache/hertzbeat/pull/424) @assassinfym
|
||||
6. [[manager] bugfix the gmtUpdate not change when update monitor param #459](https://github.com/apache/hertzbeat/pull/459)
|
||||
7. [[home] fix typo in springboot2.md #464](https://github.com/apache/hertzbeat/pull/464) @eltociear
|
||||
|
||||
----
|
||||
|
||||
@@ -52,47 +52,3 @@ Bugfix.
|
||||
Online <https://console.tancloud.cn>.
|
||||
|
||||
Have Fun!
|
||||
|
||||
----
|
||||
|
||||
## V1.2.3
|
||||
|
||||
官网: hertzbeat.com | tancloud.cn
|
||||
|
||||
大家好,HertzBeat v1.2.3发布啦!这个版本带来了重大更新,我们支持了对prometheus exporter协议监控,用户可以很方便的使用hertzbeat来适配监控prometheus exporter. 基于这个能力,这个版本我们也支持了对apache shenyu, apache iotdb的指标监控。我们更新了UI布局,修复了多个BUG,也支持了短信通知。快来体验下吧!
|
||||
|
||||
只需要一条docker命令即可安装体验heartbeat :
|
||||
`docker run -d -p 1157:1157 --name hertzbeat apache/hertzbeat`
|
||||
|
||||
感谢hertzbeat贡献者们的贡献!👍👍
|
||||
|
||||
我们急需对测试用例,新增应用监控,文档等各方面的贡献者,非常欢迎您的加入。快来吧,HertzBeat上手非常简单!
|
||||
|
||||
Feature:
|
||||
|
||||
1. [[doc] note: startup via source code not required mysql and tdengine env #472](https://github.com/apache/hertzbeat/pull/472) @xingshuaiLi
|
||||
2. [[doc] fix up:update the environment of hertzbeat to Java version 11 #473](https://github.com/apache/hertzbeat/pull/473) @BKing2020
|
||||
3. [[docs] update kubernetes.md #478](https://github.com/apache/hertzbeat/pull/478) @wangke6666
|
||||
4. [[web-app] enable alert define preset true by default #485](https://github.com/apache/hertzbeat/pull/485)
|
||||
5. [[web-app] support friendly tip when add notice receiver #486](https://github.com/apache/hertzbeat/pull/486)
|
||||
6. [[web-app] update dashboard category card ui #487](https://github.com/apache/hertzbeat/pull/487)
|
||||
7. [[collector] limit trigger sub task max num #488](https://github.com/apache/hertzbeat/pull/488)
|
||||
8. [[script] support service restart shell #489](https://github.com/apache/hertzbeat/pull/489) @zanglikun
|
||||
9. [[docs] use rainbond deploy hertzbeat #495](https://github.com/apache/hertzbeat/pull/495) @zzzhangqi
|
||||
10. [[webapp] upgrade web base angular version to 14 #501](https://github.com/apache/hertzbeat/pull/501)
|
||||
11. [[hertzbeat] support sms alert notice #503](https://github.com/apache/hertzbeat/pull/503)
|
||||
12. [add Prometheus exporter metrics parser and IoTDB monitor #505](https://github.com/apache/hertzbeat/pull/505) @Ceilzcx
|
||||
13. [support apache shenyu metrics monitoring #507](https://github.com/apache/hertzbeat/pull/507)
|
||||
|
||||
Bugfix.
|
||||
|
||||
1. [[manager] fix cross domain problem in SecurityCorsConfiguration #469](https://github.com/apache/hertzbeat/pull/469) @zenan08
|
||||
2. [[manager] bugfix linux cpu usage collect incorrect sometime #479](https://github.com/apache/hertzbeat/pull/479) @LWBobo
|
||||
3. [[collector] fix protocol ssl_cert not support #491](https://github.com/apache/hertzbeat/pull/491)
|
||||
4. [Update sqlserver.md #493](https://github.com/apache/hertzbeat/pull/493) @SuitSmile
|
||||
5. [fix: Remove Alert Unused Monitoring IDs #502](https://github.com/apache/hertzbeat/pull/502) @wang1027-wqh
|
||||
6. [[collector] bugfix npe when ssh collect error #508](https://github.com/apache/hertzbeat/pull/508)
|
||||
7. [监控k8s问题issue描述与解决方案 #511](https://github.com/apache/hertzbeat/pull/511) @MrAndyMing
|
||||
8. [[manager] springboot2 monitor support base path config #515](https://github.com/apache/hertzbeat/pull/515)
|
||||
|
||||
----
|
||||
|
||||
@@ -22,8 +22,6 @@ Website: hertzbeat.com | tancloud.cn
|
||||
>
|
||||
> Do you believe that you can immediately adapt a new monitoring type such as K8s or Docker just by defining YML?
|
||||
|
||||

|
||||
|
||||
**Github: <https://github.com/apache/hertzbeat>**
|
||||
|
||||
**Gitee: <https://gitee.com/hertzbeat/hertzbeat>**
|
||||
@@ -34,8 +32,6 @@ After a month of iterations, HertzBeat v1.3.0 was officially released last weeke
|
||||
|
||||
- Hertzbeat has powerful custom monitoring capabilities, all our supported monitoring types are mapped to a YML. This time we bring custom monitoring pagination, welcome to use and share your own monitoring type definition.
|
||||
|
||||

|
||||
|
||||
- **support for monitoring network switches**.
|
||||
hertzbeat supported snmp protocol long time ago, windows monitoring is monitored by snmp protocol, this version we not only support more windows performance metrics, but also support snmp walk, adapt several common network switches monitoring, welcome to contribute more types and metrics to the community.
|
||||
|
||||
|
||||
@@ -25,8 +25,6 @@ Website: hertzbeat.com | tancloud.cn
|
||||
> `HertzBeat`'s powerful custom-define, multi-type support, easy expansion, low coupling, hope to help developers and micro teams to quickly build their own monitoring system.
|
||||
> We also provide **[Monitoring SaaS Cloud](https://console.tancloud.cn)**, users no longer need to deploy a cumbersome monitoring system in order to monitor resources. **[Get started for free](https://console.tancloud.cn)**.
|
||||
|
||||

|
||||
|
||||
**Github: <https://github.com/apache/hertzbeat>**
|
||||
|
||||
**Gitee: <https://gitee.com/hertzbeat/hertzbeat>**
|
||||
|
||||
@@ -25,8 +25,6 @@ Website: hertzbeat.com | tancloud.cn
|
||||
> `HertzBeat`'s powerful custom-define, multi-type support, easy expansion, low coupling, hope to help developers and micro teams to quickly build their own monitoring system.
|
||||
> We also provide **[Monitoring SaaS Cloud](https://console.tancloud.cn)**, users no longer need to deploy a cumbersome monitoring system in order to monitor resources. **[Get started for free](https://console.tancloud.cn)**.
|
||||
|
||||

|
||||
|
||||
**Github: <https://github.com/apache/hertzbeat>**
|
||||
|
||||
**Gitee: <https://gitee.com/hertzbeat/hertzbeat>**
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user