mirror of
https://github.com/dromara/hertzbeat.git
synced 2026-09-17 09:40:58 +00:00
[GSOC] Added all the necessary tools across services (#3722)
Signed-off-by: Sarthak Arora <f20200060@pilani.bits-pilani.ac.in> Co-authored-by: Calvin <zhengqiwei@apache.org> Co-authored-by: Jast <shenghang@apache.org> Co-authored-by: Duansg <siguoduan@gmail.com> Co-authored-by: DeleiGuo <deleiguo@163.com> Co-authored-by: shown <yuluo08290126@gmail.com> Co-authored-by: tomsun28 <tomsun28@outlook.com> Co-authored-by: Logic <zqr10159@dromara.org> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Sherlock Yin <sherlock.yin1994@gmail.com> Co-authored-by: lynx009 <2030509072@qq.com>
This commit is contained in:
co-authored by
Calvin
Jast
Duansg
DeleiGuo
shown
tomsun28
Logic
Copilot
Sherlock Yin
lynx009
parent
7d8b55dfb8
commit
05b63903b5
@@ -57,6 +57,10 @@
|
||||
<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>
|
||||
|
||||
+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);
|
||||
}
|
||||
+1
-1
@@ -67,5 +67,5 @@ public interface MonitorServiceAdapter {
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
}
|
||||
-1
@@ -211,5 +211,4 @@ public class MonitorServiceAdapterImpl implements MonitorServiceAdapter {
|
||||
throw new RuntimeException("Failed to invoke getAppParamDefines via adapter: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+5
-1
@@ -22,6 +22,7 @@ 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;
|
||||
|
||||
@@ -32,6 +33,9 @@ import org.springframework.context.annotation.Configuration;
|
||||
@Configuration
|
||||
public class LlmConfig {
|
||||
|
||||
@Value("${spring.ai.openai.chat.options.model}")
|
||||
private String model;
|
||||
|
||||
/**
|
||||
* Create OpenAI API instance with dynamic API key
|
||||
*/
|
||||
@@ -48,7 +52,7 @@ public class LlmConfig {
|
||||
@Bean
|
||||
public OpenAiChatOptions openAiChatOptions() {
|
||||
return OpenAiChatOptions.builder()
|
||||
.model("gpt-4.1-nano-2025-04-14")
|
||||
.model(model)
|
||||
.temperature(0.3)
|
||||
.build();
|
||||
}
|
||||
|
||||
+139
-34
@@ -29,49 +29,154 @@ public class PromptProvider {
|
||||
/**
|
||||
* Static version of the HertzBeat monitoring prompt
|
||||
*/
|
||||
public static final String HERTZBEAT_MONITORING_PROMPT = """
|
||||
You are an AI assistant specialized in monitoring infrastructure and applications with HertzBeat.
|
||||
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 and analyze their monitoring data using the available tools.
|
||||
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 Monitoring Tools:
|
||||
- **list_monitors**: Query monitor information with detailed output (ID, name, type, host, status)
|
||||
- **add_monitor**: Add a new monitor to the system with comprehensive configuration
|
||||
- **list_monitor_types**: List all available monitor types (linux, mysql, redis, http, etc.)
|
||||
- **get_monitor_param_defines**: Get parameter definitions required for specific monitor types
|
||||
## 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?"
|
||||
|
||||
## HertzBeat Monitor Types:
|
||||
HertzBeat supports monitoring of:
|
||||
- **Operating Systems**: Linux, Windows, FreeBSD, macOS, etc.
|
||||
- **Databases**: MySQL, PostgreSQL, Redis, MongoDB, Oracle, SQL Server, etc.
|
||||
- **Application Services**: Tomcat, Spring Boot, Elasticsearch, Kafka, etc.
|
||||
- **Network & Infrastructure**: HTTP/HTTPS websites, DNS, ping, SSL certificates, etc.
|
||||
- **Cloud Services**: AWS, Azure, Kubernetes, Docker, etc.
|
||||
- **Custom Monitoring**: Through YAML templates and various protocols (HTTP, JDBC, SSH, JMX, SNMP, etc.)
|
||||
|
||||
## Workflow Guidelines:
|
||||
1. **For viewing monitors**: Use list_monitors with appropriate filters (by type, status, host, etc.)
|
||||
2. **For adding monitors**:
|
||||
- First use list_monitor_types to show available types
|
||||
- Then use get_monitor_param_defines to show required parameters for the chosen type
|
||||
- Finally use add_monitor with all necessary parameters
|
||||
|
||||
## Parameter Values:
|
||||
- **Monitor status**: 0 (no monitor), 1 (usable), 2 (disabled), 9 (all)
|
||||
- **Sort fields**: name, host, app, gmtCreate, gmtUpdate
|
||||
- **Sort order**: 'asc' or 'desc'
|
||||
- **Monitor intervals**: Typically 30s to 3600s (30 seconds to 1 hour)
|
||||
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:
|
||||
- Always validate monitor types using list_monitor_types before adding
|
||||
- Check parameter requirements using get_monitor_param_defines for each monitor type
|
||||
- Provide clear explanations of monitoring data and suggest actionable insights
|
||||
- For performance issues, recommend appropriate collection intervals
|
||||
- Explain HertzBeat's template-based YAML monitoring definitions when relevant
|
||||
- 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
|
||||
|
||||
Keep responses focused on monitoring topics and HertzBeat's comprehensive monitoring capabilities.
|
||||
If you're unsure about specific monitoring requirements, use the parameter definition tools to get
|
||||
accurate information.
|
||||
## 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.
|
||||
""";
|
||||
}
|
||||
+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;
|
||||
}
|
||||
+28
@@ -18,11 +18,39 @@
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
+7
@@ -17,6 +17,7 @@
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.service;
|
||||
|
||||
import org.apache.hertzbeat.ai.agent.event.OpenAiConfigChangeEvent;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.OpenAiConfigDto;
|
||||
|
||||
/**
|
||||
@@ -62,6 +63,12 @@ public interface OpenAiConfigService {
|
||||
*/
|
||||
void reloadConfig();
|
||||
|
||||
/**
|
||||
* Handle OpenAI configuration change events
|
||||
* @param event OpenAI configuration change event
|
||||
*/
|
||||
void onOpenAiConfigChange(OpenAiConfigChangeEvent event);
|
||||
|
||||
/**
|
||||
* Validation result class
|
||||
*/
|
||||
|
||||
+1
-2
@@ -85,11 +85,10 @@ public class ChatClientProviderServiceImpl implements ChatClientProviderService
|
||||
|
||||
return this.chatClient.prompt()
|
||||
.messages(messages)
|
||||
.system(PromptProvider.HERTZBEAT_MONITORING_PROMPT)
|
||||
.system(PromptProvider.HERTZBEAT_SYSTEM_PROMPT)
|
||||
.toolCallbacks(toolCallbackProvider)
|
||||
.stream()
|
||||
.content()
|
||||
.doOnNext(chunk -> log.debug("Received chunk: {}", chunk))
|
||||
.doOnComplete(() -> log.info("Streaming completed for conversation: {}", context.getConversationId()))
|
||||
.doOnError(error -> log.error("Error in streaming chat: {}", error.getMessage(), error));
|
||||
|
||||
|
||||
+13
-4
@@ -20,15 +20,18 @@ 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.apache.hertzbeat.ai.agent.tools.impl.MonitorToolsImpl;
|
||||
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 org.springframework.beans.factory.annotation.Autowired;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.web.servlet.function.RouterFunction;
|
||||
import org.springframework.web.servlet.function.ServerResponse;
|
||||
@@ -41,11 +44,17 @@ import org.springframework.web.servlet.function.ServerResponse;
|
||||
@Configuration
|
||||
public class McpServerServiceImpl implements McpServerService {
|
||||
@Autowired
|
||||
private MonitorToolsImpl monitorTools;
|
||||
private MonitorTools monitorTools;
|
||||
@Autowired
|
||||
private AlertTools alertTools;
|
||||
@Autowired
|
||||
private MetricsTools metricsTools;
|
||||
@Autowired
|
||||
private AlertDefineTools alertDefineTools;
|
||||
|
||||
@Bean
|
||||
public ToolCallbackProvider hertzbeatTools() {
|
||||
return MethodToolCallbackProvider.builder().toolObjects(monitorTools).build();
|
||||
return MethodToolCallbackProvider.builder().toolObjects(monitorTools, alertTools, alertDefineTools, metricsTools).build();
|
||||
}
|
||||
/**
|
||||
* Provides a custom SSE server transport for the MCP server.
|
||||
|
||||
+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);
|
||||
}
|
||||
+22
-1
@@ -19,7 +19,28 @@
|
||||
package org.apache.hertzbeat.ai.agent.tools;
|
||||
|
||||
/**
|
||||
* Tools for alert operations
|
||||
* 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();
|
||||
|
||||
|
||||
}
|
||||
|
||||
+29
-1
@@ -19,7 +19,35 @@
|
||||
package org.apache.hertzbeat.ai.agent.tools;
|
||||
|
||||
/**
|
||||
* Tools for metrics operations
|
||||
* 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();
|
||||
|
||||
}
|
||||
|
||||
+26
-15
@@ -27,7 +27,7 @@ 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)
|
||||
@@ -35,35 +35,47 @@ public interface MonitorTools {
|
||||
* @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 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);
|
||||
|
||||
/**
|
||||
* Query monitor information with flexible filtering and pagination.
|
||||
* Supports filtering by monitor IDs, type, status, host, labels, sorting, and
|
||||
* pagination.
|
||||
* Returns results as plain JSON string for AI tool.
|
||||
* 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 listMonitors(
|
||||
String queryMonitors(
|
||||
List<Long> ids,
|
||||
String app,
|
||||
Byte status,
|
||||
@@ -72,15 +84,14 @@ public interface MonitorTools {
|
||||
String sort,
|
||||
String order,
|
||||
Integer pageIndex,
|
||||
Integer pageSize);
|
||||
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 getMonitorParamDefines(String app);
|
||||
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+222
-2
@@ -18,8 +18,228 @@
|
||||
|
||||
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
|
||||
* Implementation of Alert Tools functionality for alarm data queries and management
|
||||
*/
|
||||
public class AlertToolsImpl {
|
||||
@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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+241
-3
@@ -18,8 +18,246 @@
|
||||
|
||||
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
|
||||
* Implementation of Metrics Tools functionality for metrics data queries and analysis
|
||||
*/
|
||||
public class MetricsToolsImpl {
|
||||
}
|
||||
@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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+280
-159
@@ -21,6 +21,7 @@ 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;
|
||||
@@ -34,7 +35,6 @@ import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Implementation of Monitoring Tools functionality
|
||||
@@ -42,7 +42,6 @@ import java.util.stream.Collectors;
|
||||
@Slf4j
|
||||
@Service
|
||||
public class MonitorToolsImpl implements MonitorTools {
|
||||
|
||||
@Autowired
|
||||
private MonitorServiceAdapter monitorServiceAdapter;
|
||||
|
||||
@@ -54,45 +53,132 @@ public class MonitorToolsImpl implements MonitorTools {
|
||||
* Returns detailed monitor information including ID, name, type, host, and status.
|
||||
*/
|
||||
@Override
|
||||
@Tool(name = "list_monitors", description = """
|
||||
Query monitor information with flexible filtering and pagination.
|
||||
Supports filtering by monitor IDs, type, status, host, labels, sorting, and pagination.
|
||||
Returns detailed results including monitor ID, name, type, host, and status for easy identification and management.
|
||||
Show the long monitor id in the brackets
|
||||
When no parameters are available, pass the default value as mentioned below. If the user doesn't provide any specific parameter, the default value will be used.
|
||||
@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 listMonitors(
|
||||
@ToolParam(description = "List of monitor IDs to filter (default: empty list)", required = false) List<Long> ids,
|
||||
@ToolParam(description = "Monitor type, e.g., 'linux' (default: null)", required = false) String app,
|
||||
@ToolParam(description = "Monitor status (0: no monitor, 1: usable, 2: disabled, 9: all) (default: null)", required = false) Byte status,
|
||||
@ToolParam(description = "Fuzzy search for host or name (default: null)", required = false) String search,
|
||||
@ToolParam(description = "Monitor labels, e.g., 'env:prod,instance:22' (default: null)", required = false) String labels,
|
||||
@ToolParam(description = "Sort field, e.g., 'name' (default: gmtCreate)", required = false) String sort,
|
||||
@ToolParam(description = "Sort order, 'asc' or 'desc' (default: desc)", required = false) String order,
|
||||
@ToolParam(description = "Page index (default: 0)", required = false) Integer pageIndex,
|
||||
@ToolParam(description = "Page size (default: 8)", required = false) Integer pageSize) {
|
||||
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);
|
||||
|
||||
// Format response to include both ID and name for better usability
|
||||
|
||||
StringBuilder response = new StringBuilder();
|
||||
response.append("Found ").append(result.getContent().size()).append(" monitors:\n\n");
|
||||
|
||||
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()) {
|
||||
log.info(String.valueOf(monitor.getId()));
|
||||
response.append("ID: ").append(monitor.getId())
|
||||
.append(" | Name: ").append(monitor.getName())
|
||||
.append(" | Type: ").append(monitor.getApp())
|
||||
.append(" | Host: ").append(monitor.getHost())
|
||||
.append(" | Status: ").append(getStatusText(monitor.getStatus()))
|
||||
.append("\n");
|
||||
.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.");
|
||||
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();
|
||||
@@ -102,26 +188,53 @@ public class MonitorToolsImpl implements MonitorTools {
|
||||
|
||||
@Override
|
||||
@Tool(name = "add_monitor", description = """
|
||||
Add a new monitor to HertzBeat with comprehensive configuration.
|
||||
This tool creates a monitor for various types like linux, mysql, http, redis, etc.
|
||||
validate the the monitor type using the list_monitor_types tool.
|
||||
ALWAYS Ask the user to give all the the parameter definitions for the asked monitor type using the get_monitor_param_defines tool.
|
||||
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/application: linux, mysql, http, redis, postgresql, etc.", required = true) String app,
|
||||
@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, depends on monitor type)", required = false) Integer port,
|
||||
@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);
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current subject in add_monitor tool: {}", subjectSum);
|
||||
|
||||
|
||||
// Validate required parameters
|
||||
if (name == null || name.trim().isEmpty()) {
|
||||
return "Error: Monitor name is required";
|
||||
@@ -132,239 +245,247 @@ public class MonitorToolsImpl implements MonitorTools {
|
||||
if (host == null || host.trim().isEmpty()) {
|
||||
return "Error: Host is required";
|
||||
}
|
||||
|
||||
// Set default values
|
||||
|
||||
// Set defaults
|
||||
if (intervals == null || intervals < 10) {
|
||||
intervals = 600; // Default 10 minutes
|
||||
intervals = 600;
|
||||
}
|
||||
|
||||
|
||||
// Create Monitor entity
|
||||
Monitor monitor = Monitor.builder()
|
||||
.name(name.trim())
|
||||
.app(app.toLowerCase().trim())
|
||||
.host(host.trim())
|
||||
.intervals(intervals)
|
||||
.status((byte) 1) // Status: Up
|
||||
.type((byte) 0) // Type: Normal
|
||||
.status((byte) 1)
|
||||
.type((byte) 0)
|
||||
.description(description != null ? description.trim() : "")
|
||||
.build();
|
||||
|
||||
// Create parameters list
|
||||
List<Param> params = new ArrayList<>();
|
||||
|
||||
// Add host parameter (always required)
|
||||
params.add(Param.builder()
|
||||
.field("host")
|
||||
.paramValue(host.trim())
|
||||
.type((byte) 0)
|
||||
.build());
|
||||
|
||||
// Add port parameter if provided
|
||||
if (port != null && port > 0) {
|
||||
params.add(Param.builder()
|
||||
.field("port")
|
||||
.paramValue(port.toString())
|
||||
.type((byte) 0)
|
||||
.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());
|
||||
}
|
||||
|
||||
// Add authentication parameters if provided
|
||||
if (username != null && !username.trim().isEmpty()) {
|
||||
params.add(Param.builder()
|
||||
.field("username")
|
||||
.paramValue(username.trim())
|
||||
.type((byte) 1) // Type: Password
|
||||
.build());
|
||||
}
|
||||
|
||||
if (password != null && !password.trim().isEmpty()) {
|
||||
params.add(Param.builder()
|
||||
.field("password")
|
||||
.paramValue(password.trim())
|
||||
.type((byte) 1) // Type: Password
|
||||
.build());
|
||||
}
|
||||
|
||||
// Add timeout parameter (default)
|
||||
params.add(Param.builder()
|
||||
.field("timeout")
|
||||
.paramValue("6000")
|
||||
.type((byte) 0)
|
||||
.build());
|
||||
|
||||
// Call the adapter to add the monitor
|
||||
|
||||
// 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 monitor '%s' with ID: %d. Monitor type: %s, Host: %s, Interval: %d seconds",
|
||||
name, monitorId, app, host, intervals);
|
||||
|
||||
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 HerzBeat.
|
||||
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())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
.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");
|
||||
.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.");
|
||||
response.append("\nExample: add_monitor(name='my-server', app='linux', host='192.168.1.100')");
|
||||
|
||||
log.info("Successfully listed {} monitor types", monitorTypes.size());
|
||||
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_param_defines", description = """
|
||||
@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,
|
||||
including field names, data types, validation rules, and whether they are required.
|
||||
Use this before adding a monitor to understand what parameters you need to provide.
|
||||
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 getMonitorParamDefines(
|
||||
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);
|
||||
+ "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()));
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to convert monitor status byte to readable text
|
||||
* @param status The status byte from monitor
|
||||
* @return Human-readable status text
|
||||
*/
|
||||
private String getStatusText(Byte status) {
|
||||
if (status == null) {
|
||||
return "Unknown";
|
||||
}
|
||||
switch (status) {
|
||||
case 0:
|
||||
return "Paused";
|
||||
case 1:
|
||||
return "Online";
|
||||
case 2:
|
||||
return "Offline";
|
||||
case 3:
|
||||
return "Unreachable";
|
||||
default:
|
||||
return "Unknown (" + status + ")";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+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;
|
||||
}
|
||||
|
||||
}
|
||||
+131
-14
@@ -2,25 +2,47 @@
|
||||
id: ai_agent_chat
|
||||
title: AI Agent Chat User Guide
|
||||
sidebar_label: AI Agent Chat
|
||||
keywords: [AI, Chat, Agent, Monitoring, Assistant, OpenAI]
|
||||
keywords: [AI, Chat, Agent, Monitoring, AI Agent, OpenAI]
|
||||
---
|
||||
|
||||
> HertzBeat AI Agent Chat is an intelligent monitoring assistant that helps you manage monitors, configure alerts, and optimize your infrastructure monitoring through natural language conversation.
|
||||
> HertzBeat AI Agent Chat is an intelligent monitoring AI Agent that helps you manage monitors, configure alerts, and optimize your infrastructure monitoring through natural language conversation.
|
||||
|
||||
## Overview
|
||||
|
||||
The AI Agent Chat feature provides an interactive chat interface where you can:
|
||||
|
||||
- 🔍 List and manage your existing monitors
|
||||
**Monitor Management:**
|
||||
|
||||
- 🔍 Query and filter existing monitors by status, type, host, and labels
|
||||
- ➕ Add new monitors for websites, APIs, databases, and services
|
||||
- 📊 Get detailed information about available monitor types and their parameters
|
||||
- ⚡ Check monitor status and troubleshoot monitoring issues
|
||||
|
||||
**Alert Management:**
|
||||
|
||||
- 🚨 Query active alerts with comprehensive filtering (type, status, search)
|
||||
- 📈 Get alert summary statistics and distribution
|
||||
- 🔔 View both single and grouped alerts
|
||||
- 📋 Analyze alert patterns and trends
|
||||
|
||||
**Metrics Analysis:**
|
||||
|
||||
- 📊 Retrieve real-time metrics data for any monitor
|
||||
- 📈 Access historical metrics with customizable time ranges
|
||||
- 💾 Check warehouse storage system status
|
||||
- 🔍 Query specific metric fields and labels
|
||||
|
||||
**Alert Rule Management:**
|
||||
|
||||
- ⚙️ Configure alert rules and thresholds
|
||||
- 📝 Manage alert definitions for different monitor types
|
||||
- 🎯 Set up custom alerting criteria
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before using the AI Agent Chat, ensure:
|
||||
|
||||
1 **OpenAI Configuration**: Valid OpenAI API key must be configured
|
||||
1 **OpenAI Configuration**: Valid OpenAI API key must be configured. Optionally, you can change the model in `application.yml` under `spring.ai.openai.chat.options.model` (default is `gpt-4.1-nano`).
|
||||
2 **Database Connection**: HertzBeat database must be accessible for monitor operations
|
||||
|
||||
## Configuration
|
||||
@@ -58,7 +80,7 @@ spring:
|
||||
### Accessing the Chat Interface
|
||||
|
||||
1. Navigate to the **AI Agent Chat** section in HertzBeat web interface
|
||||
2. You'll see the HertzBeat Monitoring Assistant welcome screen
|
||||
2. You'll see the HertzBeat Monitoring AI Agent welcome screen
|
||||
3. Start typing your questions in the chat input field
|
||||
|
||||
### Sample Conversations
|
||||
@@ -67,7 +89,7 @@ spring:
|
||||
|
||||
```text
|
||||
You: Add a new HTTP monitor for my website https://example.com
|
||||
Assistant: I'll help you add an HTTP monitor for https://example.com.
|
||||
AI Agent: I'll help you add an HTTP monitor for https://example.com.
|
||||
Let me create that monitor with the following configuration:
|
||||
- Name: example.com Website Monitor
|
||||
- Type: HTTP
|
||||
@@ -83,7 +105,7 @@ Monitor created successfully with ID: 12345
|
||||
|
||||
```text
|
||||
You: Show me all my current monitors
|
||||
Assistant: Here are your current monitors:
|
||||
AI Agent: Here are your current monitors:
|
||||
|
||||
1. **Linux Server** (ID: 1001)
|
||||
- Status: ✅ Online
|
||||
@@ -107,7 +129,7 @@ Would you like details on any specific monitor?
|
||||
|
||||
```text
|
||||
You: What parameters do I need to monitor a PostgreSQL database?
|
||||
Assistant: To monitor a PostgreSQL database, you'll need these parameters:
|
||||
AI Agent: To monitor a PostgreSQL database, you'll need these parameters:
|
||||
|
||||
**Required Parameters:**
|
||||
- **Host**: Database server IP/hostname
|
||||
@@ -124,6 +146,107 @@ Assistant: To monitor a PostgreSQL database, you'll need these parameters:
|
||||
Would you like me to help you add a PostgreSQL monitor?
|
||||
```
|
||||
|
||||
#### Alert Management
|
||||
|
||||
```text
|
||||
You: Show me all critical alerts from the last 24 hours
|
||||
AI Agent: Here are the critical alerts from the last 24 hours:
|
||||
|
||||
🚨 **Active Alerts (3 firing)**
|
||||
1. **High CPU Usage** - Linux Server (ID: 1001)
|
||||
- Status: 🔥 Firing
|
||||
- Triggered: 2 hours ago
|
||||
- Severity: Critical
|
||||
- Message: CPU usage 95% > threshold 90%
|
||||
|
||||
2. **Database Connection Failed** - MySQL Database (ID: 1002)
|
||||
- Status: 🔥 Firing
|
||||
- Triggered: 45 minutes ago
|
||||
- Severity: Critical
|
||||
- Message: Connection timeout after 10 seconds
|
||||
|
||||
3. **Website Response Time** - Website Monitor (ID: 1003)
|
||||
- Status: ⚠️ Warning
|
||||
- Triggered: 15 minutes ago
|
||||
- Severity: Warning
|
||||
- Message: Response time 5.2s > threshold 3s
|
||||
|
||||
**Alert Summary:**
|
||||
- Total alerts: 12
|
||||
- Firing: 3 (2 critical, 1 warning)
|
||||
- Resolved: 9
|
||||
|
||||
Would you like me to help you investigate any specific alert?
|
||||
```
|
||||
|
||||
#### Metrics Analysis
|
||||
|
||||
```text
|
||||
You: Show me the CPU usage for my Linux server over the last 6 hours
|
||||
AI Agent: Here's the CPU usage data for your Linux server (Monitor ID: 1001) over the last 6 hours:
|
||||
|
||||
📊 **CPU Usage Metrics - Last 6 Hours**
|
||||
```
|
||||
|
||||
Time | Usage (%) | Load Avg | Processes
|
||||
14:00-15:00 | 45.2% | 1.2 | 156
|
||||
15:00-16:00 | 67.8% | 2.1 | 178
|
||||
16:00-17:00 | 89.4% | 3.8 | 195
|
||||
17:00-18:00 | 92.1% | 4.2 | 203
|
||||
18:00-19:00 | 94.7% | 4.8 | 210
|
||||
19:00-20:00 | 88.3% | 3.5 | 188
|
||||
|
||||
``` text
|
||||
|
||||
**Analysis:**
|
||||
- Peak usage: 94.7% at 19:00
|
||||
- Average usage: 79.6%
|
||||
- Trend: High usage sustained for 3+ hours
|
||||
- Current status: Decreasing (88.3%)
|
||||
|
||||
**Real-time metrics:**
|
||||
- Current CPU: 76.2%
|
||||
- Memory usage: 82.1%
|
||||
- Disk I/O: Normal
|
||||
|
||||
Would you like me to check historical data for comparison or investigate the high usage cause?
|
||||
```
|
||||
|
||||
#### Alert Rule Configuration
|
||||
|
||||
```text
|
||||
You: Set up an alert rule for when my website response time exceeds 5 seconds
|
||||
AI Agent: I'll help you create an alert rule for website response time monitoring.
|
||||
|
||||
**Alert Rule Configuration:**
|
||||
- **Monitor Type**: HTTP/Website
|
||||
- **Metric**: Response Time
|
||||
- **Condition**: Greater than 5 seconds
|
||||
- **Severity**: Warning
|
||||
- **Evaluation**: Check every 60 seconds
|
||||
- **Notification**: Email + Dashboard
|
||||
|
||||
**Proposed Rule:**
|
||||
```yaml
|
||||
name: "Website Response Time Alert"
|
||||
condition: "response_time > 5000ms"
|
||||
severity: "warning"
|
||||
description: "Alert when website response time exceeds 5 seconds"
|
||||
evaluation_interval: 60s
|
||||
for: 2m # Alert after condition persists for 2 minutes
|
||||
```
|
||||
|
||||
This rule will:
|
||||
|
||||
1. Monitor response time every minute
|
||||
2. Trigger alert if response time > 5s for 2+ minutes
|
||||
3. Send notifications via configured channels
|
||||
4. Show in dashboard with warning status
|
||||
|
||||
Would you like me to apply this configuration or modify any settings?
|
||||
|
||||
``` text
|
||||
|
||||
### Chat Features
|
||||
|
||||
#### Message History
|
||||
@@ -228,12 +351,6 @@ You: Add HTTP monitors for all services in my staging environment:
|
||||
- admin-staging.example.com:3000
|
||||
```
|
||||
|
||||
### Integration Suggestions
|
||||
|
||||
```text
|
||||
You: What's the best way to monitor a microservices architecture with 20+ services?
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
- Requires active internet connection for OpenAI API
|
||||
|
||||
@@ -63,11 +63,32 @@ After saving, reload MCP in Cursor or restart the editor.
|
||||
|
||||
### Tools available
|
||||
|
||||
- **list_monitors**: Query monitor information with flexible filtering and pagination. Supports filtering by monitor IDs, type, status, host, labels, sorting, and pagination.
|
||||
- **add_monitor**: Add a new monitor to HertzBeat with comprehensive configuration. Creates monitors for various types like linux, mysql, http, redis, etc.
|
||||
- **list_monitor_types**: List all available monitor types that can be added to HertzBeat. Shows all supported monitor types with their display names.
|
||||
- **get_monitor_param_defines**: Get the parameter definitions required for a specific monitor type. Shows what parameters are needed when adding a monitor.
|
||||
#### Monitor Management Tools
|
||||
|
||||
- **query_monitors**: Query existing/configured monitors with comprehensive filtering, pagination, and status overview. Supports filtering by IDs, type, status, host, labels, and sorting.
|
||||
- **add_monitor**: Add a new monitoring target to HertzBeat with comprehensive configuration. Handles different parameter requirements for each monitor type.
|
||||
- **list_monitor_types**: List all available monitor types that can be added to HertzBeat. Shows all supported monitor types with their display names.
|
||||
- **get_monitor_additional_params**: Get the parameter definitions required for a specific monitor type. Shows what parameters are needed when adding a monitor.
|
||||
|
||||
#### Metrics Data Tools
|
||||
|
||||
- **query_realtime_metrics**: Get real-time metrics data for a specific monitor. Returns current metrics values including CPU, memory, disk usage, etc.
|
||||
- **get_historical_metrics**: Get historical metrics data for analysis and trending. Returns time-series data for specified metrics over a time range.
|
||||
- **get_warehouse_status**: Check the status of the metrics storage warehouse system. Returns whether the metrics storage is operational and accessible.
|
||||
|
||||
#### Alert Management Tools
|
||||
|
||||
- **query_alerts**: Query alerts with comprehensive filtering and pagination options. Supports filtering by alert type (single/group), status (firing/resolved), search terms, and sorting.
|
||||
- **get_alerts_summary**: Get alerts summary statistics including total counts, status distribution, and priority breakdown across all monitors.
|
||||
|
||||
#### Alert Rule Definition Tools
|
||||
|
||||
- **create_alert_rule**: Create a HertzBeat alert rule based on app hierarchy structure and user requirements. Supports threshold values, field conditions, and comprehensive alert configuration.
|
||||
- **list_alert_rules**: List existing alert rules with filtering options. Shows configured thresholds and alert definitions with search and pagination.
|
||||
- **get_alert_rule_details**: Get detailed information about a specific alert rule. Shows complete threshold configuration and rule settings.
|
||||
- **toggle_alert_rule**: Enable or disable an alert rule. Allows activating or deactivating threshold monitoring for specific rules.
|
||||
- **get_apps_metrics_hierarchy**: Get the hierarchical structure of all available apps and their metrics for alert rule creation. Returns structured JSON data with field parameters.
|
||||
- **bind_monitors_to_alert_rule**: Bind monitors to an alert rule. Associates specific monitors with alert rules to enable monitoring and alerting.
|
||||
|
||||
### Notes
|
||||
|
||||
|
||||
@@ -366,23 +366,25 @@ global-footer {
|
||||
border-radius: 16px !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.ant-modal-content {
|
||||
border-radius: 16px !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.ant-modal-header {
|
||||
border-radius: 16px 16px 0 0 !important;
|
||||
margin: 0 !important;
|
||||
padding: 16px !important;
|
||||
}
|
||||
|
||||
.ant-modal-body {
|
||||
border-radius: 0 0 16px 16px !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.ant-modal-close {
|
||||
top: 8px !important;
|
||||
right: 8px !important;
|
||||
|
||||
@@ -88,10 +88,12 @@
|
||||
to:
|
||||
</p>
|
||||
<ul>
|
||||
<li>🔍 List and manage your existing monitors</li>
|
||||
<li>🔍 Query and manage monitors by status, type, or labels</li>
|
||||
<li>➕ Add new monitors for websites, APIs, databases, and services</li>
|
||||
<li>📊 Get detailed information about available monitor types and their parameters</li>
|
||||
<li>⚡ Check monitor status and troubleshoot monitoring issues</li>
|
||||
<li>🚨 View and analyze alerts with comprehensive filtering</li>
|
||||
<li>📊 Access real-time and historical metrics data</li>
|
||||
<li>⚙️ Configure alert rules and thresholds</li>
|
||||
<li>⚡ Troubleshoot performance issues across your infrastructure</li>
|
||||
</ul>
|
||||
<p>Ask me anything about your monitoring setup!</p>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user