mirror of
https://github.com/dromara/hertzbeat.git
synced 2026-09-19 20:19:01 +00:00
Compare commits
67
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09103ae89c | ||
|
|
5cdfe545b5 | ||
|
+16 |
9be7295084 | ||
|
|
6905bcc986 | ||
|
|
1050032a13 | ||
|
|
9b223dd217 | ||
|
|
0a1b5961e9 | ||
|
|
a8a70b76d7 | ||
|
+1 |
05b63903b5 | ||
|
|
7d8b55dfb8 | ||
|
|
72a3e59ca6 | ||
|
|
3ab94a751b | ||
|
|
66049f189b | ||
|
|
69ec3f8751 | ||
|
|
feb43c5678 | ||
|
|
1fae67282d | ||
|
|
886f0b74f6 | ||
|
|
32e1a43aa5 | ||
|
|
dcd806802e | ||
|
|
5746cbbbd7 | ||
|
|
e23903fd0c | ||
|
|
780710f226 | ||
|
|
4607ede541 | ||
|
|
67ad4b04ef | ||
|
|
6432d689dd | ||
|
|
2eb856fffb | ||
|
|
56312831ca | ||
|
|
fe54d42bc0 | ||
|
|
2ea24c1b3a | ||
|
|
e0040e2ede | ||
|
|
0ac0fd4a8d | ||
|
|
0ef87bcc08 | ||
|
|
660bda6e14 | ||
|
|
242cf40135 | ||
|
|
7319c55430 | ||
|
|
5ea29f610b | ||
|
|
b74aff0500 | ||
|
|
d2e8f2f4aa | ||
|
|
28681a1f9e | ||
|
|
6814c7c357 | ||
|
|
f475540508 | ||
|
|
44660a5042 | ||
|
|
339386fd2b | ||
|
|
faa7cb71db | ||
|
|
014a376c30 | ||
|
|
377172e44a | ||
|
|
5b9e19cc30 | ||
|
|
0457ec7d16 | ||
|
|
2947ee218e | ||
|
|
4e841a9fb1 | ||
|
|
b7ecb9b2ec | ||
|
|
7e1873bc10 | ||
|
|
8eb60210a3 | ||
|
|
d608f732f7 | ||
|
|
2d9a2955ef | ||
|
|
cae4423f30 | ||
|
|
f0e53a9fb3 | ||
|
|
79da1a198b | ||
|
|
64cb804147 | ||
|
|
344cb25229 | ||
|
|
74d21a7acd | ||
|
|
f315bf9607 | ||
|
|
810d54bef5 | ||
|
|
48eafa1382 | ||
|
|
4b6c1e0f08 | ||
|
|
edfd857030 | ||
|
|
408a06d3eb |
+82
-4
@@ -1270,10 +1270,10 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "a-little-fool",
|
||||
"name": "小笨蛋",
|
||||
"login": "lynx009",
|
||||
"name": "lynx009",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/105542329?v=4",
|
||||
"profile": "https://github.com/a-little-fool",
|
||||
"profile": "https://github.com/lynx009",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc",
|
||||
@@ -1442,7 +1442,7 @@
|
||||
},
|
||||
{
|
||||
"login": "Calvin979",
|
||||
"name": "Naruse",
|
||||
"name": "Calvin",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/131688897?v=4",
|
||||
"profile": "https://github.com/Calvin979",
|
||||
"contributions": [
|
||||
@@ -2523,6 +2523,84 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "yy549159265",
|
||||
"name": "yy549159265",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/40821310?v=4",
|
||||
"profile": "https://github.com/yy549159265",
|
||||
"contributions": [
|
||||
"code",
|
||||
"test",
|
||||
"design"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "cto-huhang",
|
||||
"name": "cto-huhang",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/53338629?v=4",
|
||||
"profile": "https://github.com/cto-huhang",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Saramanda9988",
|
||||
"name": "LunaRain_079",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/176664901?v=4",
|
||||
"profile": "https://github.com/Saramanda9988",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "delei",
|
||||
"name": "DeleiGuo",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/17263766?v=4",
|
||||
"profile": "https://github.com/delei",
|
||||
"contributions": [
|
||||
"doc",
|
||||
"code",
|
||||
"test",
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "chingjustwe",
|
||||
"name": "Rocky, Chi",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/13643747?v=4",
|
||||
"profile": "https://github.com/chingjustwe",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "rowankid",
|
||||
"name": "Wenqi Luo",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/18652781?v=4",
|
||||
"profile": "https://github.com/rowankid",
|
||||
"contributions": [
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "tuzuy",
|
||||
"name": "tuzuy",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/95274591?v=4",
|
||||
"profile": "https://github.com/tuzuy",
|
||||
"contributions": [
|
||||
"code",
|
||||
"design"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "carlpinto25",
|
||||
"name": "carl pinto",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/117299909?v=4",
|
||||
"profile": "https://github.com/carlpinto25",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
#
|
||||
|
||||
github:
|
||||
description: Apache HertzBeat(incubating) is a real-time monitoring system with agentless, performance cluster, prometheus-compatible, custom monitoring and status page building capabilities.
|
||||
description: Real-time observability system with agentless, performance cluster, prometheus-compatible, custom monitoring and status page building capabilities.
|
||||
homepage: https://hertzbeat.apache.org/
|
||||
labels:
|
||||
- monitoring
|
||||
|
||||
@@ -67,6 +67,7 @@ header:
|
||||
- 'karma.conf.js'
|
||||
- 'proxy.conf.js'
|
||||
- '.helmignore'
|
||||
- 'script/helm/**'
|
||||
- 'web-app/src/app/core/**'
|
||||
- 'web-app/src/app/layout/**'
|
||||
- 'web-app/src/app/routes/exception/**'
|
||||
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
Apache HertzBeat (incubating) is an effort undergoing incubation at the Apache
|
||||
Software Foundation (ASF), sponsored by the Apache Incubator PMC.
|
||||
|
||||
Incubation is required of all newly accepted projects until a further review
|
||||
indicates that the infrastructure, communications, and decision making process
|
||||
have stabilized in a manner consistent with other successful ASF projects.
|
||||
|
||||
While incubation status is not necessarily a reflection of the completeness
|
||||
or stability of the code, it does indicate that the project has yet to be
|
||||
fully endorsed by the ASF.
|
||||
@@ -1,4 +1,4 @@
|
||||
Apache HertzBeat (incubating)
|
||||
Apache HertzBeat™
|
||||
Copyright 2024-2025 The Apache Software Foundation
|
||||
|
||||
This product includes software developed at
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<b><a href="README.md">English</a></b> | <a href="README_CN.md">中文</a> | <a href="README_JP.md">日本語</a>
|
||||
</p>
|
||||
|
||||
> A real-time monitoring system with agentless, performance cluster, prometheus-compatible, custom monitoring and status page building capabilities.
|
||||
> A real-time observability system with agentless, performance cluster, prometheus-compatible, custom monitoring and status page building capabilities.
|
||||
|
||||
[](https://discord.gg/Fb6M73htGr)
|
||||
[](https://www.reddit.com/r/hertzbeat/)
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
## 🎡 <font color="green">Introduction</font>
|
||||
|
||||
[Apache HertzBeat](https://github.com/apache/hertzbeat) (incubating) is an easy-to-use, open source, real-time monitoring system with agentless, high performance cluster, prometheus-compatible, offers powerful custom monitoring and status page building capabilities.
|
||||
[Apache HertzBeat™](https://github.com/apache/hertzbeat) is an easy-to-use, open source, real-time observability system with agentless, high performance cluster, prometheus-compatible, offers powerful custom monitoring and status page building capabilities.
|
||||
|
||||
### Features
|
||||
|
||||
@@ -572,8 +572,8 @@ HertzBeat is built on so many great open source projects, thanks to them!
|
||||
<p align="left">
|
||||
<img src="./home/static/img/home/cncf-landscape-left-logo.svg" width="300"> <img src="./home/static/img/home/cncf-right-logo.svg" width="345" />
|
||||
<br /><br />
|
||||
HertzBeat has been included in the <a href="https://landscape.cncf.io/guide?item=observability-and-analysis--monitoring--hertzbeat#observability-and-analysis--monitoring">
|
||||
CNCF Observability And Analysis - Monitoring Landscape.</a>
|
||||
HertzBeat has been included in the <a href="https://landscape.cncf.io/?item=observability-and-analysis--observability--hertzbeat">
|
||||
CNCF Observability And Analysis - Observability Landscape.</a>
|
||||
</p>
|
||||
|
||||
## 🛡️ License
|
||||
|
||||
+4
-4
@@ -9,7 +9,7 @@
|
||||
<a href="README.md">English</a> | <b><a href="README_CN.md">中文</a></b> | <a href="README_JP.md">日本語</a>
|
||||
</p>
|
||||
|
||||
> 实时监控系统,无需 Agent,性能集群,兼容 Prometheus,自定义监控和状态页构建能力。
|
||||
> 实时观测系统,无需 Agent,性能集群,兼容 Prometheus,自定义监控和状态页构建能力。
|
||||
|
||||
[](https://discord.gg/Fb6M73htGr)
|
||||
[](https://www.reddit.com/r/hertzbeat/)
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
## 🎡 <font color="green">介绍</font>
|
||||
|
||||
[Apache HertzBeat](https://github.com/apache/hertzbeat) (incubating)是一个易用友好的开源实时监控告警系统,无需 Agent,高性能集群,兼容 Prometheus,提供强大的自定义监控和状态页构建能力。
|
||||
[Apache HertzBeat™](https://github.com/apache/hertzbeat) 是一个易用友好的开源实时监控告警系统,无需 Agent,高性能集群,兼容 Prometheus,提供强大的自定义监控和状态页构建能力。
|
||||
|
||||
### 特点
|
||||
|
||||
@@ -572,8 +572,8 @@ HertzBeat is built on so many great open source projects, thanks to them!
|
||||
<p align="left">
|
||||
<img src="./home/static/img/home/cncf-landscape-left-logo.svg" width="300"> <img src="./home/static/img/home/cncf-right-logo.svg" width="345" />
|
||||
<br /><br />
|
||||
HertzBeat has been included in the <a href="https://landscape.cncf.io/guide?item=observability-and-analysis--monitoring--hertzbeat#observability-and-analysis--monitoring">
|
||||
CNCF Observability And Analysis - Monitoring Landscape.</a>
|
||||
HertzBeat has been included in the <a href="https://landscape.cncf.io/?item=observability-and-analysis--observability--hertzbeat">
|
||||
CNCF Observability And Analysis - Observability Landscape.</a>
|
||||
</p>
|
||||
|
||||
## 🛡️ License
|
||||
|
||||
+2
-2
@@ -576,8 +576,8 @@ HertzBeat is built on so many great open source projects, thanks to them!
|
||||
<p align="left">
|
||||
<img src="./home/static/img/home/cncf-landscape-left-logo.svg" width="300"> <img src="./home/static/img/home/cncf-right-logo.svg" width="345" />
|
||||
<br /><br />
|
||||
HertzBeat has been included in the <a href="https://landscape.cncf.io/guide?item=observability-and-analysis--monitoring--hertzbeat#observability-and-analysis--monitoring">
|
||||
CNCF Observability And Analysis - Monitoring Landscape.</a>
|
||||
HertzBeat has been included in the <a href="https://landscape.cncf.io/?item=observability-and-analysis--observability--hertzbeat">
|
||||
CNCF Observability And Analysis - Observability Landscape.</a>
|
||||
</p>
|
||||
|
||||
## 🛡️ License
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
# Security Policy
|
||||
|
||||
Apache HertzBeat follows the [ASF vulnerability handling process](https://apache.org/security/#vulnerability-handling)
|
||||
Apache HertzBeat™ follows the [ASF vulnerability handling process](https://apache.org/security/#vulnerability-handling)
|
||||
|
||||
## Supported Versions
|
||||
|
||||
@@ -17,6 +17,6 @@ To report a new vulnerability you have discovered please follow the [ASF vulnera
|
||||
|
||||
## Extra Information
|
||||
|
||||
- [Apache HertzBeat Security Model](https://hertzbeat.apache.org/docs/help/security_model)
|
||||
- [Apache HertzBeat™ Security Model](https://hertzbeat.apache.org/docs/help/security_model)
|
||||
- [ASF Security](https://www.apache.org/security/)
|
||||
- [Apache HertzBeat Document](https://hertzbeat.apache.org/docs/)
|
||||
- [Apache HertzBeat™ Document](https://hertzbeat.apache.org/docs/)
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
~ contributor license agreements. See the NOTICE file distributed with
|
||||
~ this work for additional information regarding copyright ownership.
|
||||
~ The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
~ (the "License"); you may not use this file except in compliance with
|
||||
~ the License. You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
<artifactId>hertzbeat</artifactId>
|
||||
<version>2.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>hertzbeat-ai-agent</artifactId>
|
||||
<version>${hertzbeat.version}</version>
|
||||
<properties>
|
||||
<spring-ai.version>1.0.1</spring-ai.version>
|
||||
<java.version>17</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.platform</groupId>
|
||||
<artifactId>junit-platform-launcher</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-model-openai</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
<artifactId>hertzbeat-common</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
<artifactId>hertzbeat-alerter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.usthe.sureness</groupId>
|
||||
<artifactId>spring-boot3-starter-sureness</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-bom</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
+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.ai.agent.adapters;
|
||||
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.Hierarchy;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
|
||||
import org.springframework.data.domain.Page;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Interface that provides access to alert definition information by retrieving data
|
||||
* through the underlying alert define service.
|
||||
*/
|
||||
public interface AlertDefineServiceAdapter {
|
||||
|
||||
/**
|
||||
* Add a new alert rule definition
|
||||
* @param alertDefine Alert definition to add
|
||||
* @return Created alert definition with ID
|
||||
*/
|
||||
AlertDefine addAlertDefine(AlertDefine alertDefine);
|
||||
|
||||
/**
|
||||
* Get alert definitions with filtering and pagination
|
||||
* @param search Search term
|
||||
* @param app Monitor type filter
|
||||
* @param enabled Enabled status filter
|
||||
* @param sort Sort field
|
||||
* @param order Sort order
|
||||
* @param pageIndex Page index
|
||||
* @param pageSize Page size
|
||||
* @return Page of alert definitions
|
||||
*/
|
||||
Page<AlertDefine> getAlertDefines(String search, String app, Boolean enabled, String sort, String order, int pageIndex, int pageSize);
|
||||
|
||||
/**
|
||||
* Get alert definition by ID
|
||||
* @param id Alert definition ID
|
||||
* @return Alert definition if found
|
||||
*/
|
||||
AlertDefine getAlertDefine(Long id);
|
||||
|
||||
/**
|
||||
* Enable or disable alert definition
|
||||
* @param id Alert definition ID
|
||||
* @param enabled Whether to enable
|
||||
*/
|
||||
void toggleAlertDefineStatus(Long id, boolean enabled);
|
||||
|
||||
/**
|
||||
* Modify/update an existing alert definition
|
||||
* @param alertDefine Alert definition to update
|
||||
* @return Updated alert definition
|
||||
*/
|
||||
AlertDefine modifyAlertDefine(AlertDefine alertDefine);
|
||||
|
||||
/**
|
||||
* Get specific app hierarchy structure
|
||||
* @param app App type
|
||||
* @param lang Language for localization
|
||||
* @return List of hierarchy objects for specific app
|
||||
*/
|
||||
|
||||
List<Hierarchy> getAppHierarchy(String app, String lang);
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.adapters;
|
||||
|
||||
import org.apache.hertzbeat.alert.dto.AlertSummary;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.springframework.data.domain.Page;
|
||||
|
||||
/**
|
||||
* Interface that provides access to alert information by retrieving alert data
|
||||
* through the underlying alert service.
|
||||
*/
|
||||
public interface AlertServiceAdapter {
|
||||
|
||||
/**
|
||||
* Get single alerts with filtering and pagination
|
||||
* @param status Alert status
|
||||
* @param search Search term
|
||||
* @param sort Sort field
|
||||
* @param order Sort order
|
||||
* @param pageIndex Page index
|
||||
* @param pageSize Page size
|
||||
* @return Page of single alerts
|
||||
*/
|
||||
Page<SingleAlert> getSingleAlerts(String status, String search, String sort, String order, int pageIndex, int pageSize);
|
||||
|
||||
/**
|
||||
* Get group alerts with filtering and pagination
|
||||
* @param status Alert status
|
||||
* @param search Search term
|
||||
* @param sort Sort field
|
||||
* @param order Sort order
|
||||
* @param pageIndex Page index
|
||||
* @param pageSize Page size
|
||||
* @return Page of group alerts
|
||||
*/
|
||||
Page<GroupAlert> getGroupAlerts(String status, String search, String sort, String order, int pageIndex, int pageSize);
|
||||
|
||||
/**
|
||||
* Get alerts summary statistics
|
||||
* @return Alert summary information
|
||||
*/
|
||||
AlertSummary getAlertsSummary();
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.adapters;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.dto.MetricsData;
|
||||
import org.apache.hertzbeat.common.entity.dto.MetricsHistoryData;
|
||||
|
||||
/**
|
||||
* Interface that provides access to metrics information by retrieving metrics data
|
||||
* through the underlying metrics service.
|
||||
*/
|
||||
public interface MetricsServiceAdapter {
|
||||
|
||||
/**
|
||||
* Check warehouse storage server status
|
||||
* @return true if warehouse is available, false otherwise
|
||||
*/
|
||||
Boolean getWarehouseStorageServerStatus();
|
||||
|
||||
/**
|
||||
* Query real-time metrics data
|
||||
* @param monitorId Monitor ID
|
||||
* @param metrics Metrics name
|
||||
* @return Real-time metrics data
|
||||
*/
|
||||
MetricsData getMetricsData(Long monitorId, String metrics);
|
||||
|
||||
/**
|
||||
* Query historical metrics data
|
||||
* @param monitorId Monitor ID
|
||||
* @param app Monitor type
|
||||
* @param metrics Metrics name
|
||||
* @param metric Metric field name
|
||||
* @param label Label filter
|
||||
* @param history Query historical time period
|
||||
* @param interval Whether to aggregate data
|
||||
* @return Historical metrics data
|
||||
*/
|
||||
MetricsHistoryData getMetricHistoryData(Long monitorId, String app, String metrics, String metric, String label, String history, Boolean interval);
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.ai.agent.adapters;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.apache.hertzbeat.common.entity.manager.Monitor;
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Interface that provides access to monitor information by retrieving monitor data
|
||||
* through the underlying monitor service.
|
||||
*/
|
||||
public interface MonitorServiceAdapter {
|
||||
Page<Monitor> getMonitors(
|
||||
List<Long> ids,
|
||||
String app,
|
||||
String search,
|
||||
Byte status,
|
||||
String sort,
|
||||
String order,
|
||||
Integer pageIndex,
|
||||
Integer pageSize,
|
||||
String labels
|
||||
);
|
||||
|
||||
/**
|
||||
* Add a new monitor
|
||||
*
|
||||
* @param monitor The monitor entity to create
|
||||
* @param params List of parameters for the monitor
|
||||
* @param collector Optional collector assignment
|
||||
* @return The created monitor ID
|
||||
*/
|
||||
Long addMonitor(Monitor monitor, List<Param> params, String collector);
|
||||
|
||||
/**
|
||||
* Get all available monitor types with their display names
|
||||
*
|
||||
* @param language Language code (e.g., "en-US", "zh-CN")
|
||||
* @return Map of monitor type key to display name
|
||||
*/
|
||||
Map<String, String> getAvailableMonitorTypes(String language);
|
||||
|
||||
/**
|
||||
* Get parameter definitions for a specific monitor type
|
||||
*
|
||||
* @param app Monitor type/application name (e.g., "linux", "mysql", "redis")
|
||||
* @return List of parameter definitions for the monitor type
|
||||
*/
|
||||
List<ParamDefine> getMonitorParamDefines(String app);
|
||||
}
|
||||
|
||||
+257
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
* 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.ai.agent.adapters.impl;
|
||||
|
||||
import com.usthe.sureness.subject.SubjectSum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.ai.agent.adapters.AlertDefineServiceAdapter;
|
||||
import org.apache.hertzbeat.ai.agent.config.McpContextHolder;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.Hierarchy;
|
||||
import org.apache.hertzbeat.ai.agent.utils.UtilityClass;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
|
||||
import org.apache.hertzbeat.common.support.SpringContextHolder;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Implementation of the AlertDefineServiceAdapter interface that provides access to alert definition information
|
||||
* through reflection by invoking the underlying alert define service implementation.
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class AlertDefineServiceAdapterImpl implements AlertDefineServiceAdapter {
|
||||
|
||||
@Override
|
||||
public AlertDefine addAlertDefine(AlertDefine alertDefine) {
|
||||
try {
|
||||
Object alertDefineService = null;
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for addAlertDefine: {}", subjectSum);
|
||||
|
||||
try {
|
||||
alertDefineService = SpringContextHolder.getBean("alertDefineServiceImpl");
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not find bean by name 'alertDefineServiceImpl'");
|
||||
}
|
||||
|
||||
assert alertDefineService != null;
|
||||
log.debug("AlertDefineService bean found: {}", alertDefineService.getClass().getSimpleName());
|
||||
|
||||
Method method = alertDefineService.getClass().getMethod("addAlertDefine", AlertDefine.class);
|
||||
|
||||
method.invoke(alertDefineService, alertDefine);
|
||||
|
||||
log.debug("Successfully added alert define with ID: {}", alertDefine.getId());
|
||||
return alertDefine;
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("Method not found: addAlertDefine", e);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to invoke addAlertDefine via adapter", e);
|
||||
throw new RuntimeException("Failed to invoke addAlertDefine via adapter: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<AlertDefine> getAlertDefines(String search, String app, Boolean enabled, String sort, String order, int pageIndex, int pageSize) {
|
||||
try {
|
||||
Object alertDefineService = null;
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for getAlertDefines: {}", subjectSum);
|
||||
|
||||
try {
|
||||
alertDefineService = SpringContextHolder.getBean("alertDefineServiceImpl");
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not find bean by name 'alertDefineServiceImpl'");
|
||||
}
|
||||
|
||||
assert alertDefineService != null;
|
||||
log.debug("AlertDefineService bean found: {}", alertDefineService.getClass().getSimpleName());
|
||||
|
||||
Method method = alertDefineService.getClass().getMethod(
|
||||
"getAlertDefines",
|
||||
List.class, String.class, String.class, String.class, int.class, int.class);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Page<AlertDefine> result = (Page<AlertDefine>) method.invoke(
|
||||
alertDefineService, null, search, sort, order, pageIndex, pageSize);
|
||||
|
||||
log.debug("Successfully retrieved {} alert defines", result.getContent().size());
|
||||
return result;
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("Method not found: getAlertDefines", e);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to invoke getAlertDefines via adapter", e);
|
||||
throw new RuntimeException("Failed to invoke getAlertDefines via adapter: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDefine getAlertDefine(Long id) {
|
||||
try {
|
||||
Object alertDefineService = null;
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for getAlertDefine: {}", subjectSum);
|
||||
|
||||
try {
|
||||
alertDefineService = SpringContextHolder.getBean("alertDefineServiceImpl");
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not find bean by name 'alertDefineServiceImpl'");
|
||||
}
|
||||
|
||||
assert alertDefineService != null;
|
||||
log.debug("AlertDefineService bean found: {}", alertDefineService.getClass().getSimpleName());
|
||||
|
||||
Method method = alertDefineService.getClass().getMethod("getAlertDefine", long.class);
|
||||
|
||||
AlertDefine result = (AlertDefine) method.invoke(alertDefineService, id);
|
||||
|
||||
log.debug("Successfully retrieved alert define with ID: {}", id);
|
||||
return result;
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("Method not found: getAlertDefine", e);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to invoke getAlertDefine via adapter for ID: {}", id, e);
|
||||
throw new RuntimeException("Failed to invoke getAlertDefine via adapter: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void toggleAlertDefineStatus(Long id, boolean enabled) {
|
||||
try {
|
||||
Object alertDefineService = null;
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for toggleAlertDefineStatus: {}", subjectSum);
|
||||
|
||||
try {
|
||||
alertDefineService = SpringContextHolder.getBean("alertDefineServiceImpl");
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not find bean by name 'alertDefineServiceImpl'");
|
||||
}
|
||||
|
||||
assert alertDefineService != null;
|
||||
log.debug("AlertDefineService bean found: {}", alertDefineService.getClass().getSimpleName());
|
||||
|
||||
// First get the existing AlertDefine
|
||||
Method getMethod = alertDefineService.getClass().getMethod("getAlertDefine", long.class);
|
||||
AlertDefine alertDefine = (AlertDefine) getMethod.invoke(alertDefineService, id);
|
||||
|
||||
if (alertDefine == null) {
|
||||
throw new RuntimeException("AlertDefine with ID " + id + " not found");
|
||||
}
|
||||
|
||||
// Update the enable status
|
||||
alertDefine.setEnable(enabled);
|
||||
|
||||
// Use modifyAlertDefine to save the changes
|
||||
Method modifyMethod = alertDefineService.getClass().getMethod("modifyAlertDefine", AlertDefine.class);
|
||||
modifyMethod.invoke(alertDefineService, alertDefine);
|
||||
|
||||
log.debug("Successfully toggled alert define status for ID: {} to enabled: {}", id, enabled);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to invoke toggleAlertDefineStatus via adapter for ID: {}", id, e);
|
||||
throw new RuntimeException("Failed to invoke toggleAlertDefineStatus via adapter: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDefine modifyAlertDefine(AlertDefine alertDefine) {
|
||||
try {
|
||||
Object alertDefineService = null;
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for modifyAlertDefine: {}", subjectSum);
|
||||
|
||||
try {
|
||||
alertDefineService = SpringContextHolder.getBean("alertDefineServiceImpl");
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not find bean by name 'alertDefineServiceImpl'");
|
||||
}
|
||||
|
||||
assert alertDefineService != null;
|
||||
log.debug("AlertDefineService bean found: {}", alertDefineService.getClass().getSimpleName());
|
||||
|
||||
Method method = alertDefineService.getClass().getMethod("modifyAlertDefine", AlertDefine.class);
|
||||
|
||||
method.invoke(alertDefineService, alertDefine);
|
||||
|
||||
log.debug("Successfully modified alert define with ID: {}", alertDefine.getId());
|
||||
return alertDefine;
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("Method not found: modifyAlertDefine", e);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to invoke modifyAlertDefine via adapter", e);
|
||||
throw new RuntimeException("Failed to invoke modifyAlertDefine via adapter: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the application hierarchy for a given app and language.
|
||||
* Uses reflection to call the underlying app service method.
|
||||
*
|
||||
* @param app The application name
|
||||
* @param lang The language code (optional, defaults to "en-US")
|
||||
* @return List of Hierarchy objects representing the app hierarchy
|
||||
*/
|
||||
|
||||
@Override
|
||||
public List<Hierarchy> getAppHierarchy(String app, String lang) {
|
||||
try {
|
||||
Object appService = null;
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for getAppHierarchy: {}", subjectSum);
|
||||
|
||||
try {
|
||||
appService = SpringContextHolder.getBean("appServiceImpl");
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not find bean by name 'appServiceImpl', trying by class name");
|
||||
}
|
||||
|
||||
assert appService != null;
|
||||
log.debug("AppService bean found for getAppHierarchy: {}", appService.getClass().getSimpleName());
|
||||
|
||||
// Provide default language if not specified
|
||||
if (lang == null || lang.trim().isEmpty()) {
|
||||
lang = "en-US";
|
||||
}
|
||||
|
||||
// Call getAppHierarchy method: getAppHierarchy(String app, String lang)
|
||||
Method method = appService.getClass().getMethod("getAppHierarchy", String.class, String.class);
|
||||
|
||||
List<?> managerHierarchies = (List<?>) method.invoke(appService, app, lang);
|
||||
|
||||
// Convert manager DTOs to ai-agent DTOs
|
||||
List<Hierarchy> result = UtilityClass.convertToAgentHierarchies(managerHierarchies);
|
||||
|
||||
log.debug("Successfully retrieved and converted {} hierarchies for app '{}'", result.size(), app);
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to get app hierarchy for app '{}': {}", app, e.getMessage(), e);
|
||||
throw new RuntimeException("Failed to get app hierarchy for " + app, e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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.ai.agent.adapters.impl;
|
||||
|
||||
import com.usthe.sureness.subject.SubjectSum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.ai.agent.adapters.AlertServiceAdapter;
|
||||
import org.apache.hertzbeat.ai.agent.config.McpContextHolder;
|
||||
import org.apache.hertzbeat.alert.dto.AlertSummary;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.apache.hertzbeat.common.support.SpringContextHolder;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Implementation of the AlertServiceAdapter interface that provides access to alert information
|
||||
* through reflection by invoking the underlying alert service implementation.
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class AlertServiceAdapterImpl implements AlertServiceAdapter {
|
||||
|
||||
@Override
|
||||
public Page<SingleAlert> getSingleAlerts(String status, String search, String sort, String order, int pageIndex, int pageSize) {
|
||||
try {
|
||||
Object alertService = null;
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for getSingleAlerts: {}", subjectSum);
|
||||
|
||||
try {
|
||||
alertService = SpringContextHolder.getBean("alertServiceImpl");
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not find bean by name 'alertServiceImpl'");
|
||||
}
|
||||
|
||||
assert alertService != null;
|
||||
log.debug("AlertService bean found: {}", alertService.getClass().getSimpleName());
|
||||
|
||||
Method method = alertService.getClass().getMethod(
|
||||
"getSingleAlerts",
|
||||
String.class, String.class, String.class, String.class, int.class, int.class);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Page<SingleAlert> result = (Page<SingleAlert>) method.invoke(
|
||||
alertService, status, search, sort, order, pageIndex, pageSize);
|
||||
|
||||
log.debug("Successfully retrieved {} single alerts", result.getContent().size());
|
||||
return result;
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("Method not found: getSingleAlerts", e);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to invoke getSingleAlerts via adapter", e);
|
||||
throw new RuntimeException("Failed to invoke getSingleAlerts via adapter: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<GroupAlert> getGroupAlerts(String status, String search, String sort, String order, int pageIndex, int pageSize) {
|
||||
try {
|
||||
Object alertService = null;
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for getGroupAlerts: {}", subjectSum);
|
||||
|
||||
try {
|
||||
alertService = SpringContextHolder.getBean("alertServiceImpl");
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not find bean by name 'alertServiceImpl'");
|
||||
}
|
||||
|
||||
assert alertService != null;
|
||||
log.debug("AlertService bean found: {}", alertService.getClass().getSimpleName());
|
||||
|
||||
Method method = alertService.getClass().getMethod(
|
||||
"getGroupAlerts",
|
||||
String.class, String.class, String.class, String.class, int.class, int.class);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Page<GroupAlert> result = (Page<GroupAlert>) method.invoke(
|
||||
alertService, status, search, sort, order, pageIndex, pageSize);
|
||||
|
||||
log.debug("Successfully retrieved {} group alerts", result.getContent().size());
|
||||
return result;
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("Method not found: getGroupAlerts", e);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to invoke getGroupAlerts via adapter", e);
|
||||
throw new RuntimeException("Failed to invoke getGroupAlerts via adapter: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertSummary getAlertsSummary() {
|
||||
try {
|
||||
Object alertService = null;
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for getAlertsSummary: {}", subjectSum);
|
||||
|
||||
try {
|
||||
alertService = SpringContextHolder.getBean("alertServiceImpl");
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not find bean by name 'alertServiceImpl'");
|
||||
}
|
||||
|
||||
assert alertService != null;
|
||||
log.debug("AlertService bean found: {}", alertService.getClass().getSimpleName());
|
||||
|
||||
Method method = alertService.getClass().getMethod("getAlertsSummary");
|
||||
|
||||
AlertSummary result = (AlertSummary) method.invoke(alertService);
|
||||
|
||||
log.debug("Successfully retrieved alerts summary");
|
||||
return result;
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("Method not found: getAlertsSummary", e);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to invoke getAlertsSummary via adapter", e);
|
||||
throw new RuntimeException("Failed to invoke getAlertsSummary via adapter: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* 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.ai.agent.adapters.impl;
|
||||
|
||||
import com.usthe.sureness.subject.SubjectSum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.ai.agent.adapters.MetricsServiceAdapter;
|
||||
import org.apache.hertzbeat.ai.agent.config.McpContextHolder;
|
||||
import org.apache.hertzbeat.common.entity.dto.MetricsData;
|
||||
import org.apache.hertzbeat.common.entity.dto.MetricsHistoryData;
|
||||
import org.apache.hertzbeat.common.support.SpringContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Implementation of the MetricsServiceAdapter interface that provides access to metrics information
|
||||
* through reflection by invoking the underlying metrics service implementation.
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class MetricsServiceAdapterImpl implements MetricsServiceAdapter {
|
||||
|
||||
@Override
|
||||
public Boolean getWarehouseStorageServerStatus() {
|
||||
try {
|
||||
Object metricsDataService = null;
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for getWarehouseStorageServerStatus: {}", subjectSum);
|
||||
|
||||
try {
|
||||
metricsDataService = SpringContextHolder.getBean("metricsDataServiceImpl");
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not find bean by name 'metricsDataServiceImpl'");
|
||||
}
|
||||
|
||||
assert metricsDataService != null;
|
||||
log.debug("MetricsDataService bean found: {}", metricsDataService.getClass().getSimpleName());
|
||||
|
||||
Method method = metricsDataService.getClass().getMethod("getWarehouseStorageServerStatus");
|
||||
|
||||
Boolean result = (Boolean) method.invoke(metricsDataService);
|
||||
|
||||
log.debug("Successfully retrieved warehouse storage server status: {}", result);
|
||||
return result;
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("Method not found: getWarehouseStorageServerStatus", e);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to invoke getWarehouseStorageServerStatus via adapter", e);
|
||||
throw new RuntimeException("Failed to invoke getWarehouseStorageServerStatus via adapter: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MetricsData getMetricsData(Long monitorId, String metrics) {
|
||||
try {
|
||||
Object metricsDataService = null;
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for getMetricsData: {}", subjectSum);
|
||||
|
||||
try {
|
||||
metricsDataService = SpringContextHolder.getBean("metricsDataServiceImpl");
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not find bean by name 'metricsDataServiceImpl'");
|
||||
}
|
||||
|
||||
assert metricsDataService != null;
|
||||
log.debug("MetricsDataService bean found: {}", metricsDataService.getClass().getSimpleName());
|
||||
|
||||
Method method = metricsDataService.getClass().getMethod(
|
||||
"getMetricsData",
|
||||
Long.class, String.class);
|
||||
|
||||
MetricsData result = (MetricsData) method.invoke(metricsDataService, monitorId, metrics);
|
||||
|
||||
log.debug("Successfully retrieved metrics data for monitor {} and metrics {}", monitorId, metrics);
|
||||
return result;
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("Method not found: getMetricsData", e);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to invoke getMetricsData via adapter for monitor {} and metrics {}", monitorId, metrics, e);
|
||||
throw new RuntimeException("Failed to invoke getMetricsData via adapter: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MetricsHistoryData getMetricHistoryData(Long monitorId, String app, String metrics, String metric, String label, String history, Boolean interval) {
|
||||
try {
|
||||
Object metricsDataService = null;
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for getMetricHistoryData: {}", subjectSum);
|
||||
|
||||
try {
|
||||
metricsDataService = SpringContextHolder.getBean("metricsDataServiceImpl");
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not find bean by name 'metricsDataServiceImpl'");
|
||||
}
|
||||
|
||||
assert metricsDataService != null;
|
||||
log.debug("MetricsDataService bean found: {}", metricsDataService.getClass().getSimpleName());
|
||||
|
||||
Method method = metricsDataService.getClass().getMethod(
|
||||
"getMetricHistoryData",
|
||||
Long.class, String.class, String.class, String.class, String.class, String.class, Boolean.class);
|
||||
|
||||
MetricsHistoryData result = (MetricsHistoryData) method.invoke(
|
||||
metricsDataService, monitorId, app, metrics, metric, label, history, interval);
|
||||
|
||||
log.debug("Successfully retrieved historical metrics data for monitor {} and metrics {}", monitorId, metrics);
|
||||
return result;
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("Method not found: getMetricHistoryData", e);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to invoke getMetricHistoryData via adapter for monitor {} and metrics {}", monitorId, metrics, e);
|
||||
throw new RuntimeException("Failed to invoke getMetricHistoryData via adapter: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+214
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* 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.ai.agent.adapters.impl;
|
||||
|
||||
import com.usthe.sureness.subject.SubjectSum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.ai.agent.adapters.MonitorServiceAdapter;
|
||||
import org.apache.hertzbeat.ai.agent.config.McpContextHolder;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.apache.hertzbeat.common.entity.manager.Monitor;
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.apache.hertzbeat.common.support.SpringContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Implementation of the MonitorServiceAdapter interface that provides access to monitor information
|
||||
* through reflection by invoking the underlying monitor service implementation.
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class MonitorServiceAdapterImpl implements MonitorServiceAdapter {
|
||||
|
||||
@Override
|
||||
public Page<Monitor> getMonitors(
|
||||
List<Long> ids,
|
||||
String app,
|
||||
String search,
|
||||
Byte status,
|
||||
String sort,
|
||||
String order,
|
||||
Integer pageIndex,
|
||||
Integer pageSize,
|
||||
String labels) {
|
||||
try {
|
||||
// Provide default values for all nullable parameters
|
||||
if (sort == null || sort.trim().isEmpty()) {
|
||||
sort = "gmtCreate";
|
||||
}
|
||||
if (order == null || order.trim().isEmpty()) {
|
||||
order = "desc";
|
||||
}
|
||||
if (pageIndex == null) {
|
||||
pageIndex = 0;
|
||||
}
|
||||
if (pageSize == null) {
|
||||
pageSize = 8;
|
||||
}
|
||||
|
||||
Object monitorService = null;
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject: {}", subjectSum);
|
||||
|
||||
try {
|
||||
monitorService = SpringContextHolder.getBean("monitorServiceImpl");
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not find bean by name 'monitorServiceImpl', trying by class name");
|
||||
}
|
||||
|
||||
assert monitorService != null;
|
||||
log.debug("MonitorService bean found: {}", monitorService.getClass().getSimpleName());
|
||||
Method method = monitorService.getClass().getMethod(
|
||||
"getMonitors",
|
||||
List.class, String.class, String.class, Byte.class,
|
||||
String.class, String.class, int.class, int.class, String.class);
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Page<Monitor> result = (Page<Monitor>) method.invoke(
|
||||
monitorService,
|
||||
ids, app, search, status, sort, order, pageIndex, pageSize, labels);
|
||||
log.debug("MonitorServiceAdapter.getMonitors result: {}", result.getContent());
|
||||
return result;
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("Method not found: getMonitors", e);
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to invoke getMonitors via adapter", e);
|
||||
throw new RuntimeException("Failed to invoke getMonitors via adapter", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long addMonitor(Monitor monitor, List<Param> params, String collector) {
|
||||
try {
|
||||
Object monitorService = null;
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for addMonitor: {}", subjectSum);
|
||||
|
||||
try {
|
||||
monitorService = SpringContextHolder.getBean("monitorServiceImpl");
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not find bean by name 'monitorServiceImpl', trying by class name");
|
||||
}
|
||||
|
||||
assert monitorService != null;
|
||||
log.debug("MonitorService bean found for addMonitor: {}", monitorService.getClass().getSimpleName());
|
||||
|
||||
// Call addMonitor method: addMonitor(Monitor monitor, List<Param> params, String collector, GrafanaDashboard dashboard)
|
||||
Method method = monitorService.getClass().getMethod(
|
||||
"addMonitor",
|
||||
Monitor.class, List.class, String.class,
|
||||
Class.forName("org.apache.hertzbeat.common.entity.grafana.GrafanaDashboard"));
|
||||
|
||||
// Call the method with null dashboard
|
||||
method.invoke(monitorService, monitor, params, collector, null);
|
||||
|
||||
log.debug("Successfully added monitor: {} with ID: {}", monitor.getName(), monitor.getId());
|
||||
return monitor.getId();
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("Method not found: addMonitor", e);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to invoke addMonitor via adapter", e);
|
||||
throw new RuntimeException("Failed to invoke addMonitor via adapter: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAvailableMonitorTypes(String language) {
|
||||
try {
|
||||
Object appService = null;
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for getAvailableMonitorTypes: {}", subjectSum);
|
||||
|
||||
try {
|
||||
appService = SpringContextHolder.getBean("appServiceImpl");
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not find bean by name 'appServiceImpl', trying by class name");
|
||||
}
|
||||
|
||||
assert appService != null;
|
||||
log.debug("AppService bean found for getAvailableMonitorTypes: {}", appService.getClass().getSimpleName());
|
||||
|
||||
// Provide default language if not specified
|
||||
if (language == null || language.trim().isEmpty()) {
|
||||
language = "en-US";
|
||||
}
|
||||
|
||||
// Call getI18nApps method: getI18nApps(String lang)
|
||||
Method method = appService.getClass().getMethod("getI18nApps", String.class);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> result = (Map<String, String>) method.invoke(appService, language);
|
||||
|
||||
log.debug("Successfully retrieved {} monitor types", result.size());
|
||||
return result;
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("Method not found: getI18nApps", e);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to invoke getI18nApps via adapter", e);
|
||||
throw new RuntimeException("Failed to invoke getI18nApps via adapter: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ParamDefine> getMonitorParamDefines(String app) {
|
||||
try {
|
||||
Object appService = null;
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for getMonitorParamDefines: {}", subjectSum);
|
||||
|
||||
try {
|
||||
appService = SpringContextHolder.getBean("appServiceImpl");
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not find bean by name 'appServiceImpl', trying by class name");
|
||||
}
|
||||
|
||||
assert appService != null;
|
||||
log.debug("AppService bean found for getMonitorParamDefines: {}", appService.getClass().getSimpleName());
|
||||
|
||||
// Validate app parameter
|
||||
if (app == null || app.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("Monitor type/app parameter is required");
|
||||
}
|
||||
|
||||
// Call getAppParamDefines method: getAppParamDefines(String app)
|
||||
Method method = appService.getClass().getMethod("getAppParamDefines", String.class);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<ParamDefine> result = (List<ParamDefine>) method.invoke(appService, app.toLowerCase().trim());
|
||||
|
||||
log.debug("Successfully retrieved {} parameter definitions for monitor type: {}", result.size(), app);
|
||||
return result;
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("Method not found: getAppParamDefines", e);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to invoke getAppParamDefines via adapter for app: {}", app, e);
|
||||
throw new RuntimeException("Failed to invoke getAppParamDefines via adapter: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+246
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
* 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.ai.agent.config;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.usthe.sureness.mgt.SurenessSecurityManager;
|
||||
import com.usthe.sureness.subject.SubjectSum;
|
||||
import io.modelcontextprotocol.spec.McpError;
|
||||
import io.modelcontextprotocol.spec.McpSchema;
|
||||
import io.modelcontextprotocol.spec.McpServerSession;
|
||||
import io.modelcontextprotocol.spec.McpServerTransport;
|
||||
import io.modelcontextprotocol.spec.McpServerTransportProvider;
|
||||
import io.modelcontextprotocol.util.Assert;
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.servlet.function.RouterFunction;
|
||||
import org.springframework.web.servlet.function.RouterFunctions;
|
||||
import org.springframework.web.servlet.function.ServerRequest;
|
||||
import org.springframework.web.servlet.function.ServerResponse;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* Custom Server-Sent Events transport provider for Model Context Protocol.
|
||||
*/
|
||||
@Slf4j
|
||||
public class CustomSseServerTransport implements McpServerTransportProvider {
|
||||
private final ObjectMapper objectMapper;
|
||||
private final String messageEndpoint;
|
||||
private final String sseEndpoint;
|
||||
private final String baseUrl;
|
||||
@Getter
|
||||
private final RouterFunction<ServerResponse> routerFunction;
|
||||
@Setter
|
||||
private McpServerSession.Factory sessionFactory;
|
||||
private final Map<String, Object> sessionRequest = new HashMap<>();
|
||||
private final ConcurrentHashMap<String, McpServerSession> sessions;
|
||||
private volatile boolean isClosing;
|
||||
|
||||
public CustomSseServerTransport(ObjectMapper objectMapper, String messageEndpoint) {
|
||||
this(objectMapper, messageEndpoint, "/sse");
|
||||
}
|
||||
|
||||
|
||||
public CustomSseServerTransport(ObjectMapper objectMapper, String messageEndpoint, String sseEndpoint) {
|
||||
this(objectMapper, "", messageEndpoint, sseEndpoint);
|
||||
}
|
||||
|
||||
public CustomSseServerTransport(ObjectMapper objectMapper, String baseUrl, String messageEndpoint, String sseEndpoint) {
|
||||
this.sessions = new ConcurrentHashMap();
|
||||
this.isClosing = false;
|
||||
Assert.notNull(objectMapper, "ObjectMapper must not be null");
|
||||
Assert.notNull(baseUrl, "Message base URL must not be null");
|
||||
Assert.notNull(messageEndpoint, "Message endpoint must not be null");
|
||||
Assert.notNull(sseEndpoint, "SSE endpoint must not be null");
|
||||
this.objectMapper = objectMapper;
|
||||
this.baseUrl = baseUrl;
|
||||
this.messageEndpoint = messageEndpoint;
|
||||
this.sseEndpoint = sseEndpoint;
|
||||
this.routerFunction = RouterFunctions.route().GET(this.sseEndpoint, this::handleSseConnection).POST(this.messageEndpoint, this::handleMessage).build();
|
||||
}
|
||||
|
||||
public Mono<Void> notifyClients(String method, Object params) {
|
||||
if (this.sessions.isEmpty()) {
|
||||
log.debug("No active sessions to broadcast message to");
|
||||
return Mono.empty();
|
||||
} else {
|
||||
log.debug("Attempting to broadcast message to {} active sessions", this.sessions.size());
|
||||
return Flux.fromIterable(this.sessions.values())
|
||||
.flatMap((session) -> session.sendNotification(method, params)
|
||||
.doOnError((e) -> log.error("Failed to send message to session {}: {}", session.getId(), e.getMessage()))
|
||||
.onErrorComplete())
|
||||
.then();
|
||||
}
|
||||
}
|
||||
|
||||
public Mono<Void> closeGracefully() {
|
||||
return Flux.fromIterable(this.sessions.values()).doFirst(() -> {
|
||||
this.isClosing = true;
|
||||
log.debug("Initiating graceful shutdown with {} active sessions", this.sessions.size());
|
||||
}).flatMap(McpServerSession::closeGracefully).then().doOnSuccess((v) -> log.debug("Graceful shutdown completed"));
|
||||
}
|
||||
|
||||
private ServerResponse handleSseConnection(ServerRequest request) {
|
||||
log.debug("Handling SSE connection for request: {}", request);
|
||||
HttpServletRequest servletRequest = request.servletRequest();
|
||||
|
||||
try {
|
||||
|
||||
log.debug("Processing SSE connection for servlet request: {}", servletRequest);
|
||||
log.debug("Authorization header: {}", servletRequest.getHeader("Authorization"));
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Authentication failed for SSE connection: {}", e.getMessage());
|
||||
return ServerResponse.status(HttpStatus.UNAUTHORIZED).body("Unauthorized: " + e.getMessage());
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (this.isClosing) {
|
||||
return ServerResponse.status(HttpStatus.SERVICE_UNAVAILABLE).body("Server is shutting down");
|
||||
} else {
|
||||
String sessionId = UUID.randomUUID().toString();
|
||||
log.debug("Generated session ID for SSE connection: {}", sessionId);
|
||||
log.debug("Creating new SSE connection for session: {}", sessionId);
|
||||
|
||||
|
||||
return ServerResponse.sse((sseBuilder) -> {
|
||||
sseBuilder.onComplete(() -> {
|
||||
log.debug("SSE connection completed for session: {}", sessionId);
|
||||
this.sessions.remove(sessionId);
|
||||
});
|
||||
sseBuilder.onTimeout(() -> {
|
||||
log.debug("SSE connection timed out for session: {}", sessionId);
|
||||
this.sessions.remove(sessionId);
|
||||
});
|
||||
CustomSseServerTransport.WebMvcMcpSessionTransport sessionTransport = new CustomSseServerTransport.WebMvcMcpSessionTransport(sessionId, sseBuilder);
|
||||
McpServerSession session = this.sessionFactory.create(sessionTransport);
|
||||
this.sessionRequest.put(sessionId, request.servletRequest());
|
||||
this.sessions.put(sessionId, session);
|
||||
|
||||
try {
|
||||
sseBuilder.id(sessionId).event("endpoint").data(this.baseUrl + this.messageEndpoint + "?sessionId=" + sessionId);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to send initial endpoint event: {}", e.getMessage());
|
||||
sseBuilder.error(e);
|
||||
}
|
||||
|
||||
}, Duration.ZERO);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private ServerResponse handleMessage(ServerRequest request) {
|
||||
if (this.isClosing) {
|
||||
return ServerResponse.status(HttpStatus.SERVICE_UNAVAILABLE).body("Server is shutting down");
|
||||
} else if (request.param("sessionId").isEmpty()) {
|
||||
return ServerResponse.badRequest().body(new McpError("Session ID missing in message endpoint"));
|
||||
} else {
|
||||
String sessionId = (String) request.param("sessionId").get();
|
||||
McpServerSession session = (McpServerSession) this.sessions.get(sessionId);
|
||||
log.debug("Authorization header for message request: {}", request.servletRequest().getHeader("Authorization"));
|
||||
SubjectSum subject = SurenessSecurityManager.getInstance().checkIn(sessionRequest.get(sessionId));
|
||||
McpContextHolder.setSubject(subject);
|
||||
|
||||
|
||||
if (session == null) {
|
||||
return ServerResponse.status(HttpStatus.NOT_FOUND).body(new McpError("Session not found: " + sessionId));
|
||||
} else {
|
||||
try {
|
||||
String body = request.body(String.class);
|
||||
McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(this.objectMapper, body);
|
||||
session.handle(message).block();
|
||||
return ServerResponse.ok().build();
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
log.error("Failed to deserialize message: {}", ((Exception) e).getMessage());
|
||||
return ServerResponse.badRequest().body(new McpError("Invalid message format"));
|
||||
} catch (Exception e) {
|
||||
log.error("Error handling message: {}", e.getMessage());
|
||||
return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new McpError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class WebMvcMcpSessionTransport implements McpServerTransport {
|
||||
private final String sessionId;
|
||||
private final ServerResponse.SseBuilder sseBuilder;
|
||||
|
||||
WebMvcMcpSessionTransport(String sessionId, ServerResponse.SseBuilder sseBuilder) {
|
||||
this.sessionId = sessionId;
|
||||
this.sseBuilder = sseBuilder;
|
||||
log.debug("Session transport {} initialized with SSE builder", sessionId);
|
||||
}
|
||||
|
||||
public Mono<Void> sendMessage(McpSchema.JSONRPCMessage message) {
|
||||
return Mono.fromRunnable(() -> {
|
||||
try {
|
||||
String jsonText = CustomSseServerTransport.this.objectMapper.writeValueAsString(message);
|
||||
this.sseBuilder.id(this.sessionId).event("message").data(jsonText);
|
||||
log.debug("Message sent to session {}", this.sessionId);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to send message to session {}: {}", this.sessionId, e.getMessage());
|
||||
this.sseBuilder.error(e);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public <T> T unmarshalFrom(Object data, TypeReference<T> typeRef) {
|
||||
return (T) CustomSseServerTransport.this.objectMapper.convertValue(data, typeRef);
|
||||
}
|
||||
|
||||
public Mono<Void> closeGracefully() {
|
||||
return Mono.fromRunnable(() -> {
|
||||
log.debug("Closing session transport: {}", this.sessionId);
|
||||
|
||||
try {
|
||||
this.sseBuilder.complete();
|
||||
log.debug("Successfully completed SSE builder for session {}", this.sessionId);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to complete SSE builder for session {}: {}", this.sessionId, e.getMessage());
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public void close() {
|
||||
try {
|
||||
this.sseBuilder.complete();
|
||||
log.debug("Successfully completed SSE builder for session {}", this.sessionId);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to complete SSE builder for session {}: {}", this.sessionId, e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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.ai.agent.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.OpenAiConfigDto;
|
||||
import org.apache.hertzbeat.ai.agent.service.OpenAiConfigService;
|
||||
import org.springframework.ai.model.ApiKey;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Dynamic OpenAI API Key implementation that retrieves the API key
|
||||
* from our configuration service (database first, then YAML fallback)
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DynamicOpenAiApiKey implements ApiKey {
|
||||
|
||||
private final OpenAiConfigService openAiConfigService;
|
||||
|
||||
public DynamicOpenAiApiKey(OpenAiConfigService openAiConfigService) {
|
||||
this.openAiConfigService = openAiConfigService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
try {
|
||||
OpenAiConfigDto effectiveConfig = openAiConfigService.getEffectiveConfig();
|
||||
|
||||
if (effectiveConfig != null && effectiveConfig.isEnable() && effectiveConfig.getApiKey() != null) {
|
||||
log.debug("Retrieved OpenAI API key from configuration service");
|
||||
return effectiveConfig.getApiKey();
|
||||
} else {
|
||||
log.warn("No valid OpenAI API key found in configuration");
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error retrieving OpenAI API key from configuration", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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.ai.agent.config;
|
||||
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import org.springframework.ai.openai.api.OpenAiApi;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Configuration class for Large Language Model (LLM) settings.
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
public class LlmConfig {
|
||||
|
||||
@Value("${spring.ai.openai.chat.options.model}")
|
||||
private String model;
|
||||
|
||||
/**
|
||||
* Create OpenAI API instance with dynamic API key
|
||||
*/
|
||||
@Bean
|
||||
public OpenAiApi openAiApi(DynamicOpenAiApiKey dynamicApiKey) {
|
||||
return OpenAiApi.builder()
|
||||
.apiKey(dynamicApiKey)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create OpenAI Chat Options with custom settings
|
||||
*/
|
||||
@Bean
|
||||
public OpenAiChatOptions openAiChatOptions() {
|
||||
return OpenAiChatOptions.builder()
|
||||
.model(model)
|
||||
.temperature(0.3)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create OpenAI Chat Model with custom API configuration
|
||||
*/
|
||||
@Bean
|
||||
public OpenAiChatModel openAiChatModel(OpenAiApi openAiApi, OpenAiChatOptions openAiChatOptions) {
|
||||
return OpenAiChatModel.builder()
|
||||
.openAiApi(openAiApi)
|
||||
.defaultOptions(openAiChatOptions)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ChatClient openAiChatClient(OpenAiChatModel openAiChatModel) {
|
||||
return ChatClient.create(openAiChatModel);
|
||||
}
|
||||
|
||||
}
|
||||
+53
@@ -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.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.config;
|
||||
|
||||
import com.usthe.sureness.subject.SubjectSum;
|
||||
import org.springframework.core.NamedInheritableThreadLocal;
|
||||
|
||||
/**
|
||||
* Context holder for AI agent security context.
|
||||
*/
|
||||
public final class McpContextHolder {
|
||||
private static final ThreadLocal<SubjectSum> subjectHolder =
|
||||
new NamedInheritableThreadLocal<>("MCP Security and User Identification Context");
|
||||
|
||||
private McpContextHolder() {}
|
||||
|
||||
/**
|
||||
* Attaches the user's context to the current thread.
|
||||
*/
|
||||
public static void setSubject(SubjectSum subject) {
|
||||
subjectHolder.set(subject);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the context from the current thread.
|
||||
*/
|
||||
public static SubjectSum getSubject() {
|
||||
return subjectHolder.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the context from the thread to prevent memory leaks.
|
||||
*/
|
||||
public static void clear() {
|
||||
subjectHolder.remove();
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.ai.agent.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* OpenAI YAML Configuration - reads from spring.ai.openai.api-key
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "spring.ai.openai")
|
||||
public class OpenAiYamlConfig {
|
||||
|
||||
/**
|
||||
* OpenAI API key from spring.ai.openai.api-key
|
||||
*/
|
||||
private String apiKey;
|
||||
|
||||
/**
|
||||
* Check if OpenAI is enabled (has API key)
|
||||
*/
|
||||
public boolean isEnable() {
|
||||
return apiKey != null && !apiKey.trim().isEmpty();
|
||||
}
|
||||
}
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* 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.ai.agent.config;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Provider for system prompts used in the AI agent
|
||||
*/
|
||||
|
||||
@Component
|
||||
public class PromptProvider {
|
||||
/**
|
||||
* Static version of the HertzBeat monitoring prompt
|
||||
*/
|
||||
public static final String HERTZBEAT_SYSTEM_PROMPT = """
|
||||
You are an AI Assistant specialized in monitoring infrastructure and applications with HertzBeat.
|
||||
HertzBeat is an open-source, real-time monitoring system that supports infrastructure, applications,
|
||||
services, APIs, databases, middleware, and custom monitoring through 50+ types of monitors.
|
||||
Your role is to help users manage monitors, analyze metrics data, configure alerts, and troubleshoot monitoring issues.
|
||||
*******
|
||||
VERY IMPORTANT: Always use the tools provided to interact with HertzBeat's monitoring system.
|
||||
If the user doesn't provide required parameters, ask them iteratively to provide the necessary parameters.
|
||||
********
|
||||
|
||||
## Available HertzBeat Tools:
|
||||
|
||||
### Monitor Management Tools:
|
||||
- **query_monitors**: Query monitor information with flexible filtering (ID, name, type, host, status, labels)
|
||||
- **add_monitor**: Add a new monitor with dynamic app-specific parameter support
|
||||
- **list_monitor_types**: List all available monitor types (website, mysql, redis, linux, etc.)
|
||||
- **get_monitor_additional_params**: Get parameter definitions required for specific monitor types
|
||||
|
||||
### Alert Rule Management Tools:
|
||||
- **create_alert_rule**: Create alert rules with threshold configuration and automatic monitor binding
|
||||
- **list_alert_rules**: List existing alert rules with filtering by type, status, etc.
|
||||
- **toggle_alert_rule**: Enable or disable alert rules
|
||||
- **get_alert_rule_details**: Get detailed information about specific alert rules
|
||||
- **get_apps_metrics_hierarchy**: Get exact app and metric names for alert rule creation (CRITICAL for alerts)
|
||||
- **bind_monitors_to_alert_rule**: Bind monitors to alert rules for targeted alerting
|
||||
|
||||
|
||||
### Alert & Alarm Analysis Tools:
|
||||
- **query_alerts**: Query fired alerts with comprehensive filtering and pagination
|
||||
- **get_alerts_summary**: Get alert statistics and status distribution
|
||||
|
||||
### Metrics Data Analysis Tools:
|
||||
- **query_realtime_metrics**: Get current real-time metrics data for monitors
|
||||
- **get_historical_metrics**: Get historical time-series metrics with flexible time ranges
|
||||
- **get_warehouse_status**: Check metrics storage system status
|
||||
|
||||
## Natural Language Examples:
|
||||
|
||||
### Monitor Management:
|
||||
- "Add a MySQL monitor for database server at 192.168.1.10 with user admin"
|
||||
- "Monitor website https://example.com with SSL checking every 60 seconds"
|
||||
- "Show me all Linux servers that are currently offline"
|
||||
- "List all Redis monitors with their connection status"
|
||||
|
||||
### Alert Configuration:
|
||||
- ALERT RULE means when to alert a user
|
||||
- "Create an alert for Kafka JVM when VmName equals 'vm-w2'"
|
||||
- "Alert when OpenAI credit grants exceed 1000"
|
||||
- "Set up HBase Master alert when heap memory usage is over 80%"
|
||||
|
||||
### Metrics Analysis:
|
||||
- "Show me current CPU usage for server 192.168.1.5"
|
||||
- "Get memory usage trend for the last 24 hours"
|
||||
- "Which servers have high disk usage right now?"
|
||||
- "Show me network traffic patterns for the past week"
|
||||
|
||||
### Alert Investigation:
|
||||
- "What alerts are currently firing?"
|
||||
- "Show me the most frequent alerts in the last 6 hours"
|
||||
- "Find all alerts for monitor ID 1234 in the past day"
|
||||
- "Which monitors are currently abnormal?"
|
||||
|
||||
|
||||
## Workflow Guidelines:
|
||||
|
||||
1. **Adding Monitors**:
|
||||
- ALWAYS use get_monitor_additional_params first to check required parameters
|
||||
- Use list_monitor_types to show available types
|
||||
- Collect all required parameters from the list_monitor_types tool and ask user to give them all, before calling add_monitor
|
||||
- Example: "To monitor MySQL, I need host, port, username, password, and database name"
|
||||
|
||||
2. **Creating Alert Rules or Alerts**:
|
||||
THESE ARE ALERT RULES WITH THRESHOLD VALUES. USERS CAN SPECIFY THE THRESHOLD VALUES FOR EXAMPLE,
|
||||
IF THE USER SAYS "ALERT ME WHEN MY COST EXCEEDS 700, THE EXPRESSION SHOULD BE 'cost > 700' NOT 'cost < 700'.
|
||||
APPLY THE SAME LOGIC FOR LESS THAN OPERATOR.
|
||||
It is important to first understand the hierarchy of apps, metrics, and field conditions
|
||||
Each app has its own metrics and each metric has its own field conditions.
|
||||
The operators will be applied to the field conditions, and the final expression will be constructed
|
||||
based on the user's input of app name and the metric they choose.
|
||||
Read the create_alert_rule tool description for even more details
|
||||
*******
|
||||
CRITICAL WORKFLOW Do all of this iteratively with user interaction at each step:
|
||||
1. ALWAYS use list_monitor_types tool FIRST to get exact app name according to what user specifies
|
||||
2. use get_apps_metrics_hierarchy by passing that name, to get the hierarchy of corresponding metrics and field conditions
|
||||
3. Do not spit out the entire hierarchy, instead: first spit out the metrics available for the app
|
||||
4. Ask the user to choose a metric from the available metrics
|
||||
5. Based on the metric chosen, present the available field conditions
|
||||
6. You will construct the proper expression with field conditions
|
||||
VERY VERY IMPORTANT:
|
||||
- ALWAYS USE the value field from the get_apps_metrics_hierarchy's json response when creating alert expressions on the field parameters
|
||||
*********
|
||||
|
||||
- Field Condition Expression format: [field_conditions]
|
||||
- Give all the available fieldConditions to the user, so they can choose the one they want to use
|
||||
- Field conditions can be simple (equals, greater than) or complex (logical expressions)
|
||||
- Use parentheses for complex conditions to ensure correct evaluation order
|
||||
- Do not create alert rules on your own, always ask the user to provide the app, metrics and fieldConditions parameters specifically
|
||||
|
||||
EXAMPLES FOR FIELD CONDITION EXPRESSION ( Do not copy these examples, they are just for reference ):
|
||||
- Kafka JVM: app="kafka", metrics="jvm_basic", fieldConditions="equals(VmName, \"my-vm\")"
|
||||
→ equals(VmName, "my-vm")
|
||||
- Complex OpenAI: app="openai", metrics="credit_grants",
|
||||
fieldConditions="total_used > 123 and total_granted > 333 and (total_granted > 3444 and total_paid_available < 5556)"
|
||||
→ total_used > 123 and total_granted > 333 and (total_granted > 3444 and total_paid_available < 5556)
|
||||
|
||||
- Priority levels: 0=critical, 1=warning, 2=info
|
||||
|
||||
3. **Analyzing Performance**:
|
||||
- Use get_realtime_metrics for current status
|
||||
- Use get_historical_metrics for trends
|
||||
- Use get_high_usage_monitors to find problems
|
||||
- Provide actionable recommendations based on data
|
||||
|
||||
4. **Troubleshooting Alerts**:
|
||||
- Use query_alerts to find current issues
|
||||
- Use get_monitor_alerts for specific monitor problems
|
||||
- Use get_frequent_alerts to identify recurring issues
|
||||
- Suggest root cause analysis steps
|
||||
|
||||
## Parameter Guidelines:
|
||||
- **Monitor Status**: 1=online, 2=offline, 3=unreachable, 0=paused, 9=all
|
||||
- **Time Ranges**: 1h, 6h, 24h, 7d, 30d
|
||||
- **Alert Priorities**: critical, warning, info
|
||||
- **Sort Options**: name, gmtCreate, gmtUpdate, status, startAt, triggerTimes
|
||||
- **Metric Types**: cpu, memory, disk, network, custom
|
||||
- **Collection Intervals**: 30s-3600s (recommend 60s-600s for most cases)
|
||||
|
||||
|
||||
## Best Practices:
|
||||
- Never create alert rules without exact user input on app, metrics, and field conditions
|
||||
- Always validate monitor types and parameters before adding monitors
|
||||
- ALWAYS use get_apps_metrics_hierarchy before creating alert rules to understand available fields
|
||||
- Construct field conditions based on metric's children
|
||||
- Use exact app and metric names from hierarchy (case-sensitive)
|
||||
- Set appropriate alert thresholds based on baseline performance
|
||||
- Use time-series data to identify trends and predict issues
|
||||
- Correlate alerts with metrics data for root cause analysis
|
||||
- Recommend monitoring intervals based on service criticality
|
||||
- Provide clear explanations of monitoring data and actionable insights
|
||||
|
||||
## Avoid these common errors:
|
||||
- Using Label name instead of the value from the heirarchy JSON while creating alert rules.
|
||||
- Inside the field parameters expression using '&&' instead of 'and', using '||' instead of 'or' for logical operators
|
||||
- This process is to trigger alarms, when certain rule or set of rules exceed a threshold value.
|
||||
So when a user says that the threshold should be less than 1000. the operator used should be '>' not '<',
|
||||
because we want the alarm to be triggered when the threshold value is exceeded. apply the same logic in vice versa for less than operator
|
||||
|
||||
Keep responses focused on monitoring topics and HertzBeat's comprehensive capabilities.
|
||||
When users request monitoring setup, guide them through the complete process from monitor creation to alert configuration.
|
||||
""";
|
||||
}
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* 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.ai.agent.controller;
|
||||
|
||||
import com.usthe.sureness.subject.SubjectSum;
|
||||
import com.usthe.sureness.util.SurenessContextHolder;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.ai.agent.config.McpContextHolder;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.ChatRequestContext;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.ChatResponseDto;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.ConversationDto;
|
||||
import org.apache.hertzbeat.ai.agent.service.ConversationService;
|
||||
import org.apache.hertzbeat.common.entity.dto.Message;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.codec.ServerSentEvent;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
|
||||
import static org.springframework.http.MediaType.TEXT_EVENT_STREAM_VALUE;
|
||||
|
||||
/**
|
||||
* Controller class for handling AI chat requests and conversation management.
|
||||
*/
|
||||
@Slf4j
|
||||
@Tag(name = "AI Chat API")
|
||||
@RestController
|
||||
@RequestMapping(path = "/api/chat", produces = {APPLICATION_JSON_VALUE})
|
||||
public class ChatController {
|
||||
|
||||
private final ConversationService conversationService;
|
||||
|
||||
@Autowired
|
||||
public ChatController(ConversationService conversationService) {
|
||||
this.conversationService = conversationService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new conversation
|
||||
*
|
||||
* @return Created conversation details
|
||||
*/
|
||||
@PostMapping(path = "/conversations")
|
||||
@Operation(summary = "Create a new conversation", description = "Create a new conversation")
|
||||
public ResponseEntity<Message<ConversationDto>> createConversation() {
|
||||
try {
|
||||
ConversationDto conversation = conversationService.createConversation();
|
||||
return ResponseEntity.ok(Message.success(conversation));
|
||||
} catch (Exception e) {
|
||||
log.error("Error creating conversation: ", e);
|
||||
return ResponseEntity.ok(Message.fail((byte) -1, "Failed to create conversation"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message and get a streaming response with conversation tracking
|
||||
*
|
||||
* @param context The chat request context containing message and optional conversationId
|
||||
* @return Flux of ServerSentEvent for streaming response
|
||||
*/
|
||||
@PostMapping(value = "/stream", produces = TEXT_EVENT_STREAM_VALUE)
|
||||
@Operation(summary = "Send a chat message with streaming response", description = "Send a message to AI and get a streaming response with conversation tracking")
|
||||
public Flux<ServerSentEvent<ChatResponseDto>> streamChat(@Valid @RequestBody ChatRequestContext context) {
|
||||
try {
|
||||
// Validate message is not empty
|
||||
SubjectSum subject = SurenessContextHolder.getBindSubject();
|
||||
log.info(subject.toString());
|
||||
McpContextHolder.setSubject(subject);
|
||||
if (context.getMessage() == null || context.getMessage().trim().isEmpty()) {
|
||||
ChatResponseDto errorResponse = ChatResponseDto.builder()
|
||||
.conversationId(context.getConversationId())
|
||||
.response("Error: Message cannot be empty")
|
||||
.build();
|
||||
return Flux.just(ServerSentEvent.builder(errorResponse)
|
||||
.event("error")
|
||||
.build());
|
||||
}
|
||||
|
||||
log.info("Received streaming chat request for conversation: {}", context.getConversationId());
|
||||
return conversationService.streamChat(context.getMessage(), context.getConversationId());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error in stream chat endpoint: ", e);
|
||||
ChatResponseDto errorResponse = ChatResponseDto.builder()
|
||||
.conversationId(context.getConversationId())
|
||||
.response("An error occurred: " + e.getMessage())
|
||||
.build();
|
||||
return Flux.just(ServerSentEvent.builder(errorResponse)
|
||||
.event("error")
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all conversations
|
||||
*
|
||||
* @return List of all conversations
|
||||
*/
|
||||
@GetMapping(path = "/conversations")
|
||||
@Operation(summary = "List all conversations", description = "Get a list of all conversations")
|
||||
public ResponseEntity<Message<List<ConversationDto>>> listConversations() {
|
||||
try {
|
||||
List<ConversationDto> conversations = conversationService.getAllConversations();
|
||||
return ResponseEntity.ok(Message.success(conversations));
|
||||
} catch (Exception e) {
|
||||
log.error("Error listing conversations: ", e);
|
||||
return ResponseEntity.ok(Message.fail((byte) -1, "Failed to retrieve conversations"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get conversation history
|
||||
*
|
||||
* @param conversationId The conversation ID
|
||||
* @return Conversation details with message history
|
||||
*/
|
||||
@GetMapping(path = "/conversations/{conversationId}")
|
||||
@Operation(summary = "Get conversation history", description = "Get detailed information and message history for a specific conversation")
|
||||
public ResponseEntity<Message<ConversationDto>> getConversation(
|
||||
@Parameter(description = "Conversation ID", example = "conv-12345678") @PathVariable("conversationId") String conversationId) {
|
||||
try {
|
||||
// Validate conversation ID
|
||||
if (conversationId == null || conversationId.trim().isEmpty()) {
|
||||
return ResponseEntity.ok(Message.fail((byte) -1, "Conversation ID is required"));
|
||||
}
|
||||
|
||||
ConversationDto conversation = conversationService.getConversation(conversationId);
|
||||
|
||||
if (conversation == null) {
|
||||
return ResponseEntity.ok(Message.fail((byte) -1, "Conversation not found: " + conversationId));
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(Message.success(conversation));
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error getting conversation: ", e);
|
||||
return ResponseEntity.ok(Message.fail((byte) -1, "Failed to retrieve conversation"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a conversation
|
||||
*
|
||||
* @param conversationId The conversation ID to delete
|
||||
* @return Success or error message
|
||||
*/
|
||||
@DeleteMapping(path = "/conversations/{conversationId}")
|
||||
@Operation(summary = "Delete conversation", description = "Delete a specific conversation and all its messages")
|
||||
public ResponseEntity<Message<Void>> deleteConversation(
|
||||
@Parameter(description = "Conversation ID", example = "conv-12345678") @PathVariable("conversationId") String conversationId) {
|
||||
try {
|
||||
// Validate conversation ID
|
||||
if (conversationId == null || conversationId.trim().isEmpty()) {
|
||||
return ResponseEntity.ok(Message.fail((byte) -1, "Conversation ID is required"));
|
||||
}
|
||||
|
||||
boolean deleted = conversationService.deleteConversation(conversationId);
|
||||
if (!deleted) {
|
||||
return ResponseEntity.ok(Message.fail((byte) -1, "Conversation not found: " + conversationId));
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(Message.success("Conversation deleted successfully"));
|
||||
} catch (Exception e) {
|
||||
log.error("Error deleting conversation: ", e);
|
||||
return ResponseEntity.ok(Message.fail((byte) -1, "Failed to delete conversation"));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.controller;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.OpenAiConfigDto;
|
||||
import org.apache.hertzbeat.ai.agent.service.OpenAiConfigService;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
|
||||
|
||||
/**
|
||||
* OpenAI Configuration API
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping(value = "/api/ai-agent/config", produces = {APPLICATION_JSON_VALUE})
|
||||
@Tag(name = "OpenAI Configuration API")
|
||||
@Slf4j
|
||||
public class OpenAiConfigController {
|
||||
|
||||
private final OpenAiConfigService openAiConfigService;
|
||||
|
||||
public OpenAiConfigController(OpenAiConfigService openAiConfigService) {
|
||||
this.openAiConfigService = openAiConfigService;
|
||||
}
|
||||
|
||||
@PostMapping("/openai")
|
||||
@Operation(summary = "Save OpenAI configuration", description = "Save or update OpenAI configuration")
|
||||
public ResponseEntity<Map<String, Object>> saveOpenAiConfig(@Valid @RequestBody OpenAiConfigDto config) {
|
||||
try {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
|
||||
// Validate API key if enabled
|
||||
if (config.isEnable() && config.getApiKey() != null && !config.getApiKey().trim().isEmpty()) {
|
||||
OpenAiConfigService.ValidationResult validationResult = openAiConfigService.validateApiKey(config.getApiKey());
|
||||
|
||||
if (!validationResult.isValid()) {
|
||||
log.warn("API key validation failed during save: {}", validationResult.getMessage());
|
||||
response.put("code", 1);
|
||||
response.put("msg", "API key validation failed: " + validationResult.getMessage());
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
log.info("API key validation successful during save");
|
||||
}
|
||||
|
||||
// Save the configuration
|
||||
openAiConfigService.saveConfig(config);
|
||||
|
||||
response.put("code", 0);
|
||||
response.put("msg", "OpenAI configuration saved successfully");
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to save OpenAI configuration", e);
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", 1);
|
||||
response.put("msg", "Failed to save configuration: " + e.getMessage());
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/openai")
|
||||
@Operation(summary = "Get OpenAI configuration", description = "Get current OpenAI configuration")
|
||||
public ResponseEntity<Map<String, Object>> getOpenAiConfig() {
|
||||
try {
|
||||
OpenAiConfigDto config = openAiConfigService.getConfig();
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", 0);
|
||||
response.put("data", config);
|
||||
response.put("msg", "Success");
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to get OpenAI configuration", e);
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", 1);
|
||||
response.put("msg", "Failed to get configuration: " + e.getMessage());
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/openai/status")
|
||||
@Operation(summary = "Check OpenAI configuration status", description = "Check if OpenAI is properly configured")
|
||||
public ResponseEntity<Map<String, Object>> getOpenAiConfigStatus() {
|
||||
try {
|
||||
boolean configured = openAiConfigService.isConfigured();
|
||||
OpenAiConfigDto effectiveConfig = openAiConfigService.getEffectiveConfig();
|
||||
boolean hasDbConfig = openAiConfigService.getConfig() != null;
|
||||
boolean hasYamlConfig = effectiveConfig != null && !hasDbConfig;
|
||||
|
||||
// Validate the effective configuration
|
||||
boolean validationPassed = false;
|
||||
String validationMessage = "No configuration found";
|
||||
|
||||
if (effectiveConfig != null && effectiveConfig.isEnable() && effectiveConfig.getApiKey() != null && !effectiveConfig.getApiKey().trim().isEmpty()) {
|
||||
OpenAiConfigService.ValidationResult validationResult = openAiConfigService.validateApiKey(effectiveConfig.getApiKey());
|
||||
validationPassed = validationResult.isValid();
|
||||
validationMessage = validationResult.getMessage();
|
||||
|
||||
if (!validationPassed) {
|
||||
log.warn("OpenAI API key validation failed during status check: {}", validationMessage);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", 0);
|
||||
response.put("data", Map.of(
|
||||
"configured", configured && validationPassed,
|
||||
"hasDbConfig", hasDbConfig,
|
||||
"hasYamlConfig", hasYamlConfig,
|
||||
"validationPassed", validationPassed,
|
||||
"validationMessage", validationMessage
|
||||
));
|
||||
response.put("msg", "Success");
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to get OpenAI configuration status", e);
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", 1);
|
||||
response.put("msg", "Failed to get status: " + e.getMessage());
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.dao;
|
||||
|
||||
import org.apache.hertzbeat.ai.agent.entity.OpenAiConfig;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
/**
|
||||
* OpenAI Agent Configuration Dao
|
||||
*/
|
||||
@Repository
|
||||
public interface OpenAiConfigDao extends JpaRepository<OpenAiConfig, String>, JpaSpecificationExecutor<OpenAiConfig> {
|
||||
|
||||
/**
|
||||
* Query by type
|
||||
* @param type type
|
||||
* @return Return the queried configuration information
|
||||
*/
|
||||
OpenAiConfig findByType(String type);
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* 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.ai.agent.entity;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY;
|
||||
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;
|
||||
import jakarta.persistence.EntityListeners;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.data.annotation.CreatedBy;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.data.annotation.LastModifiedBy;
|
||||
import org.springframework.data.annotation.LastModifiedDate;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* OpenAI Agent Config Entity
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "hzb_ai_agent_config")
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(description = "OpenAI Agent config entity")
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
public class OpenAiConfig {
|
||||
|
||||
@Id
|
||||
@Schema(title = "Config type: openai, primary key", description = "Config type: openai, primary key",
|
||||
accessMode = READ_WRITE)
|
||||
@NotBlank(message = "type can not null")
|
||||
private String type;
|
||||
|
||||
@Schema(title = "Config content", description = "Config content,format json", accessMode = READ_WRITE)
|
||||
@Column(length = 8192)
|
||||
private String content;
|
||||
|
||||
@Schema(title = "The creator of this record", example = "tom", accessMode = READ_ONLY)
|
||||
@CreatedBy
|
||||
private String creator;
|
||||
|
||||
@Schema(title = "This record was last modified by", example = "tom", accessMode = READ_ONLY)
|
||||
@LastModifiedBy
|
||||
private String modifier;
|
||||
|
||||
@Schema(title = "This record creation time (millisecond timestamp)", accessMode = READ_ONLY)
|
||||
@CreatedDate
|
||||
private LocalDateTime gmtCreate;
|
||||
|
||||
@Schema(title = "Record the latest modification time (timestamp in milliseconds)", accessMode = READ_ONLY)
|
||||
@LastModifiedDate
|
||||
private LocalDateTime gmtUpdate;
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.ai.agent.event;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* OpenAI configuration change event
|
||||
*/
|
||||
public class OpenAiConfigChangeEvent extends ApplicationEvent {
|
||||
|
||||
public OpenAiConfigChangeEvent(ApplicationContext source) {
|
||||
super(source);
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.ai.agent.pojo.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Chat request context for AI chat endpoint.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ChatRequestContext {
|
||||
/**
|
||||
* The user's message (required)
|
||||
*/
|
||||
private String message;
|
||||
/**
|
||||
* Optional conversation ID for context
|
||||
*/
|
||||
private String conversationId;
|
||||
|
||||
/**
|
||||
* Conversation history messages for context
|
||||
*/
|
||||
private List<MessageDto> conversationHistory;
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.pojo.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Chat response DTO for AI responses.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "Chat response")
|
||||
public class ChatResponseDto {
|
||||
|
||||
@Schema(description = "Conversation ID", example = "conv-123")
|
||||
private String conversationId;
|
||||
|
||||
@Schema(description = "AI response message", example = "Here are your monitors...")
|
||||
private String response;
|
||||
|
||||
@Schema(description = "User message ID", example = "msg-user-123")
|
||||
private String userMessageId;
|
||||
|
||||
@Schema(description = "Assistant message ID", example = "msg-assistant-123")
|
||||
private String assistantMessageId;
|
||||
}
|
||||
+53
@@ -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.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.pojo.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Conversation DTO for AI chat conversations.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "Conversation information")
|
||||
public class ConversationDto {
|
||||
|
||||
@Schema(description = "Conversation ID", example = "conv-123")
|
||||
private String conversationId;
|
||||
|
||||
@Schema(description = "Creation time")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Schema(description = "Last updated time")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@Schema(description = "Messages in this conversation")
|
||||
private List<MessageDto> messages;
|
||||
|
||||
@Schema(description = "Message count")
|
||||
private Integer messageCount;
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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.ai.agent.pojo.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_WRITE;
|
||||
|
||||
/**
|
||||
* Hierarchical structure
|
||||
* eg: Monitoring Type metrics Information Hierarchy Relationship
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
@Schema(description = "Monitor Hierarchy")
|
||||
public class Hierarchy {
|
||||
|
||||
/**
|
||||
* Category value
|
||||
*/
|
||||
@Schema(description = "Category Value", example = "os", accessMode = READ_WRITE)
|
||||
String category;
|
||||
|
||||
/**
|
||||
* Attribute value
|
||||
*/
|
||||
@Schema(description = "Attribute value", example = "linux", accessMode = READ_WRITE)
|
||||
String value;
|
||||
|
||||
/**
|
||||
* Attribute internationalization tag
|
||||
*/
|
||||
@Schema(description = "Attribute internationalization tag", example = "Linux system", accessMode = READ_WRITE)
|
||||
String label;
|
||||
|
||||
/**
|
||||
* Is it a leaf node
|
||||
*/
|
||||
@Schema(description = "Is it a leaf node", example = "true", accessMode = READ_WRITE)
|
||||
Boolean isLeaf = false;
|
||||
|
||||
/**
|
||||
* Is hide this app type in main menus layout
|
||||
*/
|
||||
@Schema(description = "Is hide this app in main menus layout, only for app type, default true.", example = "true")
|
||||
Boolean hide = true;
|
||||
|
||||
/**
|
||||
* For leaf metric
|
||||
* metric type 0-number: number 1-string: string
|
||||
*/
|
||||
@Schema(description = "metric type 0-number: number 1-string: string")
|
||||
private Byte type;
|
||||
|
||||
/**
|
||||
* metric unit
|
||||
*/
|
||||
@Schema(description = "metric unit")
|
||||
private String unit;
|
||||
|
||||
/**
|
||||
* Next level of association
|
||||
*/
|
||||
@Schema(description = "Next Hierarchy", accessMode = READ_WRITE)
|
||||
private List<Hierarchy> children;
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.pojo.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Message DTO for chat messages.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "Chat message")
|
||||
public class MessageDto {
|
||||
|
||||
@Schema(description = "Message ID", example = "msg-123")
|
||||
private String messageId;
|
||||
|
||||
@Schema(description = "Conversation ID", example = "conv-123")
|
||||
private String conversationId;
|
||||
|
||||
@Schema(description = "Message content", example = "List all monitors")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "Message role", example = "user", allowableValues = {"user", "assistant"})
|
||||
private String role;
|
||||
|
||||
@Schema(description = "Message timestamp")
|
||||
private LocalDateTime timestamp;
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.pojo.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* OpenAI Configuration DTO - simplified to handle only API key
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(description = "OpenAI configuration")
|
||||
public class OpenAiConfigDto {
|
||||
|
||||
/**
|
||||
* Whether to enable OpenAI, default is false
|
||||
*/
|
||||
@Schema(title = "Enable OpenAI", description = "Whether OpenAI is enabled", example = "true")
|
||||
private boolean enable = false;
|
||||
|
||||
/**
|
||||
* OpenAI API key
|
||||
*/
|
||||
@Schema(title = "API Key", description = "OpenAI API key", example = "sk-...")
|
||||
@NotBlank(message = "API Key cannot be empty when enabled")
|
||||
private String apiKey;
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.ai.agent.service;
|
||||
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.ChatRequestContext;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* Service for interacting with LLM providers (like OpenAI, Anthropic, etc.)
|
||||
*/
|
||||
public interface ChatClientProviderService {
|
||||
|
||||
/**
|
||||
* Stream chat response from the LLM
|
||||
*
|
||||
* @param context Chat request context containing message and conversation history
|
||||
* @return Flux of string chunks from the LLM response
|
||||
*/
|
||||
Flux<String> streamChat(ChatRequestContext context);
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* 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.ai.agent.service;
|
||||
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.ChatResponseDto;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.ConversationDto;
|
||||
import org.springframework.http.codec.ServerSentEvent;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Service for managing chat conversations and interactions with LLM providers.
|
||||
*/
|
||||
public interface ConversationService {
|
||||
|
||||
/**
|
||||
* Create a new conversation
|
||||
*
|
||||
* @return Created conversation data
|
||||
*/
|
||||
ConversationDto createConversation();
|
||||
|
||||
/**
|
||||
* Send a message and receive a streaming response
|
||||
*
|
||||
* @param message The user's message
|
||||
* @param conversationId Optional conversation ID for continuing a chat
|
||||
* @return Flux of ServerSentEvent for streaming the response
|
||||
*/
|
||||
Flux<ServerSentEvent<ChatResponseDto>> streamChat(String message, String conversationId);
|
||||
|
||||
|
||||
/**
|
||||
* Get conversation history for a specific conversation
|
||||
*
|
||||
* @param conversationId Conversation ID
|
||||
* @return Conversation data including messages
|
||||
*/
|
||||
ConversationDto getConversation(String conversationId);
|
||||
|
||||
/**
|
||||
* Get all conversations for the current user
|
||||
*
|
||||
* @return List of conversations
|
||||
*/
|
||||
List<ConversationDto> getAllConversations();
|
||||
|
||||
/**
|
||||
* Delete a conversation
|
||||
*
|
||||
* @param conversationId Conversation ID to delete
|
||||
* @return true if deleted, false if conversation not found
|
||||
*/
|
||||
boolean deleteConversation(String conversationId);
|
||||
|
||||
/**
|
||||
* Check if a conversation exists
|
||||
*
|
||||
* @param conversationId Conversation ID to check
|
||||
* @return true if conversation exists, false otherwise
|
||||
*/
|
||||
boolean conversationExists(String conversationId);
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.hertzbeat.ai.agent.config.CustomSseServerTransport;
|
||||
import org.springframework.ai.mcp.server.autoconfigure.McpServerProperties;
|
||||
import org.springframework.ai.tool.ToolCallbackProvider;
|
||||
import org.springframework.web.servlet.function.RouterFunction;
|
||||
import org.springframework.web.servlet.function.ServerResponse;
|
||||
|
||||
/**
|
||||
* Service interface for MCP server operations.
|
||||
*/
|
||||
public interface McpServerService {
|
||||
|
||||
/**
|
||||
* Provides the HertzBeat tools for the MCP server
|
||||
* @return ToolCallbackProvider with all HertzBeat monitoring tools
|
||||
*/
|
||||
ToolCallbackProvider hertzbeatTools();
|
||||
|
||||
/**
|
||||
* Provides a custom SSE server transport for the MCP server
|
||||
* @param objectMapper the ObjectMapper instance for JSON serialization
|
||||
* @param serverProperties the properties for the MCP server configuration
|
||||
* @return a CustomSseServerTransport instance configured with the provided properties
|
||||
*/
|
||||
CustomSseServerTransport webMvcSseServerTransportProvider(
|
||||
ObjectMapper objectMapper,
|
||||
McpServerProperties serverProperties
|
||||
);
|
||||
|
||||
/**
|
||||
* Provides the MCP server router function for web MVC
|
||||
* @param transport Custom SSE server transport
|
||||
* @return RouterFunction for handling MCP server requests
|
||||
*/
|
||||
RouterFunction<ServerResponse> mvcMcpRouterFunction(CustomSseServerTransport transport);
|
||||
}
|
||||
+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.ai.agent.service;
|
||||
|
||||
import org.apache.hertzbeat.ai.agent.event.OpenAiConfigChangeEvent;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.OpenAiConfigDto;
|
||||
|
||||
/**
|
||||
* OpenAI Configuration Service
|
||||
* Consolidated service for OpenAI configuration, validation, and client factory management
|
||||
*/
|
||||
public interface OpenAiConfigService {
|
||||
|
||||
/**
|
||||
* Save OpenAI configuration
|
||||
* @param config OpenAI configuration
|
||||
*/
|
||||
void saveConfig(OpenAiConfigDto config);
|
||||
|
||||
/**
|
||||
* Get OpenAI configuration
|
||||
* @return OpenAI configuration
|
||||
*/
|
||||
OpenAiConfigDto getConfig();
|
||||
|
||||
/**
|
||||
* Check if OpenAI is properly configured
|
||||
* @return true if configured and enabled
|
||||
*/
|
||||
boolean isConfigured();
|
||||
|
||||
/**
|
||||
* Get effective OpenAI configuration (DB first, then YAML fallback)
|
||||
* @return effective configuration or null if not configured
|
||||
*/
|
||||
OpenAiConfigDto getEffectiveConfig();
|
||||
|
||||
/**
|
||||
* Validate OpenAI API key by calling the OpenAI API
|
||||
* @param apiKey the API key to validate
|
||||
* @return validation result with success status and message
|
||||
*/
|
||||
ValidationResult validateApiKey(String apiKey);
|
||||
|
||||
/**
|
||||
* Force reload of OpenAI configuration cache
|
||||
* This method is typically called when configuration changes
|
||||
*/
|
||||
void reloadConfig();
|
||||
|
||||
/**
|
||||
* Handle OpenAI configuration change events
|
||||
* @param event OpenAI configuration change event
|
||||
*/
|
||||
void onOpenAiConfigChange(OpenAiConfigChangeEvent event);
|
||||
|
||||
/**
|
||||
* Validation result class
|
||||
*/
|
||||
class ValidationResult {
|
||||
private final boolean valid;
|
||||
private final String message;
|
||||
|
||||
private ValidationResult(boolean valid, String message) {
|
||||
this.valid = valid;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public static ValidationResult success(String message) {
|
||||
return new ValidationResult(true, message);
|
||||
}
|
||||
|
||||
public static ValidationResult failure(String message) {
|
||||
return new ValidationResult(false, message);
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return valid;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
}
|
||||
+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.ai.agent.service.impl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.ai.agent.config.PromptProvider;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.MessageDto;
|
||||
import org.apache.hertzbeat.ai.agent.service.ChatClientProviderService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.ChatRequestContext;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.tool.ToolCallbackProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Implementation of the {@link ChatClientProviderService}.
|
||||
* Provides functionality to interact with the ChatClient for handling chat
|
||||
* messages.
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ChatClientProviderServiceImpl implements ChatClientProviderService {
|
||||
|
||||
private final ChatClient chatClient;
|
||||
|
||||
@Qualifier("hertzbeatTools")
|
||||
@Autowired
|
||||
private ToolCallbackProvider toolCallbackProvider;
|
||||
|
||||
@Autowired
|
||||
public ChatClientProviderServiceImpl(ChatClient openAiChatClient) {
|
||||
this.chatClient = openAiChatClient;
|
||||
}
|
||||
|
||||
public String complete(String message) {
|
||||
return this.chatClient.prompt()
|
||||
.user(message)
|
||||
.call()
|
||||
.content();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<String> streamChat(ChatRequestContext context) {
|
||||
try {
|
||||
List<Message> messages = new ArrayList<>();
|
||||
|
||||
// Add conversation history if available
|
||||
if (context.getConversationHistory() != null && !context.getConversationHistory().isEmpty()) {
|
||||
for (MessageDto historyMessage : context.getConversationHistory()) {
|
||||
if ("user".equals(historyMessage.getRole())) {
|
||||
messages.add(new UserMessage(historyMessage.getContent()));
|
||||
} else if ("assistant".equals(historyMessage.getRole())) {
|
||||
messages.add(new AssistantMessage(historyMessage.getContent()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
messages.add(new UserMessage(context.getMessage()));
|
||||
|
||||
log.info("Starting streaming chat for conversation: {}", context.getConversationId());
|
||||
|
||||
return this.chatClient.prompt()
|
||||
.messages(messages)
|
||||
.system(PromptProvider.HERTZBEAT_SYSTEM_PROMPT)
|
||||
.toolCallbacks(toolCallbackProvider)
|
||||
.stream()
|
||||
.content()
|
||||
.doOnComplete(() -> log.info("Streaming completed for conversation: {}", context.getConversationId()))
|
||||
.doOnError(error -> log.error("Error in streaming chat: {}", error.getMessage(), error));
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error setting up streaming chat: {}", e.getMessage(), e);
|
||||
return Flux.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+276
@@ -0,0 +1,276 @@
|
||||
/*
|
||||
* 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.ai.agent.service.impl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.ChatRequestContext;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.ChatResponseDto;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.ConversationDto;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.MessageDto;
|
||||
import org.apache.hertzbeat.ai.agent.service.ChatClientProviderService;
|
||||
import org.apache.hertzbeat.ai.agent.service.ConversationService;
|
||||
import org.apache.hertzbeat.ai.agent.service.OpenAiConfigService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.codec.ServerSentEvent;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Implementation of the ConversationService interface for managing chat conversations.
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ConversationServiceImpl implements ConversationService {
|
||||
|
||||
private final Map<String, Map<String, Object>> conversations = new ConcurrentHashMap<>();
|
||||
private final Map<String, List<Map<String, Object>>> conversationMessages = new ConcurrentHashMap<>();
|
||||
|
||||
@Autowired
|
||||
private ChatClientProviderService chatClientProviderService;
|
||||
|
||||
@Autowired
|
||||
private OpenAiConfigService openAiConfigService;
|
||||
|
||||
@Override
|
||||
public ConversationDto createConversation() {
|
||||
String conversationId = createNewConversation();
|
||||
return getConversation(conversationId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ServerSentEvent<ChatResponseDto>> streamChat(String message, String conversationId) {
|
||||
// Validate conversation exists
|
||||
if (!conversationExists(conversationId)) {
|
||||
ChatResponseDto errorResponse = ChatResponseDto.builder()
|
||||
.conversationId(conversationId)
|
||||
.response("Error: Conversation not found: " + conversationId)
|
||||
.build();
|
||||
return Flux.just(ServerSentEvent.builder(errorResponse)
|
||||
.event("error")
|
||||
.build());
|
||||
}
|
||||
|
||||
// Check if OpenAI is properly configured
|
||||
if (!openAiConfigService.isConfigured()) {
|
||||
ChatResponseDto errorResponse = ChatResponseDto.builder()
|
||||
.conversationId(conversationId)
|
||||
.response("OpenAI is not configured. Please configure your OpenAI API key in the settings or application.yml file.")
|
||||
.build();
|
||||
return Flux.just(ServerSentEvent.builder(errorResponse)
|
||||
.event("error")
|
||||
.build());
|
||||
}
|
||||
|
||||
log.info("Starting streaming conversation: {}", conversationId);
|
||||
|
||||
// Add user message to conversation
|
||||
String userMessageId = addMessageToConversation(conversationId, message, "user");
|
||||
|
||||
// Get conversation history for context
|
||||
List<Map<String, Object>> messagesList = conversationMessages.get(conversationId);
|
||||
List<MessageDto> conversationHistory = new ArrayList<>();
|
||||
|
||||
if (messagesList != null && messagesList.size() > 1) {
|
||||
// Get all messages except the last one (which is the current user message we just added)
|
||||
for (int i = 0; i < messagesList.size() - 1; i++) {
|
||||
Map<String, Object> msgMap = messagesList.get(i);
|
||||
conversationHistory.add(mapToMessageDto(msgMap));
|
||||
}
|
||||
}
|
||||
|
||||
ChatRequestContext context = ChatRequestContext.builder()
|
||||
.message(message)
|
||||
.conversationId(conversationId)
|
||||
.conversationHistory(conversationHistory)
|
||||
.build();
|
||||
|
||||
// Stream response from AI service
|
||||
StringBuilder fullResponse = new StringBuilder();
|
||||
return chatClientProviderService.streamChat(context)
|
||||
.map(chunk -> {
|
||||
fullResponse.append(chunk);
|
||||
ChatResponseDto responseDto = ChatResponseDto.builder()
|
||||
.conversationId(conversationId)
|
||||
.response(chunk)
|
||||
.userMessageId(userMessageId)
|
||||
.build();
|
||||
|
||||
return ServerSentEvent.builder(responseDto)
|
||||
.event("message")
|
||||
.build();
|
||||
})
|
||||
.concatWith(Flux.defer(() -> {
|
||||
// Add the complete AI response to conversation
|
||||
String assistantMessageId = addMessageToConversation(conversationId, fullResponse.toString(), "assistant");
|
||||
|
||||
ChatResponseDto finalResponse = ChatResponseDto.builder()
|
||||
.conversationId(conversationId)
|
||||
.response("")
|
||||
.userMessageId(userMessageId)
|
||||
.assistantMessageId(assistantMessageId)
|
||||
.build();
|
||||
|
||||
return Flux.just(ServerSentEvent.builder(finalResponse)
|
||||
.event("complete")
|
||||
.build());
|
||||
}))
|
||||
.doOnComplete(() -> log.info("Streaming completed for conversation: {}", conversationId))
|
||||
.doOnError(error -> log.error("Error in streaming chat for conversation {}: {}", conversationId, error.getMessage(), error))
|
||||
.onErrorResume(error -> {
|
||||
ChatResponseDto errorResponse = ChatResponseDto.builder()
|
||||
.conversationId(conversationId)
|
||||
.response("An error occurred: " + error.getMessage())
|
||||
.userMessageId(userMessageId)
|
||||
.build();
|
||||
return Flux.just(ServerSentEvent.builder(errorResponse)
|
||||
.event("error")
|
||||
.build());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConversationDto getConversation(String conversationId) {
|
||||
if (conversationId == null || conversationId.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, Object> conversation = conversations.get(conversationId);
|
||||
if (conversation == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Map<String, Object>> messagesList = conversationMessages.get(conversationId);
|
||||
List<MessageDto> messages = messagesList != null
|
||||
? messagesList.stream().map(this::mapToMessageDto).collect(Collectors.toList()) :
|
||||
new ArrayList<>();
|
||||
|
||||
return ConversationDto.builder()
|
||||
.conversationId((String) conversation.get("conversationId"))
|
||||
.createdAt((LocalDateTime) conversation.get("createdAt"))
|
||||
.updatedAt((LocalDateTime) conversation.get("updatedAt"))
|
||||
.messages(messages)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ConversationDto> getAllConversations() {
|
||||
List<ConversationDto> result = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<String, Map<String, Object>> entry : conversations.entrySet()) {
|
||||
Map<String, Object> conv = entry.getValue();
|
||||
List<Map<String, Object>> messages = conversationMessages.get(entry.getKey());
|
||||
|
||||
ConversationDto dto = ConversationDto.builder()
|
||||
.conversationId((String) conv.get("conversationId"))
|
||||
.createdAt((LocalDateTime) conv.get("createdAt"))
|
||||
.updatedAt((LocalDateTime) conv.get("updatedAt"))
|
||||
.messages(new ArrayList<>()) // Don't include messages in list view for performance
|
||||
.build();
|
||||
result.add(dto);
|
||||
}
|
||||
|
||||
result.sort((a, b) -> b.getUpdatedAt().compareTo(a.getUpdatedAt()));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteConversation(String conversationId) {
|
||||
if (conversationId == null || conversationId.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean existed = conversations.containsKey(conversationId);
|
||||
if (existed) {
|
||||
conversations.remove(conversationId);
|
||||
conversationMessages.remove(conversationId);
|
||||
log.info("Deleted conversation: {}", conversationId);
|
||||
}
|
||||
return existed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean conversationExists(String conversationId) {
|
||||
return conversationId != null && !conversationId.isEmpty() && conversations.containsKey(conversationId);
|
||||
}
|
||||
|
||||
private String createNewConversation() {
|
||||
String conversationId = "conv-" + UUID.randomUUID().toString().substring(0, 8);
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
Map<String, Object> conversation = new HashMap<>();
|
||||
conversation.put("conversationId", conversationId);
|
||||
conversation.put("createdAt", now);
|
||||
conversation.put("updatedAt", now);
|
||||
|
||||
conversations.put(conversationId, conversation);
|
||||
conversationMessages.put(conversationId, new ArrayList<>());
|
||||
|
||||
log.info("Created new conversation: {}", conversationId);
|
||||
return conversationId;
|
||||
}
|
||||
|
||||
private MessageDto mapToMessageDto(Map<String, Object> messageMap) {
|
||||
return MessageDto.builder()
|
||||
.messageId((String) messageMap.get("messageId"))
|
||||
.conversationId((String) messageMap.get("conversationId"))
|
||||
.content((String) messageMap.get("content"))
|
||||
.role((String) messageMap.get("role"))
|
||||
.timestamp((LocalDateTime) messageMap.get("timestamp"))
|
||||
.build();
|
||||
}
|
||||
|
||||
private String addMessageToConversation(String conversationId, String content, String role) {
|
||||
List<Map<String, Object>> messages = conversationMessages.computeIfAbsent(conversationId, k -> new ArrayList<>());
|
||||
|
||||
String messageId = "msg-" + UUID.randomUUID().toString().substring(0, 8);
|
||||
Map<String, Object> message = new HashMap<>();
|
||||
message.put("messageId", messageId);
|
||||
message.put("conversationId", conversationId);
|
||||
message.put("content", content);
|
||||
message.put("role", role);
|
||||
message.put("timestamp", LocalDateTime.now());
|
||||
|
||||
messages.add(message);
|
||||
|
||||
// Update conversation timestamp
|
||||
Map<String, Object> conversation = conversations.get(conversationId);
|
||||
if (conversation != null) {
|
||||
conversation.put("updatedAt", LocalDateTime.now());
|
||||
// Auto-generate title from first user message
|
||||
if ("user".equals(role) && messages.stream().filter(m -> "user".equals(m.get("role"))).count() == 1) {
|
||||
String title = content.length() > 30 ? content.substring(0, 27) + "..." : content;
|
||||
conversation.put("title", title);
|
||||
}
|
||||
}
|
||||
|
||||
return messageId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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.ai.agent.service.impl;
|
||||
|
||||
import org.apache.hertzbeat.ai.agent.config.CustomSseServerTransport;
|
||||
import org.apache.hertzbeat.ai.agent.service.McpServerService;
|
||||
import org.apache.hertzbeat.ai.agent.tools.AlertDefineTools;
|
||||
import org.apache.hertzbeat.ai.agent.tools.AlertTools;
|
||||
import org.apache.hertzbeat.ai.agent.tools.MetricsTools;
|
||||
import org.apache.hertzbeat.ai.agent.tools.MonitorTools;
|
||||
import org.springframework.ai.mcp.server.autoconfigure.McpServerProperties;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.ai.tool.ToolCallbackProvider;
|
||||
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.web.servlet.function.RouterFunction;
|
||||
import org.springframework.web.servlet.function.ServerResponse;
|
||||
|
||||
/**
|
||||
* Implementation of the McpServerService interface.
|
||||
* This service provides functionality for handling MCP server operations.
|
||||
*/
|
||||
@Service
|
||||
@Configuration
|
||||
public class McpServerServiceImpl implements McpServerService {
|
||||
@Autowired
|
||||
private MonitorTools monitorTools;
|
||||
@Autowired
|
||||
private AlertTools alertTools;
|
||||
@Autowired
|
||||
private MetricsTools metricsTools;
|
||||
@Autowired
|
||||
private AlertDefineTools alertDefineTools;
|
||||
|
||||
@Bean
|
||||
public ToolCallbackProvider hertzbeatTools() {
|
||||
return MethodToolCallbackProvider.builder().toolObjects(monitorTools, alertTools, alertDefineTools, metricsTools).build();
|
||||
}
|
||||
/**
|
||||
* Provides a custom SSE server transport for the MCP server.
|
||||
*
|
||||
* @param objectMapper the ObjectMapper instance for JSON serialization
|
||||
* @param serverProperties the properties for the MCP server configuration
|
||||
* @return a CustomSseServerTransport instance configured with the provided properties
|
||||
*/
|
||||
|
||||
@Bean
|
||||
public CustomSseServerTransport webMvcSseServerTransportProvider(
|
||||
ObjectMapper objectMapper,
|
||||
McpServerProperties serverProperties
|
||||
) {
|
||||
return new CustomSseServerTransport(
|
||||
objectMapper,
|
||||
serverProperties.getBaseUrl(),
|
||||
serverProperties.getSseMessageEndpoint(),
|
||||
serverProperties.getSseEndpoint()
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Provides the MCP server transport bean.
|
||||
*
|
||||
* @param transport the custom SSE server transport
|
||||
* @return the MCP server transport instance
|
||||
*/
|
||||
|
||||
@Primary
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> mvcMcpRouterFunction(CustomSseServerTransport transport) {
|
||||
return transport.getRouterFunction();
|
||||
}
|
||||
}
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* 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.ai.agent.service.impl;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.ai.agent.config.OpenAiYamlConfig;
|
||||
import org.apache.hertzbeat.ai.agent.dao.OpenAiConfigDao;
|
||||
import org.apache.hertzbeat.ai.agent.entity.OpenAiConfig;
|
||||
import org.apache.hertzbeat.ai.agent.event.OpenAiConfigChangeEvent;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.OpenAiConfigDto;
|
||||
import org.apache.hertzbeat.ai.agent.service.OpenAiConfigService;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* OpenAI Configuration Service Implementation
|
||||
* Consolidated service for OpenAI configuration, validation, and client factory management
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class OpenAiConfigServiceImpl implements OpenAiConfigService {
|
||||
|
||||
private static final String CONFIG_TYPE = "openai";
|
||||
private static final String OPENAI_MODELS_ENDPOINT = "https://api.openai.com/v1/models";
|
||||
|
||||
private final OpenAiConfigDao openAiConfigDao;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final ApplicationContext applicationContext;
|
||||
private final OpenAiYamlConfig yamlConfig;
|
||||
private final RestTemplate restTemplate;
|
||||
|
||||
// Client factory cache
|
||||
private volatile OpenAiConfigDto currentConfig;
|
||||
|
||||
public OpenAiConfigServiceImpl(OpenAiConfigDao openAiConfigDao,
|
||||
ObjectMapper objectMapper,
|
||||
ApplicationContext applicationContext,
|
||||
OpenAiYamlConfig yamlConfig) {
|
||||
this.openAiConfigDao = openAiConfigDao;
|
||||
this.objectMapper = objectMapper;
|
||||
this.applicationContext = applicationContext;
|
||||
this.yamlConfig = yamlConfig;
|
||||
this.restTemplate = new RestTemplate();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void saveConfig(OpenAiConfigDto config) {
|
||||
try {
|
||||
String contentJson = objectMapper.writeValueAsString(config);
|
||||
|
||||
OpenAiConfig openAiConfig = OpenAiConfig.builder()
|
||||
.type(CONFIG_TYPE)
|
||||
.content(contentJson)
|
||||
.build();
|
||||
|
||||
openAiConfigDao.save(openAiConfig);
|
||||
log.info("OpenAI configuration saved successfully");
|
||||
|
||||
applicationContext.publishEvent(new OpenAiConfigChangeEvent(applicationContext));
|
||||
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new IllegalArgumentException("Failed to save OpenAI configuration: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpenAiConfigDto getConfig() {
|
||||
OpenAiConfig config = openAiConfigDao.findByType(CONFIG_TYPE);
|
||||
if (config == null || !StringUtils.hasText(config.getContent())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return objectMapper.readValue(config.getContent(), OpenAiConfigDto.class);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new IllegalArgumentException("Failed to parse OpenAI configuration: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfigured() {
|
||||
OpenAiConfigDto effective = getEffectiveConfig();
|
||||
return effective != null && effective.isEnable() && StringUtils.hasText(effective.getApiKey());
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpenAiConfigDto getEffectiveConfig() {
|
||||
OpenAiConfigDto dbConfig = getConfig();
|
||||
if (dbConfig != null && dbConfig.isEnable() && StringUtils.hasText(dbConfig.getApiKey())) {
|
||||
log.debug("Using database OpenAI configuration");
|
||||
return dbConfig;
|
||||
}
|
||||
|
||||
if (yamlConfig != null && yamlConfig.isEnable() && StringUtils.hasText(yamlConfig.getApiKey())) {
|
||||
log.debug("Using YAML OpenAI configuration from spring.ai.openai.api-key");
|
||||
OpenAiConfigDto yamlDto = new OpenAiConfigDto();
|
||||
yamlDto.setEnable(true);
|
||||
yamlDto.setApiKey(yamlConfig.getApiKey());
|
||||
return yamlDto;
|
||||
}
|
||||
|
||||
log.debug("No valid OpenAI configuration found");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidationResult validateApiKey(String apiKey) {
|
||||
if (!StringUtils.hasText(apiKey)) {
|
||||
return ValidationResult.failure("API key cannot be empty");
|
||||
}
|
||||
|
||||
if (!apiKey.startsWith("sk-")) {
|
||||
return ValidationResult.failure("Invalid API key format. OpenAI API keys should start with 'sk-'");
|
||||
}
|
||||
|
||||
try {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("Authorization", "Bearer " + apiKey);
|
||||
headers.set("Content-Type", "application/json");
|
||||
|
||||
HttpEntity<String> entity = new HttpEntity<>(headers);
|
||||
|
||||
log.debug("Validating OpenAI API key by calling models endpoint");
|
||||
ResponseEntity<String> response = restTemplate.exchange(
|
||||
OPENAI_MODELS_ENDPOINT,
|
||||
HttpMethod.GET,
|
||||
entity,
|
||||
String.class
|
||||
);
|
||||
|
||||
if (response.getStatusCode() == HttpStatus.OK) {
|
||||
log.info("OpenAI API key validation successful");
|
||||
return ValidationResult.success("API key is valid");
|
||||
} else {
|
||||
log.warn("OpenAI API key validation failed with status: {}", response.getStatusCode());
|
||||
return ValidationResult.failure("API key validation failed: " + response.getStatusCode());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error validating OpenAI API key", e);
|
||||
String errorMessage = e.getMessage();
|
||||
|
||||
// Parse common error messages
|
||||
if (errorMessage.contains("401")) {
|
||||
return ValidationResult.failure("Invalid API key - authentication failed");
|
||||
} else if (errorMessage.contains("403")) {
|
||||
return ValidationResult.failure("API key does not have permission to access models");
|
||||
} else if (errorMessage.contains("429")) {
|
||||
return ValidationResult.failure("Rate limit exceeded - please try again later");
|
||||
} else if (errorMessage.contains("timeout") || errorMessage.contains("connect")) {
|
||||
return ValidationResult.failure("Network error - unable to connect to OpenAI API");
|
||||
} else {
|
||||
return ValidationResult.failure("API key validation failed: " + errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reloadConfig() {
|
||||
synchronized (this) {
|
||||
currentConfig = null; // Force reload
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* OpenAI configuration change event listener
|
||||
*/
|
||||
@EventListener(OpenAiConfigChangeEvent.class)
|
||||
public void onOpenAiConfigChange(OpenAiConfigChangeEvent event) {
|
||||
log.info("[OpenAiConfigService] OpenAI configuration change event received");
|
||||
reloadConfig();
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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.ai.agent.tools;
|
||||
|
||||
/**
|
||||
* Tools for alert definition and threshold configuration operations
|
||||
*/
|
||||
public interface AlertDefineTools {
|
||||
|
||||
/**
|
||||
* Create a new alert rule with HertzBeat's expression format based on app hierarchy
|
||||
|
||||
*
|
||||
* @param name Alert rule name (required, must be unique)
|
||||
* @param app App name from hierarchy (must match exact hierarchy app value)
|
||||
* @param metrics Metrics name from hierarchy (must match exact hierarchy metrics value)
|
||||
* @param fieldConditions Field-specific conditions from metric's children (e.g., "VmName = 'arora'", "total_granted > 1000",
|
||||
* "total_used > 123 and total_granted > 333 and (total_granted > 3444 and total_paid_available < 5556)")
|
||||
* @param type Alert rule type: 'realtime' (default) or 'periodic'
|
||||
* @param period Execution period in seconds (only for periodic rules, default: 300)
|
||||
* @param times Number of consecutive violations before triggering (default: 3)
|
||||
* @param priority Alert priority as integer: 0=critical, 1=warning, 2=info (default: 1)
|
||||
* @param description Alert rule description (optional)
|
||||
* @param template Alert message template with variables (optional)
|
||||
* @param datasource Data source type: 'promql' (default)
|
||||
* @param labels Labels as key:value pairs separated by commas
|
||||
* @param annotations Annotations as key:value pairs separated by commas
|
||||
* @param enable Whether to enable the rule immediately (default: true)
|
||||
* @return Result message with rule ID if successful
|
||||
*/
|
||||
String createAlertRule(String name, String app, String metrics, String fieldConditions,
|
||||
String type, Integer period, Integer times, Integer priority, String description,
|
||||
String template, String datasource, String labels, String annotations, Boolean enable);
|
||||
|
||||
/**
|
||||
* List existing alert rules with filtering
|
||||
* @param search Search term for rule name or description
|
||||
* @param monitorType Filter by monitor type
|
||||
* @param enabled Filter by enabled status
|
||||
* @param pageIndex Page index
|
||||
* @param pageSize Page size
|
||||
* @return Formatted list of alert rules
|
||||
*/
|
||||
String listAlertRules(String search, String monitorType, Boolean enabled, Integer pageIndex, Integer pageSize);
|
||||
|
||||
/**
|
||||
* Enable or disable an alert rule
|
||||
* @param ruleId Alert rule ID
|
||||
* @param enabled Whether to enable the rule
|
||||
* @return Result message
|
||||
*/
|
||||
String toggleAlertRule(Long ruleId, Boolean enabled);
|
||||
|
||||
/**
|
||||
* Get detailed information about an alert rule
|
||||
* @param ruleId Alert rule ID
|
||||
* @return Detailed rule information
|
||||
*/
|
||||
String getAlertRuleDetails(Long ruleId);
|
||||
|
||||
|
||||
/**
|
||||
* Get the hierarchical structure of available apps and metrics for alert rule creation
|
||||
* @param app App type to get hierarchy for (optional, gets all if not specified)
|
||||
* @return Hierarchical structure showing apps and their available metrics
|
||||
*/
|
||||
String getAppsMetricsHierarchy(String app);
|
||||
|
||||
/**
|
||||
* Bind monitors to an alert rule by modifying the alert expression
|
||||
* @param ruleId Alert rule ID to bind monitors to
|
||||
* @param monitorIds Comma-separated list of monitor IDs to bind
|
||||
* @return Result message indicating success or failure
|
||||
*/
|
||||
String bindMonitorsToAlertRule(Long ruleId, String monitorIds);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.tools;
|
||||
|
||||
/**
|
||||
* Tools for alert operations and alarm data queries
|
||||
*/
|
||||
public interface AlertTools {
|
||||
|
||||
/**
|
||||
* Query alerts with comprehensive filtering and pagination
|
||||
* @param alertType Alert type (single, group, both)
|
||||
* @param status Alert status (firing, resolved, all)
|
||||
* @param search Search term for alert content or labels
|
||||
* @param sort Sort field (startAt, triggerTimes, status)
|
||||
* @param order Sort order (asc, desc)
|
||||
* @param pageIndex Page index
|
||||
* @param pageSize Page size
|
||||
* @return Formatted string with alert information
|
||||
*/
|
||||
String queryAlerts(String alertType, String status, String search, String sort, String order, Integer pageIndex, Integer pageSize);
|
||||
|
||||
/**
|
||||
* Get alerts summary statistics
|
||||
* @return Alert summary information including counts by status
|
||||
*/
|
||||
String getAlertsSummary();
|
||||
|
||||
|
||||
}
|
||||
+53
@@ -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.
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.tools;
|
||||
|
||||
/**
|
||||
* Tools for metrics data operations and queries
|
||||
*/
|
||||
public interface MetricsTools {
|
||||
|
||||
/**
|
||||
* Get real-time metrics data for a monitor
|
||||
* @param monitorId Monitor ID
|
||||
* @param metrics Metrics name (e.g., "system", "cpu", "memory")
|
||||
* @return Formatted real-time metrics data
|
||||
*/
|
||||
String getRealtimeMetrics(Long monitorId, String metrics);
|
||||
|
||||
/**
|
||||
* Get historical metrics data for a monitor
|
||||
* @param monitorId Monitor ID
|
||||
* @param app Monitor type (e.g., "linux", "mysql", "http")
|
||||
* @param metrics Metrics name (e.g., "system", "cpu", "memory")
|
||||
* @param metric Specific metric field (e.g., "usage", "used", "available")
|
||||
* @param label Label filter for specific instances
|
||||
* @param history Time range (e.g., "1h", "6h", "24h", "7d")
|
||||
* @param interval Whether to aggregate data with intervals
|
||||
* @return Historical metrics data formatted for display
|
||||
*/
|
||||
String getHistoricalMetrics(Long monitorId, String app, String metrics, String metric, String label, String history, Boolean interval);
|
||||
|
||||
/**
|
||||
* Check warehouse storage server status
|
||||
* @return Status of the metrics storage system
|
||||
*/
|
||||
String getWarehouseStatus();
|
||||
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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.ai.agent.tools;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Interface for Monitoring Tools
|
||||
*/
|
||||
public interface MonitorTools {
|
||||
|
||||
/**
|
||||
* Add a new monitor with comprehensive configuration
|
||||
*
|
||||
* @param name Monitor name
|
||||
* @param app Monitor type/application (e.g., 'linux', 'mysql', 'http')
|
||||
* @param host Target host (IP address or domain name)
|
||||
* @param port Target port (optional, depends on monitor type)
|
||||
* @param intervals Collection interval in seconds (default: 600)
|
||||
* @param username Username for authentication (optional)
|
||||
* @param password Password for authentication (optional)
|
||||
* @param database Database name (for database monitors)
|
||||
* @param additionalParams Additional app-specific parameters as JSON string (optional)
|
||||
* @param description Monitor description (optional)
|
||||
* @return Result message with monitor ID if successful
|
||||
*/
|
||||
String addMonitor(
|
||||
String name,
|
||||
String app,
|
||||
String host,
|
||||
Integer port,
|
||||
Integer intervals,
|
||||
String username,
|
||||
String password,
|
||||
String database,
|
||||
String additionalParams,
|
||||
String description
|
||||
);
|
||||
|
||||
/**
|
||||
* List all available monitor types that can be added
|
||||
*
|
||||
* @param language Language code for localized names (e.g., 'en-US', 'zh-CN')
|
||||
* @return Formatted string list of available monitor types with descriptions
|
||||
*/
|
||||
String listMonitorTypes(String language);
|
||||
|
||||
/**
|
||||
* Comprehensive monitor querying with flexible filtering, pagination, and specialized views
|
||||
* @param ids Specific monitor IDs to retrieve (optional)
|
||||
* @param app Monitor type filter (linux, mysql, http, etc.)
|
||||
* @param status Monitor status (1=online, 2=offline, 3=unreachable, 0=paused, 9=all)
|
||||
* @param search Search in monitor names or hosts (partial matching)
|
||||
* @param labels Label filters, format: 'key1:value1,key2:value2'
|
||||
* @param sort Sort field (name, gmtCreate, gmtUpdate, status, app)
|
||||
* @param order Sort order (asc, desc)
|
||||
* @param pageIndex Page number starting from 0
|
||||
* @param pageSize Items per page (1-100 recommended)
|
||||
* @param includeStats Include status statistics summary
|
||||
* @return Comprehensive monitor information with optional statistics
|
||||
*/
|
||||
String queryMonitors(
|
||||
List<Long> ids,
|
||||
String app,
|
||||
Byte status,
|
||||
String search,
|
||||
String labels,
|
||||
String sort,
|
||||
String order,
|
||||
Integer pageIndex,
|
||||
Integer pageSize,
|
||||
Boolean includeStats);
|
||||
|
||||
/**
|
||||
* Get parameter definitions required for a specific monitor type
|
||||
*
|
||||
* @param app Monitor type/application name (e.g., 'linux', 'mysql', 'redis')
|
||||
* @return Formatted string with parameter definitions including field names, types, and requirements
|
||||
*/
|
||||
String getMonitorAdditionalParams(String app);
|
||||
}
|
||||
+665
@@ -0,0 +1,665 @@
|
||||
/*
|
||||
* 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.ai.agent.tools.impl;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.usthe.sureness.subject.SubjectSum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.ai.agent.adapters.AlertDefineServiceAdapter;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.Hierarchy;
|
||||
import org.apache.hertzbeat.ai.agent.config.McpContextHolder;
|
||||
import org.apache.hertzbeat.ai.agent.tools.AlertDefineTools;
|
||||
import org.apache.hertzbeat.ai.agent.utils.UtilityClass;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
|
||||
import org.springframework.ai.tool.annotation.Tool;
|
||||
import org.springframework.ai.tool.annotation.ToolParam;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Implementation of Alert Define Tools functionality
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class AlertDefineToolsImpl implements AlertDefineTools {
|
||||
@Autowired
|
||||
private AlertDefineServiceAdapter alertDefineServiceAdapter;
|
||||
|
||||
|
||||
@Override
|
||||
@Tool(name = "create_alert_rule", description = """
|
||||
ALERT RULE means when to alert a user
|
||||
THESE ARE ALERT RULES WITH THRESHOLD VALUES. USERS CAN SPECIFY THE THRESHOLD VALUES FOR EXAMPLE,
|
||||
IF THE USER SAYS "ALERT ME WHEN MY COST EXCEEDS 700, THE EXPRESSION SHOULD BE 'cost > 700' NOT 'cost < 700'.
|
||||
APPLY THE SAME LOGIC FOR LESS THAN OPERATOR.
|
||||
Create a HertzBeat alert rule based on app hierarchy structure and user requirements.
|
||||
It is important to first understand the hierarchy of apps, metrics, and field conditions
|
||||
Each app has its own metrics and each metric has its own field conditions.
|
||||
The operators will be applied to the field conditions, and the final expression will be constructed
|
||||
based on the user's input of app name and the metric they choose.
|
||||
CRITICAL WORKFLOW Do all of this iteratively with user interaction at each step
|
||||
1. ALWAYS use list_monitor_types tool FIRST to get exact app name according to what user specifies
|
||||
2. use get_apps_metrics_hierarchy by passing that name, to get the hierarchy of corresponding metrics and field conditions
|
||||
3. Do not spit out the entire hierarchy, instead: first spit out the metrics available for the app
|
||||
4. Ask the user to choose a metric from the available metrics
|
||||
5. Based on the metric chosen, present the available field conditions/params
|
||||
6. You will construct the proper expression with field conditions
|
||||
7. once this tool successfully executes, ask the user if they want to bind any existing monitors to this alert rule,
|
||||
get the monitors list for a particular app using the query_monitors tool.
|
||||
8. based on the user's output, conditionally call the bind_monitors_to_alert_rule tool to bind monitors to the alert rule
|
||||
VERY VERY IMPORTANT:
|
||||
- ALWAYS USE the value field from the get_apps_metrics_hierarchy's json response when creating alert expressions on the field parameters
|
||||
|
||||
EXAMPLES FOR FIELD CONDITION EXPRESSION( Do not copy these examples, they are just for reference ):
|
||||
These are all just examples, you can take inspiration from them and create a rule based on hierarchy, always ask the user for all params, do not assume them, even for these examples:
|
||||
|
||||
1. Kafka JVM Alert:
|
||||
- App: "kafka", Metric: "jvm_basic"
|
||||
- Field condition: equals(VmName, "myVM")
|
||||
- Field condition expression: equals(VmName, "myVM")
|
||||
|
||||
2. LLM Credits Alert:
|
||||
- App: "openai", Metric: "credit_grants"
|
||||
- Field condition: total_granted > some_value
|
||||
- Field condition expression: total_granted > 1000
|
||||
|
||||
3. HBase Master Alert:
|
||||
- App: "hbase_master", Metric: "server"
|
||||
- Field condition: heap_memory_used > 80 or some_factor<100
|
||||
- Field condition expression: heap_memory_used > 80 or some_factor<100
|
||||
|
||||
4. Complex OpenAI Credits Alert:
|
||||
- App: "openai", Metric: "credit_grants"
|
||||
- Field condition: total_used > 123 and total_granted > 333 and (total_granted > 3444 and total_paid_available < 5556)
|
||||
- Field condition expression: total_used > 123 and total_granted > 333 and (total_granted > 3444 and total_paid_available < 5556)
|
||||
|
||||
FIELD CONDITIONS GUIDANCE:
|
||||
- Field names come from metric's children in hierarchy (leaf nodes)
|
||||
- Use the "value" field from the metric's children, not the label when creating conditions
|
||||
- Supported operators: >, <, >=, <=, ==, !=, exists(), !exists() for numeric fields
|
||||
- equals(), contains(), matches(),exists(), !equals(), !contains(), !matches(), !exists() for string fields
|
||||
- Supported logical operators: and, or to connect different field parameter rules or rulesets
|
||||
- ONLY USE THESE OPERATORS, when creating conditions, do not use any other operators
|
||||
- Support grouping with parentheses: (condition1 and condition2) or condition3
|
||||
- String values should be quoted: equals(VmName, "my-vm")
|
||||
- Simple conditions: heap_memory_used > 80, total_granted <= 1000
|
||||
- Complex conditions: total_used > 123 and total_granted > 333 and (total_granted > 3444 and total_paid_available < 5556)
|
||||
|
||||
PRIORITY LEVELS:
|
||||
- 0: Critical (immediate action required)
|
||||
- 1: Warning (attention needed, default)
|
||||
- 2: Info (informational only)
|
||||
""")
|
||||
public String createAlertRule(
|
||||
@ToolParam(description = "Alert rule name (required, must be unique)", required = true) String name,
|
||||
@ToolParam(description = "App name from hierarchy (must match exact hierarchy app value)", required = true) String app,
|
||||
@ToolParam(description = "Metrics name from hierarchy (must match exact hierarchy metrics value)", required = true) String metrics,
|
||||
@ToolParam(description = "Field conditions expression)", required = true) String fieldConditions,
|
||||
@ToolParam(description = "Alert rule type: 'realtime' (default) or 'periodic'", required = false) String type,
|
||||
@ToolParam(description = "Execution period in seconds (only for periodic rules, default: 300)", required = false) Integer period,
|
||||
@ToolParam(description = "Number of consecutive violations before triggering (default: 3)", required = false) Integer times,
|
||||
@ToolParam(description = "Alert priority as integer: 0=critical, 1=warning, 2=info (default: 1)", required = false) Integer priority,
|
||||
@ToolParam(description = "Alert rule description (optional)", required = false) String description,
|
||||
@ToolParam(description = "Alert message template with variables (optional)", required = false) String template,
|
||||
@ToolParam(description = "Data source type: 'promql' (default)", required = false) String datasource,
|
||||
@ToolParam(description = "Labels as key:value pairs separated by commas (e.g., 'env:prod,severity:critical')", required = false) String labels,
|
||||
@ToolParam(description = "Annotations as key:value pairs separated by commas (e.g., 'summary:High CPU')", required = false) String annotations,
|
||||
@ToolParam(description = "Whether to enable the rule immediately (default: true)", required = false) Boolean enable) {
|
||||
|
||||
try {
|
||||
log.info("Creating HertzBeat alert rule: name={}, app={}, metrics={}, fieldConditions={}", name, app, metrics, fieldConditions);
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current subject in create_alert_rule tool: {}", subjectSum);
|
||||
|
||||
// Validate required parameters
|
||||
if (name == null || name.trim().isEmpty()) {
|
||||
return "Error: Alert rule name is required";
|
||||
}
|
||||
if (app == null || app.trim().isEmpty()) {
|
||||
return "Error: App name is required (use get_apps_metrics_hierarchy to find exact names)";
|
||||
}
|
||||
if (metrics == null || metrics.trim().isEmpty()) {
|
||||
return "Error: Metrics name is required (use get_apps_metrics_hierarchy to find exact names)";
|
||||
}
|
||||
if (fieldConditions == null || fieldConditions.trim().isEmpty()) {
|
||||
return "Error: Field conditions are required (e.g., 'equals(VmName, \"arora\")', 'total_granted > 1000')";
|
||||
}
|
||||
|
||||
// Set defaults
|
||||
if (type == null || type.trim().isEmpty()) {
|
||||
type = "realtime";
|
||||
}
|
||||
if (times == null || times <= 0) {
|
||||
times = 3;
|
||||
}
|
||||
if (priority == null) {
|
||||
priority = 1; // Default to warning
|
||||
}
|
||||
if (enable == null) {
|
||||
enable = true;
|
||||
}
|
||||
if (datasource == null || datasource.trim().isEmpty()) {
|
||||
datasource = "promql";
|
||||
}
|
||||
|
||||
// Validate alert type
|
||||
if (!type.equals("realtime") && !type.equals("periodic")) {
|
||||
return "Error: Alert type must be 'realtime' or 'periodic'";
|
||||
}
|
||||
|
||||
// Validate priority
|
||||
if (priority < 0 || priority > 2) {
|
||||
return "Error: Priority must be 0 (critical), 1 (warning), or 2 (info)";
|
||||
}
|
||||
|
||||
// For periodic rules, validate period parameter
|
||||
if (type.equals("periodic")) {
|
||||
if (period == null || period <= 0) {
|
||||
period = 300; // Default 5 minutes
|
||||
}
|
||||
}
|
||||
|
||||
// CRITICAL VALIDATION: Verify app-metric-field relationships using hierarchy
|
||||
String validationResult = validateHierarchyRelationships(app.trim(), metrics.trim(), fieldConditions.trim());
|
||||
if (!validationResult.equals("VALID")) {
|
||||
return validationResult; // Return validation error message
|
||||
}
|
||||
|
||||
// EXPRESSION VALIDATION: Verify field conditions syntax and operators
|
||||
String expressionValidation = UtilityClass.validateExpressionSyntax(fieldConditions.trim());
|
||||
if (!expressionValidation.equals("VALID")) {
|
||||
return expressionValidation; // Return expression validation error message
|
||||
}
|
||||
|
||||
String expr = String.format("equals(__app__,\"%s\") && equals(__metrics__,\"%s\") && %s",
|
||||
app.trim(), metrics.trim(), fieldConditions.trim());
|
||||
|
||||
|
||||
|
||||
// Parse labels if provided
|
||||
Map<String, String> labelsMap = new HashMap<>();
|
||||
if (labels != null && !labels.trim().isEmpty()) {
|
||||
labelsMap.putAll(UtilityClass.parseKeyValuePairs(labels));
|
||||
}
|
||||
// Add severity based on priority
|
||||
String severityLabel = priority == 0 ? "critical" : (priority == 1 ? "warning" : "info");
|
||||
labelsMap.put("severity", severityLabel);
|
||||
|
||||
// Parse annotations if provided
|
||||
Map<String, String> annotationsMap = new HashMap<>();
|
||||
if (annotations != null && !annotations.trim().isEmpty()) {
|
||||
annotationsMap.putAll(UtilityClass.parseKeyValuePairs(annotations));
|
||||
}
|
||||
// Add default annotations if not provided
|
||||
if (!annotationsMap.containsKey("summary")) {
|
||||
annotationsMap.put("summary", description != null ? description :
|
||||
String.format("Alert for %s %s when %s", app, metrics, fieldConditions));
|
||||
}
|
||||
if (!annotationsMap.containsKey("description")) {
|
||||
annotationsMap.put("description", String.format("Monitor %s metrics %s with conditions: %s", app, metrics, fieldConditions));
|
||||
}
|
||||
|
||||
// Generate default template if not provided
|
||||
if (template == null || template.trim().isEmpty()) {
|
||||
template = String.format("Alert: %s %s - %s", app, metrics, fieldConditions);
|
||||
}
|
||||
|
||||
// Create comprehensive alert definition
|
||||
AlertDefine alertDefine = AlertDefine.builder()
|
||||
.name(name.trim())
|
||||
.type(type)
|
||||
.expr(expr)
|
||||
.period(period)
|
||||
.times(times)
|
||||
.labels(labelsMap)
|
||||
.annotations(annotationsMap)
|
||||
.template(template)
|
||||
.datasource(datasource)
|
||||
.enable(enable)
|
||||
.build();
|
||||
|
||||
AlertDefine createdAlertDefine = alertDefineServiceAdapter.addAlertDefine(alertDefine);
|
||||
|
||||
// Note: Monitor binding is handled separately via bind_monitors_to_alert_rule tool
|
||||
String bindingNote = String.format(" (Use bind_monitors_to_alert_rule tool to associate specific monitors)");
|
||||
|
||||
log.info("Successfully created alert rule '{}' with ID: {}", name, createdAlertDefine.getId());
|
||||
|
||||
StringBuilder response = new StringBuilder();
|
||||
response.append(String.format("Successfully created %s alert rule '%s' with ID: %d\n",
|
||||
type, name, createdAlertDefine.getId()));
|
||||
response.append(String.format("Expression: %s\n", expr));
|
||||
response.append(String.format("Priority: %d (%s)\n", priority, severityLabel));
|
||||
response.append(String.format("Trigger after: %d consecutive violations\n", times));
|
||||
if (type.equals("periodic")) {
|
||||
response.append(String.format("Execution period: %d seconds\n", period));
|
||||
}
|
||||
response.append(String.format("Data source: %s\n", datasource));
|
||||
response.append(String.format("Enabled: %s\n", enable));
|
||||
if (!labelsMap.isEmpty()) {
|
||||
response.append(String.format("Labels: %s\n", labelsMap));
|
||||
}
|
||||
response.append(bindingNote);
|
||||
|
||||
return response.toString();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to create alert rule '{}': {}", name, e.getMessage(), e);
|
||||
return "Error creating alert rule '" + name + "': " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
// ... other existing methods would go here ...
|
||||
|
||||
@Override
|
||||
@Tool(name = "list_alert_rules", description = """
|
||||
List existing alert rules with filtering options.
|
||||
Shows configured thresholds and alert definitions.
|
||||
""")
|
||||
public String listAlertRules(
|
||||
@ToolParam(description = "Search term for rule name or description", required = false) String search,
|
||||
@ToolParam(description = "Filter by monitor type", required = false) String monitorType,
|
||||
@ToolParam(description = "Filter by enabled status", required = false) Boolean enabled,
|
||||
@ToolParam(description = "Page index (default: 0)", required = false) Integer pageIndex,
|
||||
@ToolParam(description = "Page size (default: 10)", required = false) Integer pageSize) {
|
||||
|
||||
try {
|
||||
log.info("Listing alert rules: search={}, monitorType={}, enabled={}", search, monitorType, enabled);
|
||||
|
||||
if (pageIndex == null || pageIndex < 0) {
|
||||
pageIndex = 0;
|
||||
}
|
||||
if (pageSize == null || pageSize <= 0) {
|
||||
pageSize = 10;
|
||||
}
|
||||
|
||||
Page<AlertDefine> result = alertDefineServiceAdapter.getAlertDefines(
|
||||
search, monitorType, enabled, "gmtCreate", "desc", pageIndex, pageSize);
|
||||
|
||||
StringBuilder response = new StringBuilder();
|
||||
response.append("Found ").append(result.getContent().size())
|
||||
.append(" alert rules (Total: ").append(result.getTotalElements()).append("):\n\n");
|
||||
|
||||
for (AlertDefine alertDefine : result.getContent()) {
|
||||
response.append("Rule ID: ").append(alertDefine.getId()).append("\n");
|
||||
response.append("Name: ").append(alertDefine.getName()).append("\n");
|
||||
response.append("Expression: ").append(alertDefine.getExpr()).append("\n");
|
||||
response.append("Type: ").append(alertDefine.getType()).append("\n");
|
||||
response.append("Trigger Times: ").append(alertDefine.getTimes()).append("\n");
|
||||
response.append("Enabled: ").append(alertDefine.isEnable()).append("\n");
|
||||
|
||||
if (alertDefine.getLabels() != null && !alertDefine.getLabels().isEmpty()) {
|
||||
response.append("Labels: ").append(alertDefine.getLabels()).append("\n");
|
||||
}
|
||||
if (alertDefine.getAnnotations() != null && !alertDefine.getAnnotations().isEmpty()) {
|
||||
response.append("Summary: ").append(alertDefine.getAnnotations().get("summary")).append("\n");
|
||||
}
|
||||
response.append("Created: ").append(alertDefine.getGmtCreate()).append("\n");
|
||||
response.append("\n");
|
||||
}
|
||||
|
||||
if (result.getContent().isEmpty()) {
|
||||
response.append("No alert rules found matching the specified criteria.");
|
||||
}
|
||||
|
||||
return response.toString();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to list alert rules: {}", e.getMessage(), e);
|
||||
return "Error retrieving alert rules: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Tool(name = "toggle_alert_rule", description = """
|
||||
Enable or disable an alert rule.
|
||||
Allows activating or deactivating threshold monitoring.
|
||||
""")
|
||||
public String toggleAlertRule(
|
||||
@ToolParam(description = "Alert rule ID", required = true) Long ruleId,
|
||||
@ToolParam(description = "Whether to enable the rule", required = true) Boolean enabled) {
|
||||
|
||||
try {
|
||||
log.info("Toggling alert rule ID: {} to enabled: {}", ruleId, enabled);
|
||||
|
||||
alertDefineServiceAdapter.toggleAlertDefineStatus(ruleId, enabled);
|
||||
|
||||
log.info("Successfully toggled alert rule ID: {} to enabled: {}", ruleId, enabled);
|
||||
return String.format("Successfully %s alert rule ID: %d",
|
||||
enabled ? "enabled" : "disabled", ruleId);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to toggle alert rule ID {}: {}", ruleId, e.getMessage(), e);
|
||||
return "Error toggling alert rule: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Tool(name = "get_alert_rule_details", description = """
|
||||
Get detailed information about a specific alert rule.
|
||||
Shows complete threshold configuration and rule settings.
|
||||
""")
|
||||
public String getAlertRuleDetails(
|
||||
@ToolParam(description = "Alert rule ID", required = true) Long ruleId) {
|
||||
|
||||
try {
|
||||
log.info("Getting alert rule details for ID: {}", ruleId);
|
||||
|
||||
AlertDefine alertDefine = alertDefineServiceAdapter.getAlertDefine(ruleId);
|
||||
if (alertDefine == null) {
|
||||
return "Alert rule with ID " + ruleId + " not found";
|
||||
}
|
||||
|
||||
StringBuilder response = new StringBuilder();
|
||||
response.append("ALERT RULE DETAILS\n");
|
||||
response.append("==================\n\n");
|
||||
|
||||
response.append("Rule ID: ").append(alertDefine.getId()).append("\n");
|
||||
response.append("Name: ").append(alertDefine.getName()).append("\n");
|
||||
response.append("Type: ").append(alertDefine.getType()).append("\n");
|
||||
response.append("Expression: ").append(alertDefine.getExpr()).append("\n");
|
||||
response.append("Trigger Times: ").append(alertDefine.getTimes()).append("\n");
|
||||
response.append("Enabled: ").append(alertDefine.isEnable()).append("\n");
|
||||
|
||||
if (alertDefine.getPeriod() != null) {
|
||||
response.append("Period: ").append(alertDefine.getPeriod()).append(" seconds\n");
|
||||
}
|
||||
|
||||
if (alertDefine.getLabels() != null && !alertDefine.getLabels().isEmpty()) {
|
||||
response.append("Labels: ").append(alertDefine.getLabels()).append("\n");
|
||||
}
|
||||
|
||||
if (alertDefine.getAnnotations() != null && !alertDefine.getAnnotations().isEmpty()) {
|
||||
response.append("Annotations: ").append(alertDefine.getAnnotations()).append("\n");
|
||||
}
|
||||
|
||||
if (alertDefine.getTemplate() != null) {
|
||||
response.append("Template: ").append(alertDefine.getTemplate()).append("\n");
|
||||
}
|
||||
|
||||
response.append("Created: ").append(alertDefine.getGmtCreate()).append("\n");
|
||||
response.append("Modified: ").append(alertDefine.getGmtUpdate()).append("\n");
|
||||
response.append("Creator: ").append(alertDefine.getCreator()).append("\n");
|
||||
response.append("Modifier: ").append(alertDefine.getModifier()).append("\n");
|
||||
|
||||
return response.toString();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to get alert rule details for ID {}: {}", ruleId, e.getMessage(), e);
|
||||
return "Error retrieving alert rule details: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Tool(name = "get_apps_metrics_hierarchy", description = """
|
||||
Get the hierarchical structure of all available apps and their metrics for alert rule creation.
|
||||
This tool provides the exact app name, metric name and corresponding param names according to each metric.
|
||||
Returns structured JSON data showing the complete hierarchy with field parameters for alert expressions.
|
||||
|
||||
JSON Structure:
|
||||
- app: The application name
|
||||
- description: Tool description
|
||||
- hierarchy: Array of hierarchical data
|
||||
- Each node has: value, label, type, description
|
||||
- Leaf nodes have: dataType (numeric/string), unit (if applicable)
|
||||
- Non-leaf nodes have: children array
|
||||
VERY IMPORTANT:
|
||||
- ALWAYS USE the value field from the field parameters when creating alert expressions.
|
||||
|
||||
This structured data is needed to create proper alert expressions.
|
||||
""")
|
||||
public String getAppsMetricsHierarchy(
|
||||
@ToolParam(description = "App/Monitor type to get hierarchy for (e.g., 'linux', 'mysql', 'website')", required = true) String app) {
|
||||
|
||||
try {
|
||||
log.info("Getting apps metrics hierarchy for app: {}", app);
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current subject in get_apps_metrics_hierarchy tool: {}", subjectSum);
|
||||
|
||||
List<Hierarchy> hierarchies;
|
||||
hierarchies = alertDefineServiceAdapter.getAppHierarchy(app.trim().toLowerCase(), "en-US");
|
||||
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
ObjectNode result = mapper.createObjectNode();
|
||||
|
||||
result.put("app", app.toUpperCase());
|
||||
|
||||
if (hierarchies != null && !hierarchies.isEmpty()) {
|
||||
ArrayNode hierarchyArray = mapper.createArrayNode();
|
||||
for (Hierarchy hierarchy : hierarchies) {
|
||||
hierarchyArray.add(UtilityClass.formatHierarchyAsJson(mapper, hierarchy));
|
||||
}
|
||||
result.set("hierarchy", hierarchyArray);
|
||||
} else {
|
||||
result.put("message", "No hierarchy data available");
|
||||
}
|
||||
|
||||
String jsonResult = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result);
|
||||
log.info("Hierarchy JSON: {}", jsonResult);
|
||||
|
||||
return jsonResult;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to get apps metrics hierarchy: {}", e.getMessage(), e);
|
||||
return "Error retrieving apps metrics hierarchy: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Tool(name = "bind_monitors_to_alert_rule", description = """
|
||||
Bind monitors to an alert rule.
|
||||
Call this tool if users want to bind specific monitors to their alert rule.
|
||||
Get the right monitor ids for a particular app using the query_monitors tool.
|
||||
Get the alert rule ID from the create_alert_rule tool output OR use the list_alert_rules tool with app_name search filter, if the output of create_alert_rule is not applicable.
|
||||
If monitors are already bound, this will add the new ones to the existing bindings.
|
||||
""")
|
||||
public String bindMonitorsToAlertRule(
|
||||
@ToolParam(description = "Alert rule ID to bind monitors to", required = true) Long ruleId,
|
||||
@ToolParam(description = "Comma-separated list of monitor IDs to bind", required = true) String monitorIds) {
|
||||
try {
|
||||
log.info("Binding monitors to alert rule ID: {}, monitors: {}", ruleId, monitorIds);
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current subject in bind_monitors_to_alert_rule tool: {}", subjectSum);
|
||||
|
||||
if (ruleId == null || ruleId <= 0) {
|
||||
return "Error: Valid alert rule ID is required";
|
||||
}
|
||||
if (monitorIds == null) {
|
||||
return "Error: Monitor IDs are required";
|
||||
}
|
||||
|
||||
// Get the existing alert rule
|
||||
AlertDefine existingRule = alertDefineServiceAdapter.getAlertDefine(ruleId);
|
||||
if (existingRule == null) {
|
||||
return String.format("Error: Alert rule with ID %d not found", ruleId);
|
||||
}
|
||||
|
||||
// Parse monitor IDs from comma-separated string
|
||||
String[] monitorIdArray = monitorIds.split(",");
|
||||
List<String> validMonitorIds = new ArrayList<>();
|
||||
|
||||
for (String monitorId : monitorIdArray) {
|
||||
String trimmedId = monitorId.trim();
|
||||
if (!trimmedId.isEmpty()) {
|
||||
try {
|
||||
Long.parseLong(trimmedId); // Validate it's a number
|
||||
validMonitorIds.add(trimmedId);
|
||||
} catch (NumberFormatException e) {
|
||||
return String.format("Error: Invalid monitor ID '%s'. Monitor IDs must be numeric.", trimmedId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (validMonitorIds.isEmpty()) {
|
||||
return "Error: No valid monitor IDs provided";
|
||||
}
|
||||
|
||||
// Build the monitor instance condition
|
||||
String monitorCondition;
|
||||
if (validMonitorIds.size() == 1) {
|
||||
monitorCondition = String.format("equals(__instance__, \"%s\")", validMonitorIds.get(0));
|
||||
} else {
|
||||
StringBuilder conditionBuilder = new StringBuilder("(");
|
||||
for (int i = 0; i < validMonitorIds.size(); i++) {
|
||||
if (i > 0) {
|
||||
conditionBuilder.append(" or ");
|
||||
}
|
||||
conditionBuilder.append(String.format("equals(__instance__, \"%s\")", validMonitorIds.get(i)));
|
||||
}
|
||||
conditionBuilder.append(")");
|
||||
monitorCondition = conditionBuilder.toString();
|
||||
}
|
||||
|
||||
// Get the current expression and modify it
|
||||
String currentExpr = existingRule.getExpr();
|
||||
String newExpr;
|
||||
|
||||
// Check if the expression already has __instance__ conditions
|
||||
if (currentExpr.contains("__instance__")) {
|
||||
// Extract existing monitor IDs and merge with new ones
|
||||
List<String> existingMonitorIds = UtilityClass.extractExistingMonitorIds(currentExpr);
|
||||
|
||||
// Add new monitor IDs that aren't already present
|
||||
for (String newId : validMonitorIds) {
|
||||
if (!existingMonitorIds.contains(newId)) {
|
||||
existingMonitorIds.add(newId);
|
||||
}
|
||||
}
|
||||
|
||||
String updatedMonitorCondition;
|
||||
if (existingMonitorIds.size() == 1) {
|
||||
updatedMonitorCondition = String.format("equals(__instance__, \"%s\")", existingMonitorIds.get(0));
|
||||
} else {
|
||||
StringBuilder conditionBuilder = new StringBuilder("(");
|
||||
for (int i = 0; i < existingMonitorIds.size(); i++) {
|
||||
if (i > 0) {
|
||||
conditionBuilder.append(" or ");
|
||||
}
|
||||
conditionBuilder.append(String.format("equals(__instance__, \"%s\")", existingMonitorIds.get(i)));
|
||||
}
|
||||
conditionBuilder.append(")");
|
||||
updatedMonitorCondition = conditionBuilder.toString();
|
||||
}
|
||||
|
||||
// Replace existing __instance__ conditions with updated ones
|
||||
newExpr = UtilityClass.replaceInstanceConditions(currentExpr, updatedMonitorCondition);
|
||||
|
||||
// Update the alert rule
|
||||
existingRule.setExpr(newExpr);
|
||||
alertDefineServiceAdapter.modifyAlertDefine(existingRule);
|
||||
|
||||
log.info("Successfully added monitors {} to existing bindings for alert rule ID: {}", validMonitorIds, ruleId);
|
||||
return String.format("Successfully added %d new monitor(s) to alert rule ID %d.\nTotal bound monitors: %s\nUpdated expression: %s",
|
||||
validMonitorIds.size(), ruleId, String.join(", ", existingMonitorIds), newExpr);
|
||||
}
|
||||
|
||||
// Insert the monitor condition after the metrics condition
|
||||
// Pattern: equals(__app__,"app") && equals(__metrics__,"metric") && [existing_conditions]
|
||||
// Result: equals(__app__,"app") && equals(__metrics__,"metric") && [monitor_condition] && [existing_conditions]
|
||||
|
||||
if (currentExpr.matches(".*equals\\(__app__,\"[^\"]+\"\\)\\s*&&\\s*equals\\(__metrics__,\"[^\"]+\"\\)\\s*&&\\s*.*")) {
|
||||
// Find the position after the metrics condition
|
||||
String metricsPattern = "equals\\(__metrics__,\"[^\"]+\"\\)";
|
||||
java.util.regex.Pattern regex = java.util.regex.Pattern.compile(metricsPattern);
|
||||
java.util.regex.Matcher matcher = regex.matcher(currentExpr);
|
||||
|
||||
if (matcher.find()) {
|
||||
int metricsEnd = matcher.end();
|
||||
// Find the " && " after the metrics condition
|
||||
int andPosition = currentExpr.indexOf(" && ", metricsEnd);
|
||||
if (andPosition != -1) {
|
||||
String beforeAndPosition = currentExpr.substring(0, andPosition + 4); // Include " && "
|
||||
String afterAndPosition = currentExpr.substring(andPosition + 4); // Everything after " && "
|
||||
newExpr = beforeAndPosition + monitorCondition + " && " + afterAndPosition;
|
||||
} else {
|
||||
return String.format("Error: Unable to find field conditions after metrics in expression: %s", currentExpr);
|
||||
}
|
||||
} else {
|
||||
return String.format("Error: Unable to parse metrics condition in expression: %s", currentExpr);
|
||||
}
|
||||
} else {
|
||||
return String.format("Error: Expression format not supported for monitor binding: %s", currentExpr);
|
||||
}
|
||||
|
||||
// Update the alert rule
|
||||
existingRule.setExpr(newExpr);
|
||||
alertDefineServiceAdapter.modifyAlertDefine(existingRule);
|
||||
|
||||
log.info("Successfully bound monitors {} to alert rule ID: {}", validMonitorIds, ruleId);
|
||||
return String.format("Successfully bound %d monitor(s) to alert rule ID %d.\nMonitor IDs: %s\nUpdated expression: %s",
|
||||
validMonitorIds.size(), ruleId, String.join(", ", validMonitorIds), newExpr);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to bind monitors to alert rule ID {}: {}", ruleId, e.getMessage(), e);
|
||||
return String.format("Error binding monitors to alert rule: %s", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validates that the app, metric, and field conditions are valid according to hierarchy
|
||||
* @param app App name to validate
|
||||
* @param metrics Metric name to validate for the app
|
||||
* @param fieldConditions Field conditions to validate for the metric
|
||||
* @return "VALID" if all relationships are correct, error message otherwise
|
||||
*/
|
||||
private String validateHierarchyRelationships(String app, String metrics, String fieldConditions) {
|
||||
try {
|
||||
log.debug("Validating hierarchy relationships: app={}, metrics={}, fieldConditions={}", app, metrics, fieldConditions);
|
||||
|
||||
// Get hierarchy for the specified app
|
||||
List<Hierarchy> hierarchies = alertDefineServiceAdapter.getAppHierarchy(app.toLowerCase(), "en-US");
|
||||
|
||||
if (hierarchies == null || hierarchies.isEmpty()) {
|
||||
return String.format("Error: App '%s' not found in hierarchy. Please use list_monitor_types to get valid app names.", app);
|
||||
}
|
||||
|
||||
// Find the metric in the app's hierarchy
|
||||
Hierarchy metricHierarchy = UtilityClass.findMetricInHierarchy(hierarchies, metrics);
|
||||
if (metricHierarchy == null) {
|
||||
return String.format("Error: Metric '%s' not found for app '%s'. Please use get_apps_metrics_hierarchy to get valid metrics for this app.", metrics, app);
|
||||
}
|
||||
|
||||
// Extract field names from field conditions and validate them
|
||||
List<String> fieldNames = UtilityClass.extractFieldNamesFromConditions(fieldConditions);
|
||||
for (String fieldName : fieldNames) {
|
||||
if (!UtilityClass.isFieldValidForMetric(metricHierarchy, fieldName)) {
|
||||
return String.format("Error: Field '%s' not found for metric '%s' in app '%s'. Please use get_apps_metrics_hierarchy to get valid field parameters.", fieldName, metrics, app);
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("Hierarchy validation passed for app={}, metrics={}", app, metrics);
|
||||
return "VALID";
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error during hierarchy validation: {}", e.getMessage(), e);
|
||||
return String.format("Error: Unable to validate hierarchy relationships: %s", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+245
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* 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.ai.agent.tools.impl;
|
||||
|
||||
import com.usthe.sureness.subject.SubjectSum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.ai.agent.adapters.AlertServiceAdapter;
|
||||
import org.apache.hertzbeat.ai.agent.config.McpContextHolder;
|
||||
import org.apache.hertzbeat.ai.agent.tools.AlertTools;
|
||||
import org.apache.hertzbeat.ai.agent.utils.UtilityClass;
|
||||
import org.apache.hertzbeat.alert.dto.AlertSummary;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.ai.tool.annotation.Tool;
|
||||
import org.springframework.ai.tool.annotation.ToolParam;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of Alert Tools functionality for alarm data queries and management
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class AlertToolsImpl implements AlertTools {
|
||||
@Autowired
|
||||
private AlertServiceAdapter alertServiceAdapter;
|
||||
|
||||
@Override
|
||||
@Tool(name = "query_alerts", description = """
|
||||
Query alerts with comprehensive filtering and pagination options.
|
||||
|
||||
ALERT TYPES:
|
||||
- Pass alertType='single' for individual alert instances
|
||||
- Pass alertType='group' for grouped/aggregated alerts
|
||||
- Pass alertType='both' to get both types (separate sections)
|
||||
|
||||
STATUS FILTERING:
|
||||
- 'firing': Currently active alerts requiring attention
|
||||
- 'resolved': Previously active alerts that have been cleared
|
||||
- 'all': Both firing and resolved alerts (default)
|
||||
|
||||
SEARCH & FILTERING:
|
||||
- search: Search in alert content, labels, or descriptions
|
||||
- sort: Order by 'startAt' (trigger time), 'triggerTimes' (frequency), 'status'
|
||||
- order: 'asc' (oldest first) or 'desc' (newest first, default)
|
||||
|
||||
PAGINATION:
|
||||
- pageIndex: Page number starting from 0
|
||||
- pageSize: Number of alerts per page (default: 10, max recommended: 50)
|
||||
|
||||
EXAMPLE AND COMMON USE CASES:
|
||||
- Recent active alerts: alertType='single', status='firing', sort='startAt', order='desc'
|
||||
- Historical analysis: alertType='single', status='resolved', pageSize=50
|
||||
- Alert grouping overview: alertType='group', status='all'
|
||||
- Search specific issues: search='cpu', alertType='single', status='firing'
|
||||
- Find abnormal monitors: status='firing' to get active alerts indicating monitor issues
|
||||
- Monitor-specific alerts: use search parameter with monitor ID or name to find related alerts
|
||||
- Frequent alerts analysis: sort='triggerTimes', order='desc' to find most frequently triggered alerts
|
||||
- Recent recurring issues: status='all', sort='triggerTimes', order='desc', pageSize=20
|
||||
""")
|
||||
public String queryAlerts(
|
||||
@ToolParam(description = "Alert type: 'single' (individual alerts), 'group' (grouped alerts), 'both' (default: single)", required = false) String alertType,
|
||||
@ToolParam(description = "Alert status: 'firing' (active), 'resolved' (cleared), 'all' (default: all)", required = false) String status,
|
||||
@ToolParam(description = "Search term for alert content or labels", required = false) String search,
|
||||
@ToolParam(description = "Sort field: 'startAt', 'triggerTimes', 'status' (default: startAt)", required = false) String sort,
|
||||
@ToolParam(description = "Sort order: 'asc' or 'desc' (default: desc)", required = false) String order,
|
||||
@ToolParam(description = "Page index starting from 0 (default: 0)", required = false) Integer pageIndex,
|
||||
@ToolParam(description = "Page size, 1-50 recommended (default: 10)", required = false) Integer pageSize) {
|
||||
|
||||
try {
|
||||
log.info("Querying alerts: alertType={}, status={}, search={}, sort={}, order={}", alertType, status, search, sort, order);
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current subject in query_alerts tool: {}", subjectSum);
|
||||
|
||||
// Set defaults
|
||||
if (alertType == null || alertType.trim().isEmpty()) {
|
||||
alertType = "single";
|
||||
}
|
||||
if (status == null || status.trim().isEmpty()) {
|
||||
status = "all";
|
||||
}
|
||||
if (sort == null || sort.trim().isEmpty()) {
|
||||
sort = "startAt";
|
||||
}
|
||||
if (order == null || order.trim().isEmpty()) {
|
||||
order = "desc";
|
||||
}
|
||||
if (pageIndex == null) {
|
||||
pageIndex = 0;
|
||||
}
|
||||
if (pageSize == null) {
|
||||
pageSize = 10;
|
||||
}
|
||||
|
||||
StringBuilder response = new StringBuilder();
|
||||
response.append("ALERT QUERY RESULTS\n");
|
||||
response.append("===================\n\n");
|
||||
|
||||
// Handle different alert types
|
||||
if ("single".equalsIgnoreCase(alertType) || "both".equalsIgnoreCase(alertType)) {
|
||||
Page<SingleAlert> singleResult = alertServiceAdapter.getSingleAlerts(status, search, sort, order, pageIndex, pageSize);
|
||||
|
||||
response.append("SINGLE ALERTS:\n");
|
||||
response.append("Found ").append(singleResult.getContent().size()).append(" single alerts (Total: ").append(singleResult.getTotalElements()).append("):\n\n");
|
||||
|
||||
for (SingleAlert alert : singleResult.getContent()) {
|
||||
response.append("Alert ID: ").append(alert.getId()).append("\n");
|
||||
response.append("Status: ").append(alert.getStatus()).append("\n");
|
||||
response.append("Content: ").append(alert.getContent() != null ? alert.getContent() : "No content").append("\n");
|
||||
response.append("Trigger Times: ").append(alert.getTriggerTimes()).append("\n");
|
||||
|
||||
if (alert.getStartAt() != null) {
|
||||
response.append("Started At: ").append(UtilityClass.formatTimestamp(alert.getStartAt())).append("\n");
|
||||
}
|
||||
if (alert.getActiveAt() != null) {
|
||||
response.append("Active At: ").append(UtilityClass.formatTimestamp(alert.getActiveAt())).append("\n");
|
||||
}
|
||||
if (alert.getEndAt() != null) {
|
||||
response.append("Ended At: ").append(UtilityClass.formatTimestamp(alert.getEndAt())).append("\n");
|
||||
}
|
||||
|
||||
if (alert.getLabels() != null && !alert.getLabels().isEmpty()) {
|
||||
response.append("Labels: ").append(alert.getLabels()).append("\n");
|
||||
}
|
||||
response.append("\n");
|
||||
}
|
||||
|
||||
if (singleResult.getContent().isEmpty()) {
|
||||
response.append("No single alerts found matching the specified criteria.\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Handle group alerts
|
||||
if ("group".equalsIgnoreCase(alertType) || "both".equalsIgnoreCase(alertType)) {
|
||||
if ("both".equalsIgnoreCase(alertType)) {
|
||||
response.append("\n");
|
||||
}
|
||||
|
||||
Page<GroupAlert> groupResult = alertServiceAdapter.getGroupAlerts(status, search, sort, order, pageIndex, pageSize);
|
||||
|
||||
response.append("GROUP ALERTS:\n");
|
||||
response.append("Found ").append(groupResult.getContent().size()).append(" group alerts (Total: ").append(groupResult.getTotalElements()).append("):\n\n");
|
||||
|
||||
for (GroupAlert alert : groupResult.getContent()) {
|
||||
response.append("Group Alert ID: ").append(alert.getId()).append("\n");
|
||||
response.append("Status: ").append(alert.getStatus()).append("\n");
|
||||
response.append("Group Key: ").append(alert.getGroupKey() != null ? alert.getGroupKey() : "No group key").append("\n");
|
||||
|
||||
if (alert.getGmtCreate() != null) {
|
||||
response.append("Created At: ").append(alert.getGmtCreate()).append("\n");
|
||||
}
|
||||
if (alert.getGmtUpdate() != null) {
|
||||
response.append("Updated At: ").append(alert.getGmtUpdate()).append("\n");
|
||||
}
|
||||
|
||||
if (alert.getCommonLabels() != null && !alert.getCommonLabels().isEmpty()) {
|
||||
response.append("Common Labels: ").append(alert.getCommonLabels()).append("\n");
|
||||
}
|
||||
if (alert.getCommonAnnotations() != null && !alert.getCommonAnnotations().isEmpty()) {
|
||||
response.append("Annotations: ").append(alert.getCommonAnnotations()).append("\n");
|
||||
}
|
||||
response.append("\n");
|
||||
}
|
||||
|
||||
if (groupResult.getContent().isEmpty()) {
|
||||
response.append("No group alerts found matching the specified criteria.\n");
|
||||
}
|
||||
}
|
||||
|
||||
return response.toString();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to query alerts: {}", e.getMessage(), e);
|
||||
return "Error retrieving alerts: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Tool(name = "get_alerts_summary", description = """
|
||||
Get alerts summary statistics including total counts, status distribution, and recent trends.
|
||||
Returns comprehensive overview of the current alerting status across all monitors.
|
||||
""")
|
||||
public String getAlertsSummary() {
|
||||
try {
|
||||
log.info("Getting alerts summary");
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current subject in get_alerts_summary tool: {}", subjectSum);
|
||||
|
||||
AlertSummary summary = alertServiceAdapter.getAlertsSummary();
|
||||
|
||||
StringBuilder response = new StringBuilder();
|
||||
response.append("ALERTS SUMMARY\n");
|
||||
response.append("==============\n\n");
|
||||
|
||||
if (summary != null) {
|
||||
response.append("Total Alerts: ").append(summary.getTotal()).append("\n");
|
||||
response.append("Handled Alerts: ").append(summary.getDealNum()).append("\n");
|
||||
response.append("Handling Rate: ").append(String.format("%.1f", summary.getRate())).append("%\n\n");
|
||||
|
||||
response.append("Priority Breakdown (Unhandled):\n");
|
||||
response.append("- Critical: ").append(summary.getPriorityCriticalNum()).append("\n");
|
||||
response.append("- Emergency: ").append(summary.getPriorityEmergencyNum()).append("\n");
|
||||
response.append("- Warning: ").append(summary.getPriorityWarningNum()).append("\n\n");
|
||||
|
||||
long totalUnhandled = summary.getPriorityCriticalNum() + summary.getPriorityEmergencyNum() + summary.getPriorityWarningNum();
|
||||
response.append("Total Unhandled Alerts: ").append(totalUnhandled).append("\n");
|
||||
|
||||
if (totalUnhandled > 0) {
|
||||
response.append("\nUnhandled Alert Distribution:\n");
|
||||
response.append("- Critical: ").append(String.format("%.1f", (summary.getPriorityCriticalNum() * 100.0 / totalUnhandled))).append("%\n");
|
||||
response.append("- Emergency: ").append(String.format("%.1f", (summary.getPriorityEmergencyNum() * 100.0 / totalUnhandled))).append("%\n");
|
||||
response.append("- Warning: ").append(String.format("%.1f", (summary.getPriorityWarningNum() * 100.0 / totalUnhandled))).append("%\n");
|
||||
}
|
||||
} else {
|
||||
response.append("No alert summary data available.");
|
||||
}
|
||||
|
||||
return response.toString();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to get alerts summary: {}", e.getMessage(), e);
|
||||
return "Error retrieving alerts summary: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+263
@@ -0,0 +1,263 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.tools.impl;
|
||||
|
||||
import com.usthe.sureness.subject.SubjectSum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.ai.agent.adapters.MetricsServiceAdapter;
|
||||
import org.apache.hertzbeat.ai.agent.adapters.MonitorServiceAdapter;
|
||||
import org.apache.hertzbeat.ai.agent.config.McpContextHolder;
|
||||
import org.apache.hertzbeat.ai.agent.tools.MetricsTools;
|
||||
import org.apache.hertzbeat.common.entity.dto.Field;
|
||||
import org.apache.hertzbeat.common.entity.dto.MetricsData;
|
||||
import org.apache.hertzbeat.common.entity.dto.MetricsHistoryData;
|
||||
import org.apache.hertzbeat.common.entity.dto.Value;
|
||||
import org.apache.hertzbeat.common.entity.dto.ValueRow;
|
||||
import org.springframework.ai.tool.annotation.Tool;
|
||||
import org.springframework.ai.tool.annotation.ToolParam;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Implementation of Metrics Tools functionality for metrics data queries and analysis
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class MetricsToolsImpl implements MetricsTools {
|
||||
@Autowired
|
||||
private MetricsServiceAdapter metricsServiceAdapter;
|
||||
@Autowired
|
||||
private MonitorServiceAdapter monitorServiceAdapter;
|
||||
|
||||
@Override
|
||||
@Tool(name = "query_realtime_metrics", description = """
|
||||
Get the supported monitor types/names from the list_monitor_types tool, make sure to use right name in the next call
|
||||
Use the query_monitors tool to find monitor IDs in case the user does not tell the id explicitly. You might have to use this multiple times based on the user's query
|
||||
Get real-time metrics data for a specific monitor.
|
||||
Returns current metrics values including CPU, memory, disk usage, etc.
|
||||
Based on the monitor type/name, use the get_apps_metrics_hierarchy tool to get the metrics hierarchy. i.e., metrics and the field parameter (sub-metric).
|
||||
Each metric has its submetrics as well for example: cpu has field parameters or sub-metrics like 'usage', 'load', 'core'. These value might be numeric or string
|
||||
User might ask about specific metrics like 'cpu usage', 'memory used', 'disk available', etc.
|
||||
So use this tool to get the real-time metrics data for the monitor- user asked the specific metrics for, along with any logical or numeric conditions if the user mentions
|
||||
In case of multiple monitors matching the user's description, you might have to call query_realtime_metrics tool mutliple times with different parameters.
|
||||
|
||||
|
||||
EXAMPLE WORKFLOW
|
||||
|
||||
If the user asks for 'cpu usage' for a particular monitor or multiple matching monitors like 'web server', 'database server', etc.
|
||||
Get the closest matching monitor name from list_monitor_types tool.
|
||||
Call query_monitors tool to get the monitor ID/IDs with the obtained monitor type
|
||||
Call get_apps_metrics_hierarchy tool to get all the metrics (type=metric) for the obtained monitor type
|
||||
Call query_realtime_metrics tool with each of the IDs and the closest matching metrics name (e.g., 'cpu', 'memory', etc.)
|
||||
From the result, do whatever operation user wants you to do with the data. In this example case display the cpu usage for each matching monitor.
|
||||
""")
|
||||
public String getRealtimeMetrics(
|
||||
@ToolParam(description = "Monitor ID", required = true) Long monitorId,
|
||||
@ToolParam(description = "Metrics name (e.g., 'system', 'cpu', 'memory') obtained from get_apps_metrics_hierarchy result", required = true) String metrics) {
|
||||
try {
|
||||
log.info("Getting real-time metrics for monitor {} and metrics {}", monitorId, metrics);
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current subject in get_realtime_metrics tool: {}", subjectSum);
|
||||
|
||||
MetricsData metricsData = metricsServiceAdapter.getMetricsData(monitorId, metrics);
|
||||
|
||||
if (metricsData == null) {
|
||||
return String.format("No real-time metrics data found for monitor ID %d and metrics '%s'", monitorId, metrics);
|
||||
}
|
||||
|
||||
StringBuilder response = new StringBuilder();
|
||||
response.append("REAL-TIME METRICS DATA\n");
|
||||
response.append("=".repeat(50)).append("\n");
|
||||
response.append("Monitor ID: ").append(monitorId).append("\n");
|
||||
response.append("Metrics: ").append(metrics).append("\n");
|
||||
response.append("=".repeat(50)).append("\n\n");
|
||||
|
||||
if (metricsData.getValueRows() != null && !metricsData.getValueRows().isEmpty()) {
|
||||
List<Field> fields = metricsData.getFields();
|
||||
|
||||
response.append("Available Field Parameters (Sub-metrics):\n");
|
||||
response.append("-".repeat(40)).append("\n");
|
||||
|
||||
for (ValueRow valueRow : metricsData.getValueRows()) {
|
||||
// Show labels if available
|
||||
if (valueRow.getLabels() != null && !valueRow.getLabels().isEmpty()) {
|
||||
response.append("Instance Labels: ").append(valueRow.getLabels()).append("\n");
|
||||
response.append("-".repeat(20)).append("\n");
|
||||
}
|
||||
|
||||
List<Value> values = valueRow.getValues();
|
||||
for (int i = 0; i < values.size() && i < fields.size(); i++) {
|
||||
Field field = fields.get(i);
|
||||
Value value = values.get(i);
|
||||
|
||||
// Enhanced field information display
|
||||
response.append("• Field Parameter: ").append(field.getName());
|
||||
if (field.getUnit() != null && !field.getUnit().isEmpty()) {
|
||||
response.append(" (").append(field.getUnit()).append(")");
|
||||
}
|
||||
response.append("\n");
|
||||
|
||||
response.append(" Current Value: ").append(value.getOrigin());
|
||||
|
||||
// Add data type indication
|
||||
try {
|
||||
Double.parseDouble(value.getOrigin());
|
||||
response.append(" [Numeric]");
|
||||
} catch (NumberFormatException e) {
|
||||
response.append(" [String]");
|
||||
}
|
||||
response.append("\n\n");
|
||||
}
|
||||
|
||||
if (valueRow.getLabels() != null && !valueRow.getLabels().isEmpty()) {
|
||||
response.append("-".repeat(20)).append("\n");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
response.append("No field parameter data available for metrics '").append(metrics).append("'.\n");
|
||||
response.append("Use get_apps_metrics_hierarchy tool to check available metrics for this monitor type.");
|
||||
}
|
||||
|
||||
return response.toString();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to get real-time metrics: {}", e.getMessage(), e);
|
||||
return "Error retrieving real-time metrics: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Tool(name = "get_historical_metrics", description = """
|
||||
Get historical metrics data for analysis and trending.
|
||||
Returns time-series data for specified metrics over a time range.
|
||||
Use the query_monitors tool to find the correct monitor IDs/ name or type for the monitor(s) user asked the metrics for
|
||||
Pass that name into the get_apps_metrics_hierarchy tool to get the metrics hierarchy i.e metrics and the field paramater
|
||||
DO NOT USE THE LABEL FIELD ALWAYS USE THE VALUE FIELD FROM THE HIERARCHY JSON
|
||||
Ask user to provide the filters for labels, history and interval aggregation
|
||||
""")
|
||||
public String getHistoricalMetrics(
|
||||
@ToolParam(description = "Monitor ID", required = true) Long monitorId,
|
||||
@ToolParam(description = "Monitor type (e.g., 'linux', 'mysql', 'http')", required = true) String app,
|
||||
@ToolParam(description = "Metrics name (e.g., 'target', 'cpu', 'memory')", required = true) String metrics,
|
||||
@ToolParam(description = "Field Parameter (e.g., 'usage', 'used', 'available')", required = false) String fieldParameter,
|
||||
@ToolParam(description = "Label filter for specific instances", required = false) String label,
|
||||
@ToolParam(description = "Time range (e.g., '1h', '6h', '24h', '7d')", required = false) String history,
|
||||
@ToolParam(description = "Whether to aggregate data with intervals", required = false) Boolean interval) {
|
||||
|
||||
try {
|
||||
log.info("Getting historical metrics for monitor {} and metrics {}", monitorId, metrics);
|
||||
|
||||
if (history == null || history.trim().isEmpty()) {
|
||||
history = "24h";
|
||||
}
|
||||
if (interval == null) {
|
||||
interval = true;
|
||||
}
|
||||
|
||||
MetricsHistoryData historyData = metricsServiceAdapter.getMetricHistoryData(
|
||||
monitorId, app, metrics, fieldParameter, label, history, interval);
|
||||
|
||||
if (historyData == null) {
|
||||
return String.format("No historical metrics data found for monitor ID %d and metrics '%s'", monitorId, metrics);
|
||||
}
|
||||
|
||||
StringBuilder response = new StringBuilder();
|
||||
response.append("HISTORICAL METRICS: ").append(metrics).append(" (Monitor ID: ").append(monitorId).append(")\n");
|
||||
response.append("Time Range: ").append(history).append(" | Interval Aggregation: ").append(interval).append("\n");
|
||||
response.append("=".repeat(60)).append("\n\n");
|
||||
|
||||
if (historyData.getValues() != null && !historyData.getValues().isEmpty()) {
|
||||
response.append("Field: ").append(historyData.getField() != null ? historyData.getField().getName() : "Unknown").append("\n");
|
||||
|
||||
// Calculate total data points
|
||||
int totalPoints = historyData.getValues().values().stream()
|
||||
.mapToInt(List::size)
|
||||
.sum();
|
||||
response.append("Data Points: ").append(totalPoints).append("\n");
|
||||
|
||||
// Show sample data points (first 10) from all value lists
|
||||
int count = 0;
|
||||
response.append("\nSample Data Points:\n");
|
||||
for (Map.Entry<String, List<Value>> entry : historyData.getValues().entrySet()) {
|
||||
String labelKey = entry.getKey();
|
||||
List<Value> values = entry.getValue();
|
||||
|
||||
for (Value value : values) {
|
||||
if (count >= 10) break;
|
||||
response.append("Label: ").append(labelKey)
|
||||
.append(" | Time: ").append(value.getTime())
|
||||
.append(" | Value: ").append(value.getOrigin()).append("\n");
|
||||
count++;
|
||||
}
|
||||
if (count >= 10) break;
|
||||
}
|
||||
|
||||
if (totalPoints > 10) {
|
||||
response.append("... and ").append(totalPoints - 10).append(" more data points\n");
|
||||
}
|
||||
} else {
|
||||
response.append("No historical data points available.");
|
||||
}
|
||||
|
||||
return response.toString();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to get historical metrics: {}", e.getMessage(), e);
|
||||
return "Error retrieving historical metrics: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Tool(name = "get_warehouse_status", description = """
|
||||
Check the status of the metrics storage warehouse system.
|
||||
Returns whether the metrics storage is operational and accessible.
|
||||
""")
|
||||
public String getWarehouseStatus() {
|
||||
try {
|
||||
log.info("Checking warehouse storage status");
|
||||
|
||||
Boolean status = metricsServiceAdapter.getWarehouseStorageServerStatus();
|
||||
|
||||
StringBuilder response = new StringBuilder();
|
||||
response.append("METRICS WAREHOUSE STATUS\n");
|
||||
response.append("========================\n\n");
|
||||
|
||||
if (status != null && status) {
|
||||
response.append("Status: ONLINE ✓\n");
|
||||
response.append("The metrics storage warehouse is operational and accessible.\n");
|
||||
response.append("Historical metrics data queries are available.");
|
||||
} else {
|
||||
response.append("Status: OFFLINE ✗\n");
|
||||
response.append("The metrics storage warehouse is not accessible.\n");
|
||||
response.append("Only real-time metrics may be available.");
|
||||
}
|
||||
|
||||
return response.toString();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to get warehouse status: {}", e.getMessage(), e);
|
||||
return "Error checking warehouse status: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+491
@@ -0,0 +1,491 @@
|
||||
/*
|
||||
* 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.ai.agent.tools.impl;
|
||||
|
||||
import com.usthe.sureness.subject.SubjectSum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.ai.agent.adapters.MonitorServiceAdapter;
|
||||
import org.apache.hertzbeat.ai.agent.config.McpContextHolder;
|
||||
import org.apache.hertzbeat.ai.agent.utils.UtilityClass;
|
||||
import org.springframework.ai.tool.annotation.Tool;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.apache.hertzbeat.ai.agent.tools.MonitorTools;
|
||||
import org.springframework.ai.tool.annotation.ToolParam;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.apache.hertzbeat.common.entity.manager.Monitor;
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Implementation of Monitoring Tools functionality
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class MonitorToolsImpl implements MonitorTools {
|
||||
@Autowired
|
||||
private MonitorServiceAdapter monitorServiceAdapter;
|
||||
|
||||
|
||||
/**
|
||||
* Tool to query monitor information with flexible filtering and pagination.
|
||||
* Supports filtering by monitor IDs, type, status, host, labels, sorting, and
|
||||
* pagination.
|
||||
* Returns detailed monitor information including ID, name, type, host, and status.
|
||||
*/
|
||||
@Override
|
||||
@Tool(name = "query_monitors", description = """
|
||||
Query Existing/configured monitors in HertzBeat.
|
||||
This tool retrieves monitors based on various filters and parameters.
|
||||
Comprehensive monitor querying with flexible filtering, pagination, and specialized views.
|
||||
|
||||
MONITOR STATUSES:
|
||||
- status=1: Online/Active monitors (healthy, responding normally)
|
||||
- status=2: Offline monitors (not responding, connection failed)
|
||||
- status=3: Unreachable monitors (network/connectivity issues)
|
||||
- status=0: Paused monitors (manually disabled/suspended)
|
||||
- status=9 or null: All monitors regardless of status (default)
|
||||
|
||||
COMMON USE CASES & PARAMETER COMBINATIONS:
|
||||
|
||||
1. BASIC MONITOR LISTING:
|
||||
- Default: No parameters (shows all monitors, 8 per page)
|
||||
- By type: app='linux' (show only Linux monitors)
|
||||
- Search: search='web' (find monitors with 'web' in name/host)
|
||||
|
||||
2. STATUS-BASED QUERIES:
|
||||
- Healthy monitors: status=1, pageSize=50
|
||||
- Problem monitors: status=2 or status=3, pageSize=50
|
||||
- Offline monitors only: status=2
|
||||
- Unreachable monitors only: status=3
|
||||
- Paused monitors: status=0
|
||||
|
||||
3. MONITORING HEALTH OVERVIEW:
|
||||
- All statuses with statistics: status=9, includeStats=true, pageSize=100
|
||||
- Unhealthy monitors: Pass both status=2 AND status=3 (make 2 separate calls)
|
||||
|
||||
4. ADVANCED FILTERING:
|
||||
- Specific monitor types: app='mysql', status=1 (healthy MySQL monitors)
|
||||
- Label-based: labels='env:prod,critical:true'
|
||||
- Host search: search='192.168' (find by IP pattern)
|
||||
- Monitor IDs: ids=[1,2,3] (specific monitors by ID)
|
||||
|
||||
5. SORTING & PAGINATION:
|
||||
- Recently updated: sort='gmtUpdate', order='desc'
|
||||
- Alphabetical: sort='name', order='asc'
|
||||
- By creation: sort='gmtCreate', order='desc' (newest first)
|
||||
- Large datasets: pageSize=50-100 for bulk operations
|
||||
|
||||
RESPONSE FORMAT:
|
||||
- includeStats=true: Adds status distribution summary at top
|
||||
- Default: Simple list with ID, name, type, host, status
|
||||
- Shows total count and pagination info
|
||||
""")
|
||||
public String queryMonitors(
|
||||
@ToolParam(description = "Specific monitor IDs to retrieve (optional)", required = false) List<Long> ids,
|
||||
@ToolParam(description = "Monitor type filter: 'linux', 'mysql', 'http', 'redis', etc. (optional)", required = false) String app,
|
||||
@ToolParam(description = "Monitor status: 1=online, 2=offline, 3=unreachable, 0=paused, 9=all (default: 9)", required = false) Byte status,
|
||||
@ToolParam(description = "Search in monitor names or hosts (partial matching)", required = false) String search,
|
||||
@ToolParam(description = "Label filters, format: 'key1:value1,key2:value2'", required = false) String labels,
|
||||
@ToolParam(description = "Sort field: 'name', 'gmtCreate', 'gmtUpdate', 'status', 'app' (default: gmtCreate)", required = false) String sort,
|
||||
@ToolParam(description = "Sort order: 'asc' (ascending) or 'desc' (descending, default)", required = false) String order,
|
||||
@ToolParam(description = "Page number starting from 0 (default: 0)", required = false) Integer pageIndex,
|
||||
@ToolParam(description = "Items per page: 1-100 recommended (default: 20)", required = false) Integer pageSize,
|
||||
@ToolParam(description = "Include status statistics summary (default: false)", required = false) Boolean includeStats) {
|
||||
try {
|
||||
// Set defaults
|
||||
if (pageSize == null || pageSize <= 0) {
|
||||
pageSize = 20;
|
||||
}
|
||||
if (pageIndex == null) {
|
||||
pageIndex = 0;
|
||||
}
|
||||
if (includeStats == null) {
|
||||
includeStats = false;
|
||||
}
|
||||
|
||||
Page<Monitor> result = monitorServiceAdapter.getMonitors(ids, app, search, status, sort, order, pageIndex, pageSize, labels);
|
||||
log.debug("MonitorServiceAdapter.getMonitors result: {}", result);
|
||||
|
||||
StringBuilder response = new StringBuilder();
|
||||
response.append("MONITOR QUERY RESULTS\n");
|
||||
response.append("====================\n\n");
|
||||
|
||||
// Include statistics if requested
|
||||
if (includeStats) {
|
||||
// Get status distribution by calling with different status values
|
||||
long onlineCount = monitorServiceAdapter.getMonitors(null, app, search, (byte) 1, null, null, 0, 1000, labels).getTotalElements();
|
||||
long offlineCount = monitorServiceAdapter.getMonitors(null, app, search, (byte) 2, null, null, 0, 1000, labels).getTotalElements();
|
||||
long unreachableCount = monitorServiceAdapter.getMonitors(null, app, search, (byte) 3, null, null, 0, 1000, labels).getTotalElements();
|
||||
long pausedCount = monitorServiceAdapter.getMonitors(null, app, search, (byte) 0, null, null, 0, 1000, labels).getTotalElements();
|
||||
|
||||
response.append("STATUS OVERVIEW:\n");
|
||||
response.append("- Online: ").append(onlineCount).append("\n");
|
||||
response.append("- Offline: ").append(offlineCount).append("\n");
|
||||
response.append("- Unreachable: ").append(unreachableCount).append("\n");
|
||||
response.append("- Paused: ").append(pausedCount).append("\n");
|
||||
|
||||
long total = onlineCount + offlineCount + unreachableCount + pausedCount;
|
||||
if (total > 0) {
|
||||
double healthPercentage = (onlineCount * 100.0) / total;
|
||||
response.append("- Health Rate: ").append(String.format("%.1f", healthPercentage)).append("%\n");
|
||||
}
|
||||
response.append("\n");
|
||||
}
|
||||
|
||||
response.append("Query Results: ").append(result.getContent().size())
|
||||
.append(" monitors (Total: ").append(result.getTotalElements()).append(")\n");
|
||||
|
||||
if (result.getTotalPages() > 1) {
|
||||
response.append("Page ").append(pageIndex + 1).append(" of ").append(result.getTotalPages()).append("\n");
|
||||
}
|
||||
response.append("\n");
|
||||
|
||||
for (Monitor monitor : result.getContent()) {
|
||||
response.append("ID: ").append(monitor.getId())
|
||||
.append(" | Name: ").append(monitor.getName())
|
||||
.append(" | Type: ").append(monitor.getApp())
|
||||
.append(" | Host: ").append(monitor.getHost())
|
||||
.append(" | Status: ").append(UtilityClass.getStatusText(monitor.getStatus()));
|
||||
|
||||
// Add creation date for better context
|
||||
if (monitor.getGmtCreate() != null) {
|
||||
response.append(" | Created: ").append(monitor.getGmtCreate().toString(), 0, 10);
|
||||
}
|
||||
response.append("\n");
|
||||
}
|
||||
|
||||
if (result.getContent().isEmpty()) {
|
||||
response.append("No monitors found matching the specified criteria.\n");
|
||||
response.append("Try adjusting your filters or search terms.");
|
||||
}
|
||||
|
||||
return response.toString();
|
||||
} catch (Exception e) {
|
||||
return "Error retrieving monitors: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Tool(name = "add_monitor", description = """
|
||||
Add a new monitoring target to HertzBeat with comprehensive configuration.
|
||||
This tool dynamically handles different parameter requirements for each monitor type.
|
||||
|
||||
This tool creates monitors with proper app-specific parameters.
|
||||
|
||||
*********
|
||||
VERY IMPORTANT:
|
||||
ALWAYS use get_monitor_additional_params to check the additional required parameters for the chosen type before adding a monitor or even mentioning it.
|
||||
Use list_monitor_types tool to see available monitor type names to use here in the app parameter.
|
||||
Use the information obtained from this to query user for parameters.
|
||||
If the User has not given any parameters, ask them to provide the necessary parameters, until all the necessary parameters are provided.
|
||||
**********
|
||||
|
||||
Examples of natural language requests this tool handles:
|
||||
- "Monitor website example.com with HTTPS on port 443"
|
||||
- "Add MySQL monitoring for database server at 192.168.1.10 with user admin"
|
||||
- "Monitor Linux server health on host server.company.com via SSH"
|
||||
- "Set up Redis monitoring on localhost port 6379 with password"
|
||||
|
||||
PARAMETER MAPPING: The tool intelligently maps common parameters:
|
||||
- host: Target server/domain
|
||||
- port: Service port (auto-detected if not specified)
|
||||
- username: Authentication username
|
||||
- password: Authentication password
|
||||
- database: Database name (for DB monitors)
|
||||
- additionalParams: JSON string for app-specific parameters (to be obtained from get_monitor_param_defines)
|
||||
|
||||
ADDITIONAL PARAMETERS EXAMPLES:
|
||||
- Website: {"uri":"/api/health", "ssl":"true", "method":"POST"}
|
||||
- Linux: {"privateKey":"ssh-key-content", "script":"custom-script"}
|
||||
- Database: {"url":"jdbc:mysql://custom", "timeout":"10000"}
|
||||
""")
|
||||
public String addMonitor(
|
||||
@ToolParam(description = "Monitor name (required)", required = true) String name,
|
||||
@ToolParam(description = "Monitor type: website, mysql, postgresql, redis, linux, windows, etc.", required = true) String app,
|
||||
@ToolParam(description = "Target host: IP address or domain name", required = true) String host,
|
||||
@ToolParam(description = "Target port (optional, auto-detected if not specified)", required = false) Integer port,
|
||||
@ToolParam(description = "Collection interval in seconds (default: 600)", required = false) Integer intervals,
|
||||
@ToolParam(description = "Username for authentication (optional)", required = false) String username,
|
||||
@ToolParam(description = "Password for authentication (optional)", required = false) String password,
|
||||
@ToolParam(description = "Database name (for database monitors)", required = false) String database,
|
||||
@ToolParam(description = "Additional app-specific parameters as JSON: {\"uri\":\"/api\", \"ssl\":\"true\", \"method\":\"POST\"}", required = false) String additionalParams,
|
||||
@ToolParam(description = "Monitor description (optional)", required = false) String description) {
|
||||
|
||||
try {
|
||||
log.info("Adding monitor: name={}, app={}, host={}", name, app, host);
|
||||
|
||||
// Validate required parameters
|
||||
if (name == null || name.trim().isEmpty()) {
|
||||
return "Error: Monitor name is required";
|
||||
}
|
||||
if (app == null || app.trim().isEmpty()) {
|
||||
return "Error: Monitor type/application is required";
|
||||
}
|
||||
if (host == null || host.trim().isEmpty()) {
|
||||
return "Error: Host is required";
|
||||
}
|
||||
|
||||
// Set defaults
|
||||
if (intervals == null || intervals < 10) {
|
||||
intervals = 600;
|
||||
}
|
||||
|
||||
// Create Monitor entity
|
||||
Monitor monitor = Monitor.builder()
|
||||
.name(name.trim())
|
||||
.app(app.toLowerCase().trim())
|
||||
.host(host.trim())
|
||||
.intervals(intervals)
|
||||
.status((byte) 1)
|
||||
.type((byte) 0)
|
||||
.description(description != null ? description.trim() : "")
|
||||
.build();
|
||||
|
||||
List<Param> params = createBasicParams(host, port, username, password, database, additionalParams);
|
||||
|
||||
// Validate that all required parameters for this monitor type are provided
|
||||
try {
|
||||
List<ParamDefine> requiredParams = monitorServiceAdapter.getMonitorParamDefines(app);
|
||||
log.info("Checking required parameters for monitor type '{}': {}", app, requiredParams);
|
||||
List<String> missingParams = new ArrayList<>();
|
||||
|
||||
for (ParamDefine paramDefine : requiredParams) {
|
||||
if (paramDefine.isRequired()) {
|
||||
String fieldName = paramDefine.getField();
|
||||
boolean hasParam = params.stream()
|
||||
.anyMatch(param -> fieldName.equals(param.getField()));
|
||||
if (!hasParam) {
|
||||
missingParams.add(fieldName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!missingParams.isEmpty()) {
|
||||
return String.format("Error: Missing required parameters for monitor type '%s': %s. "
|
||||
+ "Use get_monitor_additional_params tool to see all required parameters.",
|
||||
app, String.join(", ", missingParams));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Could not validate required parameters for monitor type '{}': {}", app, e.getMessage());
|
||||
}
|
||||
|
||||
// Call adapter - it handles all the complexity (validation, defaults, app-specific logic)
|
||||
Long monitorId = monitorServiceAdapter.addMonitor(monitor, params, null);
|
||||
|
||||
log.info("Successfully added monitor '{}' with ID: {}", name, monitorId);
|
||||
return String.format("Successfully added %s monitor '%s' with ID: %d (Host: %s, Interval: %d seconds)",
|
||||
app.toUpperCase(), name, monitorId, host, intervals);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to add monitor '{}': {}", name, e.getMessage(), e);
|
||||
return "Error adding monitor '" + name + "': " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create basic parameter list from user inputs
|
||||
*/
|
||||
private List<Param> createBasicParams(String host, Integer port, String username,
|
||||
String password, String database, String additionalParams) {
|
||||
List<Param> params = new ArrayList<>();
|
||||
|
||||
// Add host (always required)
|
||||
params.add(Param.builder().field("host").paramValue(host.trim()).type((byte) 1).build());
|
||||
|
||||
// Add optional common parameters
|
||||
if (port != null) {
|
||||
params.add(Param.builder().field("port").paramValue(port.toString()).type((byte) 0).build());
|
||||
}
|
||||
if (username != null && !username.trim().isEmpty()) {
|
||||
params.add(Param.builder().field("username").paramValue(username.trim()).type((byte) 1).build());
|
||||
}
|
||||
if (password != null && !password.trim().isEmpty()) {
|
||||
params.add(Param.builder().field("password").paramValue(password.trim()).type((byte) 2).build());
|
||||
}
|
||||
if (database != null && !database.trim().isEmpty()) {
|
||||
params.add(Param.builder().field("database").paramValue(database.trim()).type((byte) 1).build());
|
||||
}
|
||||
|
||||
// Parse additional parameters if provided
|
||||
if (additionalParams != null && !additionalParams.trim().isEmpty()) {
|
||||
try {
|
||||
String cleaned = additionalParams.trim().replaceAll("[{}]", "");
|
||||
String[] pairs = cleaned.split(",");
|
||||
for (String pair : pairs) {
|
||||
String[] keyValue = pair.split(":");
|
||||
if (keyValue.length == 2) {
|
||||
String key = keyValue[0].trim().replaceAll("\"", "");
|
||||
String value = keyValue[1].trim().replaceAll("\"", "");
|
||||
params.add(Param.builder().field(key).paramValue(value).type((byte) 1).build());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to parse additionalParams: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Tool(name = "list_monitor_types", description = """
|
||||
List all available monitor types that can be added to HertzBeat.
|
||||
This tool shows all supported monitor types with their display names.
|
||||
Use this to see what types of monitors you can create with the add_monitor tool.
|
||||
""")
|
||||
public String listMonitorTypes(
|
||||
@ToolParam(description = "Language code for localized names (en-US, zh-CN, etc.). Default: en-US", required = false) String language) {
|
||||
|
||||
try {
|
||||
log.info("Listing available monitor types for language: {}", language);
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current subject in list_monitor_types tool: {}", subjectSum);
|
||||
|
||||
// Set default language if not provided
|
||||
if (language == null || language.trim().isEmpty()) {
|
||||
language = "en-US";
|
||||
}
|
||||
|
||||
// Get available monitor types from adapter
|
||||
Map<String, String> monitorTypes = monitorServiceAdapter.getAvailableMonitorTypes(language);
|
||||
|
||||
if (monitorTypes == null || monitorTypes.isEmpty()) {
|
||||
return "No monitor types are currently available.";
|
||||
}
|
||||
|
||||
// Format the response as a nice list
|
||||
StringBuilder response = new StringBuilder();
|
||||
response.append("Available Monitor Types (Total: ").append(monitorTypes.size()).append("):\n\n");
|
||||
|
||||
// Sort monitor types alphabetically by key
|
||||
List<Map.Entry<String, String>> sortedTypes = monitorTypes.entrySet().stream()
|
||||
.sorted(Map.Entry.comparingByKey())
|
||||
.toList();
|
||||
|
||||
for (Map.Entry<String, String> entry : sortedTypes) {
|
||||
String typeKey = entry.getKey();
|
||||
String displayName = entry.getValue();
|
||||
response.append("• ").append(typeKey)
|
||||
.append(" - ").append(displayName)
|
||||
.append("\n");
|
||||
}
|
||||
|
||||
response.append("\nTo add a monitor, use the add_monitor tool with one of these types as the 'app' parameter.");
|
||||
|
||||
log.info("Successfully listed {} monitor types", monitorTypes);
|
||||
return response.toString();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to list monitor types: {}", e.getMessage(), e);
|
||||
return "Error retrieving monitor types: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Tool(name = "get_monitor_additional_params", description = """
|
||||
Get the parameter definitions required for a specific monitor type.
|
||||
This tool shows what parameters are needed when adding a monitor of the specified type,
|
||||
ALWAYS use this before adding a monitor to understand what parameters the user needs to provide.
|
||||
Use the app parameter to specify the monitor type/application name (e.g., 'linux', 'mysql', 'redis') this can be obtained from the list_monitor_types tool.
|
||||
""")
|
||||
public String getMonitorAdditionalParams(
|
||||
@ToolParam(description = "Monitor type/application name (e.g., 'linux', 'mysql', 'redis')", required = true) String app) {
|
||||
|
||||
try {
|
||||
log.info("Getting parameter definitions for monitor type: {}", app);
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current subject in get_monitor_param_defines tool: {}", subjectSum);
|
||||
|
||||
// Validate required parameter
|
||||
if (app == null || app.trim().isEmpty()) {
|
||||
return "Error: Monitor type/application parameter is required";
|
||||
}
|
||||
|
||||
// Get parameter definitions from adapter
|
||||
List<ParamDefine> paramDefines = monitorServiceAdapter.getMonitorParamDefines(app);
|
||||
|
||||
if (paramDefines == null || paramDefines.isEmpty()) {
|
||||
return String.format("No parameter definitions found for monitor type '%s'. "
|
||||
+ "This monitor type may not exist or may not require additional parameters.", app);
|
||||
}
|
||||
|
||||
// Format the response
|
||||
StringBuilder response = new StringBuilder();
|
||||
response.append(String.format("Parameter Definitions for Monitor Type '%s' (Total: %d):\n\n",
|
||||
app, paramDefines.size()));
|
||||
|
||||
for (ParamDefine paramDefine : paramDefines) {
|
||||
response.append("• Field: ").append(paramDefine.getField()).append("\n");
|
||||
|
||||
// Add display name if available
|
||||
if (paramDefine.getName() != null && !paramDefine.getName().toString().trim().isEmpty()) {
|
||||
response.append(" Name: ").append(paramDefine.getName()).append("\n");
|
||||
}
|
||||
|
||||
// Add type
|
||||
if (paramDefine.getType() != null && !paramDefine.getType().trim().isEmpty()) {
|
||||
response.append(" Type: ").append(paramDefine.getType()).append("\n");
|
||||
}
|
||||
|
||||
// Add required status
|
||||
response.append(" Required: ").append(paramDefine.isRequired() ? "Yes" : "No").append("\n");
|
||||
|
||||
// Add default value if present
|
||||
if (paramDefine.getDefaultValue() != null && !paramDefine.getDefaultValue().trim().isEmpty()) {
|
||||
response.append(" Default: ").append(paramDefine.getDefaultValue()).append("\n");
|
||||
}
|
||||
|
||||
// Add validation range if present
|
||||
if (paramDefine.getRange() != null && !paramDefine.getRange().trim().isEmpty()) {
|
||||
response.append(" Range: ").append(paramDefine.getRange()).append("\n");
|
||||
}
|
||||
|
||||
// Add limit if present
|
||||
if (paramDefine.getLimit() != null) {
|
||||
response.append(" Limit: ").append(paramDefine.getLimit()).append("\n");
|
||||
}
|
||||
|
||||
// Add placeholder text if present
|
||||
if (paramDefine.getPlaceholder() != null && !paramDefine.getPlaceholder().trim().isEmpty()) {
|
||||
response.append(" Placeholder: ").append(paramDefine.getPlaceholder()).append("\n");
|
||||
}
|
||||
|
||||
response.append("\n");
|
||||
}
|
||||
|
||||
response.append("To add a monitor of this type, use the add_monitor tool with these parameters.\n");
|
||||
response.append(String.format("Example: add_monitor(name='my-monitor', app='%s', host='your-host', ...)", app));
|
||||
|
||||
log.info("Successfully retrieved {} parameter definitions for monitor type: {}", paramDefines.size(), app);
|
||||
return response.toString();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to get parameter definitions for monitor type '{}': {}", app, e.getMessage(), e);
|
||||
return "Error retrieving parameter definitions for monitor type '" + app + "': " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
+691
@@ -0,0 +1,691 @@
|
||||
/*
|
||||
* 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.ai.agent.utils;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.Hierarchy;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Utility class providing helper methods for field expression validation, parsing,
|
||||
* hierarchy management, timestamp formatting and metric/status conversions.
|
||||
* This class contains stateless utility methods used across the application for
|
||||
* common operations and validations.
|
||||
*/
|
||||
@Slf4j
|
||||
@lombok.experimental.UtilityClass
|
||||
public class UtilityClass {
|
||||
|
||||
/**
|
||||
* Validates the syntax of field conditions expression
|
||||
* @param fieldConditions Field conditions string to validate
|
||||
* @return "VALID" if syntax is correct, error message otherwise
|
||||
*/
|
||||
|
||||
public String validateExpressionSyntax(String fieldConditions) {
|
||||
try {
|
||||
log.debug("Validating expression syntax: {}", fieldConditions);
|
||||
|
||||
// Check for basic syntax requirements
|
||||
if (fieldConditions == null || fieldConditions.trim().isEmpty()) {
|
||||
return "Error: Field conditions cannot be empty";
|
||||
}
|
||||
|
||||
// Check for balanced parentheses
|
||||
if (!hasBalancedParentheses(fieldConditions)) {
|
||||
return "Error: Unbalanced parentheses in field conditions. Please check your expression syntax.";
|
||||
}
|
||||
|
||||
// Validate operators used in the expression
|
||||
String operatorValidation = validateOperators(fieldConditions);
|
||||
if (!operatorValidation.equals("VALID")) {
|
||||
return operatorValidation;
|
||||
}
|
||||
|
||||
// Validate logical connectors
|
||||
String logicalValidation = validateLogicalConnectors(fieldConditions);
|
||||
if (!logicalValidation.equals("VALID")) {
|
||||
return logicalValidation;
|
||||
}
|
||||
|
||||
// Validate function syntax (equals, contains, etc.)
|
||||
String functionValidation = validateFunctions(fieldConditions);
|
||||
if (!functionValidation.equals("VALID")) {
|
||||
return functionValidation;
|
||||
}
|
||||
|
||||
log.debug("Expression syntax validation passed for: {}", fieldConditions);
|
||||
return "VALID";
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error during expression syntax validation: {}", e.getMessage(), e);
|
||||
return String.format("Error: Unable to validate expression syntax: %s", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if parentheses are balanced in the expression
|
||||
*/
|
||||
public boolean hasBalancedParentheses(String expression) {
|
||||
int count = 0;
|
||||
for (char c : expression.toCharArray()) {
|
||||
if (c == '(') {
|
||||
count++;
|
||||
} else if (c == ')') {
|
||||
count--;
|
||||
if (count < 0) {
|
||||
return false; // More closing than opening
|
||||
}
|
||||
}
|
||||
}
|
||||
return count == 0; // Should be perfectly balanced
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that only supported operators are used
|
||||
*/
|
||||
public String validateOperators(String fieldConditions) {
|
||||
// Define supported operators for different field types
|
||||
String[] numericOperators = {">", "<", ">=", "<=", "==", "!=", "exists()", "!exists()"};
|
||||
String[] stringOperators = {"equals(", "contains(", "matches(", "exists()", "!equals(", "!contains(", "!matches(", "!exists()"};
|
||||
String[] logicalOperators = {" and ", " or "};
|
||||
|
||||
// Remove quotes and function calls temporarily for operator checking
|
||||
String tempExpression = fieldConditions
|
||||
.replaceAll("\"[^\"]*\"", "VALUE") // Remove quoted strings
|
||||
.replaceAll("'[^']*'", "VALUE") // Remove single quoted strings
|
||||
.replaceAll("\\w+\\([^)]*\\)", "FUNCTION"); // Remove function calls
|
||||
|
||||
// Check for invalid operators (common mistakes)
|
||||
String[] invalidOperators = {"&&", "||", "AND", "OR", "=", "!="};
|
||||
for (String invalidOp : invalidOperators) {
|
||||
if (tempExpression.contains(invalidOp)) {
|
||||
if (invalidOp.equals("&&") || invalidOp.equals("||")) {
|
||||
return String.format("Error: Use 'and'/'or' instead of '%s' for logical operations", invalidOp);
|
||||
}
|
||||
if (invalidOp.equals("AND") || invalidOp.equals("OR")) {
|
||||
return String.format("Error: Use lowercase '%s' for logical operations", invalidOp.toLowerCase());
|
||||
}
|
||||
if (invalidOp.equals("=")) {
|
||||
return "Error: Use '==' for equality comparison, not '='";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for unsupported special characters that might indicate syntax errors
|
||||
if (tempExpression.matches(".*[#$%^&*+\\[\\]{}|\\\\;:'\"`~].*")) {
|
||||
return "Error: Expression contains unsupported special characters. Use only supported operators and functions.";
|
||||
}
|
||||
|
||||
return "VALID";
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates logical connectors syntax
|
||||
*/
|
||||
public String validateLogicalConnectors(String fieldConditions) {
|
||||
// Check for proper spacing around logical operators
|
||||
if (fieldConditions.matches(".*(\\S(and|or)\\S).*")) {
|
||||
return "Error: Logical operators 'and'/'or' must be surrounded by spaces";
|
||||
}
|
||||
|
||||
// Check for consecutive logical operators
|
||||
if (fieldConditions.matches(".*(and\\s+and|or\\s+or|and\\s+or\\s+and|or\\s+and\\s+or).*")) {
|
||||
return "Error: Consecutive logical operators found. Use parentheses to group conditions properly.";
|
||||
}
|
||||
|
||||
// Check for logical operators at the beginning or end
|
||||
String trimmed = fieldConditions.trim();
|
||||
if (trimmed.startsWith("and ") || trimmed.startsWith("or ")
|
||||
|| trimmed.endsWith(" and") || trimmed.endsWith(" or")) {
|
||||
return "Error: Expression cannot start or end with logical operators 'and'/'or'";
|
||||
}
|
||||
|
||||
return "VALID";
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates function syntax (equals, contains, matches, etc.)
|
||||
*/
|
||||
public String validateFunctions(String fieldConditions) {
|
||||
// Check for properly formed function calls
|
||||
String[] supportedFunctions = {"equals", "contains", "matches", "exists", "!equals", "!contains", "!matches", "!exists"};
|
||||
|
||||
// Find all function-like patterns
|
||||
java.util.regex.Pattern functionPattern = java.util.regex.Pattern.compile("(!?\\w+)\\s*\\(([^)]*)\\)");
|
||||
java.util.regex.Matcher matcher = functionPattern.matcher(fieldConditions);
|
||||
|
||||
while (matcher.find()) {
|
||||
String functionName = matcher.group(1);
|
||||
String functionArgs = matcher.group(2);
|
||||
|
||||
// Check if function is supported
|
||||
boolean isSupported = false;
|
||||
for (String supportedFunc : supportedFunctions) {
|
||||
if (functionName.equals(supportedFunc)) {
|
||||
isSupported = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isSupported) {
|
||||
return String.format("Error: Unsupported function '%s'. Supported functions: %s",
|
||||
functionName, String.join(", ", supportedFunctions));
|
||||
}
|
||||
|
||||
// Validate function arguments
|
||||
if (functionName.equals("exists") || functionName.equals("!exists")) {
|
||||
// exists() should have one parameter or no parameters
|
||||
String[] args = functionArgs.trim().isEmpty() ? new String[0] : functionArgs.split(",");
|
||||
if (args.length > 1) {
|
||||
return String.format("Error: Function '%s' should have at most one parameter", functionName);
|
||||
}
|
||||
} else {
|
||||
// Other functions should have exactly 2 parameters
|
||||
String[] args = functionArgs.split(",");
|
||||
if (args.length != 2) {
|
||||
return String.format("Error: Function '%s' requires exactly 2 parameters (field, value)", functionName);
|
||||
}
|
||||
|
||||
// Check that parameters are not empty
|
||||
for (String arg : args) {
|
||||
if (arg.trim().isEmpty()) {
|
||||
return String.format("Error: Function '%s' has empty parameter", functionName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "VALID";
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to validate operator
|
||||
*/
|
||||
public boolean isValidOperator(String operator) {
|
||||
return operator != null && (operator.equals(">") || operator.equals("<")
|
||||
|| operator.equals(">=") || operator.equals("<=")
|
||||
|| operator.equals("==") || operator.equals("!="));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to validate priority
|
||||
*/
|
||||
public boolean isValidPriority(String priority) {
|
||||
return priority != null && (priority.equalsIgnoreCase("critical")
|
||||
|| priority.equalsIgnoreCase("warning") || priority.equalsIgnoreCase("info"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to build expression
|
||||
*/
|
||||
public String buildExpression(String metric, String operator, String threshold) {
|
||||
return String.format("%s %s %s", metric, operator, threshold);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to parse existing expression into components
|
||||
*/
|
||||
public String[] parseExpression(String expression) {
|
||||
if (expression == null || expression.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Simple parsing for basic expressions like "metric > value"
|
||||
String[] operators = {">", "<", ">=", "<=", "==", "!="};
|
||||
for (String op : operators) {
|
||||
if (expression.contains(" " + op + " ")) {
|
||||
String[] parts = expression.split(" " + op + " ");
|
||||
if (parts.length == 2) {
|
||||
return new String[]{parts[0].trim(), op, parts[1].trim()};
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to parse key-value pairs from a string
|
||||
* Format: "key1:value1, key2:value2, ..."
|
||||
*/
|
||||
public Map<String, String> parseKeyValuePairs(String input) {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
if (input == null || input.trim().isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
String[] pairs = input.split(",");
|
||||
for (String pair : pairs) {
|
||||
String[] keyValue = pair.split(":");
|
||||
if (keyValue.length == 2) {
|
||||
result.put(keyValue[0].trim(), keyValue[1].trim());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Recursively searches for a metric in the hierarchy
|
||||
*/
|
||||
public Hierarchy findMetricInHierarchy(List<Hierarchy> hierarchies, String metricName) {
|
||||
for (Hierarchy hierarchy : hierarchies) {
|
||||
// Check if this is the metric we're looking for
|
||||
if (metricName.equals(hierarchy.getValue())) {
|
||||
// Verify it has field children (leaf nodes)
|
||||
if (hierarchy.getChildren() != null && !hierarchy.getChildren().isEmpty()) {
|
||||
boolean hasLeafChildren = hierarchy.getChildren().stream()
|
||||
.anyMatch(child -> child.getIsLeaf() != null && child.getIsLeaf());
|
||||
if (hasLeafChildren) {
|
||||
return hierarchy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively search in children
|
||||
if (hierarchy.getChildren() != null) {
|
||||
Hierarchy found = findMetricInHierarchy(hierarchy.getChildren(), metricName);
|
||||
if (found != null) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a field is valid for the given metric
|
||||
*/
|
||||
public boolean isFieldValidForMetric(Hierarchy metricHierarchy, String fieldName) {
|
||||
if (metricHierarchy.getChildren() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Hierarchy child : metricHierarchy.getChildren()) {
|
||||
if (child.getIsLeaf() != null && child.getIsLeaf() && fieldName.equals(child.getValue())) {
|
||||
return true;
|
||||
}
|
||||
// Also check nested children
|
||||
if (child.getChildren() != null && isFieldValidForMetric(child, fieldName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts field names from field conditions string
|
||||
* Handles simple cases like "field > 80", "equals(field, 'value')", complex expressions
|
||||
*/
|
||||
public List<String> extractFieldNamesFromConditions(String fieldConditions) {
|
||||
List<String> fieldNames = new ArrayList<>();
|
||||
|
||||
// Split by logical operators (and, or) and parentheses, but preserve the field names
|
||||
// This is a simple implementation - could be enhanced with a proper parser
|
||||
String[] parts = fieldConditions.split("\\s+(and|or|&&|\\|\\|)\\s+|[()]+");
|
||||
|
||||
for (String part : parts) {
|
||||
part = part.trim();
|
||||
if (part.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle equals() function: equals(fieldName, "value")
|
||||
if (part.contains("equals(")) {
|
||||
String fieldName = extractFieldFromEquals(part);
|
||||
if (fieldName != null && !fieldNames.contains(fieldName)) {
|
||||
fieldNames.add(fieldName);
|
||||
}
|
||||
} else {
|
||||
// Handle simple comparisons: fieldName > value, fieldName <= value
|
||||
String fieldName = extractFieldFromComparison(part);
|
||||
if (fieldName != null && !fieldNames.contains(fieldName)) {
|
||||
fieldNames.add(fieldName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fieldNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts field name from equals() function
|
||||
*/
|
||||
public String extractFieldFromEquals(String condition) {
|
||||
// Pattern: equals(fieldName, "value") or equals(fieldName, value)
|
||||
int startParen = condition.indexOf('(');
|
||||
int comma = condition.indexOf(',');
|
||||
|
||||
if (startParen != -1 && comma != -1 && comma > startParen) {
|
||||
String fieldName = condition.substring(startParen + 1, comma).trim();
|
||||
// Remove quotes if present
|
||||
if (fieldName.startsWith("\"") && fieldName.endsWith("\"")) {
|
||||
fieldName = fieldName.substring(1, fieldName.length() - 1);
|
||||
}
|
||||
return fieldName;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts field name from comparison operation
|
||||
*/
|
||||
public String extractFieldFromComparison(String condition) {
|
||||
// Pattern: fieldName operator value
|
||||
// Updated to include all supported operators
|
||||
String[] operators = {" >= ", " <= ", " > ", " < ", " == ", " != "};
|
||||
|
||||
for (String operator : operators) {
|
||||
if (condition.contains(operator)) {
|
||||
String fieldName = condition.split(operator)[0].trim();
|
||||
// Basic validation - field names shouldn't contain quotes or special chars
|
||||
if (fieldName.matches("[a-zA-Z_][a-zA-Z0-9_]*")) {
|
||||
return fieldName;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to format hierarchy structure as JSON recursively
|
||||
*/
|
||||
public ObjectNode formatHierarchyAsJson(ObjectMapper mapper, Hierarchy hierarchy) {
|
||||
ObjectNode node = mapper.createObjectNode();
|
||||
|
||||
node.put("value", hierarchy.getValue());
|
||||
node.put("label", hierarchy.getLabel());
|
||||
|
||||
if (hierarchy.getIsLeaf() != null && hierarchy.getIsLeaf()) {
|
||||
// Leaf node - actual metric field parameter
|
||||
node.put("type", "field_parameter");
|
||||
|
||||
if (hierarchy.getType() != null) {
|
||||
node.put("dataType", hierarchy.getType() == 0 ? "numeric" : "string");
|
||||
}
|
||||
if (hierarchy.getUnit() != null && !hierarchy.getUnit().trim().isEmpty()) {
|
||||
node.put("unit", hierarchy.getUnit());
|
||||
}
|
||||
node.put("description", "Available field parameter for alert conditions");
|
||||
} else {
|
||||
// Category, app, or metric node
|
||||
// Determine node type based on children
|
||||
boolean hasLeafChildren = hierarchy.getChildren().stream()
|
||||
.anyMatch(child -> child.getIsLeaf() != null && child.getIsLeaf());
|
||||
|
||||
if (hasLeafChildren) {
|
||||
node.put("type", "metric");
|
||||
node.put("description", "Metric with available field parameters");
|
||||
} else {
|
||||
node.put("type", "app");
|
||||
node.put("description", "Application with available metrics");
|
||||
}
|
||||
|
||||
if (hierarchy.getChildren() != null && !hierarchy.getChildren().isEmpty()) {
|
||||
ArrayNode childrenArray = mapper.createArrayNode();
|
||||
for (Hierarchy child : hierarchy.getChildren()) {
|
||||
childrenArray.add(formatHierarchyAsJson(mapper, child));
|
||||
}
|
||||
node.set("children", childrenArray);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format timestamp to readable format
|
||||
*/
|
||||
public String formatTimestamp(Long timestamp) {
|
||||
if (timestamp == null) {
|
||||
return "N/A";
|
||||
}
|
||||
LocalDateTime dateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.systemDefault());
|
||||
return dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse time range string to milliseconds
|
||||
*/
|
||||
public long parseTimeRangeToMillis(String timeRange) {
|
||||
return switch (timeRange.toLowerCase()) {
|
||||
case "1h" -> 60 * 60 * 1000L;
|
||||
case "6h" -> 6 * 60 * 60 * 1000L;
|
||||
case "24h" -> 24 * 60 * 60 * 1000L;
|
||||
case "7d" -> 7 * 24 * 60 * 60 * 1000L;
|
||||
default -> 24 * 60 * 60 * 1000L; // default to 24h
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to convert monitor status byte to readable text
|
||||
* @param status The status byte from monitor
|
||||
* @return Human-readable status text
|
||||
*/
|
||||
public String getStatusText(Byte status) {
|
||||
if (status == null) {
|
||||
return "Unknown";
|
||||
}
|
||||
return switch (status) {
|
||||
case 0 -> "Paused";
|
||||
case 1 -> "Online";
|
||||
case 2 -> "Offline";
|
||||
case 3 -> "Unreachable";
|
||||
default -> "Unknown (" + status + ")";
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get metrics name for a metric type
|
||||
*/
|
||||
public String getMetricsNameForType(String metricType) {
|
||||
return switch (metricType.toLowerCase()) {
|
||||
case "cpu" -> "cpu";
|
||||
case "memory" -> "memory";
|
||||
case "disk" -> "disk";
|
||||
case "network" -> "network";
|
||||
default -> "system";
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to check if a field represents usage for a metric type
|
||||
*/
|
||||
public boolean isUsageField(String field, String metricType) {
|
||||
if (field == null) return false;
|
||||
|
||||
String fieldLower = field.toLowerCase();
|
||||
String typeLower = metricType.toLowerCase();
|
||||
|
||||
return fieldLower.contains("usage")
|
||||
|| fieldLower.contains("percent")
|
||||
|| fieldLower.contains("util")
|
||||
|| (typeLower.equals("cpu") && (fieldLower.contains("cpu") || fieldLower.contains("idle")))
|
||||
|| (typeLower.equals("memory") && fieldLower.contains("memory"))
|
||||
|| (typeLower.equals("disk") && fieldLower.contains("disk"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert manager module Hierarchy objects to ai-agent module Hierarchy objects
|
||||
* This handles the cross-module DTO conversion to avoid ClassCastException
|
||||
*/
|
||||
public List<Hierarchy> convertToAgentHierarchies(List<?> managerHierarchies) {
|
||||
List<Hierarchy> agentHierarchies = new ArrayList<>();
|
||||
|
||||
for (Object managerHierarchy : managerHierarchies) {
|
||||
Hierarchy agentHierarchy = convertToAgentHierarchy(managerHierarchy);
|
||||
agentHierarchies.add(agentHierarchy);
|
||||
}
|
||||
|
||||
return agentHierarchies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a single manager Hierarchy object to ai-agent Hierarchy object using reflection
|
||||
*/
|
||||
public Hierarchy convertToAgentHierarchy(Object managerHierarchy) {
|
||||
try {
|
||||
Hierarchy agentHierarchy = new Hierarchy();
|
||||
|
||||
// Use reflection to copy properties from manager DTO to agent DTO
|
||||
Class<?> managerClass = managerHierarchy.getClass();
|
||||
|
||||
// Copy basic properties
|
||||
agentHierarchy.setCategory(getStringField(managerHierarchy, managerClass, "category"));
|
||||
agentHierarchy.setValue(getStringField(managerHierarchy, managerClass, "value"));
|
||||
agentHierarchy.setLabel(getStringField(managerHierarchy, managerClass, "label"));
|
||||
agentHierarchy.setIsLeaf(getBooleanField(managerHierarchy, managerClass, "isLeaf"));
|
||||
agentHierarchy.setHide(getBooleanField(managerHierarchy, managerClass, "hide"));
|
||||
agentHierarchy.setType(getByteField(managerHierarchy, managerClass, "type"));
|
||||
agentHierarchy.setUnit(getStringField(managerHierarchy, managerClass, "unit"));
|
||||
|
||||
// Handle children recursively
|
||||
List<?> managerChildren = getListField(managerHierarchy, managerClass, "children");
|
||||
if (managerChildren != null && !managerChildren.isEmpty()) {
|
||||
List<Hierarchy> agentChildren = convertToAgentHierarchies(managerChildren);
|
||||
agentHierarchy.setChildren(agentChildren);
|
||||
}
|
||||
|
||||
return agentHierarchy;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to convert manager hierarchy to agent hierarchy: {}", e.getMessage(), e);
|
||||
throw new RuntimeException("Failed to convert hierarchy", e);
|
||||
}
|
||||
}
|
||||
|
||||
public String getStringField(Object obj, Class<?> clazz, String fieldName) {
|
||||
try {
|
||||
Method getter = clazz.getMethod("get" + capitalize(fieldName));
|
||||
Object value = getter.invoke(obj);
|
||||
return value != null ? value.toString() : null;
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not get string field '{}': {}", fieldName, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Boolean getBooleanField(Object obj, Class<?> clazz, String fieldName) {
|
||||
try {
|
||||
Method getter = clazz.getMethod("get" + capitalize(fieldName));
|
||||
Object value = getter.invoke(obj);
|
||||
return value instanceof Boolean ? (Boolean) value : null;
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
// Try alternative getter pattern for boolean fields
|
||||
Method isGetter = clazz.getMethod("is" + capitalize(fieldName));
|
||||
Object value = isGetter.invoke(obj);
|
||||
return value instanceof Boolean ? (Boolean) value : null;
|
||||
} catch (Exception e2) {
|
||||
log.debug("Could not get boolean field '{}': {}", fieldName, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Byte getByteField(Object obj, Class<?> clazz, String fieldName) {
|
||||
try {
|
||||
Method getter = clazz.getMethod("get" + capitalize(fieldName));
|
||||
Object value = getter.invoke(obj);
|
||||
return value instanceof Byte ? (Byte) value : null;
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not get byte field '{}': {}", fieldName, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public List<?> getListField(Object obj, Class<?> clazz, String fieldName) {
|
||||
try {
|
||||
Method getter = clazz.getMethod("get" + capitalize(fieldName));
|
||||
Object value = getter.invoke(obj);
|
||||
return value instanceof List ? (List<?>) value : null;
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not get list field '{}': {}", fieldName, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String capitalize(String str) {
|
||||
if (str == null || str.isEmpty()) {
|
||||
return str;
|
||||
}
|
||||
return str.substring(0, 1).toUpperCase() + str.substring(1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract existing monitor IDs from the alert expression
|
||||
* @param expression The alert expression containing __instance__ conditions
|
||||
* @return List of existing monitor IDs
|
||||
*/
|
||||
public List<String> extractExistingMonitorIds(String expression) {
|
||||
List<String> monitorIds = new ArrayList<>();
|
||||
String pattern = "equals\\(__instance__,\\s*\"([^\"]+)\"\\)";
|
||||
java.util.regex.Pattern regex = java.util.regex.Pattern.compile(pattern);
|
||||
java.util.regex.Matcher matcher = regex.matcher(expression);
|
||||
|
||||
while (matcher.find()) {
|
||||
monitorIds.add(matcher.group(1));
|
||||
}
|
||||
|
||||
return monitorIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace existing __instance__ conditions in the expression with new ones
|
||||
* @param expression The current expression
|
||||
* @param newMonitorCondition The new monitor condition to replace with
|
||||
* @return Updated expression
|
||||
*/
|
||||
public String replaceInstanceConditions(String expression, String newMonitorCondition) {
|
||||
// More precise pattern to match complete __instance__ condition blocks without cutting field parameters
|
||||
// Pattern matches either:
|
||||
// 1. Single: equals(__instance__, "id")
|
||||
// 2. Multiple: (equals(__instance__, "id1") or equals(__instance__, "id2") or ...)
|
||||
|
||||
// First try to match grouped conditions: (equals(__instance__, "id1") or equals(__instance__, "id2"))
|
||||
String groupedPattern = "\\(\\s*equals\\(__instance__,\\s*\"[^\"]+\"\\)(?:\\s+or\\s+equals\\(__instance__,\\s*\"[^\"]+\"\\))*\\s*\\)";
|
||||
if (expression.matches(".*" + groupedPattern + ".*")) {
|
||||
return expression.replaceFirst(groupedPattern, newMonitorCondition);
|
||||
}
|
||||
|
||||
// Then try single condition: equals(__instance__, "id")
|
||||
String singlePattern = "equals\\(__instance__,\\s*\"[^\"]+\"\\)";
|
||||
if (expression.matches(".*" + singlePattern + ".*")) {
|
||||
return expression.replaceFirst(singlePattern, newMonitorCondition);
|
||||
}
|
||||
|
||||
// If no match found, return original expression
|
||||
return expression;
|
||||
}
|
||||
|
||||
}
|
||||
+14
-4
@@ -22,10 +22,12 @@ package org.apache.hertzbeat.alert.config;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
@@ -54,16 +56,24 @@ public class AlertSseManager {
|
||||
.name("ALERT_EVENT")
|
||||
.data(data));
|
||||
} catch (IOException | IllegalStateException e) {
|
||||
emitter.complete();
|
||||
removeEmitter(clientId);
|
||||
tryCompleteAndClean(clientId, emitter);
|
||||
} catch (Exception exception) {
|
||||
log.error("Failed to broadcast alert data to client: {}", exception.getMessage());
|
||||
emitter.complete();
|
||||
removeEmitter(clientId);
|
||||
tryCompleteAndClean(clientId, emitter);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void tryCompleteAndClean(Long clientId, SseEmitter emitter) {
|
||||
try {
|
||||
Optional.ofNullable(emitter).ifPresent(ResponseBodyEmitter::complete);
|
||||
} catch (Throwable e) {
|
||||
log.debug("Failed to complete emitter for client {}: {}", clientId, e.getMessage());
|
||||
}
|
||||
// execute clear
|
||||
removeEmitter(clientId);
|
||||
}
|
||||
|
||||
private void removeEmitter(Long clientId) {
|
||||
emitters.remove(clientId);
|
||||
}
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ public class ServerChanAlertNotifyHandlerImpl extends AbstractAlertNotifyHandler
|
||||
String webHookUrl = String.format(alerterProperties.getServerChanWebhookUrl(), sanitizedToken);
|
||||
|
||||
// Validate the constructed URL against a whitelist
|
||||
List<String> allowedBaseUrls = List.of("https://api.serverchan.com", "https://serverchan.example.com");
|
||||
List<String> allowedBaseUrls = List.of("https://api.serverchan.com", "https://serverchan.example.com", "https://sctapi.ftqq.com");
|
||||
boolean isValidUrl = allowedBaseUrls.stream().anyMatch(webHookUrl::startsWith);
|
||||
if (!isValidUrl) {
|
||||
throw new AlertNoticeException("Invalid webhook URL: " + webHookUrl);
|
||||
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.config;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* alert sse manager test
|
||||
*/
|
||||
public class AlertSseManagerTest {
|
||||
|
||||
private AlertSseManager alertSseManager;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
alertSseManager = new AlertSseManager();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCompleteThrowsException() throws Exception {
|
||||
SseEmitter emitter = alertSseManager.createEmitter(1L);
|
||||
assertNotNull(emitter);
|
||||
|
||||
Map<Long, SseEmitter> emitters = new HashMap<>();
|
||||
SseEmitter spyEmitter = mock(SseEmitter.class);
|
||||
|
||||
doThrow(new IllegalStateException("Simulated output stream error")).when(spyEmitter).send(any(SseEmitter.SseEventBuilder.class));
|
||||
doThrow(new RuntimeException("Complete failed")).when(spyEmitter).complete();
|
||||
|
||||
emitters.put(1L, spyEmitter);
|
||||
|
||||
Field emittersField = AlertSseManager.class.getDeclaredField("emitters");
|
||||
emittersField.setAccessible(true);
|
||||
emittersField.set(alertSseManager, emitters);
|
||||
|
||||
assertThrows(RuntimeException.class, () -> alertSseManager.broadcast("{\"id\":1,\"content\":\"Test alert\"}"));
|
||||
Map<Long, SseEmitter> currentEmitters = (Map<Long, SseEmitter>) emittersField.get(alertSseManager);
|
||||
assertFalse(currentEmitters.containsKey(1L), "Emitter should still exist because complete() threw exception");
|
||||
}
|
||||
|
||||
}
|
||||
+5
-3
@@ -640,11 +640,13 @@ public class HttpCollectImpl extends AbstractCollect {
|
||||
.collect(Collectors.toMap(MetricFamily.Label::getName, MetricFamily.Label::getValue));
|
||||
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
|
||||
for (String aliasField : aliasFields) {
|
||||
if ("value".equals(aliasField)) {
|
||||
String columnValue = labelMap.get(aliasField);
|
||||
if (columnValue != null) {
|
||||
valueRowBuilder.addColumn(columnValue);
|
||||
} else if (CommonConstants.PROM_VALUE.equals(aliasField) || CommonConstants.PROM_METRIC_VALUE.equals(aliasField)) {
|
||||
valueRowBuilder.addColumn(String.valueOf(metric.getValue()));
|
||||
} else {
|
||||
String columnValue = labelMap.get(aliasField);
|
||||
valueRowBuilder.addColumn(columnValue == null ? CommonConstants.NULL_VALUE : columnValue);
|
||||
valueRowBuilder.addColumn(CommonConstants.NULL_VALUE);
|
||||
}
|
||||
}
|
||||
builder.addValueRow(valueRowBuilder.build());
|
||||
|
||||
+1
-2
@@ -60,7 +60,7 @@ public class PrometheusMatrixParser extends AbstractPrometheusParse {
|
||||
boolean setValueFlag = false;
|
||||
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
|
||||
for (String aliasField : aliasFields) {
|
||||
if (!CollectUtil.assertPromRequireField(aliasField)) {
|
||||
if (!CollectUtil.assertPromRequireField(aliasField) || CollectUtil.isValueAsLabel(aliasField, r.getMetric())) {
|
||||
JsonElement jsonElement = r.getMetric().get(aliasField);
|
||||
if (jsonElement != null) {
|
||||
valueRowBuilder.addColumn(jsonElement.getAsString());
|
||||
@@ -93,7 +93,6 @@ public class PrometheusMatrixParser extends AbstractPrometheusParse {
|
||||
}
|
||||
builder.addValueRow(valueRowBuilder.build());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ public class PrometheusVectorParser extends AbstractPrometheusParse {
|
||||
for (PromVectorOrMatrix.Result r : result) {
|
||||
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
|
||||
for (String aliasField : aliasFields) {
|
||||
if (!CollectUtil.assertPromRequireField(aliasField)) {
|
||||
if (!CollectUtil.assertPromRequireField(aliasField) || CollectUtil.isValueAsLabel(aliasField, r.getMetric())) {
|
||||
JsonElement jsonElement = r.getMetric().get(aliasField);
|
||||
if (jsonElement != null) {
|
||||
valueRowBuilder.addColumn(jsonElement.getAsString());
|
||||
|
||||
+23
-4
@@ -69,6 +69,10 @@ public class OnlineParser {
|
||||
parseMetric(inputStream, metricFamilyMap, stringBuilder);
|
||||
}
|
||||
i = getChar(inputStream);
|
||||
// To address the `\n\r` scenario, it is necessary to skip
|
||||
if (i == '\r') {
|
||||
i = getChar(inputStream);
|
||||
}
|
||||
}
|
||||
} catch (FormatException e) {
|
||||
log.error("prometheus parser failed because of wrong input format. {}", e.getMessage());
|
||||
@@ -185,6 +189,10 @@ public class OnlineParser {
|
||||
stringBuilder.append((char) i);
|
||||
i = getChar(inputStream);
|
||||
}
|
||||
// Skip \r character to handle Windows line endings
|
||||
if (i == '\r') {
|
||||
i = getChar(inputStream);
|
||||
}
|
||||
return new CharChecker(i);
|
||||
}
|
||||
|
||||
@@ -193,6 +201,10 @@ public class OnlineParser {
|
||||
while (i >= '0' && i <= '9') {
|
||||
i = getChar(inputStream);
|
||||
}
|
||||
// Skip \r character to handle Windows line endings
|
||||
if (i == '\r') {
|
||||
i = getChar(inputStream);
|
||||
}
|
||||
return new CharChecker(i);
|
||||
}
|
||||
|
||||
@@ -215,20 +227,27 @@ public class OnlineParser {
|
||||
}
|
||||
|
||||
private static CharChecker parseLabelValue(InputStream inputStream, StringBuilder stringBuilder) throws IOException, FormatException {
|
||||
int i = getChar(inputStream);
|
||||
int i = inputStream.read();
|
||||
while (i != '"' && i != -1) {
|
||||
if (i == '\\') {
|
||||
i = getChar(inputStream);
|
||||
i = inputStream.read();
|
||||
switch (i) {
|
||||
case 'n' -> stringBuilder.append('\n');
|
||||
case '\\' -> stringBuilder.append('\\');
|
||||
case '\"' -> stringBuilder.append('\"');
|
||||
default -> throw new FormatException();
|
||||
default -> {
|
||||
// Unknown escape, keep as-is
|
||||
// https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/lib/protoparser/prometheus/parser.go#L419
|
||||
stringBuilder.append('\\');
|
||||
if (i != -1) {
|
||||
stringBuilder.append((char) i);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
stringBuilder.append((char) i);
|
||||
}
|
||||
i = getChar(inputStream);
|
||||
i = inputStream.read();
|
||||
}
|
||||
return new CharChecker(i);
|
||||
}
|
||||
|
||||
+252
@@ -25,7 +25,10 @@ import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -289,4 +292,253 @@ class HttpCollectImplTest {
|
||||
firstRow = capturedRows.get(0);
|
||||
assertEquals("0.268751364291017", firstRow.getColumns(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParsePromQlLabelValue() throws Exception {
|
||||
// Create Prometheus format test data
|
||||
String prometheusData = """
|
||||
{
|
||||
"status": "success",
|
||||
"data": {
|
||||
"resultType": "vector",
|
||||
"result": [
|
||||
{
|
||||
"metric": {
|
||||
"__name__": "taos_cluster_info_first_ep",
|
||||
"cluster_id": "590779086215866783",
|
||||
"instance": "host.docker.internal:6043",
|
||||
"job": "tdengine",
|
||||
"value": "localhost:6030"
|
||||
},
|
||||
"value": [
|
||||
1756233177.048,
|
||||
"1"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}""";
|
||||
List<CollectRep.ValueRow> capturedRows = new ArrayList<>();
|
||||
CollectRep.MetricsData.Builder builder = new CollectRep.MetricsData.Builder() {
|
||||
@Override
|
||||
public CollectRep.MetricsData.Builder addValueRow(CollectRep.ValueRow valueRow) {
|
||||
capturedRows.add(valueRow);
|
||||
return super.addValueRow(valueRow);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMetrics() {
|
||||
return "jvm_memory_used_bytes";
|
||||
}
|
||||
};
|
||||
Method parseMethod = HttpCollectImpl.class.getDeclaredMethod(
|
||||
"parseResponseByPromQl",
|
||||
String.class,
|
||||
List.class,
|
||||
HttpProtocol.class,
|
||||
CollectRep.MetricsData.Builder.class);
|
||||
parseMethod.setAccessible(true);
|
||||
|
||||
parseMethod.invoke(httpCollectImpl, prometheusData, Lists.newArrayList("cluster_id", "value"), new HttpProtocol(), builder);
|
||||
|
||||
// Verify the results
|
||||
assertEquals(1, capturedRows.size());
|
||||
CollectRep.ValueRow firstRow = capturedRows.get(0);
|
||||
assertEquals("590779086215866783", firstRow.getColumns(0));
|
||||
assertEquals("localhost:6030", firstRow.getColumns(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParsePromQlMetricValue() throws Exception {
|
||||
// Create Prometheus format test data
|
||||
String prometheusData = """
|
||||
{
|
||||
"status": "success",
|
||||
"data": {
|
||||
"resultType": "vector",
|
||||
"result": [
|
||||
{
|
||||
"metric": {
|
||||
"__name__": "taos_cluster_info_first_ep",
|
||||
"cluster_id": "590779086215866783",
|
||||
"instance": "host.docker.internal:6043",
|
||||
"job": "tdengine",
|
||||
"value": "localhost:6030"
|
||||
},
|
||||
"value": [
|
||||
1756233177.048,
|
||||
"1"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}""";
|
||||
List<CollectRep.ValueRow> capturedRows = new ArrayList<>();
|
||||
CollectRep.MetricsData.Builder builder = new CollectRep.MetricsData.Builder() {
|
||||
@Override
|
||||
public CollectRep.MetricsData.Builder addValueRow(CollectRep.ValueRow valueRow) {
|
||||
capturedRows.add(valueRow);
|
||||
return super.addValueRow(valueRow);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMetrics() {
|
||||
return "jvm_memory_used_bytes";
|
||||
}
|
||||
};
|
||||
Method parseMethod = HttpCollectImpl.class.getDeclaredMethod(
|
||||
"parseResponseByPromQl",
|
||||
String.class,
|
||||
List.class,
|
||||
HttpProtocol.class,
|
||||
CollectRep.MetricsData.Builder.class);
|
||||
parseMethod.setAccessible(true);
|
||||
|
||||
parseMethod.invoke(httpCollectImpl, prometheusData, Lists.newArrayList("cluster_id", "value", "metric_value"), new HttpProtocol(), builder);
|
||||
|
||||
// Verify the results
|
||||
assertEquals(1, capturedRows.size());
|
||||
CollectRep.ValueRow firstRow = capturedRows.get(0);
|
||||
assertEquals("590779086215866783", firstRow.getColumns(0));
|
||||
assertEquals("localhost:6030", firstRow.getColumns(1));
|
||||
assertEquals("1", firstRow.getColumns(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParsePromQlValue() throws Exception {
|
||||
// Create Prometheus format test data
|
||||
String prometheusData = """
|
||||
{
|
||||
"status": "success",
|
||||
"data": {
|
||||
"resultType": "vector",
|
||||
"result": [
|
||||
{
|
||||
"metric": {
|
||||
"__name__": "taos_cluster_info_first_ep",
|
||||
"cluster_id": "590779086215866783",
|
||||
"instance": "host.docker.internal:6043",
|
||||
"job": "tdengine"
|
||||
},
|
||||
"value": [
|
||||
1756233177.048,
|
||||
"1"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}""";
|
||||
List<CollectRep.ValueRow> capturedRows = new ArrayList<>();
|
||||
CollectRep.MetricsData.Builder builder = new CollectRep.MetricsData.Builder() {
|
||||
@Override
|
||||
public CollectRep.MetricsData.Builder addValueRow(CollectRep.ValueRow valueRow) {
|
||||
capturedRows.add(valueRow);
|
||||
return super.addValueRow(valueRow);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMetrics() {
|
||||
return "jvm_memory_used_bytes";
|
||||
}
|
||||
};
|
||||
Method parseMethod = HttpCollectImpl.class.getDeclaredMethod(
|
||||
"parseResponseByPromQl",
|
||||
String.class,
|
||||
List.class,
|
||||
HttpProtocol.class,
|
||||
CollectRep.MetricsData.Builder.class);
|
||||
parseMethod.setAccessible(true);
|
||||
|
||||
parseMethod.invoke(httpCollectImpl, prometheusData, Lists.newArrayList("cluster_id", "value"), new HttpProtocol(), builder);
|
||||
|
||||
// Verify the results
|
||||
assertEquals(1, capturedRows.size());
|
||||
CollectRep.ValueRow firstRow = capturedRows.get(0);
|
||||
assertEquals("590779086215866783", firstRow.getColumns(0));
|
||||
assertEquals("1", firstRow.getColumns(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParsePrometheusLabelValue() throws Exception {
|
||||
// Create Prometheus format test data
|
||||
String prometheusData = """
|
||||
# HELP jvm_memory_used_bytes The amount of used memory in bytes
|
||||
# TYPE jvm_memory_used_bytes gauge
|
||||
jvm_memory_used_bytes{area="heap",value="G1 Survivor Space"} 1048576
|
||||
""";
|
||||
InputStream inputStream = new ByteArrayInputStream(prometheusData.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
List<CollectRep.ValueRow> capturedRows = new ArrayList<>();
|
||||
CollectRep.MetricsData.Builder builder = new CollectRep.MetricsData.Builder() {
|
||||
@Override
|
||||
public CollectRep.MetricsData.Builder addValueRow(CollectRep.ValueRow valueRow) {
|
||||
capturedRows.add(valueRow);
|
||||
return super.addValueRow(valueRow);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMetrics() {
|
||||
return "jvm_memory_used_bytes";
|
||||
}
|
||||
};
|
||||
Method parseMethod = HttpCollectImpl.class.getDeclaredMethod(
|
||||
"parseResponseByPrometheusExporter",
|
||||
InputStream.class,
|
||||
List.class,
|
||||
CollectRep.MetricsData.Builder.class);
|
||||
parseMethod.setAccessible(true);
|
||||
|
||||
parseMethod.invoke(httpCollectImpl, inputStream, Lists.newArrayList("area", "value"), builder);
|
||||
|
||||
// Verify the results
|
||||
assertEquals(1, capturedRows.size());
|
||||
CollectRep.ValueRow firstRow = capturedRows.get(0);
|
||||
assertEquals("heap", firstRow.getColumns(0));
|
||||
assertEquals("G1 Survivor Space", firstRow.getColumns(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParsePrometheus() throws Exception {
|
||||
// Create Prometheus format test data
|
||||
String prometheusData = """
|
||||
# HELP jvm_memory_used_bytes The amount of used memory
|
||||
# TYPE jvm_memory_used_bytes gauge
|
||||
jvm_memory_used_bytes{area="heap",id="G1 Eden Space"} 1.63577856E8
|
||||
jvm_memory_used_bytes{area="heap",id="G1 Old Gen"} 2.7874304E7
|
||||
jvm_memory_used_bytes{area="heap",id="G1 Survivor Space"} 512032.0
|
||||
jvm_memory_used_bytes{area="nonheap",id="CodeCache"} 1.460288E7
|
||||
jvm_memory_used_bytes{area="nonheap",id="Compressed Class Space"} 5844504.0
|
||||
jvm_memory_used_bytes{area="nonheap",id="Metaspace"} 4.1576344E7
|
||||
""";
|
||||
InputStream inputStream = new ByteArrayInputStream(prometheusData.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
List<CollectRep.ValueRow> capturedRows = new ArrayList<>();
|
||||
CollectRep.MetricsData.Builder builder = new CollectRep.MetricsData.Builder() {
|
||||
@Override
|
||||
public CollectRep.MetricsData.Builder addValueRow(CollectRep.ValueRow valueRow) {
|
||||
capturedRows.add(valueRow);
|
||||
return super.addValueRow(valueRow);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMetrics() {
|
||||
return "jvm_memory_used_bytes";
|
||||
}
|
||||
};
|
||||
Method parseMethod = HttpCollectImpl.class.getDeclaredMethod(
|
||||
"parseResponseByPrometheusExporter",
|
||||
InputStream.class,
|
||||
List.class,
|
||||
CollectRep.MetricsData.Builder.class);
|
||||
parseMethod.setAccessible(true);
|
||||
|
||||
parseMethod.invoke(httpCollectImpl, inputStream, Lists.newArrayList("area", "id"), builder);
|
||||
|
||||
// Verify the results
|
||||
assertEquals(6, capturedRows.size());
|
||||
CollectRep.ValueRow firstRow = capturedRows.get(0);
|
||||
assertEquals("heap", firstRow.getColumns(0));
|
||||
assertEquals("G1 Eden Space", firstRow.getColumns(1));
|
||||
capturedRows.forEach(t -> assertEquals(2, t.getColumnsList().size()));
|
||||
}
|
||||
}
|
||||
|
||||
+354
@@ -43,6 +43,16 @@ class OnlineParserTest {
|
||||
assertNotNull(metricFamilyMap);
|
||||
}
|
||||
|
||||
@Disabled // because unless you have already saved the locally tested files
|
||||
@Test
|
||||
void parseTestFile() throws Exception {
|
||||
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("test_file.txt");
|
||||
assertNotNull(inputStream, "Failed to load test_file.txt resource");
|
||||
Map<String, MetricFamily> metricFamilyMap = OnlineParser.parseMetrics(inputStream);
|
||||
assertNotNull(metricFamilyMap);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void parseMetrics2() throws Exception {
|
||||
String str = """
|
||||
@@ -105,4 +115,348 @@ class OnlineParserTest {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseMetricsWithCrLf() throws Exception {
|
||||
String str = "# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.\r\n"
|
||||
+ "# TYPE go_gc_duration_seconds summary\r\n"
|
||||
+ "jvm_gc_pause_seconds_count{action=\"end of major GC\",cause=\"Metadata GC Threshold\",} 1.0\r\n"
|
||||
+ "jvm_gc_pause_seconds_sum{action=\"end of major GC\",cause=\"Metadata GC Threshold\",} 0.139\r\n";
|
||||
InputStream inputStream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
|
||||
Map<String, MetricFamily> metricFamilyMap = OnlineParser.parseMetrics(inputStream);
|
||||
assertNotNull(metricFamilyMap);
|
||||
assertEquals(2, metricFamilyMap.values().size());
|
||||
|
||||
MetricFamily metricFamily = metricFamilyMap.get("jvm_gc_pause_seconds_count");
|
||||
assertEquals("jvm_gc_pause_seconds_count", metricFamily.getName());
|
||||
assertEquals(1.0, metricFamily.getMetricList().get(0).getValue());
|
||||
|
||||
MetricFamily metricFamily1 = metricFamilyMap.get("jvm_gc_pause_seconds_sum");
|
||||
assertEquals("jvm_gc_pause_seconds_sum", metricFamily1.getName());
|
||||
assertEquals(0.139, metricFamily1.getMetricList().get(0).getValue());
|
||||
|
||||
str = "# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.\r\n"
|
||||
+ "# TYPE go_gc_duration_seconds summary\r\n"
|
||||
+ "jvm_gc_pause_seconds_count{action=\"end of major GC\",cause=\"Metadata GC Threshold\",} 1.0 1234567890\r\n"
|
||||
+ "jvm_gc_pause_seconds_sum{action=\"end of major GC\",cause=\"Metadata GC Threshold\",} 0.139 1234567890\r\n";
|
||||
inputStream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
|
||||
metricFamilyMap = OnlineParser.parseMetrics(inputStream);
|
||||
assertNotNull(metricFamilyMap);
|
||||
assertEquals(2, metricFamilyMap.values().size());
|
||||
|
||||
metricFamily = metricFamilyMap.get("jvm_gc_pause_seconds_count");
|
||||
assertEquals("jvm_gc_pause_seconds_count", metricFamily.getName());
|
||||
assertEquals(1.0, metricFamily.getMetricList().get(0).getValue());
|
||||
|
||||
metricFamily1 = metricFamilyMap.get("jvm_gc_pause_seconds_sum");
|
||||
assertEquals("jvm_gc_pause_seconds_sum", metricFamily1.getName());
|
||||
assertEquals(0.139, metricFamily1.getMetricList().get(0).getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseMetricsWithLf() throws Exception {
|
||||
String str = "# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.\n"
|
||||
+ "# TYPE go_gc_duration_seconds summary\n"
|
||||
+ "jvm_gc_pause_seconds_count{action=\"end of major GC\",cause=\"Metadata GC Threshold\",} 1.0\n"
|
||||
+ "jvm_gc_pause_seconds_sum{action=\"end of major GC\",cause=\"Metadata GC Threshold\",} 0.139\n";
|
||||
InputStream inputStream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
|
||||
Map<String, MetricFamily> metricFamilyMap = OnlineParser.parseMetrics(inputStream);
|
||||
assertNotNull(metricFamilyMap);
|
||||
assertEquals(2, metricFamilyMap.values().size());
|
||||
|
||||
MetricFamily metricFamily = metricFamilyMap.get("jvm_gc_pause_seconds_count");
|
||||
assertEquals("jvm_gc_pause_seconds_count", metricFamily.getName());
|
||||
assertEquals(1.0, metricFamily.getMetricList().get(0).getValue());
|
||||
|
||||
MetricFamily metricFamily1 = metricFamilyMap.get("jvm_gc_pause_seconds_sum");
|
||||
assertEquals("jvm_gc_pause_seconds_sum", metricFamily1.getName());
|
||||
assertEquals(0.139, metricFamily1.getMetricList().get(0).getValue());
|
||||
|
||||
str = "# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.\n"
|
||||
+ "# TYPE go_gc_duration_seconds summary\n"
|
||||
+ "jvm_gc_pause_seconds_count{action=\"end of major GC\",cause=\"Metadata GC Threshold\",} 1.0 1234567890\n"
|
||||
+ "jvm_gc_pause_seconds_sum{action=\"end of major GC\",cause=\"Metadata GC Threshold\",} 0.139 1234567890\n";
|
||||
inputStream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
|
||||
metricFamilyMap = OnlineParser.parseMetrics(inputStream);
|
||||
assertNotNull(metricFamilyMap);
|
||||
assertEquals(2, metricFamilyMap.values().size());
|
||||
|
||||
metricFamily = metricFamilyMap.get("jvm_gc_pause_seconds_count");
|
||||
assertEquals("jvm_gc_pause_seconds_count", metricFamily.getName());
|
||||
assertEquals(1.0, metricFamily.getMetricList().get(0).getValue());
|
||||
|
||||
metricFamily1 = metricFamilyMap.get("jvm_gc_pause_seconds_sum");
|
||||
assertEquals("jvm_gc_pause_seconds_sum", metricFamily1.getName());
|
||||
assertEquals(0.139, metricFamily1.getMetricList().get(0).getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseMetricsWithoutFinalNewline() throws Exception {
|
||||
String str = "# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.\r\n"
|
||||
+ "# TYPE go_gc_duration_seconds summary\r\n"
|
||||
+ "jvm_gc_pause_seconds_count{action=\"end of major GC\",cause=\"Metadata GC Threshold\",} 1.0\r\n"
|
||||
+ "jvm_gc_pause_seconds_sum{action=\"end of major GC\",cause=\"Metadata GC Threshold\",} 0.139";
|
||||
|
||||
InputStream inputStream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
|
||||
Map<String, MetricFamily> metricFamilyMap = OnlineParser.parseMetrics(inputStream);
|
||||
|
||||
assertNotNull(metricFamilyMap);
|
||||
assertEquals(2, metricFamilyMap.values().size());
|
||||
|
||||
MetricFamily metricFamily = metricFamilyMap.get("jvm_gc_pause_seconds_count");
|
||||
assertEquals("jvm_gc_pause_seconds_count", metricFamily.getName());
|
||||
assertEquals(1.0, metricFamily.getMetricList().get(0).getValue());
|
||||
|
||||
MetricFamily metricFamily1 = metricFamilyMap.get("jvm_gc_pause_seconds_sum");
|
||||
assertEquals("jvm_gc_pause_seconds_sum", metricFamily1.getName());
|
||||
assertEquals(0.139, metricFamily1.getMetricList().get(0).getValue());
|
||||
|
||||
str = "# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.\n"
|
||||
+ "# TYPE go_gc_duration_seconds summary\n"
|
||||
+ "jvm_gc_pause_seconds_count{action=\"end of major GC\",cause=\"Metadata GC Threshold\",} 1.0\n"
|
||||
+ "jvm_gc_pause_seconds_sum{action=\"end of major GC\",cause=\"Metadata GC Threshold\",} 0.139";
|
||||
|
||||
inputStream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
|
||||
metricFamilyMap = OnlineParser.parseMetrics(inputStream);
|
||||
|
||||
assertNotNull(metricFamilyMap);
|
||||
assertEquals(2, metricFamilyMap.values().size());
|
||||
|
||||
metricFamily = metricFamilyMap.get("jvm_gc_pause_seconds_count");
|
||||
assertEquals("jvm_gc_pause_seconds_count", metricFamily.getName());
|
||||
assertEquals(1.0, metricFamily.getMetricList().get(0).getValue());
|
||||
|
||||
metricFamily1 = metricFamilyMap.get("jvm_gc_pause_seconds_sum");
|
||||
assertEquals("jvm_gc_pause_seconds_sum", metricFamily1.getName());
|
||||
assertEquals(0.139, metricFamily1.getMetricList().get(0).getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseMetricsWithEmptyLabelsAndCrLf() throws Exception {
|
||||
String str = "# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.\r\n"
|
||||
+ "# TYPE go_gc_duration_seconds summary\r\n"
|
||||
+ "jvm_gc_pause_seconds_count 1.0\r\n"
|
||||
+ "jvm_gc_pause_seconds_sum{} 0.139";
|
||||
|
||||
InputStream inputStream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
|
||||
Map<String, MetricFamily> metricFamilyMap = OnlineParser.parseMetrics(inputStream);
|
||||
|
||||
assertNotNull(metricFamilyMap);
|
||||
assertEquals(2, metricFamilyMap.values().size());
|
||||
|
||||
MetricFamily metricFamily = metricFamilyMap.get("jvm_gc_pause_seconds_count");
|
||||
assertEquals("jvm_gc_pause_seconds_count", metricFamily.getName());
|
||||
assertEquals(1.0, metricFamily.getMetricList().get(0).getValue());
|
||||
|
||||
MetricFamily metricFamily1 = metricFamilyMap.get("jvm_gc_pause_seconds_sum");
|
||||
assertEquals("jvm_gc_pause_seconds_sum", metricFamily1.getName());
|
||||
assertEquals(0.139, metricFamily1.getMetricList().get(0).getValue());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseMetricsWithEmptyLabelsAndLf() throws Exception {
|
||||
String str = "# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.\n"
|
||||
+ "# TYPE go_gc_duration_seconds summary\n"
|
||||
+ "jvm_gc_pause_seconds_count 1.0\n"
|
||||
+ "jvm_gc_pause_seconds_sum{} 0.139";
|
||||
|
||||
InputStream inputStream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
|
||||
Map<String, MetricFamily> metricFamilyMap = OnlineParser.parseMetrics(inputStream);
|
||||
|
||||
assertNotNull(metricFamilyMap);
|
||||
assertEquals(2, metricFamilyMap.values().size());
|
||||
|
||||
MetricFamily metricFamily = metricFamilyMap.get("jvm_gc_pause_seconds_count");
|
||||
assertEquals("jvm_gc_pause_seconds_count", metricFamily.getName());
|
||||
assertEquals(1.0, metricFamily.getMetricList().get(0).getValue());
|
||||
|
||||
MetricFamily metricFamily1 = metricFamilyMap.get("jvm_gc_pause_seconds_sum");
|
||||
assertEquals("jvm_gc_pause_seconds_sum", metricFamily1.getName());
|
||||
assertEquals(0.139, metricFamily1.getMetricList().get(0).getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseMetricsWithMixedLineEndings() throws Exception {
|
||||
String str = "# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.\r\n"
|
||||
+ "# TYPE go_gc_duration_seconds summary\n"
|
||||
+ "jvm_gc_pause_seconds_count 1.0\n"
|
||||
+ "jvm_gc_pause_seconds_sum{} 0.139\r\n";
|
||||
|
||||
InputStream inputStream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
|
||||
Map<String, MetricFamily> metricFamilyMap = OnlineParser.parseMetrics(inputStream);
|
||||
|
||||
assertNotNull(metricFamilyMap);
|
||||
assertEquals(2, metricFamilyMap.values().size());
|
||||
|
||||
MetricFamily metricFamily = metricFamilyMap.get("jvm_gc_pause_seconds_count");
|
||||
assertEquals("jvm_gc_pause_seconds_count", metricFamily.getName());
|
||||
assertEquals(1.0, metricFamily.getMetricList().get(0).getValue());
|
||||
|
||||
MetricFamily metricFamily1 = metricFamilyMap.get("jvm_gc_pause_seconds_sum");
|
||||
assertEquals("jvm_gc_pause_seconds_sum", metricFamily1.getName());
|
||||
assertEquals(0.139, metricFamily1.getMetricList().get(0).getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseMetricsWithLfCrLineEnding() throws Exception {
|
||||
String str = "# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.\n\r"
|
||||
+ "# TYPE go_gc_duration_seconds summary\n\r"
|
||||
+ "jvm_gc_pause_seconds_count 1.0\n\r"
|
||||
+ "jvm_gc_pause_seconds_sum{} 0.139\n\r";
|
||||
|
||||
InputStream inputStream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
|
||||
Map<String, MetricFamily> metricFamilyMap = OnlineParser.parseMetrics(inputStream);
|
||||
|
||||
assertNotNull(metricFamilyMap);
|
||||
assertEquals(2, metricFamilyMap.values().size());
|
||||
|
||||
MetricFamily metricFamily = metricFamilyMap.get("jvm_gc_pause_seconds_count");
|
||||
assertEquals("jvm_gc_pause_seconds_count", metricFamily.getName());
|
||||
assertEquals(1.0, metricFamily.getMetricList().get(0).getValue());
|
||||
|
||||
MetricFamily metricFamily1 = metricFamilyMap.get("jvm_gc_pause_seconds_sum");
|
||||
assertEquals("jvm_gc_pause_seconds_sum", metricFamily1.getName());
|
||||
assertEquals(0.139, metricFamily1.getMetricList().get(0).getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseMetricsWithMixedLineEndingsIncludingLfCr() throws Exception {
|
||||
String str = "# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.\r\n"
|
||||
+ "# TYPE go_gc_duration_seconds summary\n"
|
||||
+ "jvm_gc_pause_seconds_count 1.0\n\r"
|
||||
+ "jvm_gc_pause_seconds_sum{} 0.139\r\n"
|
||||
+ "jvm_gc_pause_seconds_max{} 0.139\n";
|
||||
|
||||
InputStream inputStream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
|
||||
Map<String, MetricFamily> metricFamilyMap = OnlineParser.parseMetrics(inputStream);
|
||||
|
||||
assertNotNull(metricFamilyMap);
|
||||
assertEquals(3, metricFamilyMap.values().size());
|
||||
|
||||
MetricFamily metricFamily = metricFamilyMap.get("jvm_gc_pause_seconds_count");
|
||||
assertEquals("jvm_gc_pause_seconds_count", metricFamily.getName());
|
||||
assertEquals(1.0, metricFamily.getMetricList().get(0).getValue());
|
||||
|
||||
MetricFamily metricFamily1 = metricFamilyMap.get("jvm_gc_pause_seconds_sum");
|
||||
assertEquals("jvm_gc_pause_seconds_sum", metricFamily1.getName());
|
||||
assertEquals(0.139, metricFamily1.getMetricList().get(0).getValue());
|
||||
|
||||
MetricFamily metricFamily2 = metricFamilyMap.get("jvm_gc_pause_seconds_max");
|
||||
assertEquals("jvm_gc_pause_seconds_max", metricFamily2.getName());
|
||||
assertEquals(0.139, metricFamily2.getMetricList().get(0).getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseMetricsWithOnlyLfCr() throws Exception {
|
||||
String str = "jvm_gc_pause_seconds_sum 42.0\n\r";
|
||||
InputStream inputStream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
|
||||
Map<String, MetricFamily> metricFamilyMap = OnlineParser.parseMetrics(inputStream);
|
||||
|
||||
assertNotNull(metricFamilyMap);
|
||||
assertEquals(1, metricFamilyMap.values().size());
|
||||
|
||||
MetricFamily metricFamily = metricFamilyMap.get("jvm_gc_pause_seconds_sum");
|
||||
assertEquals("jvm_gc_pause_seconds_sum", metricFamily.getName());
|
||||
assertEquals(42.0, metricFamily.getMetricList().get(0).getValue());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testParseMetricEscape1() throws Exception {
|
||||
// test escape '\\'
|
||||
String str = "# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.\r\n"
|
||||
+ "# TYPE go_gc_duration_seconds summary\n"
|
||||
+ "windows_service_info{display_name=\"\\\\Application Layer \nGateway Service\",name=\"alg\\\\\",process_id=\"0\",run_as=\"NT AUTHORITY\\\\LocalService\"} 1\n";
|
||||
InputStream inputStream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
Map<String, MetricFamily> metricFamilyMap = OnlineParser.parseMetrics(inputStream);
|
||||
|
||||
assertNotNull(metricFamilyMap);
|
||||
assertEquals(1, metricFamilyMap.values().size());
|
||||
|
||||
MetricFamily metricFamily = metricFamilyMap.get("windows_service_info");
|
||||
assertEquals("windows_service_info", metricFamily.getName());
|
||||
assertNotNull(metricFamily.getMetricList().get(0).getLabels());
|
||||
assertEquals(4, metricFamily.getMetricList().get(0).getLabels().size());
|
||||
assertEquals(1.0, metricFamily.getMetricList().get(0).getValue());
|
||||
|
||||
assertEquals("display_name", metricFamily.getMetricList().get(0).getLabels().get(0).getName());
|
||||
assertEquals("\\Application Layer \nGateway Service", metricFamily.getMetricList().get(0).getLabels().get(0).getValue());
|
||||
|
||||
assertEquals("name", metricFamily.getMetricList().get(0).getLabels().get(1).getName());
|
||||
assertEquals("alg\\", metricFamily.getMetricList().get(0).getLabels().get(1).getValue());
|
||||
|
||||
assertEquals("process_id", metricFamily.getMetricList().get(0).getLabels().get(2).getName());
|
||||
assertEquals("0", metricFamily.getMetricList().get(0).getLabels().get(2).getValue());
|
||||
|
||||
assertEquals("run_as", metricFamily.getMetricList().get(0).getLabels().get(3).getName());
|
||||
assertEquals("NT AUTHORITY\\LocalService", metricFamily.getMetricList().get(0).getLabels().get(3).getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseMetricEscape2() throws Exception {
|
||||
// test escape '\"'
|
||||
String str = "# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.\r\n"
|
||||
+ "# TYPE go_gc_duration_seconds summary\n"
|
||||
+ "windows_service_info{display_name=\"\\\"Application Layer \\\"Gateway Service\",name=\"alg\\\"\",process_id=\"0\",run_as=\"NT AUTHORITY\\\"LocalService\"} 1\n";
|
||||
InputStream inputStream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
Map<String, MetricFamily> metricFamilyMap = OnlineParser.parseMetrics(inputStream);
|
||||
|
||||
assertNotNull(metricFamilyMap);
|
||||
assertEquals(1, metricFamilyMap.values().size());
|
||||
|
||||
MetricFamily metricFamily = metricFamilyMap.get("windows_service_info");
|
||||
assertEquals("windows_service_info", metricFamily.getName());
|
||||
assertNotNull(metricFamily.getMetricList().get(0).getLabels());
|
||||
assertEquals(4, metricFamily.getMetricList().get(0).getLabels().size());
|
||||
assertEquals(1.0, metricFamily.getMetricList().get(0).getValue());
|
||||
|
||||
assertEquals("display_name", metricFamily.getMetricList().get(0).getLabels().get(0).getName());
|
||||
assertEquals("\"Application Layer \"Gateway Service", metricFamily.getMetricList().get(0).getLabels().get(0).getValue());
|
||||
|
||||
assertEquals("name", metricFamily.getMetricList().get(0).getLabels().get(1).getName());
|
||||
assertEquals("alg\"", metricFamily.getMetricList().get(0).getLabels().get(1).getValue());
|
||||
|
||||
assertEquals("process_id", metricFamily.getMetricList().get(0).getLabels().get(2).getName());
|
||||
assertEquals("0", metricFamily.getMetricList().get(0).getLabels().get(2).getValue());
|
||||
|
||||
assertEquals("run_as", metricFamily.getMetricList().get(0).getLabels().get(3).getName());
|
||||
assertEquals("NT AUTHORITY\"LocalService", metricFamily.getMetricList().get(0).getLabels().get(3).getValue());
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseMetricEscape3() throws Exception {
|
||||
// test escape '\n'
|
||||
String str = "# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.\r\n"
|
||||
+ "# TYPE go_gc_duration_seconds summary\n"
|
||||
+ "windows_service_info{display_name=\"\nApplication Layer \nGateway Service\",name=\"alg\n\",process_id=\"0\",run_as=\"NT AUTHORITY\nLocalService\"} 1\n";
|
||||
InputStream inputStream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
Map<String, MetricFamily> metricFamilyMap = OnlineParser.parseMetrics(inputStream);
|
||||
|
||||
assertNotNull(metricFamilyMap);
|
||||
assertEquals(1, metricFamilyMap.values().size());
|
||||
|
||||
MetricFamily metricFamily = metricFamilyMap.get("windows_service_info");
|
||||
assertEquals("windows_service_info", metricFamily.getName());
|
||||
assertNotNull(metricFamily.getMetricList().get(0).getLabels());
|
||||
assertEquals(4, metricFamily.getMetricList().get(0).getLabels().size());
|
||||
assertEquals(1.0, metricFamily.getMetricList().get(0).getValue());
|
||||
|
||||
assertEquals("display_name", metricFamily.getMetricList().get(0).getLabels().get(0).getName());
|
||||
assertEquals("\nApplication Layer \nGateway Service", metricFamily.getMetricList().get(0).getLabels().get(0).getValue());
|
||||
|
||||
assertEquals("name", metricFamily.getMetricList().get(0).getLabels().get(1).getName());
|
||||
assertEquals("alg\n", metricFamily.getMetricList().get(0).getLabels().get(1).getValue());
|
||||
|
||||
assertEquals("process_id", metricFamily.getMetricList().get(0).getLabels().get(2).getName());
|
||||
assertEquals("0", metricFamily.getMetricList().get(0).getLabels().get(2).getValue());
|
||||
|
||||
assertEquals("run_as", metricFamily.getMetricList().get(0).getLabels().get(3).getName());
|
||||
assertEquals("NT AUTHORITY\nLocalService", metricFamily.getMetricList().get(0).getLabels().get(3).getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,4 +345,4 @@
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
</project>
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ spring:
|
||||
|
||||
collector:
|
||||
info:
|
||||
version: ${COLLECTOR_VERSION:1.7.2}
|
||||
version: ${COLLECTOR_VERSION:1.7.3}
|
||||
ip: ${COLLECTOR_IP:}
|
||||
dispatch:
|
||||
entrance:
|
||||
|
||||
+10
-1
@@ -171,7 +171,16 @@ public final class CollectUtil {
|
||||
* assert prom field
|
||||
*/
|
||||
public static Boolean assertPromRequireField(String aliasField) {
|
||||
return CommonConstants.PROM_TIME.equals(aliasField) || CommonConstants.PROM_VALUE.equals(aliasField);
|
||||
return CommonConstants.PROM_TIME.equals(aliasField)
|
||||
|| CommonConstants.PROM_VALUE.equals(aliasField)
|
||||
|| CommonConstants.PROM_METRIC_VALUE.equals(aliasField);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if "value" field should be treated as a label instead of special field
|
||||
*/
|
||||
public static Boolean isValueAsLabel(String aliasField, JsonObject metric) {
|
||||
return CommonConstants.PROM_VALUE.equals(aliasField) && null != metric && metric.has(aliasField);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -83,13 +83,10 @@
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
<artifactId>hertzbeat-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>apache-${project.artifactId}-${hzb.version}</finalName>
|
||||
</build>
|
||||
|
||||
|
||||
|
||||
</project>
|
||||
|
||||
+5
@@ -197,6 +197,11 @@ public interface CommonConstants {
|
||||
*/
|
||||
String PROM_VALUE = "value";
|
||||
|
||||
/**
|
||||
* prometheus metric value alias
|
||||
*/
|
||||
String PROM_METRIC_VALUE = "metric_value";
|
||||
|
||||
/**
|
||||
* Parameter Type Number
|
||||
*/
|
||||
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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.manager;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.UniqueConstraint;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
|
||||
/**
|
||||
* Metrics Favorite Entity
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "hzb_metrics_favorite",
|
||||
uniqueConstraints = @UniqueConstraint(columnNames = {"creator", "monitor_id", "metrics_name"}))
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class MetricsFavorite {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@NotBlank(message = "Creator cannot be null or blank")
|
||||
@Size(max = 255, message = "Creator length cannot exceed 255 characters")
|
||||
@Column(name = "creator", nullable = false)
|
||||
private String creator;
|
||||
|
||||
@NotNull(message = "Monitor ID cannot be null")
|
||||
@Column(name = "monitor_id", nullable = false)
|
||||
private Long monitorId;
|
||||
|
||||
@NotBlank(message = "Metrics name cannot be null or blank")
|
||||
@Size(max = 255, message = "Metrics name length cannot exceed 255 characters")
|
||||
@Column(name = "metrics_name", nullable = false)
|
||||
private String metricsName;
|
||||
|
||||
@CreatedDate
|
||||
@Column(name = "create_time", updatable = false)
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
+323
@@ -0,0 +1,323 @@
|
||||
/*
|
||||
* 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.manager;
|
||||
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import jakarta.validation.Validation;
|
||||
import jakarta.validation.Validator;
|
||||
import jakarta.validation.ValidatorFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* Test case for {@link MetricsFavorite}
|
||||
*/
|
||||
class MetricsFavoriteTest {
|
||||
|
||||
private final ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
|
||||
private final Validator validator = factory.getValidator();
|
||||
|
||||
@Test
|
||||
void testBuilder() {
|
||||
String creator = "testUser";
|
||||
Long monitorId = 1L;
|
||||
String metricsName = "cpu";
|
||||
LocalDateTime createTime = LocalDateTime.now();
|
||||
|
||||
MetricsFavorite favorite = MetricsFavorite.builder()
|
||||
.id(1L)
|
||||
.creator(creator)
|
||||
.monitorId(monitorId)
|
||||
.metricsName(metricsName)
|
||||
.createTime(createTime)
|
||||
.build();
|
||||
|
||||
assertNotNull(favorite);
|
||||
assertEquals(1L, favorite.getId());
|
||||
assertEquals(creator, favorite.getCreator());
|
||||
assertEquals(monitorId, favorite.getMonitorId());
|
||||
assertEquals(metricsName, favorite.getMetricsName());
|
||||
assertEquals(createTime, favorite.getCreateTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBuilderWithoutOptionalFields() {
|
||||
String creator = "testUser";
|
||||
Long monitorId = 1L;
|
||||
String metricsName = "cpu";
|
||||
|
||||
MetricsFavorite favorite = MetricsFavorite.builder()
|
||||
.creator(creator)
|
||||
.monitorId(monitorId)
|
||||
.metricsName(metricsName)
|
||||
.build();
|
||||
|
||||
assertNotNull(favorite);
|
||||
assertNull(favorite.getId());
|
||||
assertEquals(creator, favorite.getCreator());
|
||||
assertEquals(monitorId, favorite.getMonitorId());
|
||||
assertEquals(metricsName, favorite.getMetricsName());
|
||||
assertNull(favorite.getCreateTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefaultConstructor() {
|
||||
MetricsFavorite favorite = new MetricsFavorite();
|
||||
|
||||
assertNotNull(favorite);
|
||||
assertNull(favorite.getId());
|
||||
assertNull(favorite.getCreator());
|
||||
assertNull(favorite.getMonitorId());
|
||||
assertNull(favorite.getMetricsName());
|
||||
assertNull(favorite.getCreateTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSettersAndGetters() {
|
||||
MetricsFavorite favorite = new MetricsFavorite();
|
||||
String creator = "testUser";
|
||||
Long monitorId = 1L;
|
||||
String metricsName = "cpu";
|
||||
LocalDateTime createTime = LocalDateTime.now();
|
||||
|
||||
favorite.setId(1L);
|
||||
favorite.setCreator(creator);
|
||||
favorite.setMonitorId(monitorId);
|
||||
favorite.setMetricsName(metricsName);
|
||||
favorite.setCreateTime(createTime);
|
||||
|
||||
assertEquals(1L, favorite.getId());
|
||||
assertEquals(creator, favorite.getCreator());
|
||||
assertEquals(monitorId, favorite.getMonitorId());
|
||||
assertEquals(metricsName, favorite.getMetricsName());
|
||||
assertEquals(createTime, favorite.getCreateTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidation_ValidEntity() {
|
||||
MetricsFavorite favorite = MetricsFavorite.builder()
|
||||
.creator("testUser")
|
||||
.monitorId(1L)
|
||||
.metricsName("cpu")
|
||||
.createTime(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
Set<ConstraintViolation<MetricsFavorite>> violations = validator.validate(favorite);
|
||||
|
||||
assertTrue(violations.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidation_NullCreator() {
|
||||
MetricsFavorite favorite = MetricsFavorite.builder()
|
||||
.creator(null)
|
||||
.monitorId(1L)
|
||||
.metricsName("cpu")
|
||||
.createTime(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
Set<ConstraintViolation<MetricsFavorite>> violations = validator.validate(favorite);
|
||||
|
||||
assertFalse(violations.isEmpty());
|
||||
assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("creator")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidation_BlankCreator() {
|
||||
MetricsFavorite favorite = MetricsFavorite.builder()
|
||||
.creator(" ")
|
||||
.monitorId(1L)
|
||||
.metricsName("cpu")
|
||||
.createTime(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
Set<ConstraintViolation<MetricsFavorite>> violations = validator.validate(favorite);
|
||||
|
||||
assertFalse(violations.isEmpty());
|
||||
assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("creator")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidation_CreatorTooLong() {
|
||||
String longCreator = "a".repeat(256);
|
||||
MetricsFavorite favorite = MetricsFavorite.builder()
|
||||
.creator(longCreator)
|
||||
.monitorId(1L)
|
||||
.metricsName("cpu")
|
||||
.createTime(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
Set<ConstraintViolation<MetricsFavorite>> violations = validator.validate(favorite);
|
||||
|
||||
assertFalse(violations.isEmpty());
|
||||
assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("creator")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidation_NullMonitorId() {
|
||||
MetricsFavorite favorite = MetricsFavorite.builder()
|
||||
.creator("testUser")
|
||||
.monitorId(null)
|
||||
.metricsName("cpu")
|
||||
.createTime(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
Set<ConstraintViolation<MetricsFavorite>> violations = validator.validate(favorite);
|
||||
|
||||
assertFalse(violations.isEmpty());
|
||||
assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("monitorId")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidation_NullMetricsName() {
|
||||
MetricsFavorite favorite = MetricsFavorite.builder()
|
||||
.creator("testUser")
|
||||
.monitorId(1L)
|
||||
.metricsName(null)
|
||||
.createTime(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
Set<ConstraintViolation<MetricsFavorite>> violations = validator.validate(favorite);
|
||||
|
||||
assertFalse(violations.isEmpty());
|
||||
assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("metricsName")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidation_BlankMetricsName() {
|
||||
MetricsFavorite favorite = MetricsFavorite.builder()
|
||||
.creator("testUser")
|
||||
.monitorId(1L)
|
||||
.metricsName(" ")
|
||||
.createTime(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
Set<ConstraintViolation<MetricsFavorite>> violations = validator.validate(favorite);
|
||||
|
||||
assertFalse(violations.isEmpty());
|
||||
assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("metricsName")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidation_MetricsNameTooLong() {
|
||||
String longMetricsName = "a".repeat(256);
|
||||
MetricsFavorite favorite = MetricsFavorite.builder()
|
||||
.creator("testUser")
|
||||
.monitorId(1L)
|
||||
.metricsName(longMetricsName)
|
||||
.createTime(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
Set<ConstraintViolation<MetricsFavorite>> violations = validator.validate(favorite);
|
||||
|
||||
assertFalse(violations.isEmpty());
|
||||
assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("metricsName")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEqualsAndHashCode() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
MetricsFavorite favorite1 = MetricsFavorite.builder()
|
||||
.id(1L)
|
||||
.creator("testUser")
|
||||
.monitorId(1L)
|
||||
.metricsName("cpu")
|
||||
.createTime(now)
|
||||
.build();
|
||||
|
||||
MetricsFavorite favorite2 = MetricsFavorite.builder()
|
||||
.id(1L)
|
||||
.creator("testUser")
|
||||
.monitorId(1L)
|
||||
.metricsName("cpu")
|
||||
.createTime(now)
|
||||
.build();
|
||||
|
||||
MetricsFavorite favorite3 = MetricsFavorite.builder()
|
||||
.id(2L)
|
||||
.creator("testUser")
|
||||
.monitorId(1L)
|
||||
.metricsName("cpu")
|
||||
.createTime(now)
|
||||
.build();
|
||||
|
||||
assertEquals(favorite1, favorite2);
|
||||
assertEquals(favorite1.hashCode(), favorite2.hashCode());
|
||||
assertNotEquals(favorite1, favorite3);
|
||||
assertNotEquals(favorite1.hashCode(), favorite3.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testToString() {
|
||||
MetricsFavorite favorite = MetricsFavorite.builder()
|
||||
.id(1L)
|
||||
.creator("testUser")
|
||||
.monitorId(1L)
|
||||
.metricsName("cpu")
|
||||
.createTime(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
String toString = favorite.toString();
|
||||
|
||||
assertNotNull(toString);
|
||||
assertTrue(toString.contains("MetricsFavorite"));
|
||||
assertTrue(toString.contains("testUser"));
|
||||
assertTrue(toString.contains("cpu"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreatorMaxLength() {
|
||||
String maxLengthCreator = "a".repeat(255);
|
||||
MetricsFavorite favorite = MetricsFavorite.builder()
|
||||
.creator(maxLengthCreator)
|
||||
.monitorId(1L)
|
||||
.metricsName("cpu")
|
||||
.createTime(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
Set<ConstraintViolation<MetricsFavorite>> violations = validator.validate(favorite);
|
||||
|
||||
assertTrue(violations.isEmpty());
|
||||
assertEquals(255, favorite.getCreator().length());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMetricsNameMaxLength() {
|
||||
String maxLengthMetricsName = "a".repeat(255);
|
||||
MetricsFavorite favorite = MetricsFavorite.builder()
|
||||
.creator("testUser")
|
||||
.monitorId(1L)
|
||||
.metricsName(maxLengthMetricsName)
|
||||
.createTime(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
Set<ConstraintViolation<MetricsFavorite>> violations = validator.validate(favorite);
|
||||
|
||||
assertTrue(violations.isEmpty());
|
||||
assertEquals(255, favorite.getMetricsName().length());
|
||||
}
|
||||
}
|
||||
+1
-5
@@ -128,11 +128,7 @@ public class DashboardService {
|
||||
? GrafanaConstants.generateUseDatasource(currentDatasourceName) : "";
|
||||
|
||||
String relativeDashboardUrl = grafanaDashboard.getUrl();
|
||||
if (relativeDashboardUrl != null && grafanaProperties.getUrl() != null && relativeDashboardUrl.startsWith(grafanaProperties.getUrl())) {
|
||||
relativeDashboardUrl = relativeDashboardUrl.substring(grafanaProperties.getUrl().length());
|
||||
}
|
||||
String fullDashboardUrl = grafanaProperties.exposeUrl().replaceAll("/$", "")
|
||||
+ (relativeDashboardUrl != null ? relativeDashboardUrl.replaceAll("^/", "") : "");
|
||||
String fullDashboardUrl = grafanaProperties.exposeUrl().replaceAll("/$", "") + relativeDashboardUrl;
|
||||
|
||||
grafanaDashboard.setUrl(fullDashboardUrl + KIOSK + REFRESH + INSTANCE + monitorId + useDatasource);
|
||||
|
||||
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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.grafana.service;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.grafana.GrafanaDashboard;
|
||||
import org.apache.hertzbeat.grafana.config.GrafanaProperties;
|
||||
import org.apache.hertzbeat.grafana.dao.DashboardDao;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
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.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Test case for {@link DashboardService}
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class DashboardServiceTest {
|
||||
|
||||
@Mock
|
||||
private ServiceAccountService serviceAccountService;
|
||||
|
||||
@Mock
|
||||
private DashboardDao dashboardDao;
|
||||
|
||||
@Mock
|
||||
private GrafanaProperties grafanaProperties;
|
||||
|
||||
@Mock
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
@Mock
|
||||
private DatasourceService datasourceService;
|
||||
|
||||
@InjectMocks
|
||||
private DashboardService dashboardService;
|
||||
|
||||
static final String GRAFANA_API_RESULT = "{"
|
||||
+ " \"folderUid\": \"\","
|
||||
+ " \"id\": 3,"
|
||||
+ " \"slug\": \"prometheus-dashboard\","
|
||||
+ " \"status\": \"success\","
|
||||
+ " \"uid\": \"5d7d89b0-b273-40fe-bb30-d652b82f47eb\","
|
||||
+ " \"url\": \"/d/5d7d89b0-b273-40fe-bb30-d652b82f47eb/prometheus-dashboard\","
|
||||
+ " \"version\": 3"
|
||||
+ "}";
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
when(datasourceService.getCurrentDatasourceName()).thenReturn("hertzbeat-vm-localhost-8428");
|
||||
when(grafanaProperties.enabled()).thenReturn(Boolean.TRUE);
|
||||
when(grafanaProperties.getPrefix()).thenReturn("");
|
||||
when(grafanaProperties.getUrl()).thenReturn("http://127.0.0.1:3000");
|
||||
when(grafanaProperties.exposeUrl()).thenReturn("http://127.0.0.1:3000");
|
||||
when(serviceAccountService.getToken()).thenReturn("test-token");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testCreateOrUpdateDashboard() {
|
||||
ResponseEntity<String> responseEntity = new ResponseEntity<>(GRAFANA_API_RESULT, HttpStatus.OK);
|
||||
|
||||
when(restTemplate.postForEntity(
|
||||
eq("http://127.0.0.1:3000/api/dashboards/db"), any(HttpEntity.class), eq(String.class)
|
||||
)).thenReturn(responseEntity);
|
||||
|
||||
ArgumentCaptor<GrafanaDashboard> dashboardCaptor = ArgumentCaptor.forClass(GrafanaDashboard.class);
|
||||
dashboardService.createOrUpdateDashboard("{\"id\":11}", 1L);
|
||||
verify(dashboardDao).save(dashboardCaptor.capture());
|
||||
|
||||
GrafanaDashboard savedDashboard = dashboardCaptor.getValue();
|
||||
assertNotNull(savedDashboard);
|
||||
assertNotNull(savedDashboard.getUrl());
|
||||
|
||||
String expectedBaseUrl = "http://127.0.0.1:3000/d/5d7d89b0-b273-40fe-bb30-d652b82f47eb";
|
||||
|
||||
// Verify that the URL begins with the expected base URL.
|
||||
assertTrue(savedDashboard.getUrl().startsWith(expectedBaseUrl), "URL should start with: " + expectedBaseUrl + ", but was: " + savedDashboard.getUrl());
|
||||
|
||||
assertTrue(savedDashboard.getUrl().contains("kiosk=tv"), "URL should contain kiosk parameter");
|
||||
assertTrue(savedDashboard.getUrl().contains("refresh=15s"), "URL should contain refresh parameter");
|
||||
assertTrue(savedDashboard.getUrl().contains("var-instance=1"), "URL should contain instance parameter");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateOrUpdateDashboardWithTrailingSlash() {
|
||||
when(grafanaProperties.exposeUrl()).thenReturn("http://127.0.0.1:3000/");
|
||||
|
||||
ResponseEntity<String> responseEntity = new ResponseEntity<>(GRAFANA_API_RESULT, HttpStatus.OK);
|
||||
|
||||
when(restTemplate.postForEntity(
|
||||
eq("http://127.0.0.1:3000/api/dashboards/db"), any(HttpEntity.class), eq(String.class)
|
||||
)).thenReturn(responseEntity);
|
||||
|
||||
ArgumentCaptor<GrafanaDashboard> dashboardCaptor = ArgumentCaptor.forClass(GrafanaDashboard.class);
|
||||
dashboardService.createOrUpdateDashboard("{\"id\":11}", 1L);
|
||||
verify(dashboardDao).save(dashboardCaptor.capture());
|
||||
|
||||
GrafanaDashboard savedDashboard = dashboardCaptor.getValue();
|
||||
assertNotNull(savedDashboard);
|
||||
assertNotNull(savedDashboard.getUrl());
|
||||
|
||||
String expectedBaseUrl = "http://127.0.0.1:3000/d/5d7d89b0-b273-40fe-bb30-d652b82f47eb";
|
||||
|
||||
// Verify that the URL begins with the expected base URL.
|
||||
assertTrue(savedDashboard.getUrl().startsWith(expectedBaseUrl), "URL should start with: " + expectedBaseUrl + ", but was: " + savedDashboard.getUrl());
|
||||
|
||||
assertTrue(savedDashboard.getUrl().contains("kiosk=tv"), "URL should contain kiosk parameter");
|
||||
assertTrue(savedDashboard.getUrl().contains("refresh=15s"), "URL should contain refresh parameter");
|
||||
assertTrue(savedDashboard.getUrl().contains("var-instance=1"), "URL should contain instance parameter");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -210,6 +210,11 @@
|
||||
<groupId>org.apache.arrow</groupId>
|
||||
<artifactId>arrow-memory-netty</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
<artifactId>hertzbeat-ai-agent</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.micrometer</groupId>
|
||||
<artifactId>micrometer-registry-prometheus</artifactId>
|
||||
@@ -288,6 +293,21 @@
|
||||
</descriptors>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>make-docker-zip</id>
|
||||
<!--Bound maven operation-->
|
||||
<phase>package</phase>
|
||||
<!--Run once-->
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>../dist</outputDirectory>
|
||||
<descriptors>
|
||||
<descriptor>../script/assembly/server/assembly-docker.xml</descriptor>
|
||||
</descriptors>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>make-docker-compose-script</id>
|
||||
<!--Bound maven operation-->
|
||||
|
||||
@@ -52,4 +52,4 @@ public class Manager {
|
||||
public void init() {
|
||||
System.setProperty("jdk.jndi.object.factoriesFilter", "!com.zaxxer.hikari.HikariJNDIFactory");
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
-4
@@ -26,10 +26,12 @@ import org.apache.hertzbeat.common.entity.dto.ManagerMessage;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
@@ -57,16 +59,24 @@ public class ManagerSseManager {
|
||||
.name(eventName)
|
||||
.data(data));
|
||||
} catch (IOException | IllegalStateException e) {
|
||||
emitter.complete();
|
||||
removeEmitter(clientId);
|
||||
tryCompleteAndClean(clientId, emitter);
|
||||
} catch (Exception exception) {
|
||||
log.error("Failed to broadcast manager message data to client: {}", exception.getMessage());
|
||||
emitter.complete();
|
||||
removeEmitter(clientId);
|
||||
tryCompleteAndClean(clientId, emitter);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void tryCompleteAndClean(Long clientId, SseEmitter emitter) {
|
||||
try {
|
||||
Optional.ofNullable(emitter).ifPresent(ResponseBodyEmitter::complete);
|
||||
} catch (Throwable e) {
|
||||
log.debug("Failed to complete emitter for client {}: {}", clientId, e.getMessage());
|
||||
}
|
||||
// execute clear
|
||||
removeEmitter(clientId);
|
||||
}
|
||||
|
||||
public void broadcastImportTaskInProgress(String taskName, Integer progress){
|
||||
ManagerMessage managerMessage = ImportTaskMessage.createInProgressMessage(taskName, progress);
|
||||
broadcast(ManagerEventTypeEnum.IMPORT_TASK_EVENT.getValue(), JsonUtil.toJson(managerMessage));
|
||||
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.controller;
|
||||
|
||||
import com.usthe.sureness.subject.SubjectSum;
|
||||
import com.usthe.sureness.util.SurenessContextHolder;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.common.entity.dto.Message;
|
||||
import org.apache.hertzbeat.manager.service.MetricsFavoriteService;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import static org.apache.hertzbeat.common.constants.CommonConstants.LOGIN_FAILED_CODE;
|
||||
|
||||
/**
|
||||
* Metrics Favorite Controller
|
||||
*/
|
||||
@Tag(name = "Metrics Favorite API")
|
||||
@RestController
|
||||
@RequestMapping(path = "/api/metrics/favorite")
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class MetricsFavoriteController {
|
||||
|
||||
private final MetricsFavoriteService metricsFavoriteService;
|
||||
|
||||
@PostMapping("/{monitorId}/{metricsName}")
|
||||
@Operation(summary = "Add metrics to favorites", description = "Add specific metrics to user's favorites")
|
||||
public ResponseEntity<Message<Void>> addMetricsFavorite(
|
||||
@Parameter(description = "Monitor ID", example = "6565463543") @PathVariable Long monitorId,
|
||||
@Parameter(description = "Metrics name", example = "cpu") @PathVariable String metricsName) {
|
||||
String user = getCurrentUser();
|
||||
if (user == null) {
|
||||
return ResponseEntity.ok(Message.fail(LOGIN_FAILED_CODE, "User not authenticated"));
|
||||
}
|
||||
metricsFavoriteService.addMetricsFavorite(user, monitorId, metricsName);
|
||||
return ResponseEntity.ok(Message.success("Metrics added to favorites successfully"));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{monitorId}/{metricsName}")
|
||||
@Operation(summary = "Remove metrics from favorites", description = "Remove specific metrics from user's favorites")
|
||||
public ResponseEntity<Message<Void>> removeMetricsFavorite(
|
||||
@Parameter(description = "Monitor ID", example = "6565463543") @PathVariable Long monitorId,
|
||||
@Parameter(description = "Metrics name", example = "cpu") @PathVariable String metricsName) {
|
||||
|
||||
String user = getCurrentUser();
|
||||
if (user == null) {
|
||||
return ResponseEntity.ok(Message.fail(LOGIN_FAILED_CODE, "User not authenticated"));
|
||||
}
|
||||
metricsFavoriteService.removeMetricsFavorite(user, monitorId, metricsName);
|
||||
return ResponseEntity.ok(Message.success("Metrics removed from favorites successfully"));
|
||||
}
|
||||
|
||||
@GetMapping("/{monitorId}")
|
||||
@Operation(summary = "Get user's all favorited metrics", description = "Get all favorited metrics for current user")
|
||||
public ResponseEntity<Message<Set<String>>> getUserFavoritedMetrics(@Parameter(description = "Monitor ID", example = "6565463543") @PathVariable Long monitorId) {
|
||||
String user = getCurrentUser();
|
||||
if (user == null) {
|
||||
return ResponseEntity.ok(Message.fail(LOGIN_FAILED_CODE, "User not authenticated"));
|
||||
}
|
||||
Set<String> favoritedMetrics = metricsFavoriteService.getUserFavoritedMetrics(user, monitorId);
|
||||
return ResponseEntity.ok(Message.success(favoritedMetrics));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current user ID for favorite status
|
||||
*
|
||||
* @return user id
|
||||
*/
|
||||
private String getCurrentUser() {
|
||||
try {
|
||||
SubjectSum subjectSum = SurenessContextHolder.getBindSubject();
|
||||
return String.valueOf(subjectSum.getPrincipal());
|
||||
} catch (Exception e) {
|
||||
log.error("No user found, favorites will be disabled");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
-8
@@ -17,10 +17,14 @@
|
||||
|
||||
package org.apache.hertzbeat.manager.controller;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import org.springframework.data.domain.Page;
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.dto.Message;
|
||||
@@ -37,6 +41,7 @@ import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
@@ -49,7 +54,7 @@ public class StatusPageController {
|
||||
|
||||
@Autowired
|
||||
private StatusPageService statusPageService;
|
||||
|
||||
|
||||
@GetMapping("/org")
|
||||
@Operation(summary = "Query Status Page Organization")
|
||||
public ResponseEntity<Message<StatusPageOrg>> queryStatusPageOrg() {
|
||||
@@ -66,35 +71,35 @@ public class StatusPageController {
|
||||
StatusPageOrg org = statusPageService.saveStatusPageOrg(statusPageOrg);
|
||||
return ResponseEntity.ok(Message.success(org));
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/component")
|
||||
@Operation(summary = "Query Status Page Components")
|
||||
public ResponseEntity<Message<List<StatusPageComponent>>> queryStatusPageComponent() {
|
||||
List<StatusPageComponent> statusPageComponents = statusPageService.queryStatusPageComponents();
|
||||
return ResponseEntity.ok(Message.success(statusPageComponents));
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/component")
|
||||
@Operation(summary = "Save Status Page Component")
|
||||
public ResponseEntity<Message<Void>> newStatusPageComponent(@Valid @RequestBody StatusPageComponent statusPageComponent) {
|
||||
statusPageService.newStatusPageComponent(statusPageComponent);
|
||||
return ResponseEntity.ok(Message.success("Add success"));
|
||||
}
|
||||
|
||||
|
||||
@PutMapping("/component")
|
||||
@Operation(summary = "Update Status Page Component")
|
||||
public ResponseEntity<Message<Void>> updateStatusPageComponent(@Valid @RequestBody StatusPageComponent statusPageComponent) {
|
||||
statusPageService.updateStatusPageComponent(statusPageComponent);
|
||||
return ResponseEntity.ok(Message.success("Update success"));
|
||||
}
|
||||
|
||||
|
||||
@DeleteMapping("/component/{id}")
|
||||
@Operation(summary = "Delete Status Page Component")
|
||||
public ResponseEntity<Message<Void>> deleteStatusPageComponent(@PathVariable("id") final long id) {
|
||||
statusPageService.deleteStatusPageComponent(id);
|
||||
return ResponseEntity.ok(Message.success("Delete success"));
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/component/{id}")
|
||||
@Operation(summary = "Query Status Page Component")
|
||||
public ResponseEntity<Message<StatusPageComponent>> queryStatusPageComponent(@PathVariable("id") final long id) {
|
||||
@@ -132,8 +137,13 @@ public class StatusPageController {
|
||||
|
||||
@GetMapping("/incident")
|
||||
@Operation(summary = "Query Status Page Incidents")
|
||||
public ResponseEntity<Message<List<StatusPageIncident>>> queryStatusPageIncident() {
|
||||
List<StatusPageIncident> incidents = statusPageService.queryStatusPageIncidents();
|
||||
public ResponseEntity<Message<Page<StatusPageIncident>>> queryStatusPageIncident(
|
||||
@Parameter(description = "Search-Target", example = "x") @RequestParam(required = false) String search,
|
||||
@Parameter(description = "Start Time", example = "1756384301907") @RequestParam(required = false) Long startTime,
|
||||
@Parameter(description = "End Time", example = "1756384301907") @RequestParam(required = false) Long endTime,
|
||||
@Parameter(description = "List current page", example = "0") @RequestParam(defaultValue = "0") int pageIndex,
|
||||
@Parameter(description = "Number of list pages", example = "8") @RequestParam(defaultValue = "8") int pageSize) {
|
||||
Page<StatusPageIncident> incidents = statusPageService.queryStatusPageIncidents(search, startTime, endTime, pageIndex, pageSize);
|
||||
return ResponseEntity.ok(Message.success(incidents));
|
||||
}
|
||||
}
|
||||
|
||||
+15
-5
@@ -17,10 +17,14 @@
|
||||
|
||||
package org.apache.hertzbeat.manager.controller;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import org.springframework.data.domain.Page;
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.dto.Message;
|
||||
import org.apache.hertzbeat.common.entity.manager.StatusPageIncident;
|
||||
@@ -32,6 +36,7 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
@@ -44,7 +49,7 @@ public class StatusPagePublicController {
|
||||
|
||||
@Autowired
|
||||
private StatusPageService statusPageService;
|
||||
|
||||
|
||||
@GetMapping("/org")
|
||||
@Operation(summary = "Query Status Page Organization")
|
||||
public ResponseEntity<Message<StatusPageOrg>> queryStatusPageOrg() {
|
||||
@@ -54,14 +59,14 @@ public class StatusPagePublicController {
|
||||
}
|
||||
return ResponseEntity.ok(Message.success(statusPageOrg));
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/component")
|
||||
@Operation(summary = "Query Status Page Components")
|
||||
public ResponseEntity<Message<List<ComponentStatus>>> queryStatusPageComponent() {
|
||||
List<ComponentStatus> componentStatusList = statusPageService.queryComponentsStatus();
|
||||
return ResponseEntity.ok(Message.success(componentStatusList));
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/component/{id}")
|
||||
@Operation(summary = "Query Status Page Component")
|
||||
public ResponseEntity<Message<ComponentStatus>> queryStatusPageComponent(@PathVariable("id") final long id) {
|
||||
@@ -71,8 +76,13 @@ public class StatusPagePublicController {
|
||||
|
||||
@GetMapping("/incident")
|
||||
@Operation(summary = "Query Status Page Incidents")
|
||||
public ResponseEntity<Message<List<StatusPageIncident>>> queryStatusPageIncident() {
|
||||
List<StatusPageIncident> incidents = statusPageService.queryStatusPageIncidents();
|
||||
public ResponseEntity<Message<Page<StatusPageIncident>>> queryStatusPageIncident(
|
||||
@Parameter(description = "Search-Target", example = "x") @RequestParam(required = false) String search,
|
||||
@Parameter(description = "Start Time", example = "1756384301907") @RequestParam(required = false) Long startTime,
|
||||
@Parameter(description = "End Time", example = "1756384301907") @RequestParam(required = false) Long endTime,
|
||||
@Parameter(description = "List current page", example = "0") @RequestParam(defaultValue = "0") int pageIndex,
|
||||
@Parameter(description = "Number of list pages", example = "10") @RequestParam(defaultValue = "10") int pageSize) {
|
||||
Page<StatusPageIncident> incidents = statusPageService.queryStatusPageIncidents(search, startTime, endTime, pageIndex, pageSize);
|
||||
return ResponseEntity.ok(Message.success(incidents));
|
||||
}
|
||||
}
|
||||
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.dao;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.manager.MetricsFavorite;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* MetricsFavorite dao
|
||||
*/
|
||||
public interface MetricsFavoriteDao extends JpaRepository<MetricsFavorite, Long> {
|
||||
|
||||
/**
|
||||
* Find metrics favorite by creator and monitor id and metrics name
|
||||
*
|
||||
* @param creator user id
|
||||
* @param monitorId monitor id
|
||||
* @param metricsName metrics name
|
||||
* @return optional metrics favorite
|
||||
*/
|
||||
Optional<MetricsFavorite> findByCreatorAndMonitorIdAndMetricsName(String creator, Long monitorId, String metricsName);
|
||||
|
||||
/**
|
||||
* Find all metrics favorites by user id and monitor id
|
||||
*
|
||||
* @param creator user id
|
||||
* @param monitorId monitor id
|
||||
* @return list of metrics favorites
|
||||
*/
|
||||
List<MetricsFavorite> findByCreatorAndMonitorId(String creator, Long monitorId);
|
||||
|
||||
/**
|
||||
* Delete metrics favorite by user id and monitor id and metrics name
|
||||
*
|
||||
* @param creator user id
|
||||
* @param monitorId monitor id
|
||||
* @param metricsName metrics name
|
||||
*/
|
||||
@Modifying
|
||||
@Query("DELETE FROM MetricsFavorite mf WHERE mf.creator = :creator AND mf.monitorId = :monitorId AND mf.metricsName = :metricsName")
|
||||
void deleteByUserIdAndMonitorIdAndMetricsName(@Param("creator") String creator,
|
||||
@Param("monitorId") Long monitorId,
|
||||
@Param("metricsName") String metricsName);
|
||||
|
||||
/**
|
||||
* Delete metrics favorites by monitor ids
|
||||
*
|
||||
* @param monitorIds monitor ids
|
||||
*/
|
||||
@Modifying
|
||||
@Query("DELETE FROM MetricsFavorite mf WHERE mf.monitorId IN :monitorIds")
|
||||
void deleteFavoritesByMonitorIdIn(@Param("monitorIds") Set<Long> monitorIds);
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.pojo.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Metrics Information with favorite status
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(description = "Metrics information with favorite status")
|
||||
public class MetricsInfo {
|
||||
|
||||
@Schema(description = "Metrics name", example = "cpu")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "Whether the metrics is favorited by current user")
|
||||
private Boolean favorited;
|
||||
}
|
||||
+1
-1
@@ -47,7 +47,7 @@ public class MonitorDto {
|
||||
private List<Param> params;
|
||||
|
||||
@Schema(description = "Monitor Metrics", accessMode = READ_ONLY)
|
||||
private List<String> metrics;
|
||||
private List<MetricsInfo> metrics;
|
||||
|
||||
@Schema(description = "pinned collector, default null if system dispatch", accessMode = READ_WRITE)
|
||||
private String collector;
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.service;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Metrics Favorite Service
|
||||
*/
|
||||
public interface MetricsFavoriteService {
|
||||
|
||||
/**
|
||||
* Add metrics to favorites
|
||||
*
|
||||
* @param creator user id
|
||||
* @param monitorId monitor id
|
||||
* @param metricsName metrics name
|
||||
*/
|
||||
void addMetricsFavorite(String creator, Long monitorId, String metricsName);
|
||||
|
||||
/**
|
||||
* Remove metrics from favorites
|
||||
*
|
||||
* @param userId user id
|
||||
* @param monitorId monitor id
|
||||
* @param metricsName metrics name
|
||||
*/
|
||||
void removeMetricsFavorite(String userId, Long monitorId, String metricsName);
|
||||
|
||||
/**
|
||||
* Get user's favorited metrics names for a specific monitor
|
||||
*
|
||||
* @param userId user id
|
||||
* @param monitorId monitor id
|
||||
* @return set of favorited metrics names
|
||||
*/
|
||||
Set<String> getUserFavoritedMetrics(String userId, Long monitorId);
|
||||
|
||||
/**
|
||||
* Remove metrics from monitor ids
|
||||
*
|
||||
* @param monitorIds monitor ids
|
||||
*/
|
||||
void deleteFavoritesByMonitorIdIn(Set<Long> monitorIds);
|
||||
}
|
||||
+31
-10
@@ -18,10 +18,12 @@
|
||||
package org.apache.hertzbeat.manager.service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.manager.StatusPageComponent;
|
||||
import org.apache.hertzbeat.common.entity.manager.StatusPageIncident;
|
||||
import org.apache.hertzbeat.common.entity.manager.StatusPageOrg;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.ComponentStatus;
|
||||
import org.springframework.data.domain.Page;
|
||||
|
||||
/**
|
||||
* status page service interface.
|
||||
@@ -30,42 +32,49 @@ public interface StatusPageService {
|
||||
|
||||
/**
|
||||
* query status page organization.
|
||||
*
|
||||
* @return status page organization
|
||||
*/
|
||||
StatusPageOrg queryStatusPageOrg();
|
||||
|
||||
/**
|
||||
* save status page organization.
|
||||
*
|
||||
* @return status page organization
|
||||
*/
|
||||
StatusPageOrg saveStatusPageOrg(StatusPageOrg statusPageOrg);
|
||||
|
||||
|
||||
/**
|
||||
* query status page components.
|
||||
*
|
||||
* @return status page components
|
||||
*/
|
||||
List<StatusPageComponent> queryStatusPageComponents();
|
||||
|
||||
|
||||
/**
|
||||
* save status page component.
|
||||
*
|
||||
* @param statusPageComponent status page component
|
||||
*/
|
||||
void newStatusPageComponent(StatusPageComponent statusPageComponent);
|
||||
|
||||
|
||||
/**
|
||||
* update status page component.
|
||||
*
|
||||
* @param statusPageComponent status page component
|
||||
*/
|
||||
void updateStatusPageComponent(StatusPageComponent statusPageComponent);
|
||||
|
||||
/**
|
||||
* delete status page component.
|
||||
*
|
||||
* @param id status page component id
|
||||
*/
|
||||
void deleteStatusPageComponent(long id);
|
||||
|
||||
/**
|
||||
* query status page component.
|
||||
*
|
||||
* @param id status page component id
|
||||
* @return status page component
|
||||
*/
|
||||
@@ -73,44 +82,56 @@ public interface StatusPageService {
|
||||
|
||||
/**
|
||||
* query status page components status.
|
||||
*
|
||||
* @return status page components status
|
||||
*/
|
||||
List<ComponentStatus> queryComponentsStatus();
|
||||
|
||||
/**
|
||||
* query status page component status.
|
||||
*
|
||||
* @param id status page component id
|
||||
* @return status page component status
|
||||
*/
|
||||
ComponentStatus queryComponentStatus(long id);
|
||||
|
||||
|
||||
/**
|
||||
* query status page incidents.
|
||||
* @return status page incidents
|
||||
*
|
||||
* @param search Search Target
|
||||
* @param startTime Start Time
|
||||
* @param endTime End Time
|
||||
* @param pageIndex List current page
|
||||
* @param pageSize Number of list pages
|
||||
* @return The query results
|
||||
*/
|
||||
List<StatusPageIncident> queryStatusPageIncidents();
|
||||
|
||||
Page<StatusPageIncident> queryStatusPageIncidents(String search, Long startTime, Long endTime, int pageIndex, int pageSize);
|
||||
|
||||
/**
|
||||
* query status page incident.
|
||||
*
|
||||
* @param id status page incident id
|
||||
* @return status page incident
|
||||
*/
|
||||
StatusPageIncident queryStatusPageIncident(long id);
|
||||
|
||||
|
||||
/**
|
||||
* save status page incident.
|
||||
*
|
||||
* @param statusPageIncident status page incident
|
||||
*/
|
||||
void newStatusPageIncident(StatusPageIncident statusPageIncident);
|
||||
|
||||
|
||||
/**
|
||||
* update status page incident.
|
||||
*
|
||||
* @param statusPageIncident status page incident
|
||||
*/
|
||||
void updateStatusPageIncident(StatusPageIncident statusPageIncident);
|
||||
|
||||
|
||||
/**
|
||||
* delete status page incident.
|
||||
*
|
||||
* @param id status page incident id
|
||||
*/
|
||||
void deleteStatusPageIncident(long id);
|
||||
|
||||
+1
-1
@@ -212,7 +212,7 @@ public class AppServiceImpl implements AppService, InitializingBean {
|
||||
var name = job.getName();
|
||||
var i18nName = CommonUtil.getLangMappingValueFromI18nMap(lang, name);
|
||||
if (i18nName != null) {
|
||||
i18nMap.put("monitor.app." + job.getApp().toLowerCase(), i18nName);
|
||||
i18nMap.put("monitor.app." + job.getApp(), i18nName);
|
||||
}
|
||||
var help = job.getHelp();
|
||||
var i18nHelp = CommonUtil.getLangMappingValueFromI18nMap(lang, help);
|
||||
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.service.impl;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.hertzbeat.common.entity.manager.MetricsFavorite;
|
||||
import org.apache.hertzbeat.manager.dao.MetricsFavoriteDao;
|
||||
import org.apache.hertzbeat.manager.service.MetricsFavoriteService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Metrics Favorite Service Implementation
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public class MetricsFavoriteServiceImpl implements MetricsFavoriteService {
|
||||
|
||||
private final MetricsFavoriteDao metricsFavoriteDao;
|
||||
|
||||
@Override
|
||||
public void addMetricsFavorite(String creator, Long monitorId, String metricsName) {
|
||||
Optional<MetricsFavorite> existing = metricsFavoriteDao
|
||||
.findByCreatorAndMonitorIdAndMetricsName(creator, monitorId, metricsName);
|
||||
if (existing.isPresent()) {
|
||||
throw new RuntimeException("Metrics favorite already exists: " + metricsName);
|
||||
}
|
||||
MetricsFavorite favorite = MetricsFavorite.builder()
|
||||
.creator(creator)
|
||||
.monitorId(monitorId)
|
||||
.metricsName(metricsName)
|
||||
.createTime(LocalDateTime.now())
|
||||
.build();
|
||||
metricsFavoriteDao.save(favorite);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeMetricsFavorite(String userId, Long monitorId, String metricsName) {
|
||||
metricsFavoriteDao.deleteByUserIdAndMonitorIdAndMetricsName(userId, monitorId, metricsName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Set<String> getUserFavoritedMetrics(String userId, Long monitorId) {
|
||||
if (null == userId || null == monitorId) {
|
||||
return Set.of();
|
||||
}
|
||||
List<MetricsFavorite> favorites = metricsFavoriteDao.findByCreatorAndMonitorId(userId, monitorId);
|
||||
if (null == favorites || favorites.isEmpty()) {
|
||||
return Set.of();
|
||||
}
|
||||
return favorites.stream()
|
||||
.map(MetricsFavorite::getMetricsName)
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteFavoritesByMonitorIdIn(Set<Long> monitorIds) {
|
||||
if (null == monitorIds || monitorIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
metricsFavoriteDao.deleteFavoritesByMonitorIdIn(monitorIds);
|
||||
}
|
||||
|
||||
}
|
||||
+29
-9
@@ -19,6 +19,9 @@ package org.apache.hertzbeat.manager.service.impl;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.primitives.Longs;
|
||||
import com.usthe.sureness.subject.SubjectSum;
|
||||
import com.usthe.sureness.util.SurenessContextHolder;
|
||||
import jakarta.persistence.criteria.CriteriaBuilder;
|
||||
import jakarta.persistence.criteria.Predicate;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
@@ -44,7 +47,6 @@ import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
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;
|
||||
@@ -59,11 +61,13 @@ 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.pojo.dto.AppCount;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.MetricsInfo;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.MonitorDto;
|
||||
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.LabelService;
|
||||
import org.apache.hertzbeat.manager.service.MetricsFavoriteService;
|
||||
import org.apache.hertzbeat.manager.service.MonitorService;
|
||||
import org.apache.hertzbeat.manager.support.exception.MonitorDatabaseException;
|
||||
import org.apache.hertzbeat.manager.support.exception.MonitorDetectException;
|
||||
@@ -138,6 +142,8 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
private LabelDao labelDao;
|
||||
@Autowired
|
||||
private LabelService labelService;
|
||||
@Autowired
|
||||
private MetricsFavoriteService metricsFavoriteService;
|
||||
|
||||
public MonitorServiceImpl(List<ImExportService> imExportServiceList) {
|
||||
imExportServiceList.forEach(it -> imExportServiceMap.put(it.type(), it));
|
||||
@@ -575,6 +581,7 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
Set<Long> monitorIds = monitors.stream().map(Monitor::getId).collect(Collectors.toSet());
|
||||
alertDefineBindDao.deleteAlertDefineMonitorBindsByMonitorIdIn(monitorIds);
|
||||
monitorBindDao.deleteMonitorBindByBizIdIn(monitorIds);
|
||||
metricsFavoriteService.deleteFavoritesByMonitorIdIn(monitorIds);
|
||||
for (Monitor monitor : monitors) {
|
||||
monitorBindDao.deleteByMonitorId(monitor.getId());
|
||||
collectorMonitorBindDao.deleteCollectorMonitorBindsByMonitorId(monitor.getId());
|
||||
@@ -589,24 +596,37 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
public MonitorDto getMonitorDto(long id) throws RuntimeException {
|
||||
Optional<Monitor> monitorOptional = monitorDao.findById(id);
|
||||
if (monitorOptional.isPresent()) {
|
||||
// Get current user ID for favorite status
|
||||
String currentUserId = null;
|
||||
try {
|
||||
SubjectSum subjectSum = SurenessContextHolder.getBindSubject();
|
||||
currentUserId = String.valueOf(subjectSum.getPrincipal());
|
||||
} catch (Exception e) {
|
||||
log.debug("No user context found, favorites will be disabled");
|
||||
}
|
||||
Set<String> favoritedMetrics = metricsFavoriteService.getUserFavoritedMetrics(currentUserId, id);
|
||||
|
||||
Monitor monitor = monitorOptional.get();
|
||||
MonitorDto monitorDto = new MonitorDto();
|
||||
List<Param> params = paramDao.findParamsByMonitorId(id);
|
||||
monitorDto.setParams(params);
|
||||
List<MetricsInfo> metricsInfos;
|
||||
if (DispatchConstants.PROTOCOL_PROMETHEUS.equalsIgnoreCase(monitor.getApp()) || monitor.getType() == CommonConstants.MONITOR_TYPE_PUSH_AUTO_CREATE) {
|
||||
List<CollectRep.MetricsData> metricsDataList = warehouseService.queryMonitorMetricsData(id);
|
||||
List<String> metrics = metricsDataList.stream().map(CollectRep.MetricsData::getMetrics).collect(Collectors.toList());
|
||||
monitorDto.setMetrics(metrics);
|
||||
metricsInfos = metricsDataList.stream()
|
||||
.map(t -> MetricsInfo.builder().name(t.getMetrics()).favorited(favoritedMetrics.contains(t.getMetrics())).build())
|
||||
.collect(Collectors.toList());
|
||||
monitorDto.setGrafanaDashboard(dashboardService.getDashboardByMonitorId(id));
|
||||
} else {
|
||||
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()
|
||||
metricsInfos = job.getMetrics().stream()
|
||||
.filter(Metrics::isVisible)
|
||||
.map(Metrics::getName).collect(Collectors.toList());
|
||||
monitorDto.setMetrics(metrics);
|
||||
.map(t -> MetricsInfo.builder().name(t.getName()).favorited(favoritedMetrics.contains(t.getName())).build())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
monitorDto.setMetrics(metricsInfos);
|
||||
monitorDto.setMonitor(monitor);
|
||||
Optional<CollectorMonitorBind> bindOptional = collectorMonitorBindDao.findCollectorMonitorBindByMonitorId(monitor.getId());
|
||||
bindOptional.ifPresent(bind -> monitorDto.setCollector(bind.getCollector()));
|
||||
@@ -642,9 +662,9 @@ public class MonitorServiceImpl implements MonitorService {
|
||||
if (StringUtils.hasText(search)) {
|
||||
Predicate predicateHost = criteriaBuilder.like(root.get("host"), "%" + search + "%");
|
||||
Predicate predicateName = criteriaBuilder.like(criteriaBuilder.lower(root.get("name")), "%" + search.toLowerCase() + "%");
|
||||
if (CommonUtil.isNumeric(search)){
|
||||
Predicate predicateId = criteriaBuilder.equal(root.get("id"), Long.parseLong(search));
|
||||
orList.add(predicateId);
|
||||
Long id = Longs.tryParse(search);
|
||||
if (id != null) {
|
||||
orList.add(criteriaBuilder.equal(root.get("id"), id));
|
||||
}
|
||||
orList.add(predicateHost);
|
||||
orList.add(predicateName);
|
||||
|
||||
+28
-2
@@ -21,9 +21,12 @@ import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jakarta.persistence.criteria.Predicate;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.manager.StatusPageComponent;
|
||||
@@ -40,8 +43,12 @@ import org.apache.hertzbeat.manager.dao.StatusPageOrgDao;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.ComponentStatus;
|
||||
import org.apache.hertzbeat.manager.service.StatusPageService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* status page service implement.
|
||||
@@ -257,9 +264,28 @@ public class StatusPageServiceImpl implements StatusPageService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<StatusPageIncident> queryStatusPageIncidents() {
|
||||
public Page<StatusPageIncident> queryStatusPageIncidents(String search, Long startTime, Long endTime, int pageIndex, int pageSize) {
|
||||
// build search condition
|
||||
Specification<StatusPageIncident> specification = (root, query, criteriaBuilder) -> {
|
||||
List<Predicate> andList = new ArrayList<>();
|
||||
if (StringUtils.hasText(search)) {
|
||||
Predicate predicateName = criteriaBuilder.like(criteriaBuilder.lower(root.get("name")), "%" + search.toLowerCase() + "%");
|
||||
andList.add(predicateName);
|
||||
}
|
||||
if (startTime != null) {
|
||||
andList.add(criteriaBuilder.greaterThanOrEqualTo(root.get("startTime"), startTime));
|
||||
}
|
||||
if (endTime != null) {
|
||||
andList.add(criteriaBuilder.lessThanOrEqualTo(root.get("endTime"), endTime));
|
||||
}
|
||||
|
||||
Predicate[] predicates = new Predicate[andList.size()];
|
||||
return criteriaBuilder.and(andList.toArray(predicates));
|
||||
};
|
||||
|
||||
Sort sort = Sort.by(Sort.Direction.DESC, "startTime");
|
||||
return statusPageIncidentDao.findAll(sort);
|
||||
PageRequest pageRequest = PageRequest.of(pageIndex, pageSize, sort);
|
||||
return statusPageIncidentDao.findAll(specification, pageRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+1
-1
@@ -251,6 +251,6 @@ public class GlobalExceptionHandler {
|
||||
}
|
||||
log.error("[monitor]-[unknown error happen]-{}", errorMessage, exception);
|
||||
Message<Void> message = Message.fail(MONITOR_CONFLICT_CODE, errorMessage);
|
||||
return ResponseEntity.status(HttpStatus.CONFLICT).body(message);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,33 @@ spring:
|
||||
name: ${HOSTNAME:@hertzbeat@}${PID}
|
||||
profiles:
|
||||
active: prod
|
||||
ai:
|
||||
mcp:
|
||||
server:
|
||||
enabled: true
|
||||
stdio: false
|
||||
name: sse-mcp-server
|
||||
version: 1.0.0
|
||||
resource-change-notification: true
|
||||
tool-change-notification: true
|
||||
prompt-change-notification: true
|
||||
sse-endpoint: /api/sse
|
||||
sse-message-endpoint: /api/mcp/message
|
||||
type: SYNC
|
||||
capabilities:
|
||||
tool: true
|
||||
resource: true
|
||||
prompt: true
|
||||
completion: true
|
||||
chat:
|
||||
client:
|
||||
enabled: false
|
||||
openai:
|
||||
api-key: OPENAI_API_KEY
|
||||
chat:
|
||||
options:
|
||||
model: gpt-4.1-nano-2025-04-14
|
||||
|
||||
mvc:
|
||||
static-path-pattern: /**
|
||||
jackson:
|
||||
@@ -38,6 +65,7 @@ spring:
|
||||
max-file-size: 100MB
|
||||
max-request-size: 100MB
|
||||
|
||||
|
||||
management:
|
||||
health:
|
||||
mail:
|
||||
@@ -180,6 +208,11 @@ warehouse:
|
||||
database: public
|
||||
username: greptime
|
||||
password: greptime
|
||||
questdb:
|
||||
enabled: false
|
||||
url: localhost:9000
|
||||
username: admin
|
||||
password: quest
|
||||
iot-db:
|
||||
enabled: false
|
||||
host: 127.0.0.1
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
-- 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.
|
||||
|
||||
-- ensure every sql can rerun without error
|
||||
|
||||
-- Modify message column to TEXT (H2 TEXT is equivalent to CLOB)
|
||||
ALTER TABLE HZB_STATUS_PAGE_INCIDENT_CONTENT ALTER COLUMN message CLOB;
|
||||
@@ -0,0 +1,52 @@
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you under the Apache License, Version 2.0 (the
|
||||
-- "License"); you may not use this file except in compliance
|
||||
-- with the License. You may obtain a copy of the License at
|
||||
--
|
||||
-- http://www.apache.org/licenses/LICENSE-2.0
|
||||
--
|
||||
-- Unless required by applicable law or agreed to in writing,
|
||||
-- software distributed under the License is distributed on an
|
||||
-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
-- KIND, either express or implied. See the License for the
|
||||
-- specific language governing permissions and limitations
|
||||
-- under the License.
|
||||
|
||||
-- ensure every sql can rerun without error
|
||||
|
||||
-- Modify hzb_status_page_incident_content table columns to TEXT type to resolve MySQL row size limit issue
|
||||
|
||||
DELIMITER //
|
||||
CREATE PROCEDURE ModifyStatusIncidentContentColumns()
|
||||
BEGIN
|
||||
DECLARE table_exists INT;
|
||||
DECLARE col_exists INT;
|
||||
|
||||
-- Check if the table exists
|
||||
SELECT COUNT(*) INTO table_exists
|
||||
FROM INFORMATION_SCHEMA.TABLES
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'HZB_STATUS_PAGE_INCIDENT_CONTENT';
|
||||
|
||||
IF table_exists = 1 THEN
|
||||
-- Check and modify message column to TEXT
|
||||
SELECT COUNT(*) INTO col_exists
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'HZB_STATUS_PAGE_INCIDENT_CONTENT'
|
||||
AND COLUMN_NAME = 'message'
|
||||
AND DATA_TYPE != 'text';
|
||||
|
||||
IF col_exists = 1 THEN
|
||||
ALTER TABLE HZB_STATUS_PAGE_INCIDENT_CONTENT MODIFY COLUMN message TEXT;
|
||||
END IF;
|
||||
END IF;
|
||||
END //
|
||||
|
||||
DELIMITER ;
|
||||
|
||||
CALL ModifyStatusIncidentContentColumns();
|
||||
DROP PROCEDURE IF EXISTS ModifyStatusIncidentContentColumns;
|
||||
COMMIT;
|
||||
@@ -0,0 +1,23 @@
|
||||
-- Licensed to the Apache Software Foundation (ASF) under one
|
||||
-- or more contributor license agreements. See the NOTICE file
|
||||
-- distributed with this work for additional information
|
||||
-- regarding copyright ownership. The ASF licenses this file
|
||||
-- to you under the Apache License, Version 2.0 (the
|
||||
-- "License"); you may not use this file except in compliance
|
||||
-- with the License. You may obtain a copy of the License at
|
||||
--
|
||||
-- http://www.apache.org/licenses/LICENSE-2.0
|
||||
--
|
||||
-- Unless required by applicable law or agreed to in writing,
|
||||
-- software distributed under the License is distributed on an
|
||||
-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
-- KIND, either express or implied. See the License for the
|
||||
-- specific language governing permissions and limitations
|
||||
-- under the License.
|
||||
|
||||
-- ensure every sql can rerun without error
|
||||
|
||||
-- Modify message column to TEXT
|
||||
ALTER TABLE HZB_STATUS_PAGE_INCIDENT_CONTENT ALTER COLUMN message TYPE TEXT;
|
||||
|
||||
commit;
|
||||
@@ -0,0 +1,438 @@
|
||||
# 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.
|
||||
category: mid
|
||||
# The monitoring application type name (consistent with the file name)
|
||||
app: apollo
|
||||
# The monitoring i18n name
|
||||
name:
|
||||
zh-CN: Apollo配置中心
|
||||
en-US: Apollo Configuration Center
|
||||
# The description and help of this monitoring type
|
||||
help:
|
||||
zh-CN: HertzBeat 对 Apollo 配置中心的通用指标进行测量监控。<br>您可以点击 “<i>新建 Apollo 配置中心</i>” 并进行配置,或者选择“<i>更多操作</i>”,导入已有配置。<br>⚠️注意:adminservice、configservice、portal目前需要分别配置,只需要更改地址+端口即可。
|
||||
en-US: HertzBeat monitors common metrics for Apollo Configuration Centers.<br>You can click "<i>Create New Apollo Configuration Center</i>" to configure it, or select "<i>More Actions</i>" to import an existing configuration. <br>⚠️Note:adminservice, configservice, and portal currently require separate configuration. Simply modify the address and port.
|
||||
helpLink:
|
||||
zh-CN: https://hertzbeat.apache.org/zh-cn/docs/help/apollo
|
||||
en-US: https://hertzbeat.apache.org/docs/help/apollo
|
||||
# Input params define for monitoring(render web ui by the definition)
|
||||
params:
|
||||
# field-param field key
|
||||
- field: host
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 目标Host
|
||||
en-US: Target Host
|
||||
ja-JP: 目標ホスト
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: host
|
||||
# required-true or false
|
||||
required: true
|
||||
# field-param field key
|
||||
- field: port
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 端口
|
||||
en-US: Port
|
||||
ja-JP: ポート
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: number
|
||||
# when type is number, range is required
|
||||
range: '[0,65535]'
|
||||
required: true
|
||||
defaultValue: 8080
|
||||
# field-param field key
|
||||
- field: timeout
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 查询超时时间
|
||||
en-US: Query Timeout
|
||||
ja-JP: クエリタイムアウト
|
||||
type: number
|
||||
required: false
|
||||
# hide param-true or false
|
||||
hide: true
|
||||
# default value
|
||||
defaultValue: 6000
|
||||
|
||||
# collect metrics config list
|
||||
metrics:
|
||||
# Basic information indicators
|
||||
- name: application_ready_time_seconds
|
||||
i18n:
|
||||
zh-CN: 应用启动耗时
|
||||
en-US: Application ready time
|
||||
# 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: main_application_class
|
||||
type: 1
|
||||
label: true
|
||||
i18n:
|
||||
zh-CN: 主应用类
|
||||
en-US: Main class
|
||||
- field: metric_value
|
||||
type: 0
|
||||
unit: 's'
|
||||
i18n:
|
||||
zh-CN: 耗时
|
||||
en-US: Time Consuming
|
||||
# the protocol used for monitoring, eg: sql, ssh, http, telnet, wmi, snmp, sdk
|
||||
protocol: http
|
||||
# the config content when protocol is http
|
||||
http:
|
||||
# http host: ipv4 ipv6 domain
|
||||
host: ^_^host^_^
|
||||
# http port
|
||||
port: ^_^port^_^
|
||||
# http url
|
||||
url: /prometheus
|
||||
# http method: GET POST PUT DELETE PATCH
|
||||
method: GET
|
||||
# http response data parse type: default-hertzbeat rule, jsonpath-jsonpath script, website-for website monitoring, prometheus-prometheus exporter rule
|
||||
parseType: prometheus
|
||||
|
||||
- name: process_uptime_seconds
|
||||
i18n:
|
||||
zh-CN: 进程已运行时间
|
||||
en-US: Process uptime
|
||||
priority: 1
|
||||
fields:
|
||||
- field: metric_value
|
||||
type: 0
|
||||
unit: 's'
|
||||
i18n:
|
||||
zh-CN: 运行时间
|
||||
en-US: uptime
|
||||
protocol: http
|
||||
http:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
url: /prometheus
|
||||
method: GET
|
||||
parseType: prometheus
|
||||
|
||||
- name: process_cpu_usage
|
||||
i18n:
|
||||
zh-CN: 进程 CPU 使用率
|
||||
en-US: Process CPU usage
|
||||
priority: 2
|
||||
fields:
|
||||
- field: metric_value
|
||||
type: 0
|
||||
unit: '%'
|
||||
i18n:
|
||||
zh-CN: 使用率
|
||||
en-US: Usage
|
||||
aliasFields:
|
||||
- metric_value
|
||||
calculates:
|
||||
- metric_value=metric_value*100
|
||||
protocol: http
|
||||
http:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
url: /prometheus
|
||||
method: GET
|
||||
parseType: prometheus
|
||||
|
||||
- name: system_cpu_usage
|
||||
i18n:
|
||||
zh-CN: 系统 CPU 使用率
|
||||
en-US: System CPU usage
|
||||
priority: 3
|
||||
fields:
|
||||
- field: metric_value
|
||||
type: 0
|
||||
unit: '%'
|
||||
i18n:
|
||||
zh-CN: 使用率
|
||||
en-US: Usage
|
||||
aliasFields:
|
||||
- metric_value
|
||||
calculates:
|
||||
- metric_value=metric_value*100
|
||||
protocol: http
|
||||
http:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
url: /prometheus
|
||||
method: GET
|
||||
parseType: prometheus
|
||||
|
||||
- name: system_load_average_1m
|
||||
i18n:
|
||||
zh-CN: 系统最近一分钟平均负载
|
||||
en-US: System load average (1m)
|
||||
priority: 4
|
||||
fields:
|
||||
- field: metric_value
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: 负载
|
||||
en-US: load
|
||||
protocol: http
|
||||
http:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
url: /prometheus
|
||||
method: GET
|
||||
parseType: prometheus
|
||||
|
||||
- name: jvm_memory_committed_bytes
|
||||
i18n:
|
||||
zh-CN: JVM 已提交内存数
|
||||
en-US: JVM memory committed bytes
|
||||
priority: 5
|
||||
fields:
|
||||
- field: area
|
||||
label: true
|
||||
i18n:
|
||||
zh-CN: 区域
|
||||
en-US: Area
|
||||
type: 1
|
||||
- field: id
|
||||
label: true
|
||||
i18n:
|
||||
zh-CN: 内存块Id
|
||||
en-US: Memory Block Id
|
||||
type: 1
|
||||
- field: metric_value
|
||||
i18n:
|
||||
zh-CN: JVM请求内存大小(当前)
|
||||
en-US: JVM requests memory size (current)
|
||||
type: 0
|
||||
unit: MB
|
||||
units:
|
||||
- value=B->MB
|
||||
protocol: http
|
||||
http:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
url: /prometheus
|
||||
method: GET
|
||||
parseType: prometheus
|
||||
|
||||
- name: jvm_memory_used_bytes
|
||||
i18n:
|
||||
zh-CN: JVM 已使用内存数
|
||||
en-US: JVM memory used bytes
|
||||
priority: 6
|
||||
fields:
|
||||
- field: area
|
||||
label: true
|
||||
i18n:
|
||||
zh-CN: 区域
|
||||
en-US: Area
|
||||
type: 1
|
||||
- field: id
|
||||
label: true
|
||||
i18n:
|
||||
zh-CN: 内存块Id
|
||||
en-US: Memory Block Id
|
||||
type: 1
|
||||
- field: metric_value
|
||||
i18n:
|
||||
zh-CN: JVM已使用内存大小
|
||||
en-US: JVM used memory size
|
||||
type: 0
|
||||
unit: MB
|
||||
units:
|
||||
- value=B->MB
|
||||
protocol: http
|
||||
http:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
url: /prometheus
|
||||
method: GET
|
||||
parseType: prometheus
|
||||
|
||||
- name: jvm_memory_max_bytes
|
||||
i18n:
|
||||
zh-CN: JVM 最大内存数
|
||||
en-US: JVM memory max bytes
|
||||
priority: 7
|
||||
fields:
|
||||
- field: area
|
||||
label: true
|
||||
i18n:
|
||||
zh-CN: 区域
|
||||
en-US: Area
|
||||
type: 1
|
||||
- field: id
|
||||
label: true
|
||||
i18n:
|
||||
zh-CN: 内存块Id
|
||||
en-US: Memory Block Id
|
||||
type: 1
|
||||
- field: metric_value
|
||||
i18n:
|
||||
zh-CN: JVM 最大内存大小
|
||||
en-US: JVM used memory size
|
||||
type: 0
|
||||
unit: MB
|
||||
units:
|
||||
- value=B->MB
|
||||
protocol: http
|
||||
http:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
url: /prometheus
|
||||
method: GET
|
||||
parseType: prometheus
|
||||
|
||||
- name: jvm_gc_pause_seconds_count
|
||||
i18n:
|
||||
zh-CN: JVM GC 暂停次数
|
||||
en-US: Number of JVM GC pause events
|
||||
priority: 8
|
||||
fields:
|
||||
- field: action
|
||||
label: true
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 行为
|
||||
en-US: action
|
||||
- field: cause
|
||||
label: true
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 原因
|
||||
en-US: cause
|
||||
- field: metric_value
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: 数量
|
||||
en-US: count
|
||||
protocol: http
|
||||
http:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
url: /prometheus
|
||||
method: GET
|
||||
parseType: prometheus
|
||||
|
||||
- name: jvm_gc_pause_seconds_sum
|
||||
i18n:
|
||||
zh-CN: JVM GC 暂停耗时
|
||||
en-US: Total JVM GC pause time
|
||||
priority: 9
|
||||
fields:
|
||||
- field: action
|
||||
label: true
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 行为
|
||||
en-US: action
|
||||
- field: cause
|
||||
label: true
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 原因
|
||||
en-US: cause
|
||||
- field: metric_value
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: 耗时
|
||||
en-US: time-consuming
|
||||
protocol: http
|
||||
http:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
url: /prometheus
|
||||
method: GET
|
||||
parseType: prometheus
|
||||
|
||||
- name: jvm_memory_usage_after_gc_percent
|
||||
i18n:
|
||||
zh-CN: GC 执行后 JVM 内存使用率百分比
|
||||
en-US: JVM heap usage after garbage collection (%)
|
||||
priority: 10
|
||||
fields:
|
||||
- field: area
|
||||
label: true
|
||||
i18n:
|
||||
zh-CN: 区域
|
||||
en-US: Area
|
||||
type: 1
|
||||
- field: pool
|
||||
label: true
|
||||
i18n:
|
||||
zh-CN: 内存池
|
||||
en-US: Heap pool
|
||||
type: 1
|
||||
- field: metric_value
|
||||
type: 0
|
||||
unit: '%'
|
||||
i18n:
|
||||
zh-CN: 使用率
|
||||
en-US: Usage
|
||||
aliasFields:
|
||||
- area
|
||||
- pool
|
||||
- metric_value
|
||||
calculates:
|
||||
- area
|
||||
- pool
|
||||
- metric_value=metric_value*100
|
||||
protocol: http
|
||||
http:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
url: /prometheus
|
||||
method: GET
|
||||
parseType: prometheus
|
||||
|
||||
- name: process_files_max_files
|
||||
i18n:
|
||||
zh-CN: 允许进程打开的最大文件描述符数
|
||||
en-US: Process maximum open files
|
||||
priority: 11
|
||||
fields:
|
||||
- field: metric_value
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: 数量
|
||||
en-US: quantity
|
||||
protocol: http
|
||||
http:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
url: /prometheus
|
||||
method: GET
|
||||
parseType: prometheus
|
||||
|
||||
- name: process_files_open_files
|
||||
i18n:
|
||||
zh-CN: 进程打开的文件描述符数量
|
||||
en-US: Process open files
|
||||
priority: 12
|
||||
fields:
|
||||
- field: metric_value
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: 数量
|
||||
en-US: quantity
|
||||
protocol: http
|
||||
http:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
url: /prometheus
|
||||
method: GET
|
||||
parseType: prometheus
|
||||
@@ -0,0 +1,619 @@
|
||||
# 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: bigdata
|
||||
# The monitoring type eg: linux windows tomcat mysql aws...
|
||||
app: dolphinscheduler
|
||||
# The monitoring i18n name
|
||||
name:
|
||||
zh-CN: Apache DolphinScheduler
|
||||
en-US: Apache DolphinScheduler
|
||||
ja-JP: Apache DolphinScheduler
|
||||
# The description and help of this monitoring type
|
||||
help:
|
||||
zh-CN: Hertzbeat 对 Apache DolphinScheduler (支持v3.3.0或更高版本)通用指标进行测量监控。<br>您可以点击 “<i>新建 Apache DolphinScheduler</i>” 并进行配置,或者选择“<i>更多操作</i>”,导入已有配置。
|
||||
en-US: Hertzbeat measures and monitors the general metrics of Apache DolphinScheduler (support v3.3.0 or later).<br>You can click on “<i>Create New Apache DolphinScheduler</i>” to configure it, or select “<i>More Actions</i>” to import an existing configuration.
|
||||
ja-JP: Hertzbeat は Apache DolphinScheduler(v3.3.0+)の一般的なメトリクスを監視します。<br>「<i>新規 Apache DolphinScheduler</i>」をクリックしてパラメタを設定した後、新規することができます。
|
||||
helpLink:
|
||||
zh-CN: https://hertzbeat.apache.org/zh-cn/docs/help/dolphinscheduler
|
||||
en-US: https://hertzbeat.apache.org/docs/help/dolphinscheduler
|
||||
# Input params define for monitoring(render web ui by the definition)
|
||||
params:
|
||||
# field-param field key
|
||||
- field: host
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 目标Host
|
||||
en-US: Target Host
|
||||
ja-JP: 目標ホスト
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: host
|
||||
# required-true or false
|
||||
required: true
|
||||
# field-param field key
|
||||
- field: port
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 端口
|
||||
en-US: Port
|
||||
ja-JP: ポート
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: number
|
||||
# when type is number, range is required
|
||||
range: '[0,65535]'
|
||||
# required-true or false
|
||||
required: true
|
||||
# default value
|
||||
defaultValue: 12345
|
||||
# field-param field key
|
||||
- field: timeout
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 查询超时时间
|
||||
en-US: Query Timeout
|
||||
ja-JP: クエリタイムアウト
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: number
|
||||
# required-true or false
|
||||
required: false
|
||||
# hide param-true or false
|
||||
hide: true
|
||||
# default value
|
||||
defaultValue: 6000
|
||||
# field-param field key
|
||||
- field: ssl
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 启用HTTPS
|
||||
en-US: SSL
|
||||
ja-JP: SSL
|
||||
# type-param field type(boolean mapping the html h tag)
|
||||
type: boolean
|
||||
# required-true or false
|
||||
required: true
|
||||
- field: token
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 令牌
|
||||
en-US: Token
|
||||
ja-JP: トークン
|
||||
type: text
|
||||
limit: 100
|
||||
required: true
|
||||
|
||||
# collect metrics config list
|
||||
metrics:
|
||||
- name: master
|
||||
i18n:
|
||||
zh-CN: Master
|
||||
en-US: Master
|
||||
ja-JP: マスター情報
|
||||
# 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: 主机地址
|
||||
en-US: Host
|
||||
ja-JP: ホスト
|
||||
- field: port
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 端口
|
||||
en-US: Port
|
||||
ja-JP: ポート
|
||||
- field: serverStatus
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 状态
|
||||
en-US: Server Status
|
||||
ja-JP: サーバーステータス
|
||||
- field: processId
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 进程 ID
|
||||
en-US: Process Id
|
||||
ja-JP: プロセスID
|
||||
- field: runningTime
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: 运行时间
|
||||
en-US: Up Time
|
||||
ja-JP: アップタイム
|
||||
- field: cpuUsage
|
||||
type: 0
|
||||
unit: '%'
|
||||
i18n:
|
||||
zh-CN: 处理器使用量
|
||||
en-US: CPU Usage
|
||||
ja-JP: CPU使用率
|
||||
- field: memoryUsage
|
||||
type: 0
|
||||
unit: '%'
|
||||
i18n:
|
||||
zh-CN: 内存使用量
|
||||
en-US: Memory Usage
|
||||
ja-JP: メモリ使用率
|
||||
- field: diskUsage
|
||||
type: 0
|
||||
unit: '%'
|
||||
i18n:
|
||||
zh-CN: 磁盘使用量
|
||||
en-US: Disk Usage
|
||||
ja-JP: ディスク使用率
|
||||
- field: jvmCpuUsage
|
||||
type: 0
|
||||
unit: '%'
|
||||
i18n:
|
||||
zh-CN: JVM CPU 使用量
|
||||
en-US: JVM CPU Usage
|
||||
ja-JP: Java仮想マシンのCPU使用率
|
||||
- field: jvmMemoryUsage
|
||||
type: 0
|
||||
unit: '%'
|
||||
i18n:
|
||||
zh-CN: JVM 内存 使用量
|
||||
en-US: JVM Memory Usage
|
||||
ja-JP: Java仮想マシンのメモリ使用率
|
||||
- field: jvmHeapUsed
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: JVM 已使用的堆内存大小
|
||||
en-US: JVM Heap Used
|
||||
ja-JP: Java仮想マシンが使用したヒープメモリのサイズ
|
||||
- field: jvmNonHeapUsed
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: JVM 已使用的非堆内存大小
|
||||
en-US: JVM NonHeap Used
|
||||
ja-JP: Java仮想マシンが使用したノンヒープメモリのサイズ
|
||||
- field: jvmHeapMax
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: JVM 配置的最大堆内存大小
|
||||
en-US: JVM Heap Max
|
||||
ja-JP: Java仮想マシンのヒープメモリの最大値
|
||||
- field: jvmNonHeapMax
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: JVM 配置的最大非堆内存大小
|
||||
en-US: JVM NonHeap Max
|
||||
ja-JP: Java仮想マシンのノンヒープメモリの最大値
|
||||
# (optional)metrics field alias name, it is used as an alias field to map and convert the collected data and metrics field
|
||||
aliasFields:
|
||||
- $.heartBeatInfo
|
||||
# mapping and conversion expressions, use these and aliasField above to calculate metrics value
|
||||
# eg: cores=core1+core2, usage=usage, waitTime=allTime-runningTime
|
||||
calculates:
|
||||
- host=json:apply($.heartBeatInfo).host
|
||||
- port=json:apply($.heartBeatInfo).port
|
||||
- serverStatus=json:apply($.heartBeatInfo).serverStatus
|
||||
- processId=json:apply($.heartBeatInfo).processId
|
||||
- runningTime=(now()-json:apply($.heartBeatInfo).startupTime)/86400000
|
||||
- cpuUsage=json:apply($.heartBeatInfo).cpuUsage * 100
|
||||
- memoryUsage=json:apply($.heartBeatInfo).memoryUsage * 100
|
||||
- diskUsage=json:apply($.heartBeatInfo).diskUsage * 100
|
||||
- jvmCpuUsage=json:apply($.heartBeatInfo).jvmCpuUsage
|
||||
- jvmMemoryUsage=json:apply($.heartBeatInfo).jvmMemoryUsage
|
||||
- jvmHeapUsed=json:apply($.heartBeatInfo).jvmHeapUsed
|
||||
- jvmNonHeapUsed=json:apply($.heartBeatInfo).jvmNonHeapUsed
|
||||
- jvmHeapMax=json:apply($.heartBeatInfo).jvmHeapMax
|
||||
- jvmNonHeapMax=json:apply($.heartBeatInfo).jvmNonHeapMax
|
||||
# the protocol used for monitoring, eg: sql, ssh, http, telnet, wmi, snmp, sdk
|
||||
protocol: http
|
||||
# the config content when protocol is http
|
||||
http:
|
||||
# http host: ipv4 ipv6 domain
|
||||
host: ^_^host^_^
|
||||
# http port
|
||||
port: ^_^port^_^
|
||||
# http url
|
||||
url: /dolphinscheduler/monitor/MASTER
|
||||
# http method: GET POST PUT DELETE PATCH
|
||||
method: GET
|
||||
# if enabled https
|
||||
ssl: ^_^ssl^_^
|
||||
# http request header content
|
||||
headers:
|
||||
token: ^_^token^_^
|
||||
# http response data parse type: default-hertzbeat rule, jsonpath-jsonpath script, website-for website monitoring, prometheus-prometheus exporter rule
|
||||
parseType: jsonPath
|
||||
parseScript: '$.data[*]'
|
||||
|
||||
- name: worker
|
||||
i18n:
|
||||
zh-CN: Worker
|
||||
en-US: Worker
|
||||
ja-JP: ワーカー情報
|
||||
# metrics scheduling priority(0->127)->(high->low), metrics with the same priority will be scheduled in parallel
|
||||
# priority 0's metrics is availability metrics, it will be scheduled first, only availability metrics collect success will the scheduling continue
|
||||
priority: 1
|
||||
# collect metrics content
|
||||
fields:
|
||||
# field-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: 主机地址
|
||||
en-US: Host
|
||||
ja-JP: ホスト
|
||||
- field: port
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 端口
|
||||
en-US: Port
|
||||
ja-JP: ポート
|
||||
- field: serverStatus
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 状态
|
||||
en-US: Server Status
|
||||
ja-JP: サーバーステータス
|
||||
- field: processId
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 进程 ID
|
||||
en-US: Process Id
|
||||
ja-JP: プロセスID
|
||||
- field: runningTime
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: 运行时间
|
||||
en-US: Up Time
|
||||
ja-JP: アップタイム
|
||||
- field: cpuUsage
|
||||
type: 0
|
||||
unit: '%'
|
||||
i18n:
|
||||
zh-CN: 处理器使用量
|
||||
en-US: CPU Usage
|
||||
ja-JP: CPU使用率
|
||||
- field: memoryUsage
|
||||
type: 0
|
||||
unit: '%'
|
||||
i18n:
|
||||
zh-CN: 内存使用量
|
||||
en-US: Memory Usage
|
||||
ja-JP: メモリ使用率
|
||||
- field: diskUsage
|
||||
type: 0
|
||||
unit: '%'
|
||||
i18n:
|
||||
zh-CN: 磁盘使用量
|
||||
en-US: Disk Usage
|
||||
ja-JP: ディスク使用率
|
||||
- field: jvmCpuUsage
|
||||
type: 0
|
||||
unit: '%'
|
||||
i18n:
|
||||
zh-CN: JVM CPU 使用量
|
||||
en-US: JVM CPU Usage
|
||||
ja-JP: Java仮想マシンのCPU使用率
|
||||
- field: jvmMemoryUsage
|
||||
type: 0
|
||||
unit: '%'
|
||||
i18n:
|
||||
zh-CN: JVM 内存 使用量
|
||||
en-US: JVM Memory Usage
|
||||
ja-JP: Java仮想マシンのメモリ使用率
|
||||
- field: jvmHeapUsed
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: JVM 已使用的堆内存大小
|
||||
en-US: JVM Heap Used
|
||||
ja-JP: Java仮想マシンが使用したヒープメモリのサイズ
|
||||
- field: jvmNonHeapUsed
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: JVM 已使用的非堆内存大小
|
||||
en-US: JVM NonHeap Used
|
||||
ja-JP: Java仮想マシンが使用したノンヒープメモリのサイズ
|
||||
- field: jvmHeapMax
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: JVM 配置的最大堆内存大小
|
||||
en-US: JVM Heap Max
|
||||
ja-JP: Java仮想マシンのヒープメモリの最大値
|
||||
- field: jvmNonHeapMax
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: JVM 配置的最大非堆内存大小
|
||||
en-US: JVM NonHeap Max
|
||||
ja-JP: Java仮想マシンのノンヒープメモリの最大値
|
||||
- field: workerHostWeight
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: 权重
|
||||
en-US: Weight
|
||||
ja-JP: ウェイト
|
||||
- field: threadPoolUsage
|
||||
type: 0
|
||||
unit: '%'
|
||||
i18n:
|
||||
zh-CN: 线程池使用量
|
||||
en-US: Thread Pool Usage
|
||||
ja-JP: スレッドプールの使用率
|
||||
- field: workerGroup
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: Worker 组
|
||||
en-US: Worker Group
|
||||
ja-JP: ワーカーグループ
|
||||
# (optional)metrics field alias name, it is used as an alias field to map and convert the collected data and metrics field
|
||||
aliasFields:
|
||||
- $.heartBeatInfo
|
||||
# mapping and conversion expressions, use these and aliasField above to calculate metrics value
|
||||
# eg: cores=core1+core2, usage=usage, waitTime=allTime-runningTime
|
||||
calculates:
|
||||
- host=json:apply($.heartBeatInfo).host
|
||||
- port=json:apply($.heartBeatInfo).port
|
||||
- serverStatus=json:apply($.heartBeatInfo).serverStatus
|
||||
- processId=json:apply($.heartBeatInfo).processId
|
||||
- runningTime=(now()-json:apply($.heartBeatInfo).startupTime)/86400000
|
||||
- cpuUsage=json:apply($.heartBeatInfo).cpuUsage * 100
|
||||
- memoryUsage=json:apply($.heartBeatInfo).memoryUsage * 100
|
||||
- diskUsage=json:apply($.heartBeatInfo).diskUsage * 100
|
||||
- jvmCpuUsage=json:apply($.heartBeatInfo).jvmCpuUsage
|
||||
- jvmMemoryUsage=json:apply($.heartBeatInfo).jvmMemoryUsage
|
||||
- jvmHeapUsed=json:apply($.heartBeatInfo).jvmHeapUsed
|
||||
- jvmNonHeapUsed=json:apply($.heartBeatInfo).jvmNonHeapUsed
|
||||
- jvmHeapMax=json:apply($.heartBeatInfo).jvmHeapMax
|
||||
- jvmNonHeapMax=json:apply($.heartBeatInfo).jvmNonHeapMax
|
||||
- workerHostWeight=json:apply($.heartBeatInfo).workerHostWeight
|
||||
- threadPoolUsage=json:apply($.heartBeatInfo).threadPoolUsage
|
||||
- workerGroup=json:apply($.heartBeatInfo).workerGroup
|
||||
# the protocol used for monitoring, eg: sql, ssh, http, telnet, wmi, snmp, sdk
|
||||
protocol: http
|
||||
# the config content when protocol is http
|
||||
http:
|
||||
# http host: ipv4 ipv6 domain
|
||||
host: ^_^host^_^
|
||||
# http port
|
||||
port: ^_^port^_^
|
||||
# http url
|
||||
url: /dolphinscheduler/monitor/WORKER
|
||||
# http method: GET POST PUT DELETE PATCH
|
||||
method: GET
|
||||
# if enabled https
|
||||
ssl: ^_^ssl^_^
|
||||
# http request header content
|
||||
headers:
|
||||
token: ^_^token^_^
|
||||
# http response data parse type: default-hertzbeat rule, jsonpath-jsonpath script, website-for website monitoring, prometheus-prometheus exporter rule
|
||||
parseType: jsonPath
|
||||
parseScript: '$.data[*]'
|
||||
|
||||
- name: alert-server
|
||||
i18n:
|
||||
zh-CN: Alert Server
|
||||
en-US: Alert Server
|
||||
ja-JP: アラートサーバーの情報
|
||||
# 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: 主机地址
|
||||
en-US: Host
|
||||
ja-JP: ホスト
|
||||
- field: port
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 端口
|
||||
en-US: Port
|
||||
ja-JP: ポート
|
||||
- field: serverStatus
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 状态
|
||||
en-US: Server Status
|
||||
ja-JP: サーバーステータス
|
||||
- field: processId
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 进程 ID
|
||||
en-US: Process Id
|
||||
ja-JP: プロセスID
|
||||
- field: runningTime
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: 运行时间
|
||||
en-US: Up Time
|
||||
ja-JP: アップタイム
|
||||
- field: cpuUsage
|
||||
type: 0
|
||||
unit: '%'
|
||||
i18n:
|
||||
zh-CN: 处理器使用量
|
||||
en-US: CPU Usage
|
||||
ja-JP: CPU使用率
|
||||
- field: memoryUsage
|
||||
type: 0
|
||||
unit: '%'
|
||||
i18n:
|
||||
zh-CN: 内存使用量
|
||||
en-US: Memory Usage
|
||||
ja-JP: メモリ使用率
|
||||
- field: diskUsage
|
||||
type: 0
|
||||
unit: '%'
|
||||
i18n:
|
||||
zh-CN: 磁盘使用量
|
||||
en-US: Disk Usage
|
||||
ja-JP: ディスク使用率
|
||||
- field: jvmCpuUsage
|
||||
type: 0
|
||||
unit: '%'
|
||||
i18n:
|
||||
zh-CN: JVM CPU 使用量
|
||||
en-US: JVM CPU Usage
|
||||
ja-JP: Java仮想マシンのCPU使用率
|
||||
- field: jvmMemoryUsage
|
||||
type: 0
|
||||
unit: '%'
|
||||
i18n:
|
||||
zh-CN: JVM 内存 使用量
|
||||
en-US: JVM Memory Usage
|
||||
ja-JP: Java仮想マシンのメモリ使用率
|
||||
- field: jvmHeapUsed
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: JVM 已使用的堆内存大小
|
||||
en-US: JVM Heap Used
|
||||
ja-JP: Java仮想マシンが使用したヒープメモリのサイズ
|
||||
- field: jvmNonHeapUsed
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: JVM 已使用的非堆内存大小
|
||||
en-US: JVM NonHeap Used
|
||||
ja-JP: Java仮想マシンが使用したノンヒープメモリのサイズ
|
||||
- field: jvmHeapMax
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: JVM 配置的最大堆内存大小
|
||||
en-US: JVM Heap Max
|
||||
ja-JP: Java仮想マシンのヒープメモリの最大値
|
||||
- field: jvmNonHeapMax
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: JVM 配置的最大非堆内存大小
|
||||
en-US: JVM NonHeap Max
|
||||
ja-JP: Java仮想マシンのノンヒープメモリの最大値
|
||||
# (optional)metrics field alias name, it is used as an alias field to map and convert the collected data and metrics field
|
||||
aliasFields:
|
||||
- $.heartBeatInfo
|
||||
# mapping and conversion expressions, use these and aliasField above to calculate metrics value
|
||||
# eg: cores=core1+core2, usage=usage, waitTime=allTime-runningTime
|
||||
calculates:
|
||||
- host=json:apply($.heartBeatInfo).host
|
||||
- port=json:apply($.heartBeatInfo).port
|
||||
- serverStatus=json:apply($.heartBeatInfo).serverStatus
|
||||
- processId=json:apply($.heartBeatInfo).processId
|
||||
- runningTime=(now()-json:apply($.heartBeatInfo).startupTime)/86400000
|
||||
- cpuUsage=json:apply($.heartBeatInfo).cpuUsage * 100
|
||||
- memoryUsage=json:apply($.heartBeatInfo).memoryUsage * 100
|
||||
- diskUsage=json:apply($.heartBeatInfo).diskUsage * 100
|
||||
- jvmCpuUsage=json:apply($.heartBeatInfo).jvmCpuUsage
|
||||
- jvmMemoryUsage=json:apply($.heartBeatInfo).jvmMemoryUsage
|
||||
- jvmHeapUsed=json:apply($.heartBeatInfo).jvmHeapUsed
|
||||
- jvmNonHeapUsed=json:apply($.heartBeatInfo).jvmNonHeapUsed
|
||||
- jvmHeapMax=json:apply($.heartBeatInfo).jvmHeapMax
|
||||
- jvmNonHeapMax=json:apply($.heartBeatInfo).jvmNonHeapMax
|
||||
# the protocol used for monitoring, eg: sql, ssh, http, telnet, wmi, snmp, sdk
|
||||
protocol: http
|
||||
# the config content when protocol is http
|
||||
http:
|
||||
# http host: ipv4 ipv6 domain
|
||||
host: ^_^host^_^
|
||||
# http port
|
||||
port: ^_^port^_^
|
||||
# http url
|
||||
url: /dolphinscheduler/monitor/ALERT_SERVER
|
||||
# http method: GET POST PUT DELETE PATCH
|
||||
method: GET
|
||||
# if enabled https
|
||||
ssl: ^_^ssl^_^
|
||||
# http request header content
|
||||
headers:
|
||||
token: ^_^token^_^
|
||||
# http response data parse type: default-hertzbeat rule, jsonpath-jsonpath script, website-for website monitoring, prometheus-prometheus exporter rule
|
||||
parseType: jsonPath
|
||||
parseScript: '$.data[*]'
|
||||
|
||||
- name: database
|
||||
i18n:
|
||||
zh-CN: 数据库
|
||||
en-US: Database
|
||||
ja-JP: データベース情報
|
||||
# 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: 2
|
||||
# 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: dbType
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 数据库类型
|
||||
en-US: Database Type
|
||||
ja-JP: データベースのタイプ
|
||||
- field: state
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 状态
|
||||
en-US: State
|
||||
ja-JP: 状態
|
||||
- field: maxConnections
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: 最大连接数
|
||||
en-US: Max Connections
|
||||
ja-JP: 最大接続数
|
||||
- field: threadsConnections
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: 当前连接数
|
||||
en-US: Threads Connections
|
||||
ja-JP: 現在接続数
|
||||
- field: threadsRunningConnections
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: 当前活跃连接数
|
||||
en-US: Threads Running Connections
|
||||
ja-JP: 現在のアクティブな接続数
|
||||
# (optional)metrics field alias name, it is used as an alias field to map and convert the collected data and metrics field
|
||||
aliasFields:
|
||||
- dbType
|
||||
- state
|
||||
- maxConnections
|
||||
- threadsConnections
|
||||
- threadsRunningConnections
|
||||
# mapping and conversion expressions, use these and aliasField above to calculate metrics value
|
||||
# eg: cores=core1+core2, usage=usage, waitTime=allTime-runningTime
|
||||
calculates:
|
||||
- dbType=dbType
|
||||
- state=state
|
||||
- maxConnections=maxConnections
|
||||
- threadsConnections=threadsConnections
|
||||
- threadsRunningConnections=threadsRunningConnections
|
||||
# the protocol used for monitoring, eg: sql, ssh, http, telnet, wmi, snmp, sdk
|
||||
protocol: http
|
||||
# the config content when protocol is http
|
||||
http:
|
||||
# http host: ipv4 ipv6 domain
|
||||
host: ^_^host^_^
|
||||
# http port
|
||||
port: ^_^port^_^
|
||||
# http url
|
||||
url: /dolphinscheduler/monitor/databases
|
||||
# http method: GET POST PUT DELETE PATCH
|
||||
method: GET
|
||||
# if enabled https
|
||||
ssl: ^_^ssl^_^
|
||||
# http request header content
|
||||
headers:
|
||||
token: ^_^token^_^
|
||||
# http response data parse type: default-hertzbeat rule, jsonpath-jsonpath script, website-for website monitoring, prometheus-prometheus exporter rule
|
||||
parseType: jsonPath
|
||||
parseScript: '$.data[0]'
|
||||
@@ -490,7 +490,7 @@ metrics:
|
||||
zh-CN: 次数
|
||||
en-US: Num
|
||||
ja-JP: 数量
|
||||
- field: size
|
||||
- field: Size
|
||||
type: 0
|
||||
unit: B
|
||||
i18n:
|
||||
|
||||
@@ -62,6 +62,7 @@ params:
|
||||
type: number
|
||||
required: false
|
||||
hide: true
|
||||
defaultValue: 6000
|
||||
- field: authType
|
||||
name:
|
||||
zh-CN: 认证方式
|
||||
@@ -125,7 +126,7 @@ metrics:
|
||||
zh-CN: 状态
|
||||
en-US: Status
|
||||
ja-JP: ステータス
|
||||
- field: size
|
||||
- field: Size
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: 数量
|
||||
@@ -137,6 +138,18 @@ metrics:
|
||||
zh-CN: 可用数量
|
||||
en-US: Available Size
|
||||
ja-JP: 利用可能なサイズ
|
||||
aliasFields:
|
||||
- $.app
|
||||
- $.category
|
||||
- $.status
|
||||
- $.size
|
||||
- $.availableSize
|
||||
calculates:
|
||||
- app=$.app
|
||||
- category=$.category
|
||||
- status=$.status
|
||||
- Size=$.size
|
||||
- availableSize=$.availableSize
|
||||
# the protocol used for monitoring, eg: sql, ssh, http, telnet, wmi, snmp, sdk, we use HTTP protocol here
|
||||
protocol: http
|
||||
# the config content when protocol is http
|
||||
@@ -228,7 +241,7 @@ metrics:
|
||||
priority: 1
|
||||
fields:
|
||||
- field: state
|
||||
type: 2
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 状态
|
||||
en-US: State
|
||||
|
||||
@@ -160,7 +160,7 @@ metrics:
|
||||
zh-CN: 状态
|
||||
en-US: Status
|
||||
ja-JP: ステータス
|
||||
- field: size
|
||||
- field: Size
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: 数量
|
||||
@@ -172,6 +172,18 @@ metrics:
|
||||
zh-CN: 可用数量
|
||||
en-US: Available Size
|
||||
ja-JP: 利用可能なサイズ
|
||||
aliasFields:
|
||||
- $.app
|
||||
- $.category
|
||||
- $.status
|
||||
- $.size
|
||||
- $.availableSize
|
||||
calculates:
|
||||
- app=$.app
|
||||
- category=$.category
|
||||
- status=$.status
|
||||
- Size=$.size
|
||||
- availableSize=$.availableSize
|
||||
protocol: http
|
||||
http:
|
||||
host: ^_^host^_^
|
||||
|
||||
@@ -0,0 +1,797 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# The monitoring type category:service-application service monitoring db-database monitoring custom-custom monitoring os-operating system monitoring
|
||||
category: server
|
||||
# The monitoring type eg: linux windows tomcat mysql aws...
|
||||
app: idrac
|
||||
# The monitoring i18n name
|
||||
name:
|
||||
zh-CN: Dell iDRAC
|
||||
en-US: Dell iDRAC
|
||||
ja-JP: Dell iDRAC
|
||||
# The description and help of this monitoring type
|
||||
help:
|
||||
zh-CN: Hertzbeat 使用 <a class='help_module_content' href='https://hertzbeat.apache.org/docs/advanced/extend-snmp'> SNMP 协议</a> 对 Dell iDRAC 的通用指标(可用性,系统信息,传感器状态等)进行采集监控。<br>您可以点击 “<i>新建 Dell iDRAC</i>” 并进行配置SNMP相关参数添加,或者选择“<i>更多操作</i>”,导入已有配置。
|
||||
en-US: HertzBeat uses <a class='help_module_content' href='https://hertzbeat.apache.org/docs/advanced/extend-snmp'> SNMP Protocol</a> to monitoring Dell iDRAC general performance metrics. <br>You can click the "<i>New Dell iDRAC</i>" button and config snmp params to add monitor or import an existing setup through the "<i>More Actions</i>" menu.
|
||||
ja-JP: Hertzbeat は <a class='help_module_content' href='https://hertzbeat.apache.org/docs/advanced/extend-snmp'> SNMP プロトコルを介して</a> Dell iDRACの一般的なメトリクスを監視します。<br>「<i>新規 Dell iDRAC</i>」をクリックしてSNMPなどのパラメタを設定した後、新規することができます。
|
||||
helpLink:
|
||||
zh-CN: https://hertzbeat.apache.org/zh-cn/docs/help/idrac
|
||||
en-US: https://hertzbeat.apache.org/docs/help/idrac
|
||||
# Input params define for monitoring(render web ui by the definition)
|
||||
params:
|
||||
# field-param field key
|
||||
- field: host
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 目标Host
|
||||
en-US: Target Host
|
||||
ja-JP: 目標ホスト
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: host
|
||||
# required-true or false
|
||||
required: true
|
||||
# field-param field key
|
||||
- field: port
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 端口
|
||||
en-US: Port
|
||||
ja-JP: ポート
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: number
|
||||
# when type is number, range is required
|
||||
range: '[0,65535]'
|
||||
# required-true or false
|
||||
required: true
|
||||
# default value
|
||||
defaultValue: 161
|
||||
# field-param field key
|
||||
- field: snmpVersion
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: SNMP 版本
|
||||
en-US: SNMP Version
|
||||
ja-JP: SNMPバージョン
|
||||
# type-param field type(radio mapping the html radio tag)
|
||||
type: radio
|
||||
# required-true or false
|
||||
required: true
|
||||
# when type is radio checkbox, use option to show optional values {name1:value1,name2:value2}
|
||||
options:
|
||||
- label: SNMPv1
|
||||
value: 0
|
||||
- label: SNMPv2c
|
||||
value: 1
|
||||
- label: SNMPv3
|
||||
value: 3
|
||||
# field-param field key
|
||||
- field: community
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: SNMP 团体字
|
||||
en-US: SNMP Community
|
||||
ja-JP: SNMPコミュニティ
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: text
|
||||
# when type is text, use limit to limit string length
|
||||
limit: 100
|
||||
# required-true or false
|
||||
required: false
|
||||
# param field input placeholder
|
||||
placeholder: 'Snmp community for v1 v2c'
|
||||
# dependent parameter values list
|
||||
depend:
|
||||
snmpVersion:
|
||||
- 0
|
||||
- 1
|
||||
# field-param field key
|
||||
- field: username
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: SNMP username
|
||||
en-US: SNMP username
|
||||
ja-JP: SNMPユーザー名
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: text
|
||||
# when type is text, use limit to limit string length
|
||||
limit: 50
|
||||
# required-true or false
|
||||
required: false
|
||||
# param field input placeholder
|
||||
placeholder: 'Snmp username for v3'
|
||||
# dependent parameter values list
|
||||
depend:
|
||||
snmpVersion:
|
||||
- 3
|
||||
# field-param field key
|
||||
- field: contextName
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: SNMP contextName
|
||||
en-US: SNMP contextName
|
||||
ja-JP: SNMPコンテキスト名
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: text
|
||||
# when type is text, use limit to limit string length
|
||||
limit: 100
|
||||
# required-true or false
|
||||
required: false
|
||||
# param field input placeholder
|
||||
placeholder: 'Snmp contextName for v3'
|
||||
# dependent parameter values list
|
||||
depend:
|
||||
snmpVersion:
|
||||
- 3
|
||||
# field-param field key
|
||||
- field: authPassphrase
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: SNMP authPassword
|
||||
en-US: SNMP authPassword
|
||||
ja-JP: SNMP認証パスワード
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: text
|
||||
# when type is text, use limit to limit string length
|
||||
limit: 100
|
||||
# required-true or false
|
||||
required: false
|
||||
# param field input placeholder
|
||||
placeholder: 'Snmp authPassword for v3'
|
||||
# dependent parameter values list
|
||||
depend:
|
||||
snmpVersion:
|
||||
- 3
|
||||
# field-param field key
|
||||
- field: authPasswordEncryption
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: authPassword 加密方式
|
||||
en-US: authPassword Encryption
|
||||
ja-JP: 認証暗号
|
||||
# type-param field type(radio mapping the html radio tag)
|
||||
type: radio
|
||||
# required-true or false
|
||||
required: false
|
||||
# when type is radio checkbox, use option to show optional values {name1:value1,name2:value2}
|
||||
options:
|
||||
- label: MD5
|
||||
value: 0
|
||||
- label: SHA1
|
||||
value: 1
|
||||
# dependent parameter values list
|
||||
depend:
|
||||
snmpVersion:
|
||||
- 3
|
||||
# field-param field key
|
||||
- field: privPassphrase
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: SNMP privPassphrase
|
||||
en-US: SNMP privPassphrase
|
||||
ja-JP: SNMPパスワードフレーズ
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: text
|
||||
# when type is text, use limit to limit string length
|
||||
limit: 100
|
||||
# required-true or false
|
||||
required: false
|
||||
# param field input placeholder
|
||||
placeholder: 'Snmp authPassword for v3'
|
||||
# dependent parameter values list
|
||||
depend:
|
||||
snmpVersion:
|
||||
- 3
|
||||
# field-param field key
|
||||
- field: privPasswordEncryption
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: privPassword 加密方式
|
||||
en-US: privPassword Encryption
|
||||
ja-JP: パスワードの暗号
|
||||
# type-param field type(radio mapping the html radio tag)
|
||||
type: radio
|
||||
# required-true or false
|
||||
required: false
|
||||
# when type is radio checkbox, use option to show optional values {name1:value1,name2:value2}
|
||||
options:
|
||||
- label: DES
|
||||
value: 0
|
||||
- label: AES128
|
||||
value: 1
|
||||
# dependent parameter values list
|
||||
depend:
|
||||
snmpVersion:
|
||||
- 3
|
||||
# field-param field key
|
||||
- field: timeout
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 超时时间(ms)
|
||||
en-US: Timeout(ms)
|
||||
ja-JP: タイムアウト(ms)
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: number
|
||||
# when type is number, range is required
|
||||
range: '[0,100000]'
|
||||
# required-true or false
|
||||
required: false
|
||||
# hide-is hide this field and put it in advanced layout
|
||||
hide: true
|
||||
# default value
|
||||
defaultValue: 6000
|
||||
# collect metrics config list
|
||||
metrics:
|
||||
# metrics - system
|
||||
- name: system
|
||||
i18n:
|
||||
zh-CN: 系统状态
|
||||
en-US: System Status
|
||||
ja-JP: システム情報
|
||||
# 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: global
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 全局系统状态
|
||||
en-US: Global System Status
|
||||
ja-JP: システム状態
|
||||
- field: lcd
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 系统前面板状态
|
||||
en-US: System LCD Status
|
||||
ja-JP: システムLCDの状態
|
||||
- field: storage
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 全局存储状态
|
||||
en-US: Global Storage Status
|
||||
ja-JP: ストレージ状態
|
||||
- field: power
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 系统电源状态
|
||||
en-US: System Power State
|
||||
ja-JP: システム電源状態
|
||||
- field: up_time
|
||||
type: 1
|
||||
unit: day
|
||||
i18n:
|
||||
zh-CN: 系统运行时间
|
||||
en-US: System Power Up Time
|
||||
ja-JP: アップタイム
|
||||
# (optional)metrics field alias name, it is used as an alias field to map and convert the collected data and metrics field
|
||||
aliasFields:
|
||||
- globalSystemStatusEnum
|
||||
- systemLCDStatusEnum
|
||||
- globalStorageStatusEnum
|
||||
- systemPowerStateEnum
|
||||
- systemPowerUpTime
|
||||
|
||||
# mapping and conversion expressions, use these and aliasField above to calculate metrics value
|
||||
# eg: cores=core1+core2, usage=usage, waitTime=allTime-runningTime
|
||||
calculates:
|
||||
- "global=(globalSystemStatusEnum == 1) ? 'other' : (globalSystemStatusEnum == 2) ? 'unknown' : (globalSystemStatusEnum == 3) ? 'ok' : (globalSystemStatusEnum == 4) ? 'nonCritical' : (globalSystemStatusEnum == 5) ? 'critical' : (globalSystemStatusEnum == 6) ? 'nonRecoverable' : 'unknown'"
|
||||
- "lcd=(systemLCDStatusEnum == 1) ? 'other' : (systemLCDStatusEnum == 2) ? 'unknown' : (systemLCDStatusEnum == 3) ? 'ok' : (systemLCDStatusEnum == 4) ? 'nonCritical' : (systemLCDStatusEnum == 5) ? 'critical' : (systemLCDStatusEnum == 6) ? 'nonRecoverable' : 'unknown'"
|
||||
- "storage=(globalStorageStatusEnum == 1) ? 'other' : (globalStorageStatusEnum == 2) ? 'unknown' : (globalStorageStatusEnum == 3) ? 'ok' : (globalStorageStatusEnum == 4) ? 'nonCritical' : (globalStorageStatusEnum == 5) ? 'critical' : (globalStorageStatusEnum == 6) ? 'nonRecoverable' : 'unknown'"
|
||||
- "power=(systemPowerStateEnum == 1) ? 'other' : (systemPowerStateEnum == 2) ? 'unknown' : (systemPowerStateEnum == 3) ? 'off' : (systemPowerStateEnum == 4) ? 'ok' : 'unknown'"
|
||||
- up_time=systemPowerUpTime/60/60/24
|
||||
|
||||
# the protocol used for monitoring, eg: sql, ssh, http, telnet, wmi, snmp, sdk
|
||||
protocol: snmp
|
||||
# the config content when protocol is snmp
|
||||
snmp:
|
||||
# server host: ipv4 ipv6 domain
|
||||
host: ^_^host^_^
|
||||
# server port
|
||||
port: ^_^port^_^
|
||||
# snmp username
|
||||
username: ^_^username^_^
|
||||
# snmp authPassphrase
|
||||
authPassphrase: ^_^authPassphrase^_^
|
||||
# snmp privPassphrase
|
||||
privPassphrase: ^_^privPassphrase^_^
|
||||
# snmp privPasswordEncryption
|
||||
privPasswordEncryption: ^_^privPasswordEncryption^_^
|
||||
# snmp authPasswordEncryption
|
||||
authPasswordEncryption: ^_^authPasswordEncryption^_^
|
||||
# contextName
|
||||
contextName: ^_^contextName^_^
|
||||
# snmp connect timeout
|
||||
timeout: ^_^timeout^_^
|
||||
# snmp community
|
||||
community: ^_^community^_^
|
||||
# snmp version
|
||||
version: ^_^snmpVersion^_^
|
||||
# snmp operation: get, walk
|
||||
operation: get
|
||||
# metrics oids: metric_name - oid_value
|
||||
oids:
|
||||
globalSystemStatusEnum: 1.3.6.1.4.1.674.10892.5.2.1.0
|
||||
systemLCDStatusEnum: 1.3.6.1.4.1.674.10892.5.2.2.0
|
||||
globalStorageStatusEnum: 1.3.6.1.4.1.674.10892.5.2.3.0
|
||||
systemPowerStateEnum: 1.3.6.1.4.1.674.10892.5.2.4.0
|
||||
systemPowerUpTime: 1.3.6.1.4.1.674.10892.5.2.5.0
|
||||
|
||||
- name: power_supply
|
||||
i18n:
|
||||
zh-CN: 电源供应器
|
||||
en-US: Power Supply
|
||||
ja-JP: 電源供給ユニット
|
||||
priority: 1
|
||||
fields:
|
||||
- field: index
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 索引
|
||||
en-US: Index
|
||||
ja-JP: 番号
|
||||
- field: name
|
||||
type: 1
|
||||
label: true
|
||||
i18n:
|
||||
zh-CN: 名称
|
||||
en-US: Name
|
||||
ja-JP: 名前
|
||||
- field: wattage
|
||||
type: 1
|
||||
unit: W
|
||||
i18n:
|
||||
zh-CN: 额定功率
|
||||
en-US: Wattage
|
||||
ja-JP: ワット数
|
||||
- field: input_wattage
|
||||
type: 0
|
||||
unit: W
|
||||
i18n:
|
||||
zh-CN: 输入功率
|
||||
en-US: Input Wattage
|
||||
ja-JP: 入力ワット数
|
||||
- field: max_input_voltage
|
||||
type: 1
|
||||
unit: V
|
||||
i18n:
|
||||
zh-CN: 最大输入电压
|
||||
en-US: Max Input Voltage
|
||||
ja-JP: 最大入力電圧
|
||||
- field: type
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 类型
|
||||
en-US: Type
|
||||
ja-JP: タイプ
|
||||
- field: status
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 状态
|
||||
en-US: Status
|
||||
ja-JP: 状態
|
||||
aliasFields:
|
||||
- powerSupplyIndex
|
||||
- powerSupplyLocationName
|
||||
- powerSupplyStatusEnum
|
||||
- powerSupplyOutputWatts
|
||||
- powerSupplyTypeEnum
|
||||
- powerSupplyRatedInputWattage
|
||||
- powerSupplyMaximumInputVoltage
|
||||
calculates:
|
||||
- "index=powerSupplyIndex"
|
||||
- "name=powerSupplyLocationName"
|
||||
- "status=(powerSupplyStatusEnum == 1) ? 'other' : (powerSupplyStatusEnum == 2) ? 'unknown' : (powerSupplyStatusEnum == 3) ? 'ok' : (powerSupplyStatusEnum == 4) ? 'nonCritical' : (powerSupplyStatusEnum == 5) ? 'critical' : (powerSupplyStatusEnum == 6) ? 'nonRecoverable' : 'unknown'"
|
||||
- "location=powerSupplyLocationName"
|
||||
- "wattage=powerSupplyOutputWatts / 10"
|
||||
- "type=(powerSupplyTypeEnum == 1) ? 'other' : (powerSupplyTypeEnum == 2) ? 'unknown' : (powerSupplyTypeEnum == 3) ? 'Linear' : (powerSupplyTypeEnum == 4) ? 'Switching' : (powerSupplyTypeEnum == 5) ? 'Battery' : (powerSupplyTypeEnum == 6) ? 'UPS' : (powerSupplyTypeEnum == 7) ? 'Converter' : (powerSupplyTypeEnum == 8) ? 'Regulator' : (powerSupplyTypeEnum == 9) ? 'AC' : (powerSupplyTypeEnum == 10) ? 'DC' : (powerSupplyTypeEnum == 11) ? 'VRM' : 'unknown'"
|
||||
- "input_wattage=powerSupplyRatedInputWattage / 10"
|
||||
- "max_input_voltage=powerSupplyMaximumInputVoltage"
|
||||
protocol: snmp
|
||||
snmp:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
username: ^_^username^_^
|
||||
authPassphrase: ^_^authPassphrase^_^
|
||||
privPassphrase: ^_^privPassphrase^_^
|
||||
privPasswordEncryption: ^_^privPasswordEncryption^_^
|
||||
authPasswordEncryption: ^_^authPasswordEncryption^_^
|
||||
contextName: ^_^contextName^_^
|
||||
timeout: ^_^timeout^_^
|
||||
community: ^_^community^_^
|
||||
version: ^_^snmpVersion^_^
|
||||
operation: walk
|
||||
oids:
|
||||
powerSupplyIndex: 1.3.6.1.4.1.674.10892.5.4.600.12.1.2
|
||||
powerSupplyLocationName: 1.3.6.1.4.1.674.10892.5.4.600.12.1.8
|
||||
powerSupplyStatusEnum: 1.3.6.1.4.1.674.10892.5.4.600.12.1.5
|
||||
powerSupplyTypeEnum: 1.3.6.1.4.1.674.10892.5.4.600.12.1.7
|
||||
powerSupplyOutputWatts: 1.3.6.1.4.1.674.10892.5.4.600.12.1.6
|
||||
powerSupplyRatedInputWattage: 1.3.6.1.4.1.674.10892.5.4.600.12.1.14
|
||||
powerSupplyMaximumInputVoltage: 1.3.6.1.4.1.674.10892.5.4.600.12.1.9
|
||||
|
||||
- name: cooling
|
||||
i18n:
|
||||
zh-CN: 冷却设备
|
||||
en-US: Cooling Devices
|
||||
ja-JP: クーリング情報
|
||||
priority: 1
|
||||
fields:
|
||||
- field: index
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 索引
|
||||
en-US: Index
|
||||
ja-JP: 番号
|
||||
- field: name
|
||||
type: 1
|
||||
label: true
|
||||
i18n:
|
||||
zh-CN: 名称
|
||||
en-US: Name
|
||||
ja-JP: 名前
|
||||
- field: type
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 类型
|
||||
en-US: Type
|
||||
ja-JP: タイプ
|
||||
- field: reading
|
||||
type: 0
|
||||
unit: RPM
|
||||
i18n:
|
||||
zh-CN: 转速
|
||||
en-US: Reading
|
||||
ja-JP: 回転数
|
||||
- field: status
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 状态
|
||||
en-US: Status
|
||||
ja-JP: 状態
|
||||
aliasFields:
|
||||
- coolingDeviceIndex
|
||||
- coolingDeviceLocationName
|
||||
- coolingDeviceStatusEnum
|
||||
- coolingDeviceReading
|
||||
- coolingDeviceTypeEnum
|
||||
calculates:
|
||||
- "index=coolingDeviceIndex"
|
||||
- "name=coolingDeviceLocationName"
|
||||
- "status=(coolingDeviceStatusEnum == 1) ? 'other' : (coolingDeviceStatusEnum == 2) ? 'unknown' : (coolingDeviceStatusEnum == 3) ? 'ok' : (coolingDeviceStatusEnum == 4) ? 'nonCriticalUpper' : (coolingDeviceStatusEnum == 5) ? 'criticalUpper' : (coolingDeviceStatusEnum == 6) ? 'nonRecoverableUpper' : (coolingDeviceStatusEnum == 7) ? 'noncriticalLower' : (coolingDeviceStatusEnum == 8) ? 'criticalLower' : (coolingDeviceStatusEnum == 9) ? 'nonRecoverableLower' : (coolingDeviceStatusEnum == 10) ? 'failed' : 'unknown'"
|
||||
- "reading=coolingDeviceReading"
|
||||
- "type=(coolingDeviceTypeEnum == 1) ? 'other' : (coolingDeviceTypeEnum == 2) ? 'unknown' : (coolingDeviceTypeEnum == 3) ? 'AFan' : (coolingDeviceTypeEnum == 4) ? 'ABlower' : (coolingDeviceTypeEnum == 5) ? 'AChipFan' : (coolingDeviceTypeEnum == 6) ? 'ACabinetFan' : (coolingDeviceTypeEnum == 7) ? 'APowerSupplyFan' : (coolingDeviceTypeEnum == 8) ? 'AHeatPipe' : (coolingDeviceTypeEnum == 9) ? 'Refrigeration' : (coolingDeviceTypeEnum == 10) ? 'ActiveCooling' : (coolingDeviceTypeEnum == 11) ? 'PassiveCooling' : 'unknown'"
|
||||
protocol: snmp
|
||||
snmp:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
username: ^_^username^_^
|
||||
authPassphrase: ^_^authPassphrase^_^
|
||||
privPassphrase: ^_^privPassphrase^_^
|
||||
privPasswordEncryption: ^_^privPasswordEncryption^_^
|
||||
authPasswordEncryption: ^_^authPasswordEncryption^_^
|
||||
contextName: ^_^contextName^_^
|
||||
timeout: ^_^timeout^_^
|
||||
community: ^_^community^_^
|
||||
version: ^_^snmpVersion^_^
|
||||
operation: walk
|
||||
oids:
|
||||
coolingDeviceIndex: 1.3.6.1.4.1.674.10892.5.4.700.12.1.2
|
||||
coolingDeviceLocationName: 1.3.6.1.4.1.674.10892.5.4.700.12.1.8
|
||||
coolingDeviceStatusEnum: 1.3.6.1.4.1.674.10892.5.4.700.12.1.5
|
||||
coolingDeviceReading: 1.3.6.1.4.1.674.10892.5.4.700.12.1.6
|
||||
coolingDeviceTypeEnum: 1.3.6.1.4.1.674.10892.5.4.700.12.1.7
|
||||
|
||||
- name: temperature
|
||||
i18n:
|
||||
zh-CN: 温度传感器
|
||||
en-US: Temperature Sensors
|
||||
ja-JP: 内部温度情報
|
||||
priority: 1
|
||||
fields:
|
||||
- field: index
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 索引
|
||||
en-US: Index
|
||||
ja-JP: 番号
|
||||
- field: name
|
||||
type: 1
|
||||
label: true
|
||||
i18n:
|
||||
zh-CN: 名称
|
||||
en-US: Name
|
||||
ja-JP: 名前
|
||||
- field: reading
|
||||
type: 0
|
||||
unit: °C
|
||||
i18n:
|
||||
zh-CN: 温度
|
||||
en-US: Temperature
|
||||
ja-JP: 温度
|
||||
- field: status
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 状态
|
||||
en-US: Status
|
||||
ja-JP: 状態
|
||||
aliasFields:
|
||||
- temperatureProbeIndex
|
||||
- temperatureProbeLocationName
|
||||
- temperatureProbeStatusEnum
|
||||
- temperatureProbeReading
|
||||
calculates:
|
||||
- "index=temperatureProbeIndex"
|
||||
- "name=temperatureProbeLocationName"
|
||||
- "status=(temperatureProbeStatusEnum == 1) ? 'other' : (temperatureProbeStatusEnum == 2) ? 'unknown' : (temperatureProbeStatusEnum == 3) ? 'ok' : (temperatureProbeStatusEnum == 4) ? 'nonCriticalUpper' : (temperatureProbeStatusEnum == 5) ? 'criticalUpper' : (temperatureProbeStatusEnum == 6) ? 'nonRecoverableUpper' : (temperatureProbeStatusEnum == 7) ? 'noncriticalLower' : (temperatureProbeStatusEnum == 8) ? 'criticalLower' : (temperatureProbeStatusEnum == 9) ? 'nonRecoverableLower' : (temperatureProbeStatusEnum == 10) ? 'failed' : 'unknown'"
|
||||
- "reading=temperatureProbeReading / 10"
|
||||
protocol: snmp
|
||||
snmp:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
username: ^_^username^_^
|
||||
authPassphrase: ^_^authPassphrase^_^
|
||||
privPassphrase: ^_^privPassphrase^_^
|
||||
privPasswordEncryption: ^_^privPasswordEncryption^_^
|
||||
authPasswordEncryption: ^_^authPasswordEncryption^_^
|
||||
contextName: ^_^contextName^_^
|
||||
timeout: ^_^timeout^_^
|
||||
community: ^_^community^_^
|
||||
version: ^_^snmpVersion^_^
|
||||
operation: walk
|
||||
oids:
|
||||
temperatureProbeIndex: 1.3.6.1.4.1.674.10892.5.4.700.20.1.2
|
||||
temperatureProbeLocationName: 1.3.6.1.4.1.674.10892.5.4.700.20.1.8
|
||||
temperatureProbeStatusEnum: 1.3.6.1.4.1.674.10892.5.4.700.20.1.5
|
||||
temperatureProbeReading: 1.3.6.1.4.1.674.10892.5.4.700.20.1.6
|
||||
|
||||
- name: voltage
|
||||
i18n:
|
||||
zh-CN: 电压传感器
|
||||
en-US: Voltage Sensors
|
||||
ja-JP: 電圧情報
|
||||
priority: 1
|
||||
fields:
|
||||
- field: index
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 索引
|
||||
en-US: Index
|
||||
ja-JP: 番号
|
||||
- field: name
|
||||
type: 1
|
||||
label: true
|
||||
i18n:
|
||||
zh-CN: 名称
|
||||
en-US: Name
|
||||
ja-JP: 名前
|
||||
- field: reading
|
||||
type: 0
|
||||
unit: V
|
||||
i18n:
|
||||
zh-CN: 电压
|
||||
en-US: Voltage
|
||||
ja-JP: 電圧
|
||||
- field: type
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 类型
|
||||
en-US: Type
|
||||
ja-JP: タイプ
|
||||
- field: status
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 状态
|
||||
en-US: Status
|
||||
ja-JP: 状態
|
||||
aliasFields:
|
||||
- voltageProbeIndex
|
||||
- voltageProbeLocationName
|
||||
- voltageProbeStatusEnum
|
||||
- voltageProbeReading
|
||||
- voltageProbeTypeEnum
|
||||
calculates:
|
||||
- "index=voltageProbeIndex"
|
||||
- "name=voltageProbeLocationName"
|
||||
- "status=(voltageProbeStatusEnum == 1) ? 'other' : (voltageProbeStatusEnum == 2) ? 'unknown' : (voltageProbeStatusEnum == 3) ? 'ok' : (voltageProbeStatusEnum == 4) ? 'nonCriticalUpper' : (voltageProbeStatusEnum == 5) ? 'criticalUpper' : (voltageProbeStatusEnum == 6) ? 'nonRecoverableUpper' : (voltageProbeStatusEnum == 7) ? 'nonCriticalLower' : (voltageProbeStatusEnum == 8) ? 'criticalLower' : (voltageProbeStatusEnum == 9) ? 'nonRecoverableLower' : (voltageProbeStatusEnum == 10) ? 'failed' : 'unknown'"
|
||||
- "reading=voltageProbeReading / 1000"
|
||||
- "type=(voltageProbeTypeEnum == 1) ? 'other' : (voltageProbeTypeEnum == 2) ? 'unknown' : (voltageProbeTypeEnum == 3) ? '1.5V' : (voltageProbeTypeEnum == 4) ? '3.3V' : (voltageProbeTypeEnum == 5) ? '5V' : (voltageProbeTypeEnum == 6) ? '-5V' : (voltageProbeTypeEnum == 7) ? '12V' : (voltageProbeTypeEnum == 8) ? '-12V' : (voltageProbeTypeEnum == 9) ? 'IO' : (voltageProbeTypeEnum == 10) ? 'Core' : (voltageProbeTypeEnum == 11) ? 'FLEA' : (voltageProbeTypeEnum == 12) ? 'Battery' : (voltageProbeTypeEnum == 13) ? 'Terminator' : (voltageProbeTypeEnum == 14) ? '2.5V' : (voltageProbeTypeEnum == 15) ? 'GTL' : (voltageProbeTypeEnum == 16) ? 'Discrete' : (voltageProbeTypeEnum == 17) ? 'GenericDiscrete' : (voltageProbeTypeEnum == 18) ? 'PSVoltage' : (voltageProbeTypeEnum == 19) ? 'MemoryStatus' : 'unknown'"
|
||||
protocol: snmp
|
||||
snmp:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
username: ^_^username^_^
|
||||
authPassphrase: ^_^authPassphrase^_^
|
||||
privPassphrase: ^_^privPassphrase^_^
|
||||
privPasswordEncryption: ^_^privPasswordEncryption^_^
|
||||
authPasswordEncryption: ^_^authPasswordEncryption^_^
|
||||
contextName: ^_^contextName^_^
|
||||
timeout: ^_^timeout^_^
|
||||
community: ^_^community^_^
|
||||
version: ^_^snmpVersion^_^
|
||||
operation: walk
|
||||
oids:
|
||||
voltageProbeIndex: 1.3.6.1.4.1.674.10892.5.4.600.20.1.2
|
||||
voltageProbeLocationName: 1.3.6.1.4.1.674.10892.5.4.600.20.1.8
|
||||
voltageProbeStatusEnum: 1.3.6.1.4.1.674.10892.5.4.600.20.1.5
|
||||
voltageProbeReading: 1.3.6.1.4.1.674.10892.5.4.600.20.1.6
|
||||
voltageProbeTypeEnum: 1.3.6.1.4.1.674.10892.5.4.600.20.1.7
|
||||
|
||||
|
||||
- name: memory
|
||||
i18n:
|
||||
zh-CN: 内存设备
|
||||
en-US: Memory Devices
|
||||
ja-JP: メモリ情報
|
||||
priority: 1
|
||||
fields:
|
||||
- field: index
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 索引
|
||||
en-US: Index
|
||||
ja-JP: 番号
|
||||
- field: name
|
||||
type: 1
|
||||
label: true
|
||||
i18n:
|
||||
zh-CN: 名称
|
||||
en-US: Name
|
||||
ja-JP: 名前
|
||||
- field: type
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 类型
|
||||
en-US: Type
|
||||
ja-JP: タイプ
|
||||
- field: sizes
|
||||
type: 1
|
||||
unit: G
|
||||
i18n:
|
||||
zh-CN: 容量
|
||||
en-US: Size
|
||||
ja-JP: メモリ容量
|
||||
- field: speed
|
||||
type: 1
|
||||
unit: MHz
|
||||
i18n:
|
||||
zh-CN: 频率
|
||||
en-US: Speed
|
||||
ja-JP: メモリの最大速度
|
||||
- field: current_speed
|
||||
type: 0
|
||||
unit: MHz
|
||||
i18n:
|
||||
zh-CN: 运行频率
|
||||
en-US: Current Speed
|
||||
ja-JP: 現在のメモリ速度
|
||||
- field: status
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 状态
|
||||
en-US: Status
|
||||
ja-JP: 状態
|
||||
aliasFields:
|
||||
- memoryDeviceIndex
|
||||
- memoryDeviceLocationName
|
||||
- memoryDeviceStatusEnum
|
||||
- memoryDeviceSize
|
||||
- memoryDeviceTypeEnum
|
||||
- memoryDeviceSpeed
|
||||
- memoryDeviceCurrentOperatingSpeed
|
||||
calculates:
|
||||
- "index=memoryDeviceIndex"
|
||||
- "name=memoryDeviceLocationName"
|
||||
- "status=(memoryDeviceStatusEnum == 1) ? 'other' : (memoryDeviceStatusEnum == 2) ? 'unknown' : (memoryDeviceStatusEnum == 3) ? 'ok' : (memoryDeviceStatusEnum == 4) ? 'nonCritical' : (memoryDeviceStatusEnum == 5) ? 'critical' : (memoryDeviceStatusEnum == 6) ? 'nonRecoverable' : 'unknown'"
|
||||
- "sizes=memoryDeviceSize/1024/1024"
|
||||
- "type=(memoryDeviceTypeEnum == 1) ? 'other' : (memoryDeviceTypeEnum == 2) ? 'unknown' : (memoryDeviceTypeEnum == 3) ? 'DRAM' : (memoryDeviceTypeEnum == 4) ? 'EDRAM' : (memoryDeviceTypeEnum == 5) ? 'VRAM' : (memoryDeviceTypeEnum == 6) ? 'SRAM' : (memoryDeviceTypeEnum == 7) ? 'RAM' : (memoryDeviceTypeEnum == 8) ? 'ROM' : (memoryDeviceTypeEnum == 9) ? 'FLASH' : (memoryDeviceTypeEnum == 10) ? 'EEPROM' : (memoryDeviceTypeEnum == 11) ? 'FEPROM' : (memoryDeviceTypeEnum == 12) ? 'EPROM' : (memoryDeviceTypeEnum == 13) ? 'CDRAM' : (memoryDeviceTypeEnum == 14) ? '3DRAM' : (memoryDeviceTypeEnum == 15) ? 'SDRAM' : (memoryDeviceTypeEnum == 16) ? 'SGRAM' : (memoryDeviceTypeEnum == 17) ? 'RDRAM' : (memoryDeviceTypeEnum == 18) ? 'DDR' : (memoryDeviceTypeEnum == 19) ? 'DDR2' : (memoryDeviceTypeEnum == 20) ? 'DDR2FBDIMM' : (memoryDeviceTypeEnum == 24) ? 'DDR3' : (memoryDeviceTypeEnum == 25) ? 'FBD2' : (memoryDeviceTypeEnum == 26) ? 'DDR4' : 'unknown'"
|
||||
- "speed=memoryDeviceSpeed"
|
||||
- "current_speed=memoryDeviceCurrentOperatingSpeed"
|
||||
|
||||
protocol: snmp
|
||||
snmp:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
username: ^_^username^_^
|
||||
authPassphrase: ^_^authPassphrase^_^
|
||||
privPassphrase: ^_^privPassphrase^_^
|
||||
privPasswordEncryption: ^_^privPasswordEncryption^_^
|
||||
authPasswordEncryption: ^_^authPasswordEncryption^_^
|
||||
contextName: ^_^contextName^_^
|
||||
timeout: ^_^timeout^_^
|
||||
community: ^_^community^_^
|
||||
version: ^_^snmpVersion^_^
|
||||
operation: walk
|
||||
oids:
|
||||
memoryDeviceIndex: 1.3.6.1.4.1.674.10892.5.4.1100.50.1.2
|
||||
memoryDeviceLocationName: 1.3.6.1.4.1.674.10892.5.4.1100.50.1.8
|
||||
memoryDeviceStatusEnum: 1.3.6.1.4.1.674.10892.5.4.1100.50.1.5
|
||||
memoryDeviceSize: 1.3.6.1.4.1.674.10892.5.4.1100.50.1.14
|
||||
memoryDeviceTypeEnum: 1.3.6.1.4.1.674.10892.5.4.1100.50.1.7
|
||||
memoryDeviceSpeed: 1.3.6.1.4.1.674.10892.5.4.1100.50.1.15
|
||||
memoryDeviceCurrentOperatingSpeed: 1.3.6.1.4.1.674.10892.5.4.1100.50.1.27
|
||||
|
||||
- name: processor
|
||||
i18n:
|
||||
zh-CN: 处理器
|
||||
en-US: Processors
|
||||
ja-JP: プロセッサ情報
|
||||
priority: 1
|
||||
fields:
|
||||
- field: index
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 索引
|
||||
en-US: Index
|
||||
ja-JP: 番号
|
||||
- field: name
|
||||
type: 1
|
||||
label: true
|
||||
i18n:
|
||||
zh-CN: 名称
|
||||
en-US: Name
|
||||
ja-JP: 名前
|
||||
- field: speed
|
||||
type: 0
|
||||
unit: MHz
|
||||
i18n:
|
||||
zh-CN: 当前频率
|
||||
en-US: Current Speed
|
||||
ja-JP: プロセッサ速度
|
||||
- field: family
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 家族
|
||||
en-US: Family
|
||||
ja-JP: ファミリー
|
||||
- field: status
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 状态
|
||||
en-US: Status
|
||||
ja-JP: 状態
|
||||
aliasFields:
|
||||
- processorDeviceIndex
|
||||
- processorDeviceLocationName
|
||||
- processorDeviceStatusEnum
|
||||
- processorDeviceSpeed
|
||||
- processorDeviceFamilyEnum
|
||||
calculates:
|
||||
- "index=processorDeviceIndex"
|
||||
- "name=processorDeviceLocationName"
|
||||
- "status=(processorDeviceStatusEnum == 1) ? 'other' : (processorDeviceStatusEnum == 2) ? 'unknown' : (processorDeviceStatusEnum == 3) ? 'ok' : (processorDeviceStatusEnum == 4) ? 'nonCritical' : (processorDeviceStatusEnum == 5) ? 'critical' : (processorDeviceStatusEnum == 6) ? 'nonRecoverable' : 'unknown'"
|
||||
- "speed=processorDeviceSpeed"
|
||||
- "family=(processorDeviceFamilyEnum == 1) ? 'other' : (processorDeviceFamilyEnum == 2) ? 'unknown' : (processorDeviceFamilyEnum == 3) ? '8086' : (processorDeviceFamilyEnum == 4) ? '80286' : (processorDeviceFamilyEnum == 5) ? 'Intel386' : (processorDeviceFamilyEnum == 6) ? 'Intel486' : (processorDeviceFamilyEnum == 11) ? 'Pentium' : (processorDeviceFamilyEnum == 12) ? 'PentiumPro' : (processorDeviceFamilyEnum == 13) ? 'PentiumII' : (processorDeviceFamilyEnum == 14) ? 'PentiumMMX' : (processorDeviceFamilyEnum == 15) ? 'Celeron' : (processorDeviceFamilyEnum == 16) ? 'PentiumIIXeon' : (processorDeviceFamilyEnum == 17) ? 'PentiumIII' : (processorDeviceFamilyEnum == 18) ? 'PentiumIIIXeon' : (processorDeviceFamilyEnum == 19) ? 'PentiumIIISpeedStep' : (processorDeviceFamilyEnum == 20) ? 'Itanium' : (processorDeviceFamilyEnum == 21) ? 'IntelXeon' : (processorDeviceFamilyEnum == 22) ? 'Pentium4' : (processorDeviceFamilyEnum == 23) ? 'IntelXeonMP' : (processorDeviceFamilyEnum == 24) ? 'IntelItanium2' : (processorDeviceFamilyEnum == 40) ? 'IntelCoreDuo' : (processorDeviceFamilyEnum == 41) ? 'IntelCoreDuoMobile' : (processorDeviceFamilyEnum == 42) ? 'IntelCoreSoloMobile' : (processorDeviceFamilyEnum == 43) ? 'IntelAtom' : (processorDeviceFamilyEnum == 131) ? 'AMDAthlon64' : (processorDeviceFamilyEnum == 132) ? 'AMDOpteron' : (processorDeviceFamilyEnum == 134) ? 'AMDTurion64Mobile' : (processorDeviceFamilyEnum == 190) ? 'IntelCore2' : (processorDeviceFamilyEnum == 191) ? 'IntelCore2Duo' : (processorDeviceFamilyEnum == 194) ? 'IntelCore2Quad' : (processorDeviceFamilyEnum == 198) ? 'IntelCorei7' : (processorDeviceFamilyEnum == 205) ? 'IntelCorei5' : (processorDeviceFamilyEnum == 206) ? 'IntelCorei3' : 'unknown'"
|
||||
protocol: snmp
|
||||
snmp:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
username: ^_^username^_^
|
||||
authPassphrase: ^_^authPassphrase^_^
|
||||
privPassphrase: ^_^privPassphrase^_^
|
||||
privPasswordEncryption: ^_^privPasswordEncryption^_^
|
||||
authPasswordEncryption: ^_^authPasswordEncryption^_^
|
||||
contextName: ^_^contextName^_^
|
||||
timeout: ^_^timeout^_^
|
||||
community: ^_^community^_^
|
||||
version: ^_^snmpVersion^_^
|
||||
operation: walk
|
||||
oids:
|
||||
processorDeviceIndex: 1.3.6.1.4.1.674.10892.5.4.1100.30.1.2
|
||||
processorDeviceLocationName: 1.3.6.1.4.1.674.10892.5.4.1100.30.1.8
|
||||
processorDeviceStatusEnum: 1.3.6.1.4.1.674.10892.5.4.1100.30.1.5
|
||||
processorDeviceSpeed: 1.3.6.1.4.1.674.10892.5.4.1100.30.1.12
|
||||
processorDeviceFamilyEnum: 1.3.6.1.4.1.674.10892.5.4.1100.30.1.10
|
||||
@@ -26,7 +26,7 @@ help:
|
||||
zh-CN: Hertzbeat 使用 Prometheus PromQL 从 Prometheus 服务器中查询到 InfluxDB 的通用指标数据来进行监控。此方案适用于 Prometheus 已监控 InfluxDB,需要从 Prometheus 服务器抓取 InfluxDB 的监控数据。<br>您可以点击 “<i>新建 InfluxDB-PromQL</i>” 并进行配置,或者选择“<i>更多操作</i>”,导入已有配置。
|
||||
en-US: HertzBeat uses Prometheus PromQL query metrics data from Prometheus Server to monitoring InfluxDB. This solution is suitable for Prometheus to monitor InfluxDB, and it need to capture InfluxDB monitoring data from the Prometheus server. <br>You could click the "<i>New InfluxDB-PromQL</i>" button and proceed with the configuration or import an existing setup through the "<i>More Actions</i>" menu.
|
||||
zh-TW: Hertzbeat 使用 Prometheus PromQL 從 Prometheus 服務器中查詢到 InfluxDB 的通用指標數據來進行監控。此方案適用于 Prometheus 已監控 InfluxDB,需要從 Prometheus 服務器抓取 InfluxDB 的監控數據。<br>您可以點擊 “<i>新建 InfluxDB-PromQL</i>” 並進行配置,或者選擇“<i>更多操作</i>”,導入已有配置。
|
||||
ja-JP: Hertzbeat は Prometheus PromQL を介して Prometheus サーバーに InfluxDB の一般的なパフォーマンスのメトリクスをクエリして監視します。このシナリオは、PrometheusがすでにInfluxDBを監視しており、PrometheusサーバーからInfluxDBの監視データを取得する必要がある場合に適用されます。。<br>「<i>新規 InfluxDB-PromQL</i>」をクリックして設定しましょう。
|
||||
ja-JP: Hertzbeat は Prometheus PromQL を介して Prometheus サーバーに InfluxDB の一般的なパフォーマンスのメトリクスをクエリして監視します。このシナリオは、PrometheusがすでにInfluxDBを監視しており、PrometheusサーバーからInfluxDBの監視データを取得する必要がある場合に適用されます。<br>「<i>新規 InfluxDB-PromQL</i>」をクリックして設定しましょう。
|
||||
helpLink:
|
||||
zh-CN: https://hertzbeat.apache.org/zh-cn/docs/help/influxdb_promql
|
||||
en-US: https://hertzbeat.apache.org/docs/help/influxdb_promql
|
||||
|
||||
@@ -26,7 +26,7 @@ help:
|
||||
zh-CN: Hertzbeat 使用 Prometheus PromQL 从 Prometheus 服务器中查询到 Kafka 的通用指标数据来进行监控。此方案适用于 Prometheus 已监控 Kafka,需要从 Prometheus 服务器抓取 Kafka 的监控数据。<br>您可以点击 “<i>新建 Kafka-PromQL</i>” 并进行配置,或者选择“<i>更多操作</i>”,导入已有配置。
|
||||
en-US: HertzBeat uses Prometheus PromQL query metrics data from Prometheus Server to monitoring Kafka. This solution is suitable for Prometheus to monitor Kafka, and it need to capture Kafka monitoring data from the Prometheus server. <br>You could click the "<i>New Kafka-PromQL</i>" button and proceed with the configuration or import an existing setup through the "<i>More Actions</i>" menu.
|
||||
zh-TW: Hertzbeat 使用 Prometheus PromQL 從 Prometheus 服務器中查詢到 Kafka 的通用指標數據來進行監控。此方案適用于 Prometheus 已監控 Kafka,需要從 Prometheus 服務器抓取 Kafka 的監控數據。<br>您可以點擊 “<i>新建 Kafka-PromQL</i>” 並進行配置,或者選擇“<i>更多操作</i>”,導入已有配置。
|
||||
ja-JP: Hertzbeat は Prometheus PromQL を介して Prometheus サーバーに Kafka の一般的なパフォーマンスのメトリクスをクエリして監視します。このシナリオは、PrometheusがすでにKafkaを監視しており、PrometheusサーバーからKafkaの監視データを取得する必要がある場合に適用されます。。<br>「<i>新規 Kafka-PromQL</i>」をクリックして設定しましょう。
|
||||
ja-JP: Hertzbeat は Prometheus PromQL を介して Prometheus サーバーに Kafka の一般的なパフォーマンスのメトリクスをクエリして監視します。このシナリオは、PrometheusがすでにKafkaを監視しており、PrometheusサーバーからKafkaの監視データを取得する必要がある場合に適用されます。<br>「<i>新規 Kafka-PromQL</i>」をクリックして設定しましょう。
|
||||
helpLink:
|
||||
zh-CN: https://hertzbeat.apache.org/zh-cn/docs/help/kafka_promql
|
||||
en-US: https://hertzbeat.apache.org/docs/help/kafka_promql
|
||||
|
||||
@@ -490,7 +490,7 @@ metrics:
|
||||
zh-CN: 次数
|
||||
en-US: Num
|
||||
ja-JP: 数量
|
||||
- field: size
|
||||
- field: Size
|
||||
type: 0
|
||||
unit: B
|
||||
i18n:
|
||||
|
||||
@@ -0,0 +1,750 @@
|
||||
# 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: os
|
||||
# The monitoring type eg: linux windows tomcat mysql aws...
|
||||
app: macos
|
||||
# The monitoring i18n name
|
||||
name:
|
||||
zh-CN: macOS
|
||||
en-US: macOS
|
||||
ja-JP: macOS
|
||||
# The description and help of this monitoring type
|
||||
help:
|
||||
zh-CN: Hertzbeat 使用 <a class='help_module_content' href='https://hertzbeat.apache.org/docs/advanced/extend-ssh'> SSH 协议</a> 对 macOS 操作系统的通用性能指标 (系统信息、CPU、内存、磁盘、网卡、文件系统、TOP资源进程等) 进行采集监控。<br>您可以点击“<i>新建 macOS</i>”并配置 HOST 端口账户等相关参数进行添加,支持 SSH 账户密码或密钥认证。或者选择“<i>更多操作</i>”,导入已有配置。
|
||||
en-US: HertzBeat uses <a class='help_module_content' href='https://hertzbeat.apache.org/docs/advanced/extend-ssh'> SSH protocol</a> to monitors macOS operating system's general performance metrics such as cpu, memory, disk, basic, interface, disk_free, top_process etc. <br>You can click the "<i>New macOS</i>" and config host port and other related params to add, auth support password or secretKey. Or import an existing setup through the "<i>More Actions</i>" menu.
|
||||
zh-TW: Hertzbeat 使用 <a class='help_module_content' href='https://hertzbeat.apache.org/docs/advanced/extend-ssh'> SSH 协议</a> 對 macOS 操作系统的通用性能指標 (系統信息、CPU、內存、磁盤、網卡、文件系統、TOP資源進程等) 進行採集監控。<br>您可以點擊“<i>新建 macOS</i>”並配置HOST端口賬戶等相關參數進行添加,支持SSH賬戶密碼或密鑰認證。或者選擇“<i>更多操作</i>”,導入已有配寘。
|
||||
ja-JP: Hertzbeat は <a class='help_module_content' href='https://hertzbeat.apache.org/docs/advanced/extend-ssh'> SSHプロトコルを介して</a> macOS システムの一般的なパフォーマンスのメトリクスを監視します。<br>「<i>新規 macOS</i>」をクリックしてホストなどのパラメタを設定した後、新規することができます。SSHまたはキー認証をサポートします。
|
||||
helpLink:
|
||||
zh-CN: https://hertzbeat.apache.org/zh-cn/docs/help/macos/
|
||||
en-US: https://hertzbeat.apache.org/docs/help/macos/
|
||||
# Input params define for monitoring(render web ui by the definition)
|
||||
params:
|
||||
# field-param field key
|
||||
- field: host
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 目标Host
|
||||
en-US: Target Host
|
||||
ja-JP: 目標ホスト
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: host
|
||||
# required-true or false
|
||||
required: true
|
||||
# field-param field key
|
||||
- field: port
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 端口
|
||||
en-US: Port
|
||||
ja-JP: ポート
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: number
|
||||
# when type is number, range is required
|
||||
range: '[0,65535]'
|
||||
# required-true or false
|
||||
required: true
|
||||
# default value
|
||||
defaultValue: 22
|
||||
# field-param field key
|
||||
- field: timeout
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 超时时间(ms)
|
||||
en-US: Timeout(ms)
|
||||
ja-JP: タイムアウト(ms)
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: number
|
||||
# when type is number, range is required
|
||||
range: '[400,200000]'
|
||||
# required-true or false
|
||||
required: false
|
||||
# default value
|
||||
defaultValue: 6000
|
||||
# field-param field key
|
||||
- field: reuseConnection
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 复用连接
|
||||
en-US: Reuse Connection
|
||||
ja-JP: 接続再利用
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: boolean
|
||||
# required-true or false
|
||||
required: true
|
||||
defaultValue: true
|
||||
# field-param field key
|
||||
- field: useProxy
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 使用代理
|
||||
en-US: Use Proxy Connection
|
||||
ja-JP: プロキシ接続利用
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: boolean
|
||||
# required-true or false
|
||||
required: true
|
||||
defaultValue: false
|
||||
# field-param field key
|
||||
- field: username
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 用户名
|
||||
en-US: Username
|
||||
ja-JP: ユーザー名
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: text
|
||||
# when type is text, use limit to limit string length
|
||||
limit: 50
|
||||
# required-true or false
|
||||
required: true
|
||||
# field-param field key
|
||||
- field: password
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 密码
|
||||
en-US: Password
|
||||
ja-JP: パスワード
|
||||
# type-param field type(most mapping the html input tag)
|
||||
type: password
|
||||
# required-true or false
|
||||
required: false
|
||||
# field-param field key
|
||||
- field: privateKey
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 私钥
|
||||
en-US: PrivateKey
|
||||
ja-JP: 秘密鍵
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: textarea
|
||||
placeholder: -----BEGIN RSA PRIVATE KEY-----
|
||||
# required-true or false
|
||||
required: false
|
||||
# hide param-true or false
|
||||
hide: true
|
||||
# field-param field key
|
||||
- field: privateKeyPassphrase
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 密钥短语
|
||||
en-US: PrivateKey PassPhrase
|
||||
ja-JP: 秘密鍵フレーズ
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: password
|
||||
# required-true or false
|
||||
required: false
|
||||
# hide param-true or false
|
||||
hide: true
|
||||
# field-param field key
|
||||
- field: proxyHost
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 代理主机
|
||||
en-US: Proxy Host
|
||||
ja-JP: プロキシホスト
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: text
|
||||
# required-true or false
|
||||
required: false
|
||||
# hide param-true or false
|
||||
hide: true
|
||||
- field: proxyPort
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 代理端口
|
||||
en-US: Proxy Port
|
||||
ja-JP: プロキシポート
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: number
|
||||
# when type is number, range is required
|
||||
range: '[0,65535]'
|
||||
# required-true or false
|
||||
required: false
|
||||
# hide param-true or false
|
||||
hide: true
|
||||
# default value
|
||||
defaultValue: 22
|
||||
# field-param field key
|
||||
- field: proxyUsername
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 代理用户名
|
||||
en-US: Proxy Username
|
||||
ja-JP: プロキシユーザー名
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: text
|
||||
# when type is text, use limit to limit string length
|
||||
limit: 50
|
||||
# required-true or false
|
||||
required: false
|
||||
# hide param-true or false
|
||||
hide: true
|
||||
# field-param field key
|
||||
- field: proxyPassword
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 代理密码
|
||||
en-US: Proxy Password
|
||||
ja-JP: プロキシパスワード
|
||||
# type-param field type(most mapping the html input tag)
|
||||
type: password
|
||||
# required-true or false
|
||||
required: false
|
||||
# hide param-true or false
|
||||
hide: true
|
||||
# field-param field key
|
||||
- field: proxyPrivateKey
|
||||
# name-param field display i18n name
|
||||
name:
|
||||
zh-CN: 代理主机私钥
|
||||
en-US: proxyPrivateKey
|
||||
ja-JP: プロキシ秘密鍵
|
||||
# type-param field type(most mapping the html input type)
|
||||
type: textarea
|
||||
placeholder: -----BEGIN RSA PRIVATE KEY-----
|
||||
# required-true or false
|
||||
required: false
|
||||
# hide param-true or false
|
||||
hide: true
|
||||
# collect metrics config list
|
||||
metrics:
|
||||
# metrics - basic, inner monitoring metrics (responseTime - response time)
|
||||
- name: basic
|
||||
i18n:
|
||||
zh-CN: 系统基本信息
|
||||
en-US: Basic Info
|
||||
ja-JP: システム基礎情報
|
||||
# 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: hostname
|
||||
type: 1
|
||||
label: true
|
||||
i18n:
|
||||
zh-CN: 主机名称
|
||||
en-US: Host Name
|
||||
ja-JP: ホスト名
|
||||
- field: version
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 操作系统版本
|
||||
en-US: System Version
|
||||
ja-JP: オーエスバージョン
|
||||
- field: uptime
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 启动时间
|
||||
en-US: Uptime
|
||||
ja-JP: アップタイム
|
||||
# the protocol used for monitoring, eg: sql, ssh, http, telnet, wmi, snmp, sdk
|
||||
protocol: ssh
|
||||
# the config content when protocol is ssh
|
||||
ssh:
|
||||
# ssh host: ipv4 ipv6 domain
|
||||
host: ^_^host^_^
|
||||
# ssh port
|
||||
port: ^_^port^_^
|
||||
# ssh username
|
||||
username: ^_^username^_^
|
||||
# ssh password
|
||||
password: ^_^password^_^
|
||||
# ssh private key
|
||||
privateKey: ^_^privateKey^_^
|
||||
# ssh private key passphrase
|
||||
privateKeyPassphrase: ^_^privateKeyPassphrase^_^
|
||||
timeout: ^_^timeout^_^
|
||||
reuseConnection: ^_^reuseConnection^_^
|
||||
# whether to use proxy server for ssh connection
|
||||
useProxy: ^_^useProxy^_^
|
||||
# ssh proxy host: ipv4 domain
|
||||
proxyHost: ^_^proxyHost^_^
|
||||
# ssh proxy port
|
||||
proxyPort: ^_^proxyPort^_^
|
||||
# ssh proxy username
|
||||
proxyUsername: ^_^proxyUsername^_^
|
||||
# ssh proxy password
|
||||
proxyPassword: ^_^proxyPassword^_^
|
||||
# ssh proxy private key
|
||||
proxyPrivateKey: ^_^proxyPrivateKey^_^
|
||||
# ssh run collect script
|
||||
script: scutil --get ComputerName; echo "$(sw_vers -productName) $(sw_vers -productVersion) ($(sw_vers -buildVersion))"; uptime| awk '{print $1,$2,$3,$4}' | sed 's/,//'
|
||||
# ssh response data parse type: oneRow, multiRow
|
||||
parseType: oneRow
|
||||
|
||||
- name: cpu
|
||||
i18n:
|
||||
zh-CN: CPU 信息
|
||||
en-US: CPU Info
|
||||
ja-JP: CPU情報
|
||||
priority: 1
|
||||
fields:
|
||||
- field: info
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 型号
|
||||
en-US: Info
|
||||
ja-JP: バージョン
|
||||
- field: cores
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 核数
|
||||
en-US: Cores
|
||||
ja-JP: コア数
|
||||
- field: load
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 负载
|
||||
en-US: Load
|
||||
ja-JP: ロード
|
||||
- field: usage
|
||||
type: 0
|
||||
unit: '%'
|
||||
i18n:
|
||||
zh-CN: 使用率
|
||||
en-US: Usage
|
||||
ja-JP: 使用率
|
||||
# (optional)metrics field alias name, it is used as an alias field to map and convert the collected data and metrics field
|
||||
aliasFields:
|
||||
- info
|
||||
- cores
|
||||
- load
|
||||
- idle
|
||||
# mapping and conversion expressions, use these and aliasField above to calculate metrics value
|
||||
# eg: cores=core1+core2, usage=usage, waitTime=allTime-runningTime
|
||||
calculates:
|
||||
- info=info
|
||||
- cores=cores
|
||||
- load=load
|
||||
- usage=idle
|
||||
protocol: ssh
|
||||
ssh:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
username: ^_^username^_^
|
||||
password: ^_^password^_^
|
||||
privateKey: ^_^privateKey^_^
|
||||
privateKeyPassphrase: ^_^privateKeyPassphrase^_^
|
||||
timeout: ^_^timeout^_^
|
||||
reuseConnection: ^_^reuseConnection^_^
|
||||
# whether to use proxy server for ssh connection
|
||||
useProxy: ^_^useProxy^_^
|
||||
# ssh proxy host: ipv4 domain
|
||||
proxyHost: ^_^proxyHost^_^
|
||||
# ssh proxy port
|
||||
proxyPort: ^_^proxyPort^_^
|
||||
# ssh proxy username
|
||||
proxyUsername: ^_^proxyUsername^_^
|
||||
# ssh proxy password
|
||||
proxyPassword: ^_^proxyPassword^_^
|
||||
# ssh proxy private key
|
||||
proxyPrivateKey: ^_^proxyPrivateKey^_^
|
||||
script: sysctl -n machdep.cpu.brand_string; sysctl -n hw.ncpu; sysctl -n vm.loadavg | awk '{print $2,$3,$4}'; top -l 1 | grep 'CPU usage' | awk '{print $3}' | sed 's/%//'
|
||||
parseType: oneRow
|
||||
|
||||
- name: memory
|
||||
i18n:
|
||||
zh-CN: 内存信息
|
||||
en-US: Memory Info
|
||||
ja-JP: メモリ情報
|
||||
priority: 2
|
||||
fields:
|
||||
- field: total
|
||||
type: 1
|
||||
unit: Mb
|
||||
i18n:
|
||||
zh-CN: 总内存容量
|
||||
en-US: Total Memory
|
||||
ja-JP: メモリ容量
|
||||
- field: used
|
||||
type: 0
|
||||
unit: Mb
|
||||
i18n:
|
||||
zh-CN: 用户程序内存量
|
||||
en-US: User Program Memory
|
||||
ja-JP: ユーザープログラムメモリ
|
||||
- field: free
|
||||
type: 0
|
||||
unit: Mb
|
||||
i18n:
|
||||
zh-CN: 空闲内存容量
|
||||
en-US: Free Memory
|
||||
ja-JP: 空きメモリ
|
||||
- field: buff_cache
|
||||
type: 0
|
||||
unit: Mb
|
||||
i18n:
|
||||
zh-CN: 缓存占用内存
|
||||
en-US: Buff Cache Memory
|
||||
ja-JP: バッファメモリ
|
||||
- field: available
|
||||
type: 0
|
||||
unit: Mb
|
||||
i18n:
|
||||
zh-CN: 剩余可用内存
|
||||
en-US: Available Memory
|
||||
ja-JP: 使用可能のメモリ
|
||||
- field: usage
|
||||
type: 0
|
||||
unit: '%'
|
||||
i18n:
|
||||
zh-CN: 内存使用率
|
||||
en-US: Memory Usage
|
||||
ja-JP: メモリ使用率
|
||||
aliasFields:
|
||||
- total
|
||||
- used
|
||||
- free
|
||||
- buff_cache
|
||||
- available
|
||||
calculates:
|
||||
- total=total
|
||||
- used=used
|
||||
- free=free
|
||||
- buff_cache=buff_cache
|
||||
- available=available
|
||||
- usage=(used / total) * 100
|
||||
protocol: ssh
|
||||
ssh:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
username: ^_^username^_^
|
||||
password: ^_^password^_^
|
||||
privateKey: ^_^privateKey^_^
|
||||
privateKeyPassphrase: ^_^privateKeyPassphrase^_^
|
||||
timeout: ^_^timeout^_^
|
||||
reuseConnection: ^_^reuseConnection^_^
|
||||
# whether to use proxy server for ssh connection
|
||||
useProxy: ^_^useProxy^_^
|
||||
# ssh proxy host: ipv4 domain
|
||||
proxyHost: ^_^proxyHost^_^
|
||||
# ssh proxy port
|
||||
proxyPort: ^_^proxyPort^_^
|
||||
# ssh proxy username
|
||||
proxyUsername: ^_^proxyUsername^_^
|
||||
# ssh proxy password
|
||||
proxyPassword: ^_^proxyPassword^_^
|
||||
# ssh proxy private key
|
||||
proxyPrivateKey: ^_^proxyPrivateKey^_^
|
||||
script: echo "total used free buff_cache available"; vm_stat | awk 'BEGIN{cmd="sysctl -n hw.memsize";cmd|getline total;close(cmd);cmd="sysctl -n hw.pagesize";cmd|getline ps;close(cmd);total=total/1024/1024} /Pages free/{free=$3} /Pages active/{active=$3} /Pages inactive/{inactive=$3} /Pages wired/{wired=$4} /Pages occupied by compressor/{comp=$5} END{free_mb=free*ps/1024/1024;used_mb=(active+inactive+wired+comp)*ps/1024/1024;inactive_mb=inactive*ps/1024/1024;avail_mb=(free+inactive)*ps/1024/1024;printf "%.0f %.0f %.0f %.0f %.0f\n",total,used_mb,free_mb,inactive_mb,avail_mb}'
|
||||
parseType: multiRow
|
||||
|
||||
- name: disk
|
||||
i18n:
|
||||
zh-CN: 磁盘信息
|
||||
en-US: Disk Info
|
||||
ja-JP: ディスク情報
|
||||
priority: 3
|
||||
fields:
|
||||
- field: disk_num
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 磁盘总数
|
||||
en-US: Disk Num
|
||||
ja-JP: ディスク番号
|
||||
- field: partition_num
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 分区总数
|
||||
en-US: Partition Num
|
||||
ja-JP: パーティション
|
||||
- field: block_write
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: 写磁盘块数
|
||||
en-US: Block Write
|
||||
ja-JP: 書き込みディスクブロック数
|
||||
- field: block_read
|
||||
type: 0
|
||||
i18n:
|
||||
zh-CN: 读磁盘块数
|
||||
en-US: Block Read
|
||||
ja-JP: 読み取りブロック数
|
||||
- field: write_rate
|
||||
type: 0
|
||||
unit: iops
|
||||
i18n:
|
||||
zh-CN: 磁盘写速率
|
||||
en-US: Write Rate
|
||||
ja-JP: ディスク書き込み速度
|
||||
protocol: ssh
|
||||
ssh:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
username: ^_^username^_^
|
||||
password: ^_^password^_^
|
||||
privateKey: ^_^privateKey^_^
|
||||
privateKeyPassphrase: ^_^privateKeyPassphrase^_^
|
||||
timeout: ^_^timeout^_^
|
||||
reuseConnection: ^_^reuseConnection^_^
|
||||
# whether to use proxy server for ssh connection
|
||||
useProxy: ^_^useProxy^_^
|
||||
# ssh proxy host: ipv4 domain
|
||||
proxyHost: ^_^proxyHost^_^
|
||||
# ssh proxy port
|
||||
proxyPort: ^_^proxyPort^_^
|
||||
# ssh proxy username
|
||||
proxyUsername: ^_^proxyUsername^_^
|
||||
# ssh proxy password
|
||||
proxyPassword: ^_^proxyPassword^_^
|
||||
# ssh proxy private key
|
||||
proxyPrivateKey: ^_^proxyPrivateKey^_^
|
||||
script: iostat -d | awk 'NR==3{print $1}'; iostat -d | awk 'NR==3{print $2}'; vm_stat | awk '/Pages free/{print $3}' | tr -d ':'; vm_stat | awk '/Pages active/{print $3}' | tr -d ':'; sysctl -n hw.memsize | awk '{print int($1/1024/1024)}'
|
||||
parseType: oneRow
|
||||
|
||||
- name: interface
|
||||
i18n:
|
||||
zh-CN: 网卡信息
|
||||
en-US: Interface Info
|
||||
ja-JP: ネットワークカード情報
|
||||
priority: 4
|
||||
fields:
|
||||
- field: interface_name
|
||||
type: 1
|
||||
label: true
|
||||
i18n:
|
||||
zh-CN: 网卡名称
|
||||
en-US: Interface Name
|
||||
ja-JP: ネットワークカード名
|
||||
- field: receive_bytes
|
||||
type: 0
|
||||
unit: Mb
|
||||
i18n:
|
||||
zh-CN: 入站数据流量
|
||||
en-US: Receive Bytes
|
||||
ja-JP: 受信されたバイト数
|
||||
- field: transmit_bytes
|
||||
type: 0
|
||||
unit: Mb
|
||||
i18n:
|
||||
zh-CN: 出站数据流量
|
||||
en-US: Transmit Bytes
|
||||
ja-JP: 転送されたバイト数
|
||||
units:
|
||||
- receive_bytes=B->MB
|
||||
- transmit_bytes=B->MB
|
||||
protocol: ssh
|
||||
ssh:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
username: ^_^username^_^
|
||||
password: ^_^password^_^
|
||||
privateKey: ^_^privateKey^_^
|
||||
privateKeyPassphrase: ^_^privateKeyPassphrase^_^
|
||||
timeout: ^_^timeout^_^
|
||||
reuseConnection: ^_^reuseConnection^_^
|
||||
# whether to use proxy server for ssh connection
|
||||
useProxy: ^_^useProxy^_^
|
||||
# ssh proxy host: ipv4 domain
|
||||
proxyHost: ^_^proxyHost^_^
|
||||
# ssh proxy port
|
||||
proxyPort: ^_^proxyPort^_^
|
||||
# ssh proxy username
|
||||
proxyUsername: ^_^proxyUsername^_^
|
||||
# ssh proxy password
|
||||
proxyPassword: ^_^proxyPassword^_^
|
||||
# ssh proxy private key
|
||||
proxyPrivateKey: ^_^proxyPrivateKey^_^
|
||||
script: netstat -ibn | awk 'NR==1{print "interface_name receive_bytes transmit_bytes"} NR>1 && $1!~/Name/ && $7~/^[0-9]+$/{print $1,$7,$10}'
|
||||
parseType: multiRow
|
||||
|
||||
- name: disk_free
|
||||
i18n:
|
||||
zh-CN: 文件系统
|
||||
en-US: Disk Free
|
||||
ja-JP: ファイルシステム
|
||||
priority: 5
|
||||
fields:
|
||||
- field: filesystem
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 文件系统
|
||||
en-US: Filesystem
|
||||
ja-JP: ファイルシステム
|
||||
- field: used
|
||||
type: 0
|
||||
unit: Mb
|
||||
i18n:
|
||||
zh-CN: 已使用量
|
||||
en-US: Used
|
||||
ja-JP: 使用済み
|
||||
- field: available
|
||||
type: 0
|
||||
unit: Mb
|
||||
i18n:
|
||||
zh-CN: 可用量
|
||||
en-US: Available
|
||||
ja-JP: 使用可能
|
||||
- field: usage
|
||||
type: 0
|
||||
unit: '%'
|
||||
i18n:
|
||||
zh-CN: 使用率
|
||||
en-US: Usage
|
||||
ja-JP: 使用率
|
||||
- field: mounted
|
||||
type: 1
|
||||
label: true
|
||||
i18n:
|
||||
zh-CN: 挂载点
|
||||
en-US: Mounted
|
||||
ja-JP: マウント
|
||||
protocol: ssh
|
||||
ssh:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
username: ^_^username^_^
|
||||
password: ^_^password^_^
|
||||
privateKey: ^_^privateKey^_^
|
||||
privateKeyPassphrase: ^_^privateKeyPassphrase^_^
|
||||
timeout: ^_^timeout^_^
|
||||
reuseConnection: ^_^reuseConnection^_^
|
||||
# whether to use proxy server for ssh connection
|
||||
useProxy: ^_^useProxy^_^
|
||||
# ssh proxy host: ipv4 domain
|
||||
proxyHost: ^_^proxyHost^_^
|
||||
# ssh proxy port
|
||||
proxyPort: ^_^proxyPort^_^
|
||||
# ssh proxy username
|
||||
proxyUsername: ^_^proxyUsername^_^
|
||||
# ssh proxy password
|
||||
proxyPassword: ^_^proxyPassword^_^
|
||||
# ssh proxy private key
|
||||
proxyPrivateKey: ^_^proxyPrivateKey^_^
|
||||
script: df -m | tail -n +2 | awk 'BEGIN{ print "filesystem used available usage mounted"} {print $1,$3,$4,$5,$9}'
|
||||
parseType: multiRow
|
||||
|
||||
- name: top_cpu_process
|
||||
i18n:
|
||||
zh-CN: Top10 CPU 进程
|
||||
en-US: Top10 CPU Process
|
||||
ja-JP: トップ10 CPUプロセス
|
||||
priority: 6
|
||||
fields:
|
||||
- field: pid
|
||||
type: 1
|
||||
label: true
|
||||
i18n:
|
||||
zh-CN: 进程ID
|
||||
en-US: PID
|
||||
ja-JP: プロセスID
|
||||
- field: cpu_usage
|
||||
type: 0
|
||||
unit: '%'
|
||||
i18n:
|
||||
zh-CN: CPU占用率
|
||||
en-US: CPU Usage
|
||||
ja-JP: CPU使用率
|
||||
- field: mem_usage
|
||||
type: 0
|
||||
unit: '%'
|
||||
i18n:
|
||||
zh-CN: 内存占用率
|
||||
en-US: Memory Usage
|
||||
ja-JP: メモリ使用率
|
||||
- field: command
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 执行命令
|
||||
en-US: Command
|
||||
ja-JP: コマンド
|
||||
protocol: ssh
|
||||
ssh:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
username: ^_^username^_^
|
||||
password: ^_^password^_^
|
||||
privateKey: ^_^privateKey^_^
|
||||
privateKeyPassphrase: ^_^privateKeyPassphrase^_^
|
||||
timeout: ^_^timeout^_^
|
||||
reuseConnection: ^_^reuseConnection^_^
|
||||
# whether to use proxy server for ssh connection
|
||||
useProxy: ^_^useProxy^_^
|
||||
# ssh proxy host: ipv4 domain
|
||||
proxyHost: ^_^proxyHost^_^
|
||||
# ssh proxy port
|
||||
proxyPort: ^_^proxyPort^_^
|
||||
# ssh proxy username
|
||||
proxyUsername: ^_^proxyUsername^_^
|
||||
# ssh proxy password
|
||||
proxyPassword: ^_^proxyPassword^_^
|
||||
# ssh proxy private key
|
||||
proxyPrivateKey: ^_^proxyPrivateKey^_^
|
||||
script: ps aux | sort -k3nr | awk 'BEGIN{ print "pid cpu_usage mem_usage command" } {printf "%s %s %s ", $2, $3, $4; for (i=11; i<=NF; i++) { printf "%s", $i; if (i < NF) printf " "; } print ""}' | head -n 11
|
||||
parseType: multiRow
|
||||
|
||||
- name: top_mem_process
|
||||
i18n:
|
||||
zh-CN: Top10 内存进程
|
||||
en-US: Top10 Memory Process
|
||||
ja-JP: トップ10 メモリプロセス
|
||||
priority: 7
|
||||
fields:
|
||||
- field: pid
|
||||
type: 1
|
||||
label: true
|
||||
i18n:
|
||||
zh-CN: 进程ID
|
||||
en-US: PID
|
||||
ja-JP: プロセスID
|
||||
- field: mem_usage
|
||||
type: 0
|
||||
unit: '%'
|
||||
i18n:
|
||||
zh-CN: 内存占用率
|
||||
en-US: Memory Usage
|
||||
ja-JP: メモリ使用率
|
||||
- field: cpu_usage
|
||||
type: 0
|
||||
unit: '%'
|
||||
i18n:
|
||||
zh-CN: CPU占用率
|
||||
en-US: CPU Usage
|
||||
ja-JP: CPU使用率
|
||||
- field: command
|
||||
type: 1
|
||||
i18n:
|
||||
zh-CN: 执行命令
|
||||
en-US: Command
|
||||
ja-JP: コマンド
|
||||
protocol: ssh
|
||||
ssh:
|
||||
host: ^_^host^_^
|
||||
port: ^_^port^_^
|
||||
username: ^_^username^_^
|
||||
password: ^_^password^_^
|
||||
privateKey: ^_^privateKey^_^
|
||||
privateKeyPassphrase: ^_^privateKeyPassphrase^_^
|
||||
timeout: ^_^timeout^_^
|
||||
reuseConnection: ^_^reuseConnection^_^
|
||||
# whether to use proxy server for ssh connection
|
||||
useProxy: ^_^useProxy^_^
|
||||
# ssh proxy host: ipv4 domain
|
||||
proxyHost: ^_^proxyHost^_^
|
||||
# ssh proxy port
|
||||
proxyPort: ^_^proxyPort^_^
|
||||
# ssh proxy username
|
||||
proxyUsername: ^_^proxyUsername^_^
|
||||
# ssh proxy password
|
||||
proxyPassword: ^_^proxyPassword^_^
|
||||
# ssh proxy private key
|
||||
|
||||
proxyPrivateKey: ^_^proxyPrivateKey^_^
|
||||
script: ps aux | sort -k4nr | awk 'BEGIN{ print "pid cpu_usage mem_usage command" } NR>1{cmd=""; for(i=11;i<=NF;i++) cmd=cmd $i (i==NF?"":" "); printf "%s %s %s %s\n", $2, $3, $4, cmd}' | head -n 11
|
||||
parseType: multiRow
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user