Compare commits

...
Author SHA1 Message Date
vinci 8b2d586f61 fix UriComponentsBuilder in PromqlQueryExecutor 2025-04-14 19:55:19 +08:00
vinci 3d14277019 fix UriComponentsBuilder in PromqlQueryExecutor 2025-04-14 19:53:28 +08:00
tomsun28 9700161fdd [doc] add new contributors to wall (#3243)
Signed-off-by: tomsun28 <tomsun28@outlook.com>
2025-04-14 16:40:24 +08:00
f578df1deb [feat] enable label-based filtering and selection for monitoring thresholds (#3223)
Co-authored-by: aias00 <liuhongyu@apache.org>
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-04-14 14:26:52 +08:00
eb58bb76f2 [feature] switch to prometheus online parser & add query datasource (#3215)
Signed-off-by: tomsun28 <tomsun28@outlook.com>
Signed-off-by: leo <1552443053@qq.com>
Co-authored-by: Ceilzcx <1758619238@qq.com>
Co-authored-by: tomsun28 <tomsun28@outlook.com>
Co-authored-by: aias00 <liuhongyu@apache.org>
2025-04-14 14:08:11 +08:00
Cyantyandaias00 066d74629a [improve] Adjust Grafana configuration priority hierarchy and optimize API request exception handling (#3241)
Co-authored-by: aias00 <liuhongyu@apache.org>
2025-04-14 09:53:44 +08:00
tomsun28 9619638cff [doc] add new release 1.7.0 blog (#3237) 2025-04-14 09:45:53 +08:00
Yang Chenandtomsun28 b91f849594 [bugfix] Added jobContentCache.remove(jobId) to cancelAsyncCollectJob (#3240)
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-04-13 20:28:52 +08:00
Logic e1806f856f [doc]update Grafana dashboard setup instructions (#3238) 2025-04-13 19:40:33 +08:00
淞筱 95654e49c5 [doc] add code-style-check zh-ch. (#3236)
Signed-off-by: 淞筱 <105542329+a-little-fool@users.noreply.github.com>
2025-04-12 13:41:40 +08:00
9ceb6e6400 [bugfix] Fix the issue where pendingTimeouts may be incorrect in the HashedWheelTimer. (#3174)
Co-authored-by: aias00 <rokkki@163.com>
Co-authored-by: aias00 <liuhongyu@apache.org>
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-04-12 00:20:10 +08:00
淞筱andtomsun28 64cc15fabb [doc] code style check (#3232)
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-04-12 00:14:21 +08:00
27da850ca4 [doc] add alerting silence doc. (#3229)
Co-authored-by: Jast <shenghang@apache.org>
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-04-12 00:03:24 +08:00
1ad52321ea [Doc] Add Blog (#3224)
Co-authored-by: Administrator <admin@admin.com>
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-04-11 23:50:40 +08:00
Cyanty ff037a6a3a [Doc] Modify the YML configuration parameters for Spring Boot 2.0/3.0 monitoring (#3231) 2025-04-10 20:21:11 +08:00
Logic 433f09a4e3 [feature]A preliminary logging module implementation (#3218) 2025-04-10 15:21:48 +08:00
Jast 706baffd5e [Doc] Fix dead link (#3227) 2025-04-09 23:43:19 +08:00
Logic 0b6fb353f1 [feature]add support for Dahua and Uniview devices (#3214) 2025-04-07 23:05:29 +08:00
0385892674 [feature] add pushgateway to push module (#3204)
Signed-off-by: tomsun28 <tomsun28@outlook.com>
Signed-off-by: leo <1552443053@qq.com>
Co-authored-by: tomsun28 <tomsun28@outlook.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-05 17:28:11 +08:00
Logic 04e2321175 [test](hertzbeat-collector): add unit test for XML response parsing (#3212) 2025-04-05 09:18:52 +08:00
8373f0a247 [test] add test for controller AlertInhibitController (#3183)
Co-authored-by: yunfan24 <yunfan24@outlook.com>
Co-authored-by: aias00 <rokkki@163.com>
Co-authored-by: aias00 <liuhongyu@apache.org>
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-04-03 21:31:06 +08:00
Logic 5ad03687b2 [feature] Implementation of Hikvision camera monitoring and http monitoring xml parsing function (#3211) 2025-04-03 20:07:18 +08:00
tomsun28andaias00 7127159063 [doc] archive version docs (#3207)
Co-authored-by: aias00 <liuhongyu@apache.org>
2025-04-03 14:30:49 +08:00
Nick Guoandtomsun28 51715797ef [improve] optimize DnsCollectTest with status code. (#3209)
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-04-03 14:01:10 +08:00
liutianyouandtomsun28 47a1b13781 [doc] add doc for alarm grouping and alarm inhibit (#3206)
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-04-03 13:33:15 +08:00
d5fc492b98 [feature] support plc s7 protocol for siemens (#3194)
Co-authored-by: cuijianzhong <shangyuancjz@126.com>
Co-authored-by: tomsun28 <tomsun28@outlook.com>
Co-authored-by: aias00 <rokkki@163.com>
Co-authored-by: aias00 <liuhongyu@apache.org>
2025-04-02 23:09:32 +08:00
bbc30cd3db [improve] i18n Portuguese (#3193)
Co-authored-by: aias00 <rokkki@163.com>
Co-authored-by: aias00 <liuhongyu@apache.org>
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-04-02 22:46:33 +08:00
6e50d1f3a7 [improve] support the use of time expressions in HTTP payloads (#3192)
Co-authored-by: aias00 <rokkki@163.com>
Co-authored-by: aias00 <liuhongyu@apache.org>
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-04-02 22:32:18 +08:00
b18412254e [improve] optimize kafka collection logic and expand test coverage (#3189)
Co-authored-by: yunfan24 <yunfan24@outlook.com>
Co-authored-by: aias00 <rokkki@163.com>
Co-authored-by: aias00 <liuhongyu@apache.org>
2025-04-02 22:26:37 +08:00
Yang Chenandaias00 fc0ef7773c [bugfix] fix bulletin indicator selection status error (#3201)
Co-authored-by: aias00 <liuhongyu@apache.org>
2025-04-02 15:48:33 +08:00
aias00 adf71441f8 update download info (#3203) 2025-04-02 15:44:42 +08:00
tomsun28 e36842420f [improve] update monitor list ui (#3200)
Signed-off-by: tomsun28 <tomsun28@outlook.com>
2025-03-31 19:27:22 +08:00
tomsun28andaias00 a2c1123b09 [doc] update new hertzbeat ppmc (#3191)
Co-authored-by: aias00 <rokkki@163.com>
2025-03-31 14:52:37 +08:00
tomsun28 b4151a4e73 [improve] update monitor detail ui (#3199)
Signed-off-by: tomsun28 <tomsun28@outlook.com>
2025-03-31 14:33:29 +08:00
jujin 1c1683d9c6 [doc] add blog bout How Does Metrics Collection Work (#3195) 2025-03-30 14:08:15 +08:00
46d4fc2dd9 [bugfix] Modify inconsistent icons (#3190)
Co-authored-by: yinyijun <yingey2011>
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-03-29 10:38:07 +08:00
Sarthak Aroraandtomsun28 ffa2487587 [docs] add collector user guide (#3187)
Signed-off-by: tomsun28 <tomsun28@outlook.com>
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-03-29 09:13:06 +08:00
30c7b22c4c [doc] add help documentation for Alarms Center (#3181)
Co-authored-by: yunfan24 <yunfan24@outlook.com>
Co-authored-by: aias00 <rokkki@163.com>
2025-03-29 00:06:40 +08:00
彭镜肇andtomsun28 7a3e23ec59 [improve] update import for package import (#3180)
Co-authored-by: tomsun28 <tomsun28@outlook.com>
2025-03-28 00:09:45 +08:00
Jast da6556641a [Doc] Modify the error records in the document (#3178) 2025-03-27 23:45:02 +08:00
Rickandrick b008f16b67 fix: Ai is a typo (#3176)
Co-authored-by: rick <LinuxSuRen@users.noreply.github.com>
2025-03-26 12:59:17 +08:00
555 changed files with 19392 additions and 6321 deletions
+36
View File
@@ -2361,6 +2361,42 @@
"contributions": [
"code"
]
},
{
"login": "PengJingzhao",
"name": "彭镜肇",
"avatar_url": "https://avatars.githubusercontent.com/u/97368949?v=4",
"profile": "https://github.com/PengJingzhao",
"contributions": [
"code"
]
},
{
"login": "gagaradio",
"name": "Walter Jia",
"avatar_url": "https://avatars.githubusercontent.com/u/18532370?v=4",
"profile": "https://github.com/gagaradio",
"contributions": [
"code"
]
},
{
"login": "boyucjz",
"name": "boyucjz",
"avatar_url": "https://avatars.githubusercontent.com/u/18730041?v=4",
"profile": "https://github.com/boyucjz",
"contributions": [
"code"
]
},
{
"login": "Cyanty",
"name": "Cyanty",
"avatar_url": "https://avatars.githubusercontent.com/u/153884653?v=4",
"profile": "https://github.com/Cyanty",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,
+4
View File
@@ -516,6 +516,10 @@ Thanks to these wonderful people, welcome to join us:
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sarthakeash"><img src="https://avatars.githubusercontent.com/u/74091160?v=4?s=100" width="100px;" alt="Sarthak Arora"/><br /><sub><b>Sarthak Arora</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=sarthakeash" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/PengJingzhao"><img src="https://avatars.githubusercontent.com/u/97368949?v=4?s=100" width="100px;" alt="彭镜肇"/><br /><sub><b>彭镜肇</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=PengJingzhao" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gagaradio"><img src="https://avatars.githubusercontent.com/u/18532370?v=4?s=100" width="100px;" alt="Walter Jia"/><br /><sub><b>Walter Jia</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=gagaradio" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/boyucjz"><img src="https://avatars.githubusercontent.com/u/18730041?v=4?s=100" width="100px;" alt="boyucjz"/><br /><sub><b>boyucjz</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=boyucjz" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Cyanty"><img src="https://avatars.githubusercontent.com/u/153884653?v=4?s=100" width="100px;" alt="Cyanty"/><br /><sub><b>Cyanty</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Cyanty" title="Code">💻</a></td>
</tr>
</tbody>
</table>
+4
View File
@@ -515,6 +515,10 @@ Thanks these wonderful people, welcome to join us:
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sarthakeash"><img src="https://avatars.githubusercontent.com/u/74091160?v=4?s=100" width="100px;" alt="Sarthak Arora"/><br /><sub><b>Sarthak Arora</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=sarthakeash" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/PengJingzhao"><img src="https://avatars.githubusercontent.com/u/97368949?v=4?s=100" width="100px;" alt="彭镜肇"/><br /><sub><b>彭镜肇</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=PengJingzhao" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gagaradio"><img src="https://avatars.githubusercontent.com/u/18532370?v=4?s=100" width="100px;" alt="Walter Jia"/><br /><sub><b>Walter Jia</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=gagaradio" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/boyucjz"><img src="https://avatars.githubusercontent.com/u/18730041?v=4?s=100" width="100px;" alt="boyucjz"/><br /><sub><b>boyucjz</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=boyucjz" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Cyanty"><img src="https://avatars.githubusercontent.com/u/153884653?v=4?s=100" width="100px;" alt="Cyanty"/><br /><sub><b>Cyanty</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Cyanty" title="Code">💻</a></td>
</tr>
</tbody>
</table>
@@ -17,11 +17,14 @@
package org.apache.hertzbeat.alert.calculate;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -62,12 +65,14 @@ public class RealTimeAlertCalculator {
private static final String KEY_PRIORITY = "__priority__";
private static final String KEY_CODE = "__code__";
private static final String KEY_AVAILABLE = "__available__";
private static final String KEY_LABELS = "__labels__";
private static final String UP = "up";
private static final String DOWN = "down";
private static final String KEY_ROW = "__row__";
private static final Pattern APP_PATTERN = Pattern.compile("equals\\(__app__,\"([^\"]+)\"\\)");
private static final Pattern AVAILABLE_PATTERN = Pattern.compile("equals\\(__available__,\"([^\"]+)\"\\)");
private static final Pattern LABEL_PATTERN = Pattern.compile("contains\\(__labels__,\\s*\"([^\"]+)\"\\)");
private static final Pattern INSTANCE_PATTERN = Pattern.compile("equals\\(__instance__,\"(\\d+)\"\\)");
private static final Pattern METRICS_PATTERN = Pattern.compile("equals\\(__metrics__,\"([^\"]+)\"\\)");
@@ -136,8 +141,8 @@ public class RealTimeAlertCalculator {
Map<String, String> labels = metricsData.getLabels();
Map<String, String> annotations = metricsData.getAnnotations();
List<AlertDefine> thresholds = this.alertDefineService.getRealTimeAlertDefines();
// Filter thresholds by app, metrics and instance
thresholds = filterThresholdsByAppAndMetrics(thresholds, app, metrics, instance, priority);
// Filter thresholds by app, metrics, labels and instance
thresholds = filterThresholdsByAppAndMetrics(thresholds, app, metrics, labels, instance, priority);
if (thresholds.isEmpty()) {
return;
}
@@ -149,6 +154,7 @@ public class RealTimeAlertCalculator {
commonContext.put(KEY_PRIORITY, priority);
commonContext.put(KEY_CODE, code);
commonContext.put(KEY_METRICS, metrics);
commonContext.put(KEY_LABELS, String.join(",", kvLabelsToKvStringSet(labels)));
if (priority == 0) {
commonContext.put(KEY_AVAILABLE, metricsData.getCode() == CollectRep.Code.SUCCESS ? UP : DOWN);
}
@@ -264,7 +270,7 @@ public class RealTimeAlertCalculator {
* @param priority Current priority
* @return Filtered alert definitions
*/
private List<AlertDefine> filterThresholdsByAppAndMetrics(List<AlertDefine> thresholds, String app, String metrics, String instance, int priority) {
private List<AlertDefine> filterThresholdsByAppAndMetrics(List<AlertDefine> thresholds, String app, String metrics, Map<String, String> labels, String instance, int priority) {
return thresholds.stream()
.filter(define -> {
if (StringUtils.isBlank(define.getExpr())) {
@@ -294,19 +300,29 @@ public class RealTimeAlertCalculator {
// Extract and check instance - optional with multiple values
Matcher instanceMatcher = INSTANCE_PATTERN.matcher(expr);
// If no instance specified in expr, accept all instances
if (!instanceMatcher.find()) {
Matcher labelMatcher = LABEL_PATTERN.matcher(expr);
// If no instance and instance labels specified in expr, accept all instances
if (!instanceMatcher.find() && !labelMatcher.find()) {
return true;
}
// Reset matcher to check all instances
instanceMatcher.reset();
labelMatcher.reset();
// If instances specified, current instance must match one of them
while (instanceMatcher.find()) {
if (Objects.equals(instance, instanceMatcher.group(1))) {
return true;
}
}
// If instance labels specified, current instance must match one of them
Set<String> labelKvStringSet = kvLabelsToKvStringSet(labels);
while (labelMatcher.find()) {
String label = labelMatcher.group(1);
if (labelKvStringSet.contains(label)) {
return true;
}
}
return false;
})
.collect(Collectors.toList());
@@ -417,4 +433,13 @@ public class RealTimeAlertCalculator {
return Arrays.hashCode(keyList.toArray(new String[0])) + "-"
+ Arrays.hashCode(valueList.toArray(new String[0]));
}
private Set<String> kvLabelsToKvStringSet(Map<String, String> labels) {
if (labels == null || labels.isEmpty()) {
return Collections.singleton("");
}
return labels.entrySet().stream()
.map(item -> item.getKey() + ":" + item.getValue())
.collect(Collectors.toSet());
}
}
@@ -17,6 +17,7 @@
package org.apache.hertzbeat.alert.service.impl;
import jakarta.xml.bind.DatatypeConverter;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.config.TencentSmsProperties;
import org.apache.hertzbeat.alert.service.SmsClient;
@@ -34,7 +35,6 @@ import org.apache.http.util.EntityUtils;
import org.apache.hertzbeat.common.util.JsonUtil;
import com.fasterxml.jackson.databind.JsonNode;
import javax.xml.bind.DatatypeConverter;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
@@ -0,0 +1,133 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.alert.controller;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
import org.apache.hertzbeat.alert.service.AlertInhibitService;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.alerter.AlertInhibit;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import java.util.HashMap;
/**
* test case for {@link AlertInhibitControllerTest}
*/
@ExtendWith(MockitoExtension.class)
public class AlertInhibitControllerTest {
private MockMvc mockMvc;
@Mock
private AlertInhibitService alertInhibitService;
@InjectMocks
private AlertInhibitController alertInhibitController;
private AlertInhibit alertInhibit;
@BeforeEach
void setUp() {
this.mockMvc = standaloneSetup(alertInhibitController).build();
HashMap<String, String> sourceLabels = new HashMap<>();
HashMap<String, String> targetLabels = new HashMap<>();
alertInhibit = AlertInhibit.builder()
.id(1L)
.name("test")
.sourceLabels(sourceLabels)
.targetLabels(targetLabels)
.creator("test")
.build();
}
@Test
void testAddNewAlertInhibit() throws Exception {
doNothing().when(alertInhibitService).validate(any(AlertInhibit.class), eq(false));
doNothing().when(alertInhibitService).addAlertInhibit(any(AlertInhibit.class));
mockMvc.perform(post("/api/alert/inhibit")
.contentType(MediaType.APPLICATION_JSON)
.content(JsonUtil.toJson(alertInhibit)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andExpect(jsonPath("$.msg").value("Add success"));
}
@Test
void testModifyAlertInhibit() throws Exception {
doNothing().when(alertInhibitService).validate(any(AlertInhibit.class), eq(true));
doNothing().when(alertInhibitService).modifyAlertInhibit(any(AlertInhibit.class));
mockMvc.perform(put("/api/alert/inhibit")
.contentType(MediaType.APPLICATION_JSON)
.content(JsonUtil.toJson(alertInhibit)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andExpect(jsonPath("$.msg").value("Modify success"));
}
@Test
void testGetAlertInhibitExists() throws Exception {
when(alertInhibitService.getAlertInhibit(1L)).thenReturn(alertInhibit);
mockMvc.perform(get("/api/alert/inhibit/{id}", 1L)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.id").value(alertInhibit.getId()));
}
@Test
void testGetAlertInhibitNotExists() throws Exception {
when(alertInhibitService.getAlertInhibit(1L)).thenReturn(null);
mockMvc.perform(get("/api/alert/inhibit/{id}", 1L)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.MONITOR_NOT_EXIST_CODE))
.andExpect(jsonPath("$.msg").value("AlertInhibit not exist."));
}
}
@@ -41,12 +41,12 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import java.util.HashMap;
import org.apache.hertzbeat.alert.service.impl.DataSourceServiceImpl;
import org.apache.hertzbeat.warehouse.db.QueryExecutor;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Map;
import org.mockito.Mockito;
import org.apache.hertzbeat.warehouse.db.QueryExecutor;
/**
* test case for {@link DataSourceService}
@@ -155,6 +155,11 @@
<artifactId>plc4j-driver-modbus</artifactId>
<version>0.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.plc4x</groupId>
<artifactId>plc4j-driver-s7</artifactId>
<version>0.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-sftp</artifactId>
@@ -18,11 +18,14 @@
package org.apache.hertzbeat.collector.collect.http;
import static org.apache.hertzbeat.common.constants.SignConstants.RIGHT_DASH;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.StringReader;
import java.net.ConnectException;
@@ -34,19 +37,22 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.net.ssl.SSLException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.collector.collect.AbstractCollect;
import org.apache.hertzbeat.collector.collect.common.http.CommonHttpClient;
import org.apache.hertzbeat.collector.collect.http.promethus.AbstractPrometheusParse;
import org.apache.hertzbeat.collector.collect.http.promethus.PrometheusParseCreator;
import org.apache.hertzbeat.collector.collect.http.promethus.exporter.ExporterParser;
import org.apache.hertzbeat.collector.collect.http.promethus.exporter.MetricFamily;
import org.apache.hertzbeat.collector.collect.prometheus.parser.MetricFamily;
import org.apache.hertzbeat.collector.collect.prometheus.parser.OnlineParser;
import org.apache.hertzbeat.collector.constants.CollectorConstants;
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
import org.apache.hertzbeat.collector.util.CollectUtil;
@@ -63,11 +69,9 @@ import org.apache.hertzbeat.common.util.CommonUtil;
import org.apache.hertzbeat.common.util.IpDomainUtil;
import org.apache.http.Header;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpHost;
import org.apache.http.HttpStatus;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.AuthCache;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.RequestConfig;
@@ -77,8 +81,6 @@ import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.auth.DigestScheme;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
@@ -91,16 +93,25 @@ import org.xml.sax.InputSource;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.HashSet;
import java.util.ArrayList;
import java.util.Collections;
/**
* http https collect
*/
@Slf4j
public class HttpCollectImpl extends AbstractCollect {
private static final Map<Long, ExporterParser> EXPORTER_PARSER_TABLE = new ConcurrentHashMap<>();
private final Set<Integer> defaultSuccessStatusCodes = Stream.of(HttpStatus.SC_OK, HttpStatus.SC_CREATED,
HttpStatus.SC_ACCEPTED, HttpStatus.SC_MULTIPLE_CHOICES, HttpStatus.SC_MOVED_PERMANENTLY,
HttpStatus.SC_MOVED_TEMPORARILY).collect(Collectors.toSet());
private final Set<Integer> defaultSuccessStatusCodes = Set.of(
HttpStatus.SC_OK,
HttpStatus.SC_CREATED,
HttpStatus.SC_ACCEPTED,
HttpStatus.SC_MULTIPLE_CHOICES,
HttpStatus.SC_MOVED_PERMANENTLY,
HttpStatus.SC_MOVED_TEMPORARILY);
@Override
public void preCheck(Metrics metrics) throws IllegalArgumentException {
@@ -133,10 +144,11 @@ public class HttpCollectImpl extends AbstractCollect {
builder.setMsg(NetworkConstants.STATUS_CODE + SignConstants.BLANK + statusCode);
return;
}
// todo This code converts an InputStream directly to a String. For large data in Prometheus exporters,
// this could create large objects, potentially impacting JVM memory space significantly.
// Option 1: Parse using InputStream, but this requires significant code changes;
// Option 2: Manually trigger garbage collection, similar to how it's done in Dubbo for large inputs.
/*
this could create large objects, potentially impacting JVM memory space significantly.
Option 1: Parse using InputStream, but this requires significant code changes;
Option 2: Manually trigger garbage collection, similar to how it's done in Dubbo for large inputs.
*/
String resp = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
if (!StringUtils.hasText(resp)) {
log.info("http response entity is empty, status: {}.", statusCode);
@@ -150,15 +162,17 @@ public class HttpCollectImpl extends AbstractCollect {
case DispatchConstants.PARSE_PROM_QL ->
parseResponseByPromQl(resp, metrics.getAliasFields(), metrics.getHttp(), builder);
case DispatchConstants.PARSE_PROMETHEUS ->
parseResponseByPrometheusExporter(resp, metrics.getAliasFields(), builder);
parseResponseByPrometheusExporter(response.getEntity().getContent(), metrics.getAliasFields(), builder);
case DispatchConstants.PARSE_XML_PATH ->
parseResponseByXmlPath(resp, metrics.getAliasFields(), metrics.getHttp(), builder);
parseResponseByXmlPath(resp, metrics, builder, responseTime);
case DispatchConstants.PARSE_WEBSITE ->
parseResponseByWebsite(resp, metrics, metrics.getHttp(), builder, responseTime);
case DispatchConstants.PARSE_SITE_MAP ->
parseResponseBySiteMap(resp, metrics.getAliasFields(), builder);
case DispatchConstants.PARSE_HEADER ->
parseResponseByHeader(builder, metrics.getAliasFields(), response);
case DispatchConstants.PARSE_CONFIG ->
parseResponseByConfig(resp, metrics.getAliasFields(), metrics.getHttp(), builder, responseTime);
default ->
parseResponseByDefault(resp, metrics.getAliasFields(), metrics.getHttp(), builder, responseTime);
}
@@ -329,8 +343,207 @@ public class HttpCollectImpl extends AbstractCollect {
}
}
private void parseResponseByXmlPath(String resp, List<String> aliasFields, HttpProtocol http,
CollectRep.MetricsData.Builder builder) {
private void parseResponseByXmlPath(String resp, Metrics metrics,
CollectRep.MetricsData.Builder builder, Long responseTime) {
HttpProtocol http = metrics.getHttp();
List<String> aliasFields = metrics.getAliasFields();
String xpathExpression = http.getParseScript();
if (!StringUtils.hasText(xpathExpression)) {
log.warn("Http collect parse type is xmlPath, but the xpath expression is empty.");
builder.setCode(CollectRep.Code.FAIL);
builder.setMsg("XPath expression is empty");
return;
}
int keywordNum = CollectUtil.countMatchKeyword(resp, http.getKeyword());
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
DocumentBuilder db = dbf.newDocumentBuilder();
Document document = db.parse(new InputSource(new StringReader(resp)));
XPathFactory xpathFactory = XPathFactory.newInstance();
XPath xpath = xpathFactory.newXPath();
NodeList nodeList = (NodeList) xpath.evaluate(xpathExpression, document, XPathConstants.NODESET);
if (nodeList == null || nodeList.getLength() == 0) {
log.debug("XPath expression '{}' returned no nodes.", xpathExpression);
boolean requestedSummaryFields = aliasFields.stream()
.anyMatch(alias -> NetworkConstants.RESPONSE_TIME.equalsIgnoreCase(alias)
|| CollectorConstants.KEYWORD.equalsIgnoreCase(alias));
if (requestedSummaryFields) {
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
for (String alias : aliasFields) {
if (NetworkConstants.RESPONSE_TIME.equalsIgnoreCase(alias)) {
valueRowBuilder.addColumn(responseTime.toString());
} else if (CollectorConstants.KEYWORD.equalsIgnoreCase(alias)) {
valueRowBuilder.addColumn(Integer.toString(keywordNum));
} else {
valueRowBuilder.addColumn(CommonConstants.NULL_VALUE);
}
}
builder.addValueRow(valueRowBuilder.build());
}
return;
}
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
for (String alias : aliasFields) {
if (NetworkConstants.RESPONSE_TIME.equalsIgnoreCase(alias)) {
valueRowBuilder.addColumn(responseTime.toString());
} else if (CollectorConstants.KEYWORD.equalsIgnoreCase(alias)) {
valueRowBuilder.addColumn(Integer.toString(keywordNum));
} else {
try {
String value = (String) xpath.evaluate(alias, node, XPathConstants.STRING);
valueRowBuilder.addColumn(StringUtils.hasText(value) ? value : CommonConstants.NULL_VALUE);
} catch (XPathExpressionException e) {
log.warn("Failed to evaluate XPath '{}' for node [{}]: {}", alias, node.getNodeName(), e.getMessage());
valueRowBuilder.addColumn(CommonConstants.NULL_VALUE);
}
}
}
builder.addValueRow(valueRowBuilder.build());
}
} catch (Exception e) {
log.warn("Failed to parse XML response with XPath '{}': {}", xpathExpression, e.getMessage(), e);
builder.setCode(CollectRep.Code.FAIL);
builder.setMsg("Failed to parse XML response: " + e.getMessage());
}
}
/**
* Parses the response body in Properties/Config format.
* Two modes are supported:
* 1. single-object mode: if http.parseScript is null, aliasFields are treated as indicator names.
* - If there is a locator in the indicator definition, use the locator as the key of the Properties.
* - Otherwise, use aliasField (metric name) as the key for Properties.
* Generate a single row of data.
* 2. array mode: if http.parseScript is not empty (e.g. “users”), treat it as an array base path.
* Treat aliasFields as the attribute name of an array element, and generate a single row of data for each array index. locator is invalid in this mode.
*
* @param resp Response body string
* @param aliasFields List of metrics aliases (i.e., the list of fields in metrics.fields).
* @param http http protocol configuration
* @param builder The metrics data builder.
* @param responseTime response time
*/
private void parseResponseByConfig(String resp, List<String> aliasFields, HttpProtocol http,
CollectRep.MetricsData.Builder builder, Long responseTime) {
if (!StringUtils.hasText(resp)) {
log.warn("Http collect parse type is config, but response body is empty.");
builder.setCode(CollectRep.Code.FAIL);
builder.setMsg("Response body is empty");
return;
}
Properties properties = new Properties();
try (StringReader reader = new StringReader(resp)) {
properties.load(reader);
} catch (IOException e) {
log.warn("Failed to parse config response: {}", e.getMessage(), e);
builder.setCode(CollectRep.Code.FAIL);
builder.setMsg("Failed to parse config response: " + e.getMessage());
return;
}
String arrayBasePath = http.getParseScript();
int keywordNum = CollectUtil.countMatchKeyword(resp, http.getKeyword());
if (!StringUtils.hasText(arrayBasePath)) {
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
for (String alias : aliasFields) {
if (NetworkConstants.RESPONSE_TIME.equalsIgnoreCase(alias)) {
valueRowBuilder.addColumn(responseTime.toString());
} else if (CollectorConstants.KEYWORD.equalsIgnoreCase(alias)) {
valueRowBuilder.addColumn(Integer.toString(keywordNum));
} else {
String value = properties.getProperty(alias);
valueRowBuilder.addColumn(value != null ? value : CommonConstants.NULL_VALUE);
}
}
CollectRep.ValueRow valueRow = valueRowBuilder.build();
if (hasMeaningfulDataInRow(valueRow, aliasFields)) {
builder.addValueRow(valueRow);
} else {
log.warn("No meaningful data found in single config object response for aliasFields: {}", aliasFields);
}
} else {
Pattern pattern = Pattern.compile("^" + Pattern.quote(arrayBasePath) + "\\[(\\d+)]\\.");
Set<Integer> existingIndices = new HashSet<>();
for (String key : properties.stringPropertyNames()) {
Matcher matcher = pattern.matcher(key);
if (matcher.find()) {
try {
int index = Integer.parseInt(matcher.group(1));
existingIndices.add(index);
} catch (NumberFormatException e) {
log.error("Could not parse index from key: {}", key);
}
}
}
if (existingIndices.isEmpty()) {
log.warn("Could not find any array elements for base path '{}' in config response.", arrayBasePath);
return;
}
List<Integer> sortedIndices = new ArrayList<>(existingIndices);
Collections.sort(sortedIndices);
for (int i : sortedIndices) {
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
for (String alias : aliasFields) {
if (NetworkConstants.RESPONSE_TIME.equalsIgnoreCase(alias)) {
valueRowBuilder.addColumn(responseTime.toString());
} else if (CollectorConstants.KEYWORD.equalsIgnoreCase(alias)) {
valueRowBuilder.addColumn(Integer.toString(keywordNum));
} else {
String currentKey = arrayBasePath + "[" + i + "]." + alias;
String value = properties.getProperty(currentKey);
valueRowBuilder.addColumn(value != null ? value : CommonConstants.NULL_VALUE);
}
}
CollectRep.ValueRow valueRow = valueRowBuilder.build();
if (hasMeaningfulDataInRow(valueRow, aliasFields)) {
builder.addValueRow(valueRowBuilder.build());
}
}
}
}
private boolean hasMeaningfulDataInRow(CollectRep.ValueRow valueRow, List<String> aliasFields) {
if (valueRow.getColumnsCount() == 0) {
return false;
}
if (valueRow.getColumnsCount() != aliasFields.size()) {
log.error("Column count ({}) mismatch with aliasFields size ({}) when checking meaningful data.",
valueRow.getColumnsCount(), aliasFields.size());
return false;
}
boolean hasMeaningfulData = false;
for (int i = 0; i < valueRow.getColumnsCount(); i++) {
String columnValue = valueRow.getColumns(i);
String alias = aliasFields.get(i);
if (!CommonConstants.NULL_VALUE.equals(columnValue) && (!NetworkConstants.RESPONSE_TIME.equalsIgnoreCase(alias) && !CollectorConstants.KEYWORD.equalsIgnoreCase(alias))) {
hasMeaningfulData = true;
break;
}
if ((NetworkConstants.RESPONSE_TIME.equalsIgnoreCase(alias) || CollectorConstants.KEYWORD.equalsIgnoreCase(alias)) && !CommonConstants.NULL_VALUE.equals(columnValue)) {
hasMeaningfulData = true;
}
}
return hasMeaningfulData;
}
private void parseResponseByJsonPath(String resp, List<String> aliasFields, HttpProtocol http,
@@ -387,36 +600,22 @@ public class HttpCollectImpl extends AbstractCollect {
prometheusParser.handle(resp, aliasFields, http, builder);
}
private void parseResponseByPrometheusExporter(String resp, List<String> aliasFields,
CollectRep.MetricsData.Builder builder) {
if (!EXPORTER_PARSER_TABLE.containsKey(builder.getId())) {
EXPORTER_PARSER_TABLE.put(builder.getId(), new ExporterParser());
private void parseResponseByPrometheusExporter(InputStream content, List<String> aliasFields, CollectRep.MetricsData.Builder builder) throws IOException {
Map<String, MetricFamily> metricFamilyMap = OnlineParser.parseMetrics(content);
if (metricFamilyMap == null || metricFamilyMap.isEmpty()) {
return;
}
ExporterParser parser = EXPORTER_PARSER_TABLE.get(builder.getId());
Map<String, MetricFamily> metricFamilyMap = parser.textToMetric(resp);
String metrics = builder.getMetrics();
if (metricFamilyMap.containsKey(metrics)) {
MetricFamily metricFamily = metricFamilyMap.get(metrics);
for (MetricFamily.Metric metric : metricFamily.getMetricList()) {
Map<String, String> labelMap = metric.getLabelPair()
Map<String, String> labelMap = metric.getLabels()
.stream()
.collect(Collectors.toMap(MetricFamily.Label::getName, MetricFamily.Label::getValue));
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
for (String aliasField : aliasFields) {
if ("value".equals(aliasField)) {
if (metric.getCounter() != null) {
valueRowBuilder.addColumn(String.valueOf(metric.getCounter().getValue()));
} else if (metric.getGauge() != null) {
valueRowBuilder.addColumn(String.valueOf(metric.getGauge().getValue()));
} else if (metric.getUntyped() != null) {
valueRowBuilder.addColumn(String.valueOf(metric.getUntyped().getValue()));
} else if (metric.getInfo() != null) {
valueRowBuilder.addColumn(String.valueOf(metric.getInfo().getValue()));
} else if (metric.getSummary() != null) {
valueRowBuilder.addColumn(String.valueOf(metric.getSummary().getValue()));
} else if (metric.getHistogram() != null) {
valueRowBuilder.addColumn(String.valueOf(metric.getHistogram().getValue()));
}
valueRowBuilder.addColumn(String.valueOf(metric.getValue()));
} else {
String columnValue = labelMap.get(aliasField);
valueRowBuilder.addColumn(columnValue == null ? CommonConstants.NULL_VALUE : columnValue);
@@ -473,11 +672,10 @@ public class HttpCollectImpl extends AbstractCollect {
CredentialsProvider provider = new BasicCredentialsProvider();
UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(auth.getDigestAuthUsername(),
auth.getDigestAuthPassword());
provider.setCredentials(AuthScope.ANY, credentials);
AuthCache authCache = new BasicAuthCache();
authCache.put(new HttpHost(httpProtocol.getHost(), Integer.parseInt(httpProtocol.getPort())), new DigestScheme());
AuthScope authScope = new AuthScope(httpProtocol.getHost(), Integer.parseInt(httpProtocol.getPort()));
provider.setCredentials(authScope, credentials);
clientContext.setCredentialsProvider(provider);
clientContext.setAuthCache(authCache);
return clientContext;
}
}
@@ -512,7 +710,7 @@ public class HttpCollectImpl extends AbstractCollect {
Map<String, String> params = httpProtocol.getParams();
boolean enableUrlEncoding = Boolean.parseBoolean(httpProtocol.getEnableUrlEncoding());
StringBuilder queryParams = new StringBuilder();
if (params != null && !params.isEmpty()) {
for (Map.Entry<String, String> param : params.entrySet()) {
String key = param.getKey();
@@ -522,7 +720,7 @@ public class HttpCollectImpl extends AbstractCollect {
continue;
}
if (queryParams.length() > 0) {
if (!queryParams.isEmpty()) {
queryParams.append("&");
}
@@ -540,7 +738,7 @@ public class HttpCollectImpl extends AbstractCollect {
}
}
}
// The default request header can be overridden if customized
// keep-alive
requestBuilder.addHeader(HttpHeaders.CONNECTION, NetworkConstants.KEEP_ALIVE);
@@ -581,7 +779,7 @@ public class HttpCollectImpl extends AbstractCollect {
// if it has payload, would override post params
if (StringUtils.hasLength(httpProtocol.getPayload()) && (HttpMethod.POST.matches(httpMethod) || HttpMethod.PUT.matches(httpMethod))) {
requestBuilder.setEntity(new StringEntity(httpProtocol.getPayload(), StandardCharsets.UTF_8));
requestBuilder.setEntity(new StringEntity(TimeExpressionUtil.calculate(httpProtocol.getPayload()), StandardCharsets.UTF_8));
}
// uri encode
@@ -600,10 +798,10 @@ public class HttpCollectImpl extends AbstractCollect {
}
// append query params
if (queryParams.length() > 0) {
uri += (uri.contains("?") ? "&" : "?") + queryParams.toString();
if (!queryParams.isEmpty()) {
uri += (uri.contains("?") ? "&" : "?") + queryParams;
}
String finalUri;
if (IpDomainUtil.isHasSchema(httpProtocol.getHost())) {
finalUri = httpProtocol.getHost() + ":" + httpProtocol.getPort() + uri;
@@ -619,7 +817,7 @@ public class HttpCollectImpl extends AbstractCollect {
finalUri = NetworkConstants.HTTP_HEADER + baseUri;
}
}
try {
requestBuilder.setUri(finalUri);
} catch (IllegalArgumentException e) {
@@ -1,432 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.collector.collect.http.promethus.exporter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.collector.collect.http.promethus.ParseException;
import org.apache.hertzbeat.common.util.StrBuffer;
/**
* Resolves the data passed by prometheus's exporter interface http:xxx/metrics
* Reference: prometheus text_parse.go code, entry: TextToMetricFamilies
*/
@Slf4j
public class ExporterParser {
private static final String HELP = "HELP";
private static final String TYPE = "TYPE";
private static final String EOF = "EOF";
private static final String METRIC_NAME_LABEL = ".name";
private static final String QUANTILE_LABEL = "quantile";
private static final String BUCKET_LABEL = "le";
private static final String NAME_LABEL = "__name__";
private static final String SUM_SUFFIX = "_sum";
private static final String COUNT_SUFFIX = "_count";
private static final char LEFT_CURLY_BRACKET = '{';
private static final char RIGHT_CURLY_BRACKET = '}';
private static final char EQUALS = '=';
private static final char QUOTES = '"';
private static final char ENTER = '\n';
private static final char SPACE = ' ';
private static final char COMMA = ',';
private final Lock lock = new ReentrantLock();
private MetricFamily currentMetricFamily;
private String currentQuantile;
private String currentBucket;
public Map<String, MetricFamily> textToMetric(String resp) {
// key: metric name, value: metric family
Map<String, MetricFamily> metricMap = new ConcurrentHashMap<>(10);
lock.lock();
try {
String[] lines = resp.split("\n");
for (String line : lines) {
this.parseLine(metricMap, new StrBuffer(line));
}
return metricMap;
} catch (Exception e) {
log.error("parse prometheus exporter data error, msg: {}", e.getMessage());
} finally {
lock.unlock();
}
return metricMap;
}
private void parseLine(Map<String, MetricFamily> metricMap, StrBuffer buffer) {
buffer.skipBlankTabs();
if (buffer.isEmpty()) {
return;
}
switch (buffer.charAt(0)) {
case '#' -> {
buffer.read();
this.currentMetricFamily = null;
this.parseComment(metricMap, buffer);
}
case ENTER -> {
}
default -> {
this.currentBucket = null;
this.currentQuantile = null;
this.parseMetric(buffer);
}
}
}
private void parseComment(Map<String, MetricFamily> metricMap, StrBuffer buffer) {
buffer.skipBlankTabs();
if (buffer.isEmpty()) {
return;
}
String token = this.readTokenUnitWhitespace(buffer);
if (EOF.equals(token)) {
return;
}
if (!HELP.equals(token) && !TYPE.equals(token)) {
log.error("parse comment error {}, start without {} or {}", buffer.toStr(), HELP, TYPE);
return;
}
String metricName = this.readTokenAsMetricName(buffer);
this.currentMetricFamily = metricMap.computeIfAbsent(metricName, key -> new MetricFamily());
this.currentMetricFamily.setName(metricName);
switch (token) {
case HELP -> this.parseHelp(buffer);
case TYPE -> this.parseType(buffer);
default -> {
}
}
}
private void parseHelp(StrBuffer line) {
line.skipBlankTabs();
this.currentMetricFamily.setHelp(line.toStr());
}
private void parseType(StrBuffer line) {
line.skipBlankTabs();
String type = line.toStr().toLowerCase();
MetricType metricType = MetricType.getType(type);
if (metricType == null) {
throw new ParseException("pare type error");
}
this.currentMetricFamily.setMetricType(metricType);
}
private void parseMetric(StrBuffer buffer) {
String metricName = this.readTokenAsMetricName(buffer);
MetricFamily.Label label = new MetricFamily.Label();
label.setName(METRIC_NAME_LABEL);
label.setValue(metricName);
if (metricName.isEmpty()) {
log.error("error parse metric, metric name is null, line: {}", buffer.toStr());
return;
}
List<MetricFamily.Metric> metricList = this.currentMetricFamily.getMetricList();
if (metricList == null) {
metricList = new ArrayList<>();
this.currentMetricFamily.setMetricList(metricList);
}
// TODO For the time being, the data is displayed in the form of labels. If there is a better chart display method in the future, we will optimize it.
MetricFamily.Metric metric = new MetricFamily.Metric();
metricList.add(metric);
metric.setLabelPair(new ArrayList<>());
metric.getLabelPair().add(label);
this.readLabels(metric, buffer);
}
private void readLabels(MetricFamily.Metric metric, StrBuffer buffer) {
buffer.skipBlankTabs();
if (buffer.isEmpty()) {
return;
}
if (buffer.charAt(0) == LEFT_CURLY_BRACKET) {
buffer.read();
this.startReadLabelName(metric, buffer);
} else {
this.readLabelValue(metric, null, buffer);
}
}
private void startReadLabelName(MetricFamily.Metric metric, StrBuffer buffer) {
buffer.skipBlankTabs();
if (buffer.isEmpty()) {
return;
}
if (buffer.charAt(0) == RIGHT_CURLY_BRACKET) {
buffer.read();
buffer.skipBlankTabs();
if (buffer.isEmpty()) {
return;
}
this.readLabelValue(metric, new MetricFamily.Label(), buffer);
return;
}
String labelName = this.readTokenAsLabelName(buffer);
if (labelName.isEmpty() || labelName.equals(NAME_LABEL)) {
throw new ParseException("invalid label name" + labelName + ", label name size = 0 or label name equals " + NAME_LABEL);
}
MetricFamily.Label label = new MetricFamily.Label();
label.setName(labelName);
if (buffer.read() != EQUALS) {
throw new ParseException("parse error, not match the format of labelName=labelValue");
}
this.startReadLabelValue(metric, label, buffer);
}
private void startReadLabelValue(MetricFamily.Metric metric, MetricFamily.Label label, StrBuffer buffer) {
buffer.skipBlankTabs();
if (buffer.isEmpty()) {
return;
}
char c = buffer.read();
if (c != QUOTES) {
throw new ParseException("expected '\"' at start of label value, line: " + buffer.toStr());
}
String labelValue = this.readTokenAsLabelValue(buffer);
label.setValue(labelValue);
if (!this.isValidLabelValue(labelValue)) {
throw new ParseException("no valid label value: " + labelValue);
}
if (this.currentMetricFamily.getMetricType().equals(MetricType.SUMMARY) && label.getName().equals(QUANTILE_LABEL)) {
this.currentQuantile = labelValue;
} else if (this.currentMetricFamily.getMetricType().equals(MetricType.HISTOGRAM) && label.getName().equals(BUCKET_LABEL)) {
this.currentBucket = labelValue;
}
metric.getLabelPair().add(label);
if (buffer.isEmpty()) {
return;
}
c = buffer.read();
switch (c) {
case COMMA -> this.startReadLabelName(metric, buffer);
case RIGHT_CURLY_BRACKET -> this.readLabelValue(metric, label, buffer);
default -> throw new ParseException("expected '}' or ',' at end of label value, line: " + buffer.toStr());
}
}
private void readLabelValue(MetricFamily.Metric metric, MetricFamily.Label label, StrBuffer buffer) {
buffer.skipBlankTabs();
if (buffer.isEmpty()) {
return;
}
switch (this.currentMetricFamily.getMetricType()) {
case INFO -> {
MetricFamily.Info info = new MetricFamily.Info();
info.setValue(buffer.toDouble());
metric.setInfo(info);
}
case COUNTER -> {
MetricFamily.Counter counter = new MetricFamily.Counter();
counter.setValue(buffer.toDouble());
metric.setCounter(counter);
}
case GAUGE -> {
MetricFamily.Gauge gauge = new MetricFamily.Gauge();
gauge.setValue(buffer.toDouble());
metric.setGauge(gauge);
}
case UNTYPED -> {
MetricFamily.Untyped untyped = new MetricFamily.Untyped();
untyped.setValue(buffer.toDouble());
metric.setUntyped(untyped);
}
case SUMMARY -> {
// For the time being, the data is displayed in the form of labels. If there is a better chart display method in the future, we will optimize it.
MetricFamily.Summary summary = new MetricFamily.Summary();
summary.setValue(buffer.toDouble());
metric.setSummary(summary);
}
case HISTOGRAM -> {
// For the time being, the data is displayed in the form of labels. If there is a better chart display method in the future, we will optimize it.
MetricFamily.Histogram histogram = new MetricFamily.Histogram();
histogram.setValue(buffer.toDouble());
metric.setHistogram(histogram);
}
default -> throw new ParseException("no such type in metricFamily");
}
}
/**
* Reads the token before the first whitespace
*
* @param buffer A line data object
* @return token unit
*/
private String readTokenUnitWhitespace(StrBuffer buffer) {
StringBuilder builder = new StringBuilder();
while (!buffer.isEmpty()) {
char c = buffer.read();
if (c == SPACE) {
break;
}
builder.append(c);
}
return builder.toString();
}
/**
* Gets the name of the metric
*
* @param buffer A line data object
* @return token name
*/
private String readTokenAsMetricName(StrBuffer buffer) {
buffer.skipBlankTabs();
StringBuilder builder = new StringBuilder();
if (this.isValidMetricNameStart(buffer.charAt(0))) {
while (!buffer.isEmpty()) {
char c = buffer.read();
if (!this.isValidMetricNameContinuation(c)) {
buffer.rollback();
break;
}
builder.append(c);
}
return builder.toString();
}
throw new ParseException("parse metric name error");
}
/**
* Gets the name of the label
*
* @param buffer A line data object
* @return label name
*/
private String readTokenAsLabelName(StrBuffer buffer) {
buffer.skipBlankTabs();
StringBuilder builder = new StringBuilder();
char c = buffer.read();
if (this.isValidLabelNameStart(c)) {
builder.append(c);
while (!buffer.isEmpty()) {
c = buffer.read();
if (!this.isValidLabelNameContinuation(c)) {
buffer.rollback();
break;
}
builder.append(c);
}
return builder.toString();
}
throw new ParseException("parse label name error");
}
/**
* Gets the value of the label
*
* @param buffer A line data object
* @return label value
*/
private String readTokenAsLabelValue(StrBuffer buffer) {
StringBuilder builder = new StringBuilder();
boolean escaped = false;
while (!buffer.isEmpty()) {
char c = buffer.read();
// Handle '\\' escape sequences
if (escaped) {
switch (c) {
case QUOTES, '\\' -> builder.append(c);
case 'n' -> builder.append('\n');
default -> throw new ParseException("parse label value error");
}
escaped = false;
} else {
switch (c) {
case QUOTES -> {
return builder.toString();
}
case ENTER -> throw new ParseException("parse label value error, next line");
case '\\' -> escaped = true;
default -> builder.append(c);
}
}
}
return builder.toString();
}
/**
* Checks whether a character conforms to the first character rule for metric names
*
* @param c metric character
* @return true/false
*/
private boolean isValidMetricNameStart(char c) {
return isValidLabelNameStart(c) || c == ':';
}
/**
* Checks whether a character conforms to rules for metric name characters other than the first
*
* @param c metric character
* @return true/false
*/
private boolean isValidMetricNameContinuation(char c) {
return isValidLabelNameContinuation(c) || c == ':';
}
/**
* Checks whether a character conforms to the first character rule for label names
*
* @param c metric character
* @return true/false
*/
private boolean isValidLabelNameStart(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
}
/**
* Checks whether a character conforms to rules for label name characters other than the first
*
* @param c metric character
* @return true/false
*/
private boolean isValidLabelNameContinuation(char c) {
return isValidLabelNameStart(c) || (c >= '0' && c <= '9');
}
/**
* Checks if a string is a valid UTF-8 encoded string
*
* @param s label value
* @return true/false
*/
private boolean isValidLabelValue(String s) {
return s != null;
}
private boolean isSum(String s) {
return s != null && s.endsWith(SUM_SUFFIX);
}
private boolean isCount(String s) {
return s != null && s.endsWith(COUNT_SUFFIX);
}
}
@@ -1,252 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.collector.collect.http.promethus.exporter;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
import lombok.ToString;
/**
* MetricFamily.
*/
@Data
@ToString
public class MetricFamily {
/**
* metric name
*/
private String name;
/**
* metric help
*/
private String help;
/**
* metric type
*/
private MetricType metricType;
/**
* Specific metric
*/
private List<Metric> metricList;
/**
* Metric
*/
@Data
public static class Metric {
/**
* Label data, mainly corresponding to the content within {}
*/
private List<Label> labelPair;
/**
* info
*/
private Info info;
/**
* gauge
*/
private Gauge gauge;
/**
* counter
*/
private Counter counter;
/**
* summary
*/
private Summary summary;
/**
* untyped
*/
private Untyped untyped;
/**
* histogram
*/
private Histogram histogram;
/**
* timestampMs
*/
private Long timestampMs;
}
/**
* Label
*/
@Data
public static class Label {
/**
* name
*/
private String name;
/**
* value
*/
private String value;
}
/**
* Info
*/
@Data
public static class Info {
/**
* value
*/
private double value;
}
/**
* Counter
*/
@Data
public static class Counter {
/**
* value
*/
private double value;
// Exemplar
}
/**
* Gauge
*/
@Data
public static class Gauge {
/**
* value
*/
private double value;
}
/**
* untyped
*/
@Data
public static class Untyped {
/**
* value
*/
private double value;
}
/**
* Summary
*/
@Data
public static class Summary {
/**
* value
*/
private double value;
/**
* count
*/
private long count;
/**
* sum
*/
private double sum;
/**
* quantileList
*/
private List<Quantile> quantileList = new ArrayList<>();
}
/**
* Quantile
*/
@Data
public static class Quantile {
/**
* Corresponding to the quantile field in Prometheus
*/
private double xLabel;
/**
* value
*/
private double value;
}
/**
* Histogram
*/
@Data
public static class Histogram {
/**
* value
*/
private double value;
/**
* count
*/
private long count;
/**
* sum
*/
private double sum;
/**
* bucketList
*/
private List<Bucket> bucketList = new ArrayList<>();
}
/**
* Bucket
*/
@Data
public static class Bucket {
/**
* cumulativeCount
*/
private long cumulativeCount;
/**
* upperBound
*/
private double upperBound;
}
}
@@ -1,52 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.collector.collect.http.promethus.exporter;
/**
* prometheus metrics type
*/
public enum MetricType {
// for string metric info
INFO("info"),
// Represents a monotonically increasing counter, e.g., counting occurrences
COUNTER("counter"),
// A metric type that can fluctuate up and down, e.g., CPU usage rate
GAUGE("gauge"),
SUMMARY("summary"),
UNTYPED("untyped"),
HISTOGRAM("histogram");
private final String value;
MetricType(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public static MetricType getType(String value) {
for (MetricType metricType : values()) {
if (metricType.getValue().equals(value)) {
return metricType;
}
}
return null;
}
}
@@ -19,6 +19,7 @@ package org.apache.hertzbeat.collector.collect.prometheus;
import static org.apache.hertzbeat.common.constants.SignConstants.RIGHT_DASH;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.net.ConnectException;
import java.net.UnknownHostException;
@@ -34,7 +35,6 @@ import javax.net.ssl.SSLException;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.collector.collect.common.http.CommonHttpClient;
import org.apache.hertzbeat.collector.collect.prometheus.parser.MetricFamily;
import org.apache.hertzbeat.collector.collect.prometheus.parser.TextParser;
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
import org.apache.hertzbeat.collector.util.CollectUtil;
import org.apache.hertzbeat.common.constants.CommonConstants;
@@ -46,6 +46,7 @@ import org.apache.hertzbeat.common.entity.message.CollectRep;
import org.apache.hertzbeat.common.util.Base64Util;
import org.apache.hertzbeat.common.util.CommonUtil;
import org.apache.hertzbeat.common.util.IpDomainUtil;
import org.apache.hertzbeat.collector.collect.prometheus.parser.OnlineParser;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpHost;
import org.apache.http.HttpStatus;
@@ -64,7 +65,6 @@ import org.apache.http.impl.auth.DigestScheme;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
@@ -99,25 +99,12 @@ public class PrometheusAutoCollectImpl {
builder.setMsg(NetworkConstants.STATUS_CODE + SignConstants.BLANK + statusCode);
return null;
}
// todo: The InputStream is directly converted to a String here
// For large data in the Prometheus exporter, this can generate large objects, which could severely impact JVM memory space
// todo: Option one: Use InputStream for parsing, but this requires significant code changes
// Option two: Manually trigger garbage collection, which can be referenced from Dubbo for long i
String resp = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
long collectTime = System.currentTimeMillis();
builder.setTime(collectTime);
if (resp == null || !StringUtils.hasText(resp)) {
log.error("http response content is empty, status: {}.", statusCode);
try {
return parseResponseByPrometheusExporter(response.getEntity().getContent(), builder);
} catch (Exception e) {
log.info("parse error: {}.", e.getMessage(), e);
builder.setCode(CollectRep.Code.FAIL);
builder.setMsg("http response content is empty");
} else {
try {
return parseResponseByPrometheusExporter(resp, metrics.getAliasFields(), builder);
} catch (Exception e) {
log.info("parse error: {}.", e.getMessage(), e);
builder.setCode(CollectRep.Code.FAIL);
builder.setMsg("parse response data error:" + e.getMessage());
}
builder.setMsg("parse response data error:" + e.getMessage());
}
} catch (ClientProtocolException e1) {
String errorMsg = CommonUtil.getMessageFromThrowable(e1);
@@ -168,10 +155,12 @@ public class PrometheusAutoCollectImpl {
}
}
private List<CollectRep.MetricsData> parseResponseByPrometheusExporter(String resp, List<String> aliasFields,
CollectRep.MetricsData.Builder builder) {
Map<String, MetricFamily> metricFamilyMap = TextParser.textToMetricFamilies(resp);
private List<CollectRep.MetricsData> parseResponseByPrometheusExporter(InputStream inputStream, CollectRep.MetricsData.Builder builder) throws IOException {
Map<String, MetricFamily> metricFamilyMap = OnlineParser.parseMetrics(inputStream);
List<CollectRep.MetricsData> metricsDataList = new LinkedList<>();
if (metricFamilyMap == null) {
return metricsDataList;
}
for (Map.Entry<String, MetricFamily> entry : metricFamilyMap.entrySet()) {
builder.clearFields();
builder.clearValues();
@@ -17,16 +17,17 @@
package org.apache.hertzbeat.collector.collect.prometheus.parser;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -36,9 +37,47 @@ import java.util.concurrent.ConcurrentHashMap;
@Slf4j
public class OnlineParser {
private static final Map<Integer, Integer> escapeMap = new HashMap<>(8);
static {
escapeMap.put((int) 'n', (int) '\n');
escapeMap.put((int) 'b', (int) '\b');
escapeMap.put((int) 't', (int) '\t');
escapeMap.put((int) 'r', (int) '\r');
escapeMap.put((int) 'f', (int) '\f');
escapeMap.put((int) '\'', (int) '\'');
escapeMap.put((int) '\"', (int) '\"');
escapeMap.put((int) '\\', (int) '\\');
}
private OnlineParser() {
}
public static Map<String, MetricFamily> parseMetrics(InputStream inputStream) throws IOException {
Map<String, MetricFamily> metricFamilyMap = new ConcurrentHashMap<>(10);
try {
int i = getChar(inputStream);
while (i != -1) {
if (i == '#' || i == '\n') {
skipToLineEnd(inputStream).maybeEol().maybeEof().noElse();
} else {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append((char) i);
parseMetric(inputStream, metricFamilyMap, stringBuilder);
}
i = getChar(inputStream);
}
} catch (FormatException e) {
log.error("prometheus parser failed because of wrong input format. {}", e.getMessage());
return null;
}
return metricFamilyMap;
}
private static class FormatException extends Exception {
public FormatException() {}
public FormatException() {
}
public FormatException(String message) {
super(message);
@@ -117,91 +156,92 @@ public class OnlineParser {
return this.i;
}
private int getInt() throws FormatException {
private int getInt() {
return this.i;
}
}
private static CharChecker parseOneChar(InputStream inputStream) throws IOException {
private static int getChar(InputStream inputStream) throws IOException, FormatException {
int i = inputStream.read();
return new CharChecker(i);
if (i == '\\') {
i = inputStream.read();
if (escapeMap.containsKey(i)) {
return escapeMap.get(i);
} else {
throw new FormatException("Escape character failed.");
}
} else {
return i;
}
}
private static CharChecker parseOneDouble(InputStream inputStream, StringBuilder stringBuilder) throws IOException {
int i = inputStream.read();
while ((i >= '0' && i <= '9') || (i >= 'a' && i <= 'z') || (i >= 'A' && i <= 'Z') || i == '-' || i == '+' || i == 'e' || i == '.') {
private static CharChecker parseOneDouble(InputStream inputStream, StringBuilder stringBuilder) throws IOException, FormatException {
int i = getChar(inputStream);
while (i >= '0' && i <= '9' || i >= 'a' && i <= 'z' || i >= 'A' && i <= 'Z' || i == '-' || i == '+' || i == '.') {
stringBuilder.append((char) i);
i = inputStream.read();
i = getChar(inputStream);
}
return new CharChecker(i);
}
private static CharChecker skipOneLong(InputStream inputStream) throws IOException {
int i = inputStream.read();
private static CharChecker skipOneLong(InputStream inputStream) throws IOException, FormatException {
int i = getChar(inputStream);
while (i >= '0' && i <= '9') {
i = inputStream.read();
i = getChar(inputStream);
}
return new CharChecker(i);
}
private static CharChecker parseMetricName(InputStream inputStream, StringBuilder stringBuilder) throws IOException {
int i = inputStream.read();
private static CharChecker parseMetricName(InputStream inputStream, StringBuilder stringBuilder) throws IOException, FormatException {
int i = getChar(inputStream);
while ((i >= 'a' && i <= 'z') || (i >= 'A' && i <= 'Z') || (i >= '0' && i <= '9') || i == '_' || i == ':') {
stringBuilder.append((char) i);
i = inputStream.read();
i = getChar(inputStream);
}
return new CharChecker(i);
}
private static CharChecker parseLabelName(InputStream inputStream, StringBuilder stringBuilder) throws IOException {
int i = inputStream.read();
private static CharChecker parseLabelName(InputStream inputStream, StringBuilder stringBuilder) throws IOException, FormatException {
int i = getChar(inputStream);
while ((i >= 'a' && i <= 'z') || (i >= 'A' && i <= 'Z') || (i >= '0' && i <= '9') || i == '_') {
stringBuilder.append((char) i);
i = inputStream.read();
i = getChar(inputStream);
}
return new CharChecker(i);
}
private static CharChecker parseLabelValue(InputStream inputStream, StringBuilder stringBuilder) throws IOException, FormatException {
int i = inputStream.read();
int i = getChar(inputStream);
while (i != '"' && i != -1) {
if (i == '\\') {
i = inputStream.read();
i = getChar(inputStream);
switch (i) {
case 'n':
stringBuilder.append('\n');
break;
case '\\':
stringBuilder.append('\\');
break;
case '\"':
stringBuilder.append('\"');
break;
default:
throw new FormatException();
case 'n' -> stringBuilder.append('\n');
case '\\' -> stringBuilder.append('\\');
case '\"' -> stringBuilder.append('\"');
default -> throw new FormatException();
}
}
else {
} else {
stringBuilder.append((char) i);
}
i = inputStream.read();
i = getChar(inputStream);
}
return new CharChecker(i);
}
private static CharChecker skipSpaces(InputStream inputStream) throws IOException {
int i = inputStream.read();
private static CharChecker skipSpaces(InputStream inputStream) throws IOException, FormatException {
int i = getChar(inputStream);
while (i == ' ') {
i = inputStream.read();
i = getChar(inputStream);
}
return new CharChecker(i);
}
private static CharChecker skipToLineEnd(InputStream inputStream) throws IOException {
int i = inputStream.read();
private static CharChecker skipToLineEnd(InputStream inputStream) throws IOException, FormatException {
int i = getChar(inputStream);
while (i != '\n' && i != -1) {
i = inputStream.read();
i = getChar(inputStream);
}
return new CharChecker(i);
}
@@ -256,7 +296,7 @@ public class OnlineParser {
}
private static CharChecker parseMetric(InputStream inputStream, Map<String, MetricFamily> metricFamilyMap, StringBuilder stringBuilder) throws IOException, FormatException {
MetricFamily metricFamily = null;
MetricFamily metricFamily;
MetricFamily.Metric metric = new MetricFamily.Metric();
int i = parseMetricName(inputStream, stringBuilder).maybeSpace().maybeLeftBracket().noElse();
String metricName = stringBuilder.toString();
@@ -267,18 +307,18 @@ public class OnlineParser {
metricFamily.setMetricList(new ArrayList<>());
metricFamily.setName(metricName);
metricFamilyMap.put(metricName, metricFamily);
}
else {
} else {
metricFamily = metricFamilyMap.get(metricName);
}
if (i == ' ') {
i = skipSpaces(inputStream).getInt();
}
List<MetricFamily.Label> labelList = new LinkedList<>();
metric.setLabels(labelList);
if (i == '{') {
List<MetricFamily.Label> labelList = new LinkedList<>();
parseLabels(inputStream, stringBuilder, labelList);
metric.setLabels(labelList);
i = skipSpaces(inputStream).getInt();
}
@@ -304,26 +344,4 @@ public class OnlineParser {
metricFamily.getMetricList().add(metric);
return new CharChecker(i);
}
public static Map<String, MetricFamily> parseMetrics(InputStream inputStream) throws IOException {
Map<String, MetricFamily> metricFamilyMap = new ConcurrentHashMap<>(10);
int i = inputStream.read();
try {
while (i != -1) {
if (i == '#' || i == '\n') {
skipToLineEnd(inputStream).maybeEol().maybeEof().noElse();
} else {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append((char) i);
parseMetric(inputStream, metricFamilyMap, stringBuilder);
}
i = inputStream.read();
}
} catch (FormatException e) {
log.error("prometheus parser failed because of wrong input format. {}", e.getMessage());
return null;
}
return metricFamilyMap;
}
}
@@ -29,8 +29,10 @@ import org.apache.hertzbeat.common.util.StrBuffer;
/**
* Resolves the data passed by prometheus's exporter interface http:xxx/metrics
* Reference: prometheus text_parse.go code, entry: TextToMetricFamilies
* recommend use OnlineParser
*/
@Slf4j
@Deprecated(since = "1.7.0")
public class TextParser {
private static final String NAME_LABEL = "__name__";
private static final char LEFT_CURLY_BRACKET = '{';
@@ -44,7 +46,6 @@ public class TextParser {
/**
* parser prometheus exporter text metrics data
* todo use inputStream bytebuffer instead of resp string
* @param resp txt data
* @return metrics family
*/
@@ -58,13 +58,13 @@ public class PushCollectImpl extends AbstractCollect {
private static final Map<Long, Long> timeMap = new ConcurrentHashMap<>();
// ms
private static final Integer timeout = 3000;
private static final Integer DEFAULT_TIMEOUT = 3000;
private static final Integer SUCCESS_CODE = 200;
// It's hard to determine how long ago the first data collection was, because there's no way to know when the last collection occurred.
// This makes it difficult to avoid re-collecting data after a restart. The default is 30 seconds
private static final Integer firstCollectInterval = 30000;
private static final Integer FIRST_COLLECT_INTERVAL = 30000;
@Override
public void preCheck(Metrics metrics) throws IllegalArgumentException {
@@ -80,7 +80,7 @@ public class PushCollectImpl extends AbstractCollect {
long monitorId = builder.getId();
PushProtocol pushProtocol = metrics.getPush();
Long time = timeMap.getOrDefault(monitorId, curTime - firstCollectInterval);
Long time = timeMap.getOrDefault(monitorId, curTime - FIRST_COLLECT_INTERVAL);
timeMap.put(monitorId, curTime);
HttpContext httpContext = createHttpContext(pushProtocol);
@@ -145,10 +145,10 @@ public class PushCollectImpl extends AbstractCollect {
//requestBuilder.setUri(pushProtocol.getUri());
if (timeout > 0) {
if (DEFAULT_TIMEOUT > 0) {
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(timeout)
.setSocketTimeout(timeout)
.setConnectTimeout(DEFAULT_TIMEOUT)
.setSocketTimeout(DEFAULT_TIMEOUT)
.setRedirectsEnabled(true)
.build();
requestBuilder.setConfig(requestConfig);
@@ -0,0 +1,95 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.collector.collect.s7;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.collector.collect.plc.AbstractPlcCollectImpl;
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
import org.apache.hertzbeat.common.entity.job.Metrics;
import org.apache.hertzbeat.common.entity.job.protocol.PlcProtocol;
import org.apache.hertzbeat.common.entity.job.protocol.S7Protocol;
import org.apache.hertzbeat.common.entity.message.CollectRep;
import org.apache.plc4x.java.api.PlcConnection;
import org.apache.plc4x.java.api.messages.PlcReadRequest;
import org.springframework.beans.BeanUtils;
import org.springframework.util.StringUtils;
import java.util.List;
/**
* plc collect
*/
@Slf4j
public class S7CollectImpl extends AbstractPlcCollectImpl {
@Override
public void preCheck(Metrics metrics) throws IllegalArgumentException {
S7Protocol s7 = metrics.getS7();
List<String> registerAddressList = s7.getRegisterAddresses();
// check RackId
if (!StringUtils.hasText(s7.getRackId())) {
s7.setRackId("0");
}
// check SlotId
if (!StringUtils.hasText(s7.getSlotId())) {
s7.setSlotId("0");
}
// check controllerType
if (!StringUtils.hasText(s7.getControllerType())) {
s7.setControllerType("S7_1500");
}
if (!StringUtils.hasText(s7.getTimeout())) {
s7.setTimeout("5000");
}
PlcProtocol plc = metrics.getPlc() == null ? new PlcProtocol() : metrics.getPlc();
plc.setRegisterAddresses(registerAddressList);
BeanUtils.copyProperties(s7, plc);
metrics.setPlc(plc);
super.preCheck(metrics);
}
@Override
public void collect(CollectRep.MetricsData.Builder builder, Metrics metrics) {
super.collect(builder, metrics);
}
@Override
public String supportProtocol() {
return DispatchConstants.PROTOCOL_S7;
}
@Override
protected String getConnectionString(Metrics metrics) {
S7Protocol s7Protocol = metrics.getS7();
return "s7://" + s7Protocol.getHost() + ":" + s7Protocol.getPort() + "?remote-rack:" + s7Protocol.getRackId()
+ "&remote-slot:" + s7Protocol.getSlotId() + "&controller-type=" + s7Protocol.getControllerType();
}
@Override
protected PlcReadRequest buildRequest(Metrics metrics, PlcConnection connection) {
S7Protocol s7Protocol = metrics.getS7();
List<String> registerAddressList = s7Protocol.getRegisterAddresses();
// Create a new read request:
PlcReadRequest.Builder requestBuilder = connection.readRequestBuilder();
for (int i = 0; i < registerAddressList.size(); i++) {
String s1 = registerAddressList.get(i);
requestBuilder.addTagAddress(metrics.getS7().getAddressSyntax() + ":" + i, s1);
}
return requestBuilder.build();
}
}
@@ -47,31 +47,28 @@ public class DnsCollectImplTest {
.address("www.google.com")
.timeout("3000")
.port("53")
.tcp("tcp")
.build();
}
@Test
public void testPreCheck() {
assertThrows(IllegalArgumentException.class, () -> {
Metrics metrics = new Metrics();
metrics.setName("question");
metrics.setDns(dnsProtocol);
dnsCollect.preCheck(metrics);
});
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
//metrics is null
dnsCollect.collect(builder, null);
assertEquals(CollectRep.Code.FAIL, builder.getCode());
//query class is blank
//invalid DnsProtocol
Metrics metrics = new Metrics();
assertThrows(IllegalArgumentException.class, () -> {
DnsProtocol dns = DnsProtocol.builder().build();
Metrics metrics = new Metrics();
metrics.setDns(dns);
dnsCollect.preCheck(metrics);
});
// no exception throws
//validated DnsProtocol
assertDoesNotThrow(() -> {
dnsProtocol.setTcp("tcp");
Metrics metrics = new Metrics();
metrics.setName("question");
metrics.setDns(dnsProtocol);
dnsCollect.preCheck(metrics);
});
@@ -80,28 +77,23 @@ public class DnsCollectImplTest {
@Test
public void testCollect() {
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
long monitorId = 666;
String app = "testDNS";
Metrics metrics = new Metrics();
metrics.setName("question");
metrics.setDns(dnsProtocol);
metrics.setAliasFields(Collections.singletonList("section"));
dnsCollect.collect(builder, metrics);
Metrics metrics0 = Metrics.builder()
.name("question")
.dns(dnsProtocol)
.aliasFields(Collections.singletonList("section"))
.build();
dnsCollect.collect(builder, metrics0);
assertEquals(CollectRep.Code.SUCCESS, builder.getCode());
assertNotNull(builder.getValues(0).getColumns(0));
// dns is null, no exception throws
assertDoesNotThrow(() -> {
dnsCollect.collect(builder, null);
});
// metric name is header
assertDoesNotThrow(() -> {
Metrics metrics1 = new Metrics();
metrics1.setName("header");
metrics1.setDns(dnsProtocol);
metrics1.setAliasFields(Collections.singletonList("section"));
dnsCollect.collect(builder, metrics1);
});
Metrics metrics1 = Metrics.builder()
.name("header")
.dns(dnsProtocol)
.aliasFields(Collections.singletonList("section"))
.build();
dnsCollect.collect(builder, metrics1);
assertEquals(CollectRep.Code.SUCCESS, builder.getCode());
}
@Test
@@ -18,7 +18,14 @@
package org.apache.hertzbeat.collector.collect.http;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
import org.apache.hertzbeat.common.entity.job.Metrics;
import org.apache.hertzbeat.common.entity.job.protocol.HttpProtocol;
import org.apache.hertzbeat.common.entity.message.CollectRep;
@@ -41,7 +48,7 @@ class HttpCollectImplTest {
assertThrows(IllegalArgumentException.class, () -> {
httpCollectImpl.preCheck(null);
});
assertThrows(IllegalArgumentException.class, () -> {
Metrics metrics = Metrics.builder().build();
httpCollectImpl.preCheck(metrics);
@@ -65,4 +72,92 @@ class HttpCollectImplTest {
String protocol = httpCollectImpl.supportProtocol();
assert "http".equals(protocol);
}
@Test
void parseResponseByXmlPath() throws Exception {
// Create a sample XML response
String xmlResponse = """
<?xml version="1.0" encoding="UTF-8"?>
<root>
<server>
<name>Server1</name>
<status>Running</status>
<metrics>
<cpu>75.5</cpu>
<memory>1024</memory>
<disk>500</disk>
</metrics>
</server>
<server>
<name>Server2</name>
<status>Stopped</status>
<metrics>
<cpu>0.0</cpu>
<memory>0</memory>
<disk>500</disk>
</metrics>
</server>
</root>
""";
// Set up HttpProtocol with XML path parsing
HttpProtocol http = HttpProtocol.builder()
.parseType(DispatchConstants.PARSE_XML_PATH)
.parseScript("//server") // XPath to select all server nodes
.build();
// Set up Metrics with fields that have XPath expressions
List<Metrics.Field> fields = new ArrayList<>();
fields.add(Metrics.Field.builder().field("name").build());
fields.add(Metrics.Field.builder().field("status").build());
fields.add(Metrics.Field.builder().field("metrics/cpu").build());
fields.add(Metrics.Field.builder().field("metrics/memory").build());
Metrics metrics = Metrics.builder()
.http(http)
.fields(fields)
.aliasFields(Arrays.asList("name", "status", "metrics/cpu", "metrics/memory"))
.build();
// Create a custom builder that captures added rows
List<CollectRep.ValueRow> capturedRows = new ArrayList<>();
CollectRep.MetricsData.Builder builder = new CollectRep.MetricsData.Builder() {
@Override
public CollectRep.MetricsData.Builder addValueRow(CollectRep.ValueRow valueRow) {
capturedRows.add(valueRow);
return super.addValueRow(valueRow);
}
};
// Use reflection to access the private parseResponseByXmlPath method
Method parseMethod = HttpCollectImpl.class.getDeclaredMethod(
"parseResponseByXmlPath",
String.class,
Metrics.class,
CollectRep.MetricsData.Builder.class,
Long.class);
parseMethod.setAccessible(true);
// Call the method
parseMethod.invoke(httpCollectImpl, xmlResponse, metrics, builder, 100L);
// Verify the results
assertEquals(2, capturedRows.size(), "Should have parsed 2 server nodes");
// Check first server
CollectRep.ValueRow firstRow = capturedRows.get(0);
assertEquals(4, firstRow.getColumnsCount(), "First row should have 4 columns");
assertEquals("Server1", firstRow.getColumns(0), "First server name should be Server1");
assertEquals("Running", firstRow.getColumns(1), "First server status should be Running");
assertEquals("75.5", firstRow.getColumns(2), "First server CPU should be 75.5");
assertEquals("1024", firstRow.getColumns(3), "First server memory should be 1024");
// Check second server
CollectRep.ValueRow secondRow = capturedRows.get(1);
assertEquals(4, secondRow.getColumnsCount(), "Second row should have 4 columns");
assertEquals("Server2", secondRow.getColumns(0), "Second server name should be Server2");
assertEquals("Stopped", secondRow.getColumns(1), "Second server status should be Stopped");
assertEquals("0.0", secondRow.getColumns(2), "Second server CPU should be 0.0");
assertEquals("0", secondRow.getColumns(3), "Second server memory should be 0");
}
}
@@ -1,79 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.collector.collect.http.promethus.exporter;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.Map;
import org.junit.jupiter.api.Test;
/**
* Test case for {@link ExporterParser}
*/
class ExporterParserTest {
@Test
void textToMetric() {
String resp = """
# HELP disk_total_bytes Total space for path
# TYPE disk_total_bytes gauge
disk_total_bytes{path="C:\\\\hertzbeat\\\\repo\\\\testpath",} 4.29496725504E11
# HELP go_gc_cycles_automatic_gc_cycles_total Count of completed GC cycles generated by the Go runtime.
# TYPE go_gc_cycles_automatic_gc_cycles_total counter
go_gc_cycles_automatic_gc_cycles_total 0
# HELP go_gc_cycles_forced_gc_cycles_total Count of completed GC cycles forced by the application.
# TYPE go_gc_cycles_forced_gc_cycles_total counter
go_gc_cycles_forced_gc_cycles_total 0
# HELP go_gc_cycles_total_gc_cycles_total Count of all completed GC cycles.
# TYPE go_gc_cycles_total_gc_cycles_total counter
go_gc_cycles_total_gc_cycles_total 0
# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 0
go_gc_duration_seconds{quantile="0.25"} 0
go_gc_duration_seconds{quantile="0.5"} 0
go_gc_duration_seconds{quantile="0.75"} 0
go_gc_duration_seconds{quantile="1"} 0
# TYPE jvm info
# HELP jvm VM version info
jvm_info{runtime="OpenJDK Runtime Environment",vendor="Azul Systems, Inc.",version="11.0.13+8-LTS"} 1.0
# TYPE jvm_gc_collection_seconds summary
# HELP jvm_gc_collection_seconds Time spent in a given JVM garbage collector in seconds.
jvm_gc_collection_seconds_count{gc="G1 Young Generation"} 10.0
jvm_gc_collection_seconds_sum{gc="G1 Young Generation"} 0.051
jvm_gc_collection_seconds_count{gc="G1 Old Generation"} 0.0
jvm_gc_collection_seconds_sum{gc="G1 Old Generation"} 0.0
# TYPE resource_group_aggregate_usage_secs summary
resource_group_aggregate_usage_secs{cluster="standalone",quantile="0.5"} 2.69245E-4
resource_group_aggregate_usage_secs{cluster="standalone",quantile="0.9"} 3.49601E-4
resource_group_aggregate_usage_secs_count{cluster="standalone"} 13.0
resource_group_aggregate_usage_secs_sum{cluster="standalone"} 0.004832498
resource_group_aggregate_usage_secs_created{cluster="standalone"} 1.715842140749E9
# TYPE metadata_store_ops_latency_ms histogram
metadata_store_ops_latency_ms_bucket{cluster="standalone",name="metadata-store",type="get",status="success",le="1.0"} 59.0
metadata_store_ops_latency_ms_bucket{cluster="standalone",name="metadata-store",type="get",status="success",le="3.0"} 61.0
metadata_store_ops_latency_ms_bucket{cluster="standalone",name="metadata-store",type="get",status="success",le="5.0"} 61.0
# EOF""";
ExporterParser parser = new ExporterParser();
Map<String, MetricFamily> metricFamilyMap = parser.textToMetric(resp);
assertEquals(5, metricFamilyMap.get("resource_group_aggregate_usage_secs").getMetricList().size());
assertEquals(3, metricFamilyMap.get("metadata_store_ops_latency_ms").getMetricList().size());
assertEquals(5, metricFamilyMap.get("go_gc_duration_seconds").getMetricList().size());
assertEquals(9, metricFamilyMap.size());
}
}
@@ -25,8 +25,12 @@ import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
class OnlineParserTest {
@@ -44,13 +48,13 @@ class OnlineParserTest {
String str = """
# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds { quantile="0"} 2.0209e-05 321312
go_gc_duration_seconds{ quantile = "0.25" } 6.6917e-05
go_gc_duration_seconds{quantile="0"} 2.0209e-05
go_gc_duration_seconds{quantile="0.25"} 6.6917e-05
go_gc_duration_seconds{quantile="0.5"} -Inf
go_gc_duration_seconds{ quantile = "0.75"} +Inf
go_gc_duration_seconds{quantile="0.75"} +Inf
go_gc_duration_seconds{quantile="1"} NaN
go_gc_duration_seconds_sum 0.001134793 321314
go_gc_duration_seconds_count 5 43
go_gc_duration_seconds_sum 0.001134793
go_gc_duration_seconds_count 5
# HELP go_goroutines Number of goroutines that currently exist.
# TYPE go_goroutines gauge
go_goroutines 32
@@ -73,7 +77,26 @@ class OnlineParserTest {
# TYPE go_memstats_gc_sys_bytes gauge
go_memstats_gc_sys_bytes 4.614808e+06""";
InputStream inputStream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
Map<String, MetricFamily> metricFamilyMap = OnlineParser.parseMetrics(inputStream);
assertNotNull(metricFamilyMap);
Map<String, MetricFamily> metricFamilyMap1 = OnlineParser.parseMetrics(inputStream);
Map<String, MetricFamily> metricFamilyMap2 = TextParser.textToMetricFamilies(str);
assertNotNull(metricFamilyMap1);
assertNotNull(metricFamilyMap2);
assertEquals(metricFamilyMap1.size(), metricFamilyMap2.size());
metricFamilyMap2.forEach((metricFamilyName, metricFamily2) -> {
if (!metricFamilyMap1.containsKey(metricFamilyName)) {
fail("parse failed, different result from two parser.");
}
MetricFamily metricFamily1 = metricFamilyMap1.get(metricFamilyName);
assertEquals(metricFamily1.getName(), metricFamily1.getName());
Set<Double> metricValueSet = metricFamily2.getMetricList().stream().map(MetricFamily.Metric::getValue).collect(Collectors.toSet());
metricFamily1.getMetricList().forEach(metric -> {
// this is for something different between two algorithms above, and both of them is current on this parsing behavior.
if (!(metric.getValue() == Double.POSITIVE_INFINITY || metric.getValue() == Double.NEGATIVE_INFINITY)) {
if (!metricValueSet.contains(metric.getValue())) {
fail();
}
}
});
});
}
}
@@ -302,7 +302,7 @@ public class MetricsCollect implements Runnable, Comparable<MetricsCollect> {
value = String.valueOf(objValue);
}
} catch (Exception e) {
log.info("[calculates execute warning] {}.", e.getMessage());
log.warn("[calculates execute warning, use original value.] {}", e.getMessage());
value = Optional.ofNullable(fieldValueMap.get(expression.getSourceText()))
.map(String::valueOf)
.orElse(null);
@@ -29,4 +29,5 @@ org.apache.hertzbeat.collector.collect.mqtt.MqttCollectImpl
org.apache.hertzbeat.collector.collect.ipmi2.IpmiCollectImpl
org.apache.hertzbeat.collector.collect.kafka.KafkaCollectImpl
org.apache.hertzbeat.collector.collect.sd.HttpSdCollectImpl
org.apache.hertzbeat.collector.collect.modbus.ModbusCollectImpl
org.apache.hertzbeat.collector.collect.modbus.ModbusCollectImpl
org.apache.hertzbeat.collector.collect.s7.S7CollectImpl
@@ -202,6 +202,10 @@ public interface DispatchConstants {
* Parsing method prometheus exporter data
*/
String PARSE_PROMETHEUS = "prometheus";
/**
* Parse response body as config/properties format
*/
String PARSE_CONFIG = "config";
/**
* prometheus accept header
*/
@@ -227,4 +231,9 @@ public interface DispatchConstants {
* protocol modbus
*/
String PROTOCOL_MODBUS = "modbus";
/**
* protocol modbus
*/
String PROTOCOL_S7 = "s7";
}
@@ -613,9 +613,8 @@ public class HashedWheelTimer implements Timer {
HashedWheelBucket bucket = this.bucket;
if (bucket != null) {
bucket.remove(this);
} else {
timer.pendingTimeouts.decrementAndGet();
}
timer.pendingTimeouts.decrementAndGet();
}
public boolean compareAndSetState(int expected, int state) {
@@ -642,6 +641,7 @@ public class HashedWheelTimer implements Timer {
}
try {
remove();
task.run(this);
} catch (Throwable t) {
if (logger.isWarnEnabled()) {
@@ -718,7 +718,6 @@ public class HashedWheelTimer implements Timer {
while (timeout != null) {
HashedWheelTimeout next = timeout.next;
if (timeout.remainingRounds <= 0) {
next = remove(timeout);
if (timeout.deadline <= deadline) {
timeout.expire();
} else {
@@ -726,9 +725,7 @@ public class HashedWheelTimer implements Timer {
throw new IllegalStateException(String.format(
"timeout.deadline (%d) > deadline (%d)", timeout.deadline, deadline));
}
} else if (timeout.isCancelled()) {
next = remove(timeout);
} else {
} else if (!timeout.isCancelled()) {
timeout.remainingRounds--;
}
timeout = next;
@@ -761,7 +758,6 @@ public class HashedWheelTimer implements Timer {
timeout.prev = null;
timeout.next = null;
timeout.bucket = null;
timeout.timer.pendingTimeouts.decrementAndGet();
return next;
}
@@ -242,6 +242,7 @@ public class KafkaCollectImpl extends AbstractCollect {
boolean isKafkaCommand = SupportedCommand.isKafkaCommand(command);
if (!isKafkaCommand) {
log.error("Unsupported command: {}", command);
builder.setCode(CollectRep.Code.FAIL);
return;
}
@@ -273,7 +274,7 @@ public class KafkaCollectImpl extends AbstractCollect {
}
}
private AdminClient getAdminClient(KafkaProtocol kafkaProtocol) {
protected AdminClient getAdminClient(KafkaProtocol kafkaProtocol) {
CacheIdentifier kafkaAdminClientIdentifier = CacheIdentifier.builder()
.ip(kafkaProtocol.getHost()).port(kafkaProtocol.getPort())
.build();
@@ -17,12 +17,24 @@
package org.apache.hertzbeat.collector.collect.kafka;
import org.apache.hertzbeat.collector.collect.kafka.constants.SupportedCommand;
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
import org.apache.hertzbeat.common.entity.job.Metrics;
import org.apache.hertzbeat.common.entity.job.protocol.KafkaProtocol;
import org.apache.hertzbeat.common.entity.message.CollectRep;
import org.apache.kafka.clients.admin.AdminClient;
import org.apache.kafka.clients.admin.ListTopicsOptions;
import org.apache.kafka.clients.admin.ListTopicsResult;
import org.apache.kafka.common.KafkaFuture;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.HashSet;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -31,12 +43,25 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
/**
* Test case for {@link KafkaCollectImpl}
*/
@ExtendWith(MockitoExtension.class)
public class KafkaCollectTest {
private static final String HOST = "127.0.0.1";
private static final String PORT = "9092";
private KafkaCollectImpl collect;
@Mock
private AdminClient adminClient;
@BeforeEach
public void setUp() throws Exception {
collect = new KafkaCollectImpl();
collect = new KafkaCollectImpl(){
@Override
protected AdminClient getAdminClient(KafkaProtocol protocol) {
return adminClient;
}
};
}
@Test
@@ -59,38 +84,51 @@ public class KafkaCollectTest {
});
// kafka port is null
assertThrows(IllegalArgumentException.class, () -> {
kafka.setHost("127.0.0.1");
kafka.setHost(HOST);
collect.preCheck(metric);
});
// no exception throw
assertDoesNotThrow(() -> {
kafka.setPort("9092");
kafka.setPort(PORT);
collect.preCheck(metric);
});
}
@Test
void collect() {
// metrics is null
assertThrows(NullPointerException.class, () -> {
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
collect.collect(builder, null);
});
void testCollect() throws Exception {
Set<String> topicSet = new HashSet<>();
topicSet.add("test-topic");
ListTopicsResult listTopicsResult = Mockito.mock(ListTopicsResult.class);
KafkaFuture<Set<String>> future = Mockito.mock(KafkaFuture.class);
Mockito.when(adminClient.listTopics(Mockito.any(ListTopicsOptions.class))).thenReturn(listTopicsResult);
Mockito.when(listTopicsResult.names()).thenReturn(future);
Mockito.when(future.get()).thenReturn(topicSet);
KafkaProtocol kafka = KafkaProtocol.builder()
.host(HOST)
.port(PORT)
.build();
Metrics metrics = Metrics.builder()
.kclient(kafka)
.build();
// test if not kafka command
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
collect.collect(builder, metrics);
assertEquals(CollectRep.Code.FAIL, builder.getCode());
KafkaProtocol kafka = KafkaProtocol.builder().host("127.0.0.1").port("9092").build();
Metrics metrics = Metrics.builder().kclient(kafka).build();
//test if not kafka command
assertDoesNotThrow(() -> {
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
collect.collect(builder, metrics);
});
//test kafka command
assertDoesNotThrow(() -> {
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
metrics.getKclient().setCommand("topic-list");
collect.collect(builder, metrics);
});
kafka.setCommand(SupportedCommand.TOPIC_LIST.getCommand());
builder.setCode(CollectRep.Code.forNumber(6));
collect.collect(builder, metrics);
assertEquals(CollectRep.Code.SUCCESS, builder.getCode());
assertEquals(1, builder.getValuesList().size());
assertEquals("test-topic", builder.getValues(0).getColumns(0));
Mockito.verify(adminClient).listTopics(Mockito.any(ListTopicsOptions.class));
}
@Test
@@ -455,4 +455,19 @@ public interface CommonConstants {
* status page incident state resolved
*/
byte STATUS_PAGE_INCIDENT_STATE_RESOLVED = 3;
/**
* status page incident state resolved
*/
byte MONITOR_TYPE_NORMAL = 0;
/**
* status page incident state resolved
*/
byte MONITOR_TYPE_PUSH_AUTO_CREATE = 1;
/**
* status page incident state resolved
*/
byte MONITOR_TYPE_DISCOVERY_AUTO_CREATE = 2;
}
@@ -65,6 +65,8 @@ public interface ConfigConstants {
String INFO = "info";
String GRAFANA = "grafana";
String LOG = "log";
}
}
@@ -0,0 +1,59 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.common.entity.dto.query;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Metric History Range Query Data
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "Query Request Data")
public class DatasourceQuery {
@Schema(title = "Ref Id, unique id for the query")
private String refId;
@Schema(title = "datasource name")
private String datasource;
@Schema(title = "query expr, like prometheus query")
private String expr;
@Schema(title = "query expr type, like promql or sql or influxql")
private String exprType;
@Schema(title = "query range type, like range or instant")
private String timeType;
@Schema(title = "query range start time")
private Long start;
@Schema(title = "query range end time")
private Long end;
@Schema(title = "query time step, like 5m or 1h")
private String step;
}
@@ -0,0 +1,103 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.common.entity.dto.query;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Metric History Range Query Data
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "Metric Query Data")
public class DatasourceQueryData {
@Schema(title = "Ref Id, unique id for the query")
private String refId;
@Schema(title = "query status code, 200 for success, other for error")
private Integer status;
@Schema(title = "query error message")
private String msg;
@Schema(title = "query result data frames")
private List<SchemaData> frames;
/**
* Schema Data
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public static final class SchemaData {
@Schema(title = "Metric Schema")
private MetricSchema schema;
@Schema(title = "metrics row values, first is the timestamp-ts", example = "[[29,32,44],[32,34,true]]")
private List<Object[]> data;
}
/**
* Metric Schema
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public static final class MetricSchema {
@Schema(title = "Metrics Field")
private List<MetricField> fields;
@Schema(title = "This frame labels")
private Map<String, String> labels;
@Schema(title = "Meta Information")
private Map<String, String> meta;
}
/**
* Metric Field
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public static final class MetricField {
@Schema(title = "Metric Field Name")
private String name;
@Schema(title = "Field Type: number, string, time, bool")
private String type;
@Schema(title = "Field Unit: %, Mb, Kbps etc.")
private String unit;
}
}
@@ -54,6 +54,7 @@ import org.apache.hertzbeat.common.entity.job.protocol.PushProtocol;
import org.apache.hertzbeat.common.entity.job.protocol.RedfishProtocol;
import org.apache.hertzbeat.common.entity.job.protocol.RedisProtocol;
import org.apache.hertzbeat.common.entity.job.protocol.RocketmqProtocol;
import org.apache.hertzbeat.common.entity.job.protocol.S7Protocol;
import org.apache.hertzbeat.common.entity.job.protocol.ScriptProtocol;
import org.apache.hertzbeat.common.entity.job.protocol.SmtpProtocol;
import org.apache.hertzbeat.common.entity.job.protocol.SnmpProtocol;
@@ -270,6 +271,10 @@ public class Metrics {
* Monitoring configuration information using the public modBus protocol
*/
private ModbusProtocol modbus;
/**
* Monitoring configuration information using the public s7 protocol
*/
private S7Protocol s7;
/**
* collector use - Temporarily store subTask metrics response data
*/
@@ -0,0 +1,59 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.common.entity.job.protocol;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* Modbus Protocol
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class S7Protocol implements Protocol {
/**
* IP ADDRESS OR DOMAIN NAME OF THE PEER HOST
*/
private String host;
/**
* Port number
*/
private String port;
private String driverName;
private String addressSyntax;
private String rackId;
private String slotId;
private String controllerType;
private String timeout;
private List<String> registerAddresses;
}
@@ -91,6 +91,9 @@ public class Monitor {
@Max(4)
private byte status;
@Schema(title = "Task type 0: Normal, 1: push auto create, 2: discovery auto create")
private byte type;
@Schema(title = "task label", example = "{env:test}", accessMode = READ_WRITE)
@Convert(converter = JsonMapAttributeConverter.class)
@Column(length = 4096)
@@ -445,15 +445,7 @@ public final class CollectRep {
if (value != null) {
// Check byte array size, Arrow buffer size is 32768 bytes
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
if (bytes.length > 32700) {
log.warn("Value too large for Arrow buffer ({}), truncating to 32700 bytes. Meta: {}",
bytes.length, JsonUtil.toJson(metadata));
byte[] truncatedBytes = new byte[32700];
System.arraycopy(bytes, 0, truncatedBytes, 0, 32700);
vector.set(rowIndex, truncatedBytes);
} else {
vector.set(rowIndex, bytes);
}
vector.setSafe(rowIndex, bytes);
}
}
}
@@ -49,4 +49,46 @@ public final class TimePeriodUtil {
return Duration.parse("PT" + tokenTime);
}
}
/**
* transform any timestamp to milliseconds
* @param timestamp timestamp
* @return milliseconds
*/
public static long normalizeToMilliseconds(Object timestamp) {
if (timestamp instanceof String timestampStr) {
// string type, may be second, millisecond or decimal second
// eg: "1672531199000", "1672531199", "1672531199.123"
if (timestampStr.contains(".")) {
// contains decimal point, parse as second timestamp
double seconds = Double.parseDouble(timestampStr);
return (long) (seconds * 1000);
} else {
// integer form, determine second or millisecond
long numericTimestamp = Long.parseLong(timestampStr);
return convertNumericTimestamp(numericTimestamp);
}
} else if (timestamp instanceof Number) {
// number eg Integer、Long、Double
if (timestamp instanceof Double || timestamp instanceof Float) {
// float type, treat as second timestamp
double seconds = ((Number) timestamp).doubleValue();
return (long) (seconds * 1000);
} else {
// integer type, directly determine second or millisecond
long numericTimestamp = ((Number) timestamp).longValue();
return convertNumericTimestamp(numericTimestamp);
}
} else {
throw new IllegalArgumentException("Not support this timestamp type: " + timestamp.getClass().getName());
}
}
private static long convertNumericTimestamp(long numericTimestamp) {
if (String.valueOf(numericTimestamp).length() <= 10) {
return numericTimestamp * 1000;
} else {
return numericTimestamp;
}
}
}
@@ -40,6 +40,7 @@ import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import lombok.extern.slf4j.Slf4j;
@@ -71,6 +72,11 @@ public class DashboardService {
*/
@Transactional(rollbackFor = Exception.class)
public ResponseEntity<?> createOrUpdateDashboard(String dashboardJson, Long monitorId) {
if (!grafanaProperties.enabled()) {
log.info("HertzBeat Grafana config not enabled");
throw new RuntimeException("HertzBeat Grafana config not enabled");
}
String token = serviceAccountService.getToken();
String url = grafanaProperties.getPrefix() + grafanaProperties.getUrl() + CREATE_DASHBOARD_API;
@@ -103,6 +109,12 @@ public class DashboardService {
log.error("create dashboard error: {}", response.getStatusCode());
throw new RuntimeException("create dashboard error");
}
} catch (HttpClientErrorException.Forbidden ex) {
log.error("Grafana Access denied to save dashboard", ex);
throw new RuntimeException("Grafana Access denied to save dashboard", ex);
} catch (HttpClientErrorException.NotFound ex){
log.error("Grafana Dashboard not found", ex);
throw new RuntimeException("Grafana Dashboard not found", ex);
} catch (Exception ex) {
log.error("create dashboard error", ex);
throw new RuntimeException("create dashboard error", ex);
@@ -61,6 +61,11 @@ public class DatasourceService {
* Create a new datasource in Grafana.
*/
public void existOrCreateDatasource(String token) {
if (!warehouseProperties.enabled()) {
log.info("HertzBeat VictoriaMetrics config not enabled");
return;
}
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setBearerAuth(token);
+67
View File
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Licensed to the Apache Software Foundation (ASF) under one or more
~ contributor license agreements. See the NOTICE file distributed with
~ this work for additional information regarding copyright ownership.
~ The ASF licenses this file to You under the Apache License, Version 2.0
~ (the "License"); you may not use this file except in compliance with
~ the License. You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.hertzbeat</groupId>
<artifactId>hertzbeat</artifactId>
<version>2.0-SNAPSHOT</version>
</parent>
<artifactId>hertzbeat-log</artifactId>
<name>${project.artifactId}</name>
<properties>
<maven-jar-plugin.version>3.2.0</maven-jar-plugin.version>
<maven-assembly-plugin.version>3.3.0</maven-assembly-plugin.version>
</properties>
<dependencies>
<!-- hertzbeat common -->
<dependency>
<groupId>org.apache.hertzbeat</groupId>
<artifactId>hertzbeat-common</artifactId>
</dependency>
<!-- hertzbeat warehouse -->
<dependency>
<groupId>org.apache.hertzbeat</groupId>
<artifactId>hertzbeat-warehouse</artifactId>
</dependency>
<!-- OpenTelemetry -->
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk-logs</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-logback-appender-1.0</artifactId>
</dependency>
</dependencies>
</project>
@@ -15,19 +15,21 @@
* limitations under the License.
*/
package org.apache.hertzbeat.push.dao;
package org.apache.hertzbeat.log.config;
import org.apache.hertzbeat.common.entity.push.PushMetrics;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.transaction.annotation.Transactional;
import org.apache.hertzbeat.common.constants.ConfigConstants;
import org.apache.hertzbeat.common.constants.SignConstants;
import org.apache.hertzbeat.warehouse.store.history.greptime.GreptimeProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.ComponentScan;
/**
* push metrics dao
* Log auto configuration.
*/
public interface PushMetricsDao extends JpaRepository<PushMetrics, Long> {
PushMetrics findFirstByMonitorIdOrderByTimeDesc(Long monitorId);
@Transactional(rollbackFor = Exception.class)
void deleteAllByTimeBefore(Long time);
@ComponentScan(basePackages = ConfigConstants.PkgConstant.PKG
+ SignConstants.DOT
+ ConfigConstants.FunctionModuleConstants.LOG
)
@EnableConfigurationProperties(GreptimeProperties.class)
public class LogAutoConfiguration {
}
@@ -0,0 +1,117 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.hertzbeat.log.config;
import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_NAME;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter;
import io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.logs.SdkLoggerProvider;
import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor;
import io.opentelemetry.sdk.resources.Resource;
import jakarta.annotation.PostConstruct;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.hertzbeat.warehouse.store.history.greptime.GreptimeProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
/**
* OpenTelemetryConfig is responsible for initializing OpenTelemetry with the specified service name and GrepTimeDB endpoint.
* It ensures that the initialization is done in a thread-safe manner and includes authentication for GrepTimeDB.
*/
@Configuration
@Slf4j
public class OpenTelemetryConfig {
@Autowired
private GreptimeProperties greptimeProperties;
/**
* Initializes OpenTelemetry with the given service name and GrepTimeDB endpoint.
* Includes authentication if configured in GreptimeProperties.
*/
@PostConstruct
public void initializeOpenTelemetry() {
if (greptimeProperties == null || !greptimeProperties.enabled()) {
log.info("GrepTimeDB logging is disabled, skipping OpenTelemetry configuration.");
return;
}
try {
Resource resource = Resource.getDefault()
.merge(Resource.builder()
.put(SERVICE_NAME, "HertzBeat")
.build());
Map<String, String> headers = new HashMap<>();
headers.put("X-Greptime-DB-Name", "public");
headers.put("X-Greptime-Log-Table-Name", "hzb_log");
addAuthenticationHeaders(headers);
OtlpHttpLogRecordExporter logExporter = OtlpHttpLogRecordExporter.builder()
.setEndpoint(greptimeProperties.httpEndpoint() + "/v1/otlp/v1/logs")
.setHeaders(()-> headers)
.setTimeout(10, TimeUnit.SECONDS)
.build();
SdkLoggerProvider loggerProvider = SdkLoggerProvider.builder()
.setResource(resource)
.addLogRecordProcessor(
BatchLogRecordProcessor.builder(logExporter)
.setScheduleDelay(1000, TimeUnit.MILLISECONDS)
.setMaxExportBatchSize(512)
.build())
.build();
OpenTelemetry openTelemetry = OpenTelemetrySdk.builder()
.setLoggerProvider(loggerProvider)
.build();
OpenTelemetryAppender.install(openTelemetry);
log.info("OpenTelemetry successfully configured with GrepTimeDB exporter.");
} catch (Exception e) {
log.error("Failed to initialize OpenTelemetry with GrepTimeDB", e);
}
}
/**
* Adds authentication headers to the provided map if username and password are configured.
*
* @param headers the map to which authentication headers will be added
*/
private void addAuthenticationHeaders(Map<String, String> headers) {
if (StringUtils.isNotBlank(greptimeProperties.username())
&& StringUtils.isNotBlank(greptimeProperties.password())) {
String credentials = greptimeProperties.username() + ":" + greptimeProperties.password();
String encodedCredentials = Base64.getEncoder().encodeToString(credentials.getBytes());
headers.put("Authorization", "Basic " + encodedCredentials);
}
}
}
@@ -0,0 +1,17 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.apache.hertzbeat.log.config.LogAutoConfiguration
+5
View File
@@ -89,6 +89,11 @@
<groupId>org.apache.hertzbeat</groupId>
<artifactId>hertzbeat-grafana</artifactId>
</dependency>
<!-- log -->
<dependency>
<groupId>org.apache.hertzbeat</groupId>
<artifactId>hertzbeat-log</artifactId>
</dependency>
<!-- spring -->
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -28,6 +28,7 @@ import org.springframework.context.annotation.ImportRuntimeHints;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* start up class.
@@ -41,6 +42,7 @@ import org.springframework.scheduling.annotation.EnableAsync;
@ConfigurationPropertiesScan(basePackages = {"org.apache.hertzbeat"})
@ImportRuntimeHints(HertzbeatRuntimeHintsRegistrar.class)
@EnableAsync
@EnableScheduling
public class Manager {
public static void main(String[] args) {
SpringApplication.run(Manager.class, args);
@@ -427,6 +427,7 @@ public class CollectorJobScheduler implements CollectorScheduling, CollectJobSch
if (jobId == null) {
return;
}
jobContentCache.remove(jobId);
for (ConsistentHash.Node node : consistentHash.getAllNodes().values()) {
AssignJobs assignJobs = node.getAssignJobs();
if (assignJobs.getPinnedJobs().remove(jobId)
@@ -531,7 +531,7 @@ public class MonitorServiceImpl implements MonitorService {
MonitorDto monitorDto = new MonitorDto();
List<Param> params = paramDao.findParamsByMonitorId(id);
monitorDto.setParams(params);
if (DispatchConstants.PROTOCOL_PROMETHEUS.equalsIgnoreCase(monitor.getApp())) {
if (DispatchConstants.PROTOCOL_PROMETHEUS.equalsIgnoreCase(monitor.getApp()) || monitor.getType() == CommonConstants.MONITOR_TYPE_PUSH_AUTO_CREATE) {
List<CollectRep.MetricsData> metricsDataList = warehouseService.queryMonitorMetricsData(id);
List<String> metrics = metricsDataList.stream().map(CollectRep.MetricsData::getMetrics).collect(Collectors.toList());
monitorDto.setMetrics(metrics);
@@ -0,0 +1,247 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# The monitoring type categoryservice-application service monitoring db-database monitoring custom-custom monitoring os-operating system monitoring
category: server
# The monitoring type eg: linux windows tomcat mysql aws...
app: dahua
# The monitoring i18n name
name:
zh-CN: 大华
en-US: Dahua
# The description and help of this monitoring type
help:
zh-CN: 通过http接口监控大华设备状态,获取设备健康数据。
en-US: Monitor Dahua devices through http interface to collect health data.
# Input params define for monitoring(render web ui by the definition)
params:
- field: host
name:
zh-CN: 主机Host
en-US: Host
type: host
required: true
- field: port
name:
zh-CN: 端口
en-US: Port
type: number
range: '[0,65535]'
required: true
defaultValue: 80
- field: timeout
name:
zh-CN: 超时时间(ms)
en-US: Timeout(ms)
type: number
range: '[1000,60000]'
required: true
defaultValue: 5000
- field: username
name:
zh-CN: 用户名
en-US: Username
type: text
required: true
- field: password
name:
zh-CN: 密码
en-US: Password
type: password
required: true
- field: ssl
name:
zh-CN: 启用HTTPS
en-US: SSL
type: boolean
required: false
defaultValue: false
# collect metrics config list
metrics:
- name: network_info # 指标集合名称
i18n:
zh-CN: 网络信息
en-US: Network Info
priority: 0
protocol: http
http:
host: ^_^host^_^
port: ^_^port^_^
ssl: ^_^ssl^_^
url: /cgi-bin/configManager.cgi?action=getConfig&name=Network
method: GET
timeout: ^_^timeout^_^
authorization:
type: Digest Auth
digestAuthUsername: ^_^username^_^
digestAuthPassword: ^_^password^_^
parseType: config
fields:
- field: default_interface
type: 1
i18n:
zh-CN: 默认网卡
en-US: Default Interface
- field: domain_name
type: 1
i18n:
zh-CN: 域名
en-US: Domain Name
- field: hostname
type: 1
i18n:
zh-CN: 主机名
en-US: Hostname
- field: eth0_ip_address
type: 1
i18n:
zh-CN: 网卡 eth0 IP地址
en-US: Interface eth0 IP Address
- field: eth0_gateway
type: 1
i18n:
zh-CN: 网卡 eth0 默认网关
en-US: Interface eth0 Default Gateway
- field: eth0_mac_address
type: 1
i18n:
zh-CN: 网卡 eth0 物理地址
en-US: Interface eth0 Physical Address
- field: eth0_subnet_mask
type: 1
i18n:
zh-CN: 网卡 eth0 子网掩码
en-US: Interface eth0 Subnet Mask
- field: eth0_mtu
type: 0
i18n:
zh-CN: 网卡 eth0 MTU
en-US: Interface eth0 MTU
- field: eth0_dns1
type: 1
i18n:
zh-CN: 网卡 eth0 DNS服务器1
en-US: Interface eth0 DNS Server 1
- field: eth0_dns2
type: 1
i18n:
zh-CN: 网卡 eth0 DNS服务器2
en-US: Interface eth0 DNS Server 2
aliasFields:
- table.Network.DefaultInterface
- table.Network.Domain
- table.Network.Hostname
- table.Network.eth0.IPAddress
- table.Network.eth0.DefaultGateway
- table.Network.eth0.PhysicalAddress
- table.Network.eth0.SubnetMask
- table.Network.eth0.MTU
- table.Network.eth0.DnsServers[0]
- table.Network.eth0.DnsServers[1]
calculates:
- default_interface=table.Network.DefaultInterface
- domain_name=table.Network.Domain
- hostname=table.Network.Hostname
- eth0_ip_address=table.Network.eth0.IPAddress
- eth0_gateway=table.Network.eth0.DefaultGateway
- eth0_mac_address=table.Network.eth0.PhysicalAddress
- eth0_subnet_mask=table.Network.eth0.SubnetMask
- eth0_mtu=table.Network.eth0.MTU
- eth0_dns1=table.Network.eth0.DnsServers[0]
- eth0_dns2=table.Network.eth0.DnsServers[1]
- name: user_info
i18n:
zh-CN: 用户信息
en-US: User Info
priority: 0
protocol: http
http:
host: ^_^host^_^
port: ^_^port^_^
ssl: ^_^ssl^_^
url: /cgi-bin/userManager.cgi?action=getActiveUserInfoAll
method: GET
timeout: ^_^timeout^_^
authorization:
type: Digest Auth
digestAuthUsername: ^_^username^_^
digestAuthPassword: ^_^password^_^
parseType: config
parseScript: users
fields:
- field: ClientAddress
type: 1
i18n:
zh-CN: 客户端地址
en-US: ClientAddress
- field: Name
type: 1
i18n:
zh-CN: 客户端用户
en-US: Name
- field: ClientType
type: 1
i18n:
zh-CN: 客户端登录类型
en-US: ClientType
- field: LoginTime
type: 1
i18n:
zh-CN: 客户端登录时间
en-US: LoginTime
- name: ntp_info
i18n:
zh-CN: 校时信息
en-US: Ntp Info
priority: 0
protocol: http
http:
host: ^_^host^_^
port: ^_^port^_^
ssl: ^_^ssl^_^
url: /cgi-bin/configManager.cgi?action=getConfig&name=NTP
method: GET
timeout: ^_^timeout^_^
authorization:
type: Digest Auth
digestAuthUsername: ^_^username^_^
digestAuthPassword: ^_^password^_^
parseType: config
fields:
- field: ntp_address
type: 1
i18n:
zh-CN: 校时服务器
en-US: Ntp Address
- field: ntp_port
type: 1
i18n:
zh-CN: 校时端口
en-US: Ntp Port
- field: ntp_update_period
type: 1
i18n:
zh-CN: 校时间隔
en-US: Ntp Update Period
aliasFields:
- table.NTP.Address
- table.NTP.Port
- table.NTP.UpdatePeriod
calculates:
- ntp_address=table.NTP.Address
- ntp_port=table.NTP.Port
- ntp_update_period=table.NTP.UpdatePeriod
@@ -0,0 +1,268 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# The monitoring type categoryservice-application service monitoring db-database monitoring custom-custom monitoring os-operating system monitoring
category: server
# The monitoring type eg: linux windows tomcat mysql aws...
app: hikvision_isapi
# The monitoring i18n name
name:
zh-CN: 海康威视 ISAPI
en-US: Hikvision ISAPI
# The description and help of this monitoring type
help:
zh-CN: 通过ISAPI接口监控海康威视设备状态,获取设备健康数据。
en-US: Monitor Hikvision devices through ISAPI interface to collect health data.
# Input params define for monitoring(render web ui by the definition)
params:
- field: host
name:
zh-CN: 主机Host
en-US: Host
type: host
required: true
- field: port
name:
zh-CN: 端口
en-US: Port
type: number
range: '[0,65535]'
required: true
defaultValue: 80
- field: timeout
name:
zh-CN: 超时时间(ms)
en-US: Timeout(ms)
type: number
range: '[1000,60000]'
required: true
defaultValue: 5000
- field: username
name:
zh-CN: 用户名
en-US: Username
type: text
required: true
- field: password
name:
zh-CN: 密码
en-US: Password
type: password
required: true
- field: ssl
name:
zh-CN: 启用HTTPS
en-US: SSL
type: boolean
required: false
defaultValue: false
# collect metrics config list
metrics:
- name: system_info
i18n:
zh-CN: 系统信息
en-US: System Info
priority: 0
protocol: http
http:
host: ^_^host^_^
port: ^_^port^_^
ssl: ^_^ssl^_^
url: /ISAPI/System/deviceInfo
method: GET
timeout: ^_^timeout^_^
authorization:
type: Digest Auth
digestAuthUsername: ^_^username^_^
digestAuthPassword: ^_^password^_^
parseType: xmlPath
parseScript: //DeviceInfo
fields:
- field: deviceName
type: 1
i18n:
zh-CN: 设备名称
en-US: Device Name
- field: deviceID
type: 1
i18n:
zh-CN: 设备ID
en-US: Device ID
- field: firmwareVersion
type: 1
i18n:
zh-CN: 固件版本
en-US: Firmware Version
- field: model
type: 1
i18n:
zh-CN: 设备型号
en-US: Device Model
- field: macAddress
type: 1
i18n:
zh-CN: mac地址
en-US: Mac Address
- name: status
i18n:
zh-CN: 设备状态
en-US: Status
priority: 0
protocol: http
http:
host: ^_^host^_^
port: ^_^port^_^
ssl: ^_^ssl^_^
url: /ISAPI/System/status
method: GET
timeout: ^_^timeout^_^
authorization:
type: Digest Auth
digestAuthUsername: ^_^username^_^
digestAuthPassword: ^_^password^_^
parseType: xmlPath
parseScript: //DeviceStatus
fields:
- field: CPU_utilization
i18n:
zh-CN: CPU 利用率
en-US: CPU Utilization
type: 0
unit: '%'
- field: memory_usage
i18n:
zh-CN: 内存使用量
en-US: Memory Usage
type: 0
unit: MB
- field: memory_available
i18n:
zh-CN: 可用内存
en-US: Memory Available
type: 0
unit: MB
- field: cache_size
i18n:
zh-CN: 缓存大小
en-US: Cache Size
type: 0
unit: MB
- field: net_port_1_speed
i18n:
zh-CN: 网口1速度
en-US: Net Port 1 Speed
type: 0
unit: Mbps
- field: net_port_2_speed
i18n:
zh-CN: 网口2速度
en-US: Net Port 2 Speed
type: 0
unit: Mbps
- field: boot_time
i18n:
zh-CN: 启动时间
en-US: Boot Time
type: 1
- field: device_uptime
i18n:
zh-CN: 运行时长
en-US: Device Uptime
type: 1
- field: last_calibration_time
i18n:
zh-CN: 上次校时时间
en-US: Last Calibration Time
type: 1
- field: last_calibration_time_diff
i18n:
zh-CN: 上次校时时间差
en-US: Last Calibration Time Diff
type: 0
unit: s
- field: avg_upload_time
i18n:
zh-CN: 平均上传耗时
en-US: Avg Upload Time
type: 0
unit: ms
- field: max_upload_time
i18n:
zh-CN: 最大上传耗时
en-US: Max Upload Time
type: 0
unit: ms
- field: min_upload_time
i18n:
zh-CN: 最小上传耗时
en-US: Min Upload Time
type: 0
unit: ms
- field: last_calibration_mode
i18n:
zh-CN: 上次校时模式
en-US: Last Calibration Mode
type: 1
- field: last_calibration_address
i18n:
zh-CN: 上次校时地址
en-US: Last Calibration Address
type: 1
- field: response_time
i18n:
zh-CN: 响应时间
en-US: Response Time
type: 0
unit: ms
aliasFields:
- CPUList/CPU/cpuUtilization
- MemoryList/Memory/memoryUsage
- MemoryList/Memory/memoryAvailable
- MemoryList/Memory/cacheSize
- NetPortStatusList/NetPortStatus[id='1']/workSpeed
- NetPortStatusList/NetPortStatus[id='2']/workSpeed
- bootTime
- deviceUpTime
- lastCalibrationTime
- lastCalibrationTimeDiff
- uploadTimeConsumingList/avgTime
- uploadTimeConsumingList/maxTime
- uploadTimeConsumingList/minTime
- lastCalibrationTimeMode
- lastCalibrationTimeAddress
- responseTime
calculates:
- CPU_utilization=CPUList/CPU/cpuUtilization
- memory_usage=MemoryList/Memory/memoryUsage
- memory_available=MemoryList/Memory/memoryAvailable
- cache_size=MemoryList/Memory/cacheSize
- net_port_1_speed=NetPortStatusList/NetPortStatus[id='1']/workSpeed
- net_port_2_speed=NetPortStatusList/NetPortStatus[id='2']/workSpeed
- boot_time=bootTime
- device_uptime=deviceUpTime
- last_calibration_time=lastCalibrationTime
- last_calibration_time_diff=lastCalibrationTimeDiff
- avg_upload_time=uploadTimeConsumingList/avgTime
- max_upload_time=uploadTimeConsumingList/maxTime
- min_upload_time=uploadTimeConsumingList/minTime
- last_calibration_mode=lastCalibrationTimeMode
- last_calibration_address=lastCalibrationTimeAddress
- response_time=responseTime
units:
- memory_usage=KB->MB
- memory_available=KB->MB
- cache_size=KB->MB
@@ -0,0 +1,263 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# The monitoring type categoryservice-application service monitoring db-database monitoring mid-middleware custom-custom monitoring os-operating system monitoring
category: service
# The monitoring type eg: linux windows tomcat mysql aws...
app: s7
# The app api i18n name
name:
zh-CN: s7服务器
en-US: s7 Server
# The description and help of this monitoring type
help:
zh-CN: HertzBeat对支持s7协议的服务进行(保持寄存器和线圈)相关指标进行采集
en-US: HertzBeat collects metrics related to maintaining registers and coils for services that support s7 protocol
zh-TW: HertzBeat對支持s7協定的服務進行(保持寄存器和線圈)相關名額進行採集
# Input params define for monitoring(render web ui by the definition)
params:
# field-param field key
- field: host
# name-param field display i18n name
name:
zh-CN: s7服务Host
en-US: s7 Server Host
# type-param field type(most mapping the html input type)
type: host
# required-true or false
required: true
# field-param field key
- field: port
# name-param field display i18n name
name:
zh-CN: 端口
en-US: Port
# type-param field type(most mapping the html input type)
type: number
# when type is number, range is required
range: '[0,65535]'
# required-true or false
required: true
# default value
defaultValue: 102
- field: rackId
# name-param field display i18n name
name:
zh-CN: rackId
en-US: rackId
# type-param field type(most mapping the html input type)
type: text
# required-true or false
required: false
defaultValue: 0
- field: slotId
# name-param field display i18n name
name:
zh-CN: slotId
en-US: slotId
# type-param field type(most mapping the html input type)
type: text
# required-true or false
required: false
defaultValue: 0
- field: controllerType
# name-param field display i18n name
name:
zh-CN: controllerType
en-US: controllerType
# type-param field type(most mapping the html input type)
type: text
# required-true or false
required: false
defaultValue: S7_1500
- field: timeout
name:
zh-CN: 请求超时时间(ms)
en-US: Request Timeout(ms)
type: number
# when type is number, range is required
range: '[400,200000]'
required: false
defaultValue: 6000
hide: true
- field: holdingRegisterAddresses
# name-param field display i18n name
name:
zh-CN: 保持寄存器地址
en-US: Holding Registers address
# type-param field type(most mapping the html input type)
type: array
# param field input placeholder
placeholder: 'Input RegisterAddress'
# required-true or false
required: true
# hide param-true or false
# hide: true
- field: coilRegisterAddresses
# name-param field display i18n name
name:
zh-CN: 线圈寄存器地址
en-US: Coil Register address
# type-param field type(most mapping the html input type)
type: array
# param field input placeholder
placeholder: 'Input RegisterAddress'
# required-true or false
required: true
# hide param-true or false
# hide: true
# collect metrics config list
metrics:
# metrics - summary
- name: holding-register
i18n:
zh-CN: 保持寄存器 统计信息
en-US: holding-register stats
# metrics scheduling priority(0->127)->(high->low), metrics with the same priority will be scheduled in parallel
# priority 0's metrics is availability metrics, it will be scheduled first, only availability metrics collect success will the scheduling continue
priority: 0
# collect metrics content
fields:
- field: responseTime
type: 0
unit: ms
i18n:
zh-CN: 响应时间
en-US: Response Time
# field-metric name, type-metric type(0-number,1-string), unit-metric unit('%','ms','MB'), label-whether it is a metrics label field
- field: address-0
type: 0
i18n:
zh-CN: address-0
en-US: address-0
# field-metric name, type-metric type(0-number,1-string), unit-metric unit('%','ms','MB'), label-whether it is a metrics label field
- field: address-1
type: 0
i18n:
zh-CN: address-1
en-US: address-1
- field: address-2
type: 0
i18n:
zh-CN: address-2
en-US: address-2
- field: address-3
type: 0
i18n:
zh-CN: address-3
en-US: address-3
# 指标别名列表,按照寄存器地址来进行命名的
# 例如 地址为下标为0 ,值为m holding-register:0
# 地址为下标为1,值为m[2] holding-register:1-0 holding-register:1-1
aliasFields:
- responseTime
- holding-register:0
- holding-register:1
- holding-register:2
- holding-register:3
# mapping and conversion expressions, use thesand aliasField above to calculate metrics value# (可选)指标映射转换计算表达式,与上面的别名一起作用,计算出最终需要的指标值# eg: cores=core1+core2, usage=usage, waitTimeallTime-runningTime
calculates:
- responseTime=responseTime
- address-0=holding-register:0
- address-1=holding-register:1
- address-2=holding-register:2
- address-3=holding-register:3
protocol: s7
# the config content when protocol is http
s7:
# host
host: ^_^host^_^
# port
port: ^_^port^_^
driverName: s7
addressSyntax: holding-register
rackId: ^_^rackId^_^
slotId: ^_^slotId^_^
controllerType: ^_^controllerType^_^
timeout: ^_^timeout^_^
registerAddresses: [ ^_^holdingRegisterAddresses^_^ ]
# metrics - summary
- name: coil
i18n:
zh-CN: 线圈 统计信息
en-US: coil stats
# metrics scheduling priority(0->127)->(high->low), metrics with the same priority will be scheduled in parallel
# priority 0's metrics is availability metrics, it will be scheduled first, only availability metrics collect success will the scheduling continue
priority: 1
# collect metrics content
fields:
- field: responseTime
type: 0
unit: ms
i18n:
zh-CN: 响应时间
en-US: Response Time
# field-metric name, type-metric type(0-number,1-string), unit-metric unit('%','ms','MB'), label-whether it is a metrics label field
- field: address-0
type: 0
i18n:
zh-CN: address-0
en-US: address-0
# field-metric name, type-metric type(0-number,1-string), unit-metric unit('%','ms','MB'), label-whether it is a metrics label field
- field: address-1
type: 0
i18n:
zh-CN: address-1
en-US: address-1
- field: address-2
type: 0
i18n:
zh-CN: address-2
en-US: address-2
- field: address-3
type: 0
i18n:
zh-CN: address-3
en-US: address-3
# 指标别名列表,按照寄存器地址来进行命名的
# 例如 地址为下标为0 ,值为m coil:0
# 地址为下标为1,值为m[2] coil:1-0 coil:1-1
aliasFields:
- responseTime
- coil:0
- coil:1
- coil:2
- coil:3
# mapping and conversion expressions, use thesand aliasField above to calculate metrics value# (可选)指标映射转换计算表达式,与上面的别名一起作用,计算出最终需要的指标值# eg: cores=core1+core2, usage=usage, waitTimeallTime-runningTime
calculates:
- responseTime=responseTime
- address-0=coil:0
- address-1=coil:1
- address-2=coil:2
- address-3=coil:3
protocol: s7
# the config content when protocol is http
s7:
# host
host: ^_^host^_^
# port
port: ^_^port^_^
driverName: s7
addressSyntax: coil
rackId: ^_^rackId^_^
slotId: ^_^slotId^_^
controllerType: ^_^controllerType^_^
timeout: ^_^timeout^_^
registerAddresses: [ ^_^coilRegisterAddresses^_^ ]
@@ -0,0 +1,154 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# The monitoring type categoryservice-application service monitoring db-database monitoring custom-custom monitoring os-operating system monitoring
category: server
# The monitoring type eg: linux windows tomcat mysql aws...
app: uniview
# The monitoring i18n name
name:
zh-CN: 宇视
en-US: Uniview
# The description and help of this monitoring type
help:
zh-CN: 通过HTTP接口监控宇视设备状态,获取设备健康数据。
en-US: Monitor Uniview devices through HTTP interface to collect health data.
# Input params define for monitoring(render web ui by the definition)
params:
- field: host
name:
zh-CN: 主机Host
en-US: Host
type: host
required: true
- field: port
name:
zh-CN: 端口
en-US: Port
type: number
range: '[0,65535]'
required: true
defaultValue: 80
- field: timeout
name:
zh-CN: 超时时间(ms)
en-US: Timeout(ms)
type: number
range: '[1000,60000]'
required: true
defaultValue: 5000
- field: username
name:
zh-CN: 用户名
en-US: Username
type: text
required: true
- field: password
name:
zh-CN: 密码
en-US: Password
type: password
required: true
- field: ssl
name:
zh-CN: 启用HTTPS
en-US: SSL
type: boolean
required: false
defaultValue: false
# collect metrics config list
metrics:
- name: system_info
i18n:
zh-CN: 系统信息
en-US: System Info
priority: 0
protocol: http
http:
host: ^_^host^_^
port: ^_^port^_^
ssl: ^_^ssl^_^
url: /LAPI/V1.0/System/DeviceInfo
method: GET
timeout: ^_^timeout^_^
authorization:
type: Digest Auth
digestAuthUsername: ^_^username^_^
digestAuthPassword: ^_^password^_^
parseType: jsonPath
parseScript: '$.Response.Data'
fields:
- field: DeviceName
type: 1
i18n:
zh-CN: 设备名称
en-US: Device Name
- field: SerialNumber
type: 1
i18n:
zh-CN: 序列号
en-US: SerialNumber
- field: FirmwareVersion
type: 1
i18n:
zh-CN: 固件版本
en-US: Firmware Version
- field: DeviceModel
type: 1
i18n:
zh-CN: 设备型号
en-US: Device Model
- name: ntp_info
i18n:
zh-CN: 校时信息
en-US: NTP Info
priority: 0
protocol: http
http:
host: ^_^host^_^
port: ^_^port^_^
ssl: ^_^ssl^_^
url: /LAPI/V1.0/System/Time/NTP
method: GET
timeout: ^_^timeout^_^
authorization:
type: Digest Auth
digestAuthUsername: ^_^username^_^
digestAuthPassword: ^_^password^_^
parseType: jsonPath
parseScript: '$.Response.Data.NTPServerInfos[0]'
fields:
- field: IPAddress
type: 1
i18n:
zh-CN: IP地址
en-US: IPAddress
- field: Port
type: 1
i18n:
zh-CN: 端口号
en-US: Port
- field: SynchronizeInterval
type: 1
i18n:
zh-CN: 同步间隔
en-US: SynchronizeInterval
- field: Enabled
type: 1
i18n:
zh-CN: 是否启用
en-US: Enabled
@@ -76,6 +76,26 @@
</filter>
</appender>
<!-- OpenTelemetry Appender for shipping logs to GrepTimeDB -->
<appender name="OpenTelemetryAppender" class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender">
<!-- Capture source code information (class name, method name, line number) -->
<captureCodeAttributes>true</captureCodeAttributes>
<!-- Capture MDC context values -->
<captureMdcAttributes>
<pattern>.*</pattern>
</captureMdcAttributes>
<!-- Capture experimental attributes like exception details -->
<captureExperimentalAttributes>true</captureExperimentalAttributes>
<!-- Capture marker attributes -->
<captureMarkerAttribute>true</captureMarkerAttribute>
<!-- Capture logger context attributes -->
<captureLoggerContext>true</captureLoggerContext>
</appender>
<!-- Settings for this logger: for example, all output logs under the org.springframework package must be at level info or above to be output! -->
<!-- This can avoid outputting many common debug information of the spring framework! -->
<logger name="org.springframework" level="info"/>
@@ -90,12 +110,14 @@
<logger name="org.mongodb" level="warn"/>
<logger name="io.greptime" level="warn"/>
<logger name="org.apache.kafka" level="warn"/>
<logger name="io.opentelemetry" level="info"/>
<!-- Production environment configuration -->
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="SystemOutFileAppender"/>
<appender-ref ref="ErrOutFileAppender"/>
<appender-ref ref="OpenTelemetryAppender"/>
</root>
<!-- North log -->
<Logger name="com.obs.services.AbstractClient" level="OFF"
@@ -118,6 +140,7 @@
<appender-ref ref="ConsoleAppender"/>
<appender-ref ref="SystemOutFileAppender"/>
<appender-ref ref="ErrOutFileAppender"/>
<appender-ref ref="OpenTelemetryAppender"/>
</root>
</springProfile>
@@ -127,6 +150,7 @@
<appender-ref ref="ConsoleAppender"/>
<appender-ref ref="SystemOutFileAppender"/>
<appender-ref ref="ErrOutFileAppender"/>
<appender-ref ref="OpenTelemetryAppender"/>
</root>
</springProfile>
@@ -136,7 +160,8 @@
<appender-ref ref="ConsoleAppender"/>
<appender-ref ref="SystemOutFileAppender"/>
<appender-ref ref="ErrOutFileAppender"/>
<appender-ref ref="OpenTelemetryAppender"/>
</root>
</springProfile>
</configuration>
</configuration>
+3 -2
View File
@@ -29,10 +29,11 @@
<name>${project.artifactId}</name>
<dependencies>
<!-- common -->
<!-- collector basic -->
<dependency>
<groupId>org.apache.hertzbeat</groupId>
<artifactId>hertzbeat-common</artifactId>
<artifactId>hertzbeat-collector-basic</artifactId>
<version>2.0-SNAPSHOT</version>
</dependency>
<!-- spring -->
<dependency>
@@ -0,0 +1,42 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.hertzbeat.push.config;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import lombok.Getter;
/**
* push error request wrapper
*/
@Getter
public class PushErrorRequestWrapper extends HttpServletRequestWrapper {
private final String job;
private final String instance;
public PushErrorRequestWrapper(HttpServletRequest request, String job, String instance) {
super(request);
this.job = job;
this.instance = instance;
}
}
@@ -0,0 +1,47 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.hertzbeat.push.config;
import org.apache.hertzbeat.push.service.PushGatewayService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
*
*/
@Configuration
public class PushFilterConfig {
@Autowired
private PushGatewayService pushGatewayService;
private static final String URI_PREFIX = "/api/push/prometheus/*";
@Bean
public FilterRegistrationBean<PushPrometheusStreamReadingFilter> contentTypeFilter() {
FilterRegistrationBean<PushPrometheusStreamReadingFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new PushPrometheusStreamReadingFilter(pushGatewayService));
registrationBean.addUrlPatterns(URI_PREFIX);
registrationBean.setOrder(Integer.MIN_VALUE);
return registrationBean;
}
}
@@ -0,0 +1,82 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.hertzbeat.push.config;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.hertzbeat.push.service.PushGatewayService;
/**
* todo
*/
public class PushPrometheusStreamReadingFilter implements Filter {
private final PushGatewayService pushGatewayService;
private final Pattern pathPattern = Pattern.compile("^/api/push/prometheus/job/([a-zA-Z0-9_]*)(?:/instance/([a-zA-Z0-9_]*))?$");
public PushPrometheusStreamReadingFilter(PushGatewayService pushGatewayService) {
this.pushGatewayService = pushGatewayService;
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (request instanceof HttpServletRequest httpRequest) {
String uri = httpRequest.getRequestURI();
Matcher matcher = pathPattern.matcher(uri);
String job = null;
String instance = null;
if (matcher.matches()) {
job = matcher.group(1);
instance = matcher.group(2);
boolean flag = pushGatewayService.pushPrometheusMetrics(request.getInputStream(), job, instance);
if (flag) {
PushSuccessRequestWrapper successRequestWrapper = new PushSuccessRequestWrapper(httpRequest, job, instance);
chain.doFilter(successRequestWrapper, response);
} else {
PushErrorRequestWrapper errorRequestWrapper = new PushErrorRequestWrapper(httpRequest, job, instance);
chain.doFilter(errorRequestWrapper, response);
}
} else {
chain.doFilter(request, response);
}
} else {
chain.doFilter(request, response);
}
}
@Override
public void destroy() {}
}
@@ -0,0 +1,41 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.hertzbeat.push.config;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import lombok.Getter;
/**
* push success request wrapper
*/
@Getter
public class PushSuccessRequestWrapper extends HttpServletRequestWrapper {
private final String job;
private final String instance;
public PushSuccessRequestWrapper(HttpServletRequest request, String job, String instance) {
super(request);
this.job = job;
this.instance = instance;
}
}
@@ -22,11 +22,9 @@ package org.apache.hertzbeat.push.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import org.apache.hertzbeat.common.entity.dto.Message;
import org.apache.hertzbeat.push.service.PushGatewayService;
import org.springframework.beans.factory.annotation.Autowired;
import org.apache.hertzbeat.push.config.PushErrorRequestWrapper;
import org.apache.hertzbeat.push.config.PushSuccessRequestWrapper;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -37,22 +35,23 @@ import org.springframework.web.bind.annotation.RestController;
*/
@Tag(name = "Metrics Push Gateway API")
@RestController
@RequestMapping(value = "/api/push/pushgateway")
public class PushGatewayController {
@Autowired
private PushGatewayService pushGatewayService;
@RequestMapping(value = "/api/push/prometheus/**")
public class PushPrometheusController {
@PostMapping()
@Operation(summary = "Push metric data to hertzbeat pushgateway", description = "Push metric data to hertzbeat pushgateway")
public ResponseEntity<Message<Void>> pushMetrics(HttpServletRequest request) throws IOException {
InputStream inputStream = request.getInputStream();
boolean result = pushGatewayService.pushMetricsData(inputStream);
if (result) {
return ResponseEntity.ok(Message.success("Push success"));
@Operation(summary = "Prometheus push gateway", description = "Push prometheus metric data to hertzbeat")
public ResponseEntity<Message<Void>> pushMetrics(HttpServletRequest request) {
if (request instanceof PushErrorRequestWrapper error) {
return ResponseEntity.badRequest().body(Message.success(String.format("Push failed, job: %s, instance: %s",
error.getJob(), error.getInstance())));
}
else if (request instanceof PushSuccessRequestWrapper success) {
return ResponseEntity.ok(Message.success(String.format("Push success, job: %s, instance: %s",
success.getJob(), success.getInstance())));
}
else {
return ResponseEntity.ok(Message.success("Push failed"));
return ResponseEntity.badRequest()
.body(Message.success(String.format("Request %s not matched.", request.getRequestURI())));
}
}
@@ -17,6 +17,7 @@
package org.apache.hertzbeat.push.dao;
import java.util.List;
import org.apache.hertzbeat.common.entity.manager.Monitor;
import org.springframework.data.jpa.repository.JpaRepository;
@@ -24,4 +25,11 @@ import org.springframework.data.jpa.repository.JpaRepository;
* push monitor dao
*/
public interface PushMonitorDao extends JpaRepository<Monitor, Long> {
/**
* Find all monitoring entities by type
* @param type Monitoring type
* @return Monitoring entity list
*/
List<Monitor> findMonitorsByType(byte type);
}
@@ -19,17 +19,24 @@
package org.apache.hertzbeat.push.service;
import java.io.IOException;
import java.io.InputStream;
import org.springframework.stereotype.Service;
/**
* push gateway metrics
* push gateway service
*/
@Service
public interface PushGatewayService {
boolean pushMetricsData(InputStream inputStream) throws IOException;
/**
* push prometheus metrics data
* @param inputStream input stream
* @param job job name, maybe null
* @param instance instance name, maybe null
* @return push success or not
*/
boolean pushPrometheusMetrics(InputStream inputStream, String job, String instance);
}
@@ -19,9 +19,23 @@
package org.apache.hertzbeat.push.service.impl;
import java.io.IOException;
import java.io.InputStream;
import java.time.Instant;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.collector.collect.prometheus.parser.MetricFamily;
import org.apache.hertzbeat.collector.collect.prometheus.parser.OnlineParser;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.manager.Monitor;
import org.apache.hertzbeat.common.entity.message.CollectRep;
import org.apache.hertzbeat.common.queue.CommonDataQueue;
import org.apache.hertzbeat.common.util.SnowFlakeIdGenerator;
import org.apache.hertzbeat.push.dao.PushMonitorDao;
import org.apache.hertzbeat.push.service.PushGatewayService;
import org.springframework.stereotype.Service;
@@ -32,9 +46,88 @@ import org.springframework.stereotype.Service;
@Slf4j
@Service
public class PushGatewayServiceImpl implements PushGatewayService {
private final CommonDataQueue commonDataQueue;
private final PushMonitorDao pushMonitorDao;
private final Map<String, Long> jobInstanceMap;
public PushGatewayServiceImpl(CommonDataQueue commonDataQueue, PushMonitorDao pushMonitorDao) {
this.commonDataQueue = commonDataQueue;
this.pushMonitorDao = pushMonitorDao;
jobInstanceMap = new ConcurrentHashMap<>();
pushMonitorDao.findMonitorsByType((byte) 1).forEach(monitor ->
jobInstanceMap.put(monitor.getApp() + "_" + monitor.getName(), monitor.getId()));
}
@Override
public boolean pushMetricsData(InputStream inputStream) throws IOException {
return true;
public boolean pushPrometheusMetrics(InputStream inputStream, String job, String instance) {
try {
long curTime = Instant.now().toEpochMilli();
Map<String, MetricFamily> metricFamilyMap = OnlineParser.parseMetrics(inputStream);
if (metricFamilyMap == null) {
log.error("parse prometheus metrics is null, job: {}, instance: {}", job, instance);
return false;
}
long id = 0L;
if (job != null && instance != null) {
// auto create monitor when job and instance not null
// job is app, instance is the name
id = jobInstanceMap.computeIfAbsent(job + "_" + instance, key -> {
log.info("auto create monitor by prometheus push, job: {}, instance: {}", job, instance);
long monitorId = SnowFlakeIdGenerator.generateId();
Monitor monitor = Monitor.builder()
.id(monitorId)
.app(job)
.name(instance)
.host(instance)
.type((byte) 1)
.status(CommonConstants.MONITOR_UP_CODE)
.build();
this.pushMonitorDao.save(monitor);
return monitorId;
});
}
for (Map.Entry<String, MetricFamily> entry : metricFamilyMap.entrySet()) {
CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder();
builder.setId(id);
builder.setApp(job);
builder.setTime(curTime);
String metricsName = entry.getKey();
builder.setMetrics(metricsName);
MetricFamily metricFamily = entry.getValue();
if (!metricFamily.getMetricList().isEmpty()) {
List<String> metricsFields = new LinkedList<>();
for (int index = 0; index < metricFamily.getMetricList().size(); index++) {
MetricFamily.Metric metric = metricFamily.getMetricList().get(index);
if (index == 0) {
metric.getLabels().forEach(label -> {
metricsFields.add(label.getName());
builder.addField(CollectRep.Field.newBuilder().setName(label.getName())
.setType(CommonConstants.TYPE_STRING).setLabel(true).build());
});
builder.addField(CollectRep.Field.newBuilder().setName("value")
.setType(CommonConstants.TYPE_NUMBER).setLabel(false).build());
}
Map<String, String> labelMap = metric.getLabels()
.stream()
.collect(Collectors.toMap(MetricFamily.Label::getName, MetricFamily.Label::getValue));
CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder();
for (String field : metricsFields) {
String fieldValue = labelMap.get(field);
valueRowBuilder.addColumn(fieldValue == null ? CommonConstants.NULL_VALUE : fieldValue);
}
valueRowBuilder.addColumn(String.valueOf(metric.getValue()));
builder.addValueRow(valueRowBuilder.build());
}
commonDataQueue.sendMetricsData(builder.build());
}
}
return true;
} catch (Exception e) {
log.error("push prometheus metrics error", e);
return false;
}
}
}
@@ -1,143 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.push.service.impl;
import com.fasterxml.jackson.core.type.TypeReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Timer;
import java.util.TimerTask;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.entity.manager.Monitor;
import org.apache.hertzbeat.common.entity.push.PushMetrics;
import org.apache.hertzbeat.common.entity.push.PushMetricsDto;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.apache.hertzbeat.push.dao.PushMetricsDao;
import org.apache.hertzbeat.push.dao.PushMonitorDao;
import org.apache.hertzbeat.push.service.PushService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* push service impl
*/
@Slf4j
@Service
public class PushServiceImpl implements PushService {
@Autowired
private PushMonitorDao monitorDao;
@Autowired
private PushMetricsDao metricsDao;
private final Map<Long, Long> monitorIdCache; // key: monitorId, value: time stamp of last query
private static final long cacheTimeout = 5000L; // ms
private final Map<Long, PushMetricsDto.Metrics> lastPushMetrics;
private static final long deleteMetricsPeriod = 1000 * 60 * 60 * 12L;
private static final long deleteBeforeTime = deleteMetricsPeriod / 2;
public PushServiceImpl(){
monitorIdCache = new HashMap<>();
lastPushMetrics = new HashMap<>();
new Timer().schedule(new TimerTask() {
@Override
public void run() {
try {
deletePeriodically();
} catch (Exception e) {
log.error("periodical deletion failed. {}", e.getMessage());
}
}
}, 1000, deleteMetricsPeriod);
}
public void deletePeriodically(){
metricsDao.deleteAllByTimeBefore(System.currentTimeMillis() - deleteBeforeTime);
}
@Override
public void pushMetricsData(PushMetricsDto pushMetricsDto) throws RuntimeException {
List<PushMetrics> pushMetricsList = new ArrayList<>();
long curTime = System.currentTimeMillis();
for (PushMetricsDto.Metrics metrics : pushMetricsDto.getMetricsList()) {
long monitorId = metrics.getMonitorId();
metrics.setTime(curTime);
if (!monitorIdCache.containsKey(monitorId) || (monitorIdCache.containsKey(monitorId) && curTime > monitorIdCache.get(monitorId) + cacheTimeout)) {
Optional<Monitor> queryOption = monitorDao.findById(monitorId);
if (queryOption.isEmpty()) {
monitorIdCache.remove(monitorId);
continue;
}
monitorIdCache.put(monitorId, curTime);
}
PushMetrics pushMetrics = PushMetrics.builder()
.monitorId(metrics.getMonitorId())
.time(curTime)
.metrics(JsonUtil.toJson(metrics.getMetrics())).build();
lastPushMetrics.put(monitorId, metrics);
pushMetricsList.add(pushMetrics);
}
metricsDao.saveAll(pushMetricsList);
}
@Override
public PushMetricsDto getPushMetricData(final Long monitorId, final Long time) {
PushMetricsDto.Metrics metrics;
PushMetricsDto pushMetricsDto = new PushMetricsDto();
if (lastPushMetrics.containsKey(monitorId)) {
metrics = lastPushMetrics.get(monitorId);
}
else {
try {
PushMetrics pushMetrics = metricsDao.findFirstByMonitorIdOrderByTimeDesc(monitorId);
if (pushMetrics == null || pushMetrics.getMetrics() == null) {
return pushMetricsDto;
}
List<Map<String, String>> jsonMap = JsonUtil.fromJson(pushMetrics.getMetrics(), new TypeReference<>() {
});
metrics = PushMetricsDto.Metrics.builder().monitorId(monitorId).metrics(jsonMap).time(pushMetrics.getTime()).build();
lastPushMetrics.put(monitorId, metrics);
}
catch (Exception e) {
log.error("no metrics found, monitor id: {}, {}", monitorId, e.getMessage(), e);
return pushMetricsDto;
}
}
if (time > metrics.getTime()) {
// return void because time param is invalid
return pushMetricsDto;
}
pushMetricsDto.getMetricsList().add(metrics);
return pushMetricsDto;
}
}
@@ -1,93 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.hertzbeat.push.controller;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.push.PushMetricsDto;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.apache.hertzbeat.push.service.PushService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
/**
* test case for {@link PushController}
*/
@ExtendWith(MockitoExtension.class)
class PushControllerTest {
private MockMvc mockMvc;
@Mock
private PushService pushService;
@InjectMocks
private PushController pushController;
private PushMetricsDto mockPushMetricsDto;
@BeforeEach
void setUp() {
this.mockMvc = standaloneSetup(this.pushController).build();
mockPushMetricsDto = PushMetricsDto.builder().build();
}
@Test
void testPushMetrics() throws Exception {
this.mockMvc.perform(MockMvcRequestBuilders.post("/api/push")
.contentType(MediaType.APPLICATION_JSON)
.content(JsonUtil.toJson(mockPushMetricsDto)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andReturn();
}
@Test
void testGetMetrics() throws Exception {
Long id = 6565463543L;
Long time = 6565463543L;
when(pushService.getPushMetricData(id, time)).thenReturn(mockPushMetricsDto);
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/push")
.contentType(MediaType.APPLICATION_JSON)
.param("id", id.toString())
.param("time", time.toString()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andReturn();
}
}
@@ -22,12 +22,11 @@ package org.apache.hertzbeat.push.controller;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.io.InputStream;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.push.service.PushGatewayService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
@@ -38,11 +37,12 @@ import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
/**
* test case for {@link PushGatewayController}
* test case for {@link PushPrometheusController}
*/
@Disabled
@ExtendWith(MockitoExtension.class)
class PushGatewayControllerTest {
class PushPrometheusControllerTest {
private MockMvc mockMvc;
@@ -50,7 +50,7 @@ class PushGatewayControllerTest {
private PushGatewayService pushGatewayService;
@InjectMocks
private PushGatewayController gatewayController;
private PushPrometheusController gatewayController;
@BeforeEach
void setUp() {
@@ -63,14 +63,12 @@ class PushGatewayControllerTest {
String mockData = "some metric data";
when(pushGatewayService.pushMetricsData(any(InputStream.class))).thenReturn(true);
when(pushGatewayService.pushPrometheusMetrics(any(InputStream.class), any(), any())).thenReturn(true);
mockMvc.perform(post("/api/push/pushgateway")
.contentType(MediaType.APPLICATION_JSON)
.content(mockData))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
.andExpect(jsonPath("$.msg").value("Push success"));
.andExpect(status().isOk());
}
@Test
@@ -78,13 +76,12 @@ class PushGatewayControllerTest {
String mockData = "some metric data";
when(pushGatewayService.pushMetricsData(any(InputStream.class))).thenReturn(false);
when(pushGatewayService.pushPrometheusMetrics(any(InputStream.class), any(), any())).thenReturn(false);
mockMvc.perform(post("/api/push/pushgateway")
.contentType(MediaType.APPLICATION_JSON)
.content(mockData))
.andExpect(status().isOk())
.andExpect(jsonPath("$.msg").value("Push failed"));
.andExpect(status().isOk());
}
}
@@ -1,77 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hertzbeat.push.dao;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.apache.hertzbeat.common.entity.push.PushMetrics;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.jupiter.MockitoExtension;
/**
* test case for {@link PushMetricsDao}
*/
@ExtendWith(MockitoExtension.class)
public class PushMetricsDaoTest {
@Mock
private PushMetricsDao pushMetricsDao;
@InjectMocks
private PushMetricsDaoTest pushMetricsDaoTest;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
void shallFindFirstByMonitorIdOrderByTimeDesc() {
PushMetrics expectedMetrics = new PushMetrics();
expectedMetrics.setMonitorId(1L);
expectedMetrics.setTime(System.currentTimeMillis());
when(pushMetricsDao.findFirstByMonitorIdOrderByTimeDesc(1L)).thenReturn(expectedMetrics);
PushMetrics actualMetrics = pushMetricsDao.findFirstByMonitorIdOrderByTimeDesc(1L);
assertEquals(expectedMetrics, actualMetrics);
verify(pushMetricsDao, times(1)).findFirstByMonitorIdOrderByTimeDesc(1L);
}
@Test
void shallDeleteAllByTimeBefore() {
doNothing().when(pushMetricsDao).deleteAllByTimeBefore(anyLong());
pushMetricsDao.deleteAllByTimeBefore(1000L);
verify(pushMetricsDao, times(1)).deleteAllByTimeBefore(1000L);
}
}
@@ -1,132 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.hertzbeat.push.service;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.apache.hertzbeat.common.entity.manager.Monitor;
import org.apache.hertzbeat.common.entity.push.PushMetrics;
import org.apache.hertzbeat.common.entity.push.PushMetricsDto;
import org.apache.hertzbeat.push.dao.PushMetricsDao;
import org.apache.hertzbeat.push.dao.PushMonitorDao;
import org.apache.hertzbeat.push.service.impl.PushServiceImpl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;
/**
* test case for {@link PushServiceImpl}
*/
@ExtendWith(MockitoExtension.class)
class PushServiceTest {
@Mock
private PushMonitorDao monitorDao;
@Mock
private PushMetricsDao metricsDao;
@InjectMocks
private PushServiceImpl pushService;
@BeforeEach
void setUp() {
pushService = new PushServiceImpl();
ReflectionTestUtils.setField(pushService, "monitorDao", monitorDao);
ReflectionTestUtils.setField(pushService, "metricsDao", metricsDao);
}
@Test
void testPushMetricsData() {
PushMetricsDto pushMetricsDto = new PushMetricsDto();
List<PushMetricsDto.Metrics> metricsList = new ArrayList<>();
PushMetricsDto.Metrics metrics = new PushMetricsDto.Metrics();
metrics.setMonitorId(1L);
metricsList.add(metrics);
pushMetricsDto.setMetricsList(metricsList);
when(monitorDao.findById(anyLong())).thenReturn(Optional.of(new Monitor()));
pushService.pushMetricsData(pushMetricsDto);
verify(metricsDao, times(1)).saveAll(any());
}
@Test
void testGetPushMetricData() {
Long monitorId = 1L;
Long time = System.currentTimeMillis();
PushMetrics pushMetrics = PushMetrics.builder()
.monitorId(monitorId)
.time(time)
.metrics("[{\"key\":\"value\"}]")
.build();
when(metricsDao.findFirstByMonitorIdOrderByTimeDesc(monitorId)).thenReturn(pushMetrics);
PushMetricsDto result = pushService.getPushMetricData(monitorId, time);
assertEquals(1, result.getMetricsList().size());
assertEquals(monitorId, result.getMetricsList().get(0).getMonitorId());
}
@Test
void testGetPushMetricDataTimeInvalid() {
Long monitorId = 1L;
Long time = System.currentTimeMillis() + 10000;
PushMetrics pushMetrics = PushMetrics.builder()
.monitorId(monitorId)
.time(System.currentTimeMillis())
.metrics("[{\"key\":\"value\"}]")
.build();
when(metricsDao.findFirstByMonitorIdOrderByTimeDesc(monitorId)).thenReturn(pushMetrics);
PushMetricsDto result = pushService.getPushMetricData(monitorId, time);
assertTrue(result.getMetricsList().isEmpty());
}
@Test
void testDeletePeriodically() {
pushService.deletePeriodically();
verify(metricsDao, times(1)).deleteAllByTimeBefore(anyLong());
}
}
@@ -56,4 +56,12 @@ public interface WarehouseConstants {
String MEMORY = "memory";
}
String PROMQL = "promql";
String SQL = "sql";
String RANGE = "range";
String INSTANT = "instant";
}
@@ -15,48 +15,39 @@
* limitations under the License.
*/
package org.apache.hertzbeat.push.controller;
package org.apache.hertzbeat.warehouse.controller;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import org.apache.hertzbeat.common.entity.dto.Message;
import org.apache.hertzbeat.common.entity.push.PushMetricsDto;
import org.apache.hertzbeat.push.service.PushService;
import org.apache.hertzbeat.common.entity.dto.query.DatasourceQuery;
import org.apache.hertzbeat.common.entity.dto.query.DatasourceQueryData;
import org.apache.hertzbeat.warehouse.service.DatasourceQueryService;
import org.springframework.beans.factory.annotation.Autowired;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* push controller
* Metrics Data Query API
*/
@Tag(name = "Metrics Push API")
@RestController
@RequestMapping(value = "/api/push", produces = {APPLICATION_JSON_VALUE})
public class PushController {
@RequestMapping(produces = {APPLICATION_JSON_VALUE})
@Tag(name = "Metrics Data Query API")
public class DataQueryController {
@Autowired(required = false)
private DatasourceQueryService datasourceQueryService;
@Autowired
private PushService pushService;
@PostMapping
@Operation(summary = "Push metric data to hertzbeat", description = "Push metric data to hertzbeat")
public ResponseEntity<Message<Void>> pushMetrics(@RequestBody PushMetricsDto pushMetricsDto) {
pushService.pushMetricsData(pushMetricsDto);
return ResponseEntity.ok(Message.success("Push success"));
}
@GetMapping()
@Operation(summary = "Get metric data for hertzbeat", description = "Get metric data for hertzbeat")
public ResponseEntity<Message<PushMetricsDto>> getMetrics(
@Parameter(description = "Monitor ID", example = "6565463543") @RequestParam("id") final Long id,
@Parameter(description = "Last pull time", example = "6565463543") @RequestParam("time") final Long time) {
PushMetricsDto pushMetricsDto = pushService.getPushMetricData(id, time);
return ResponseEntity.ok(Message.success(pushMetricsDto));
@PostMapping("/api/warehouse/query")
@Operation(summary = "Warehouse Query")
public ResponseEntity<Message<List<DatasourceQueryData>>> query(
@Parameter(description = "Query Expr") @RequestBody List<DatasourceQuery> queries) {
return ResponseEntity.ok(Message.success(datasourceQueryService.query(queries)));
}
}
@@ -18,29 +18,12 @@
package org.apache.hertzbeat.warehouse.db;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.constants.NetworkConstants;
import org.apache.hertzbeat.common.constants.SignConstants;
import org.apache.hertzbeat.common.util.Base64Util;
import org.apache.hertzbeat.warehouse.store.history.greptime.GreptimeProperties;
import org.apache.hertzbeat.warehouse.store.history.vm.PromQlQueryContent;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
/**
* query executor for victor metrics
@@ -48,71 +31,23 @@ import org.springframework.web.util.UriComponentsBuilder;
@Component
@ConditionalOnProperty(prefix = "warehouse.store.greptime", name = "enabled", havingValue = "true")
@Slf4j
public class GreptimePromqlQueryExecutor implements QueryExecutor {
public class GreptimePromqlQueryExecutor extends PromqlQueryExecutor {
private static final String QUERY_PATH = "/v1/prometheus/api/v1/query";
private static final String QUERY_PATH = "/v1/prometheus";
private static final String Datasource = "Greptime";
private final GreptimeProperties greptimeProperties;
private final RestTemplate restTemplate;
public GreptimePromqlQueryExecutor(GreptimeProperties greptimeProperties, RestTemplate restTemplate) {
super(restTemplate, new HttpPromqlProperties(greptimeProperties.httpEndpoint() + QUERY_PATH,
greptimeProperties.username(), greptimeProperties.password()));
this.greptimeProperties = greptimeProperties;
this.restTemplate = restTemplate;
}
@Override
public List<Map<String, Object>> execute(String query) {
// http run the promql query
List<Map<String, Object>> results = new LinkedList<>();
try {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(List.of(MediaType.APPLICATION_JSON));
if (StringUtils.hasText(greptimeProperties.username())
&& StringUtils.hasText(greptimeProperties.password())) {
String authStr = greptimeProperties.username() + ":" + greptimeProperties.password();
String encodedAuth = Base64Util.encode(authStr);
headers.add(HttpHeaders.AUTHORIZATION, NetworkConstants.BASIC + SignConstants.BLANK + encodedAuth);
}
HttpEntity<Void> httpEntity = new HttpEntity<>(headers);
URI uri = UriComponentsBuilder.fromHttpUrl(greptimeProperties.httpEndpoint() + QUERY_PATH)
.queryParam("query", URLEncoder.encode(query, StandardCharsets.UTF_8))
.build(true).toUri();
ResponseEntity<PromQlQueryContent> responseEntity = restTemplate.exchange(uri,
HttpMethod.GET, httpEntity, PromQlQueryContent.class);
if (responseEntity.getStatusCode().is2xxSuccessful()) {
if (responseEntity.getBody() != null && responseEntity.getBody().getData() != null
&& responseEntity.getBody().getData().getResult() != null) {
List<PromQlQueryContent.ContentData.Content> contents = responseEntity.getBody().getData().getResult();
for (PromQlQueryContent.ContentData.Content content : contents) {
Map<String, String> labels = content.getMetric();
Map<String, Object> queryResult = new HashMap<>(8);
queryResult.putAll(labels);
if (content.getValue() != null && content.getValue().length == 2) {
queryResult.put("__timestamp__", content.getValue()[0]);
queryResult.put("__value__", content.getValue()[1]);
} else if (content.getValues() != null && !content.getValues().isEmpty()) {
List<Object> values = new LinkedList<>();
for (Object[] valueArr : content.getValues()) {
values.add(valueArr[1]);
}
queryResult.put("__value__", values);
}
results.add(queryResult);
}
}
} else {
log.error("query metrics data from greptime failed. {}", responseEntity);
}
} catch (Exception e) {
log.error(e.toString(), e);
}
return results;
public String getDatasource() {
return Datasource;
}
@Override
public boolean support(String datasource) {
return "promql".equals(datasource);
}
}
@@ -0,0 +1,217 @@
/*
* 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.warehouse.db;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.hertzbeat.common.constants.NetworkConstants;
import org.apache.hertzbeat.common.constants.SignConstants;
import org.apache.hertzbeat.common.entity.dto.query.DatasourceQuery;
import org.apache.hertzbeat.common.entity.dto.query.DatasourceQueryData;
import org.apache.hertzbeat.common.util.Base64Util;
import org.apache.hertzbeat.common.util.TimePeriodUtil;
import static org.apache.hertzbeat.warehouse.constants.WarehouseConstants.INSTANT;
import static org.apache.hertzbeat.warehouse.constants.WarehouseConstants.PROMQL;
import static org.apache.hertzbeat.warehouse.constants.WarehouseConstants.RANGE;
import org.apache.hertzbeat.warehouse.store.history.vm.PromQlQueryContent;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpEntity;
import org.springframework.http.MediaType;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* abstract class for promql query executor
*/
@Slf4j
public abstract class PromqlQueryExecutor implements QueryExecutor {
private static final String supportQueryLanguage = PROMQL;
private static final String QUERY_RANGE_PATH = "/api/v1/query_range";
private static final String QUERY_PATH = "/api/v1/query";
protected static final String HTTP_QUERY_PARAM = "query";
protected static final String HTTP_TIME_PARAM = "time";
protected static final String HTTP_START_PARAM = "start";
protected static final String HTTP_END_PARAM = "end";
protected static final String HTTP_STEP_PARAM = "step";
private static final String INNER_KEY_TIME = "__ts__";
private static final String INNER_KEY_VALUE = "__value__";
private final RestTemplate restTemplate;
private final HttpPromqlProperties httpPromqlProperties;
PromqlQueryExecutor(RestTemplate restTemplate, HttpPromqlProperties httpPromqlProperties) {
this.restTemplate = restTemplate;
this.httpPromqlProperties = httpPromqlProperties;
}
/**
* record class for promql http connection
*/
protected record HttpPromqlProperties(
String url,
String username,
String password
) {
}
@Override
public List<Map<String, Object>> execute(String queryString) {
List<Map<String, Object>> results = new LinkedList<>();
try {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(List.of(MediaType.APPLICATION_JSON));
if (StringUtils.hasText(httpPromqlProperties.username())
&& StringUtils.hasText(httpPromqlProperties.password())) {
String authStr = httpPromqlProperties.username() + ":" + httpPromqlProperties.password();
String encodedAuth = Base64Util.encode(authStr);
headers.add(HttpHeaders.AUTHORIZATION, NetworkConstants.BASIC + SignConstants.BLANK + encodedAuth);
}
HttpEntity<Void> httpEntity = new HttpEntity<>(headers);
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromHttpUrl(httpPromqlProperties.url + QUERY_PATH);
uriComponentsBuilder.queryParam(HTTP_QUERY_PARAM, queryString);
URI uri = uriComponentsBuilder.build().toUri();
ResponseEntity<PromQlQueryContent> responseEntity = restTemplate.exchange(uri,
HttpMethod.GET, httpEntity, PromQlQueryContent.class);
if (responseEntity.getStatusCode().is2xxSuccessful()) {
if (responseEntity.getBody() != null && responseEntity.getBody().getData() != null
&& responseEntity.getBody().getData().getResult() != null) {
List<PromQlQueryContent.ContentData.Content> contents = responseEntity.getBody().getData().getResult();
for (PromQlQueryContent.ContentData.Content content : contents) {
Map<String, String> labels = content.getMetric();
Map<String, Object> queryResult = new HashMap<>(8);
queryResult.putAll(labels);
if (content.getValue() != null && content.getValue().length == 2) {
queryResult.put("__timestamp__", content.getValue()[0]);
queryResult.put("__value__", content.getValue()[1]);
} else if (content.getValues() != null && !content.getValues().isEmpty()) {
List<Object> values = new LinkedList<>();
for (Object[] valueArr : content.getValues()) {
values.add(valueArr[1]);
}
queryResult.put("__value__", values);
}
results.add(queryResult);
}
}
} else {
log.error("query metrics data from greptime failed. {}", responseEntity);
}
} catch (Exception e) {
log.error(e.toString(), e);
}
return results;
}
@Override
public DatasourceQueryData query(DatasourceQuery datasourceQuery) {
DatasourceQueryData.DatasourceQueryDataBuilder queryDataBuilder = DatasourceQueryData.builder()
.refId(datasourceQuery.getRefId()).status(200);
try {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(List.of(MediaType.APPLICATION_JSON));
if (StringUtils.hasText(httpPromqlProperties.username())
&& StringUtils.hasText(httpPromqlProperties.password())) {
String authStr = httpPromqlProperties.username() + ":" + httpPromqlProperties.password();
String encodedAuth = new String(Base64.encodeBase64(authStr.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
headers.add(HttpHeaders.AUTHORIZATION, NetworkConstants.BASIC + " " + encodedAuth);
}
HttpEntity<Void> httpEntity = new HttpEntity<>(headers);
URI uri;
if (datasourceQuery.getTimeType().equals(RANGE)) {
uri = UriComponentsBuilder.fromHttpUrl(httpPromqlProperties.url() + QUERY_RANGE_PATH)
.queryParam(HTTP_QUERY_PARAM, datasourceQuery.getExpr())
.queryParam(HTTP_START_PARAM, datasourceQuery.getStart())
.queryParam(HTTP_END_PARAM, datasourceQuery.getEnd())
.queryParam(HTTP_STEP_PARAM, datasourceQuery.getStep())
.build().toUri();
} else if (datasourceQuery.getTimeType().equals(INSTANT)) {
uri = UriComponentsBuilder.fromHttpUrl(httpPromqlProperties.url() + QUERY_PATH)
.queryParam(HTTP_QUERY_PARAM, datasourceQuery.getExpr())
.build().toUri();
} else {
throw new IllegalArgumentException(String.format("no such time type for query id {}.", datasourceQuery.getRefId()));
}
ResponseEntity<PromQlQueryContent> responseEntity = restTemplate.exchange(uri, HttpMethod.GET, httpEntity,
PromQlQueryContent.class);
if (responseEntity.getStatusCode().is2xxSuccessful()) {
log.debug("query metrics data from promql http api success. {}", uri);
if (responseEntity.getBody() != null && responseEntity.getBody().getData() != null
&& responseEntity.getBody().getData().getResult() != null) {
List<PromQlQueryContent.ContentData.Content> contents = responseEntity.getBody().getData().getResult();
List<DatasourceQueryData.SchemaData> schemaDataList = new LinkedList<>();
for (PromQlQueryContent.ContentData.Content content : contents) {
DatasourceQueryData.MetricSchema.MetricSchemaBuilder schemaBuilder = DatasourceQueryData.MetricSchema
.builder().fields(List.of(
// todo: unit?
DatasourceQueryData.MetricField.builder().name(INNER_KEY_TIME)
.type("time").build(),
DatasourceQueryData.MetricField.builder().name(INNER_KEY_VALUE)
.type("number").build()
)).labels(content.getMetric());
List<Object[]> values;
if (datasourceQuery.getTimeType().equals(RANGE)) {
values = content.getValues();
}
else {
values = List.<Object[]>of(content.getValue());
}
values.forEach(objects -> {
objects[0] = TimePeriodUtil.normalizeToMilliseconds(objects[0]);
});
DatasourceQueryData.SchemaData.SchemaDataBuilder schemaData = DatasourceQueryData.SchemaData.builder()
.schema(schemaBuilder.build()).data(values);
schemaDataList.add(schemaData.build());
}
queryDataBuilder.frames(schemaDataList);
}
} else {
log.error("query metrics data from victoria-metrics failed. {}", responseEntity);
queryDataBuilder.msg("query metrics data from victoria-metrics failed. ");
queryDataBuilder.status(responseEntity.getStatusCode().value());
}
} catch (Exception e) {
log.error("query metrics data from victoria-metrics error. {}.", e.getMessage(), e);
queryDataBuilder.msg("query metrics data from victoria-metrics error: " + e.getMessage());
queryDataBuilder.status(400);
}
return queryDataBuilder.build();
}
@Override
public boolean support(String queryLanguage) {
return StringUtils.hasText(queryLanguage) && queryLanguage.equalsIgnoreCase(supportQueryLanguage);
}
}
@@ -17,6 +17,9 @@
package org.apache.hertzbeat.warehouse.db;
import org.apache.hertzbeat.common.entity.dto.query.DatasourceQuery;
import org.apache.hertzbeat.common.entity.dto.query.DatasourceQueryData;
import java.util.List;
import java.util.Map;
@@ -24,8 +27,12 @@ import java.util.Map;
* query executor interface
*/
public interface QueryExecutor {
List<Map<String, Object>> execute(String query);
boolean support(String datasource);
DatasourceQueryData query(DatasourceQuery datasourceQuery);
String getDatasource();
boolean support(String queryLanguage);
}
@@ -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.warehouse.db;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.entity.dto.query.DatasourceQuery;
import org.apache.hertzbeat.common.entity.dto.query.DatasourceQueryData;
import static org.apache.hertzbeat.warehouse.constants.WarehouseConstants.SQL;
import org.springframework.util.StringUtils;
import java.util.List;
import java.util.Map;
/**
* abstract class for sql query executor
*/
@Slf4j
public abstract class SqlQueryExecutor implements QueryExecutor {
private static final String supportQueryLanguage = SQL;
/**
* record class for sql connection
*/
protected record ConnectorSqlProperties () {}
@Override
public List<Map<String, Object>> execute(String query) {
return null;
}
@Override
public DatasourceQueryData query(DatasourceQuery datasourceQuery) {
return null;
}
@Override
public boolean support(String queryLanguage) {
return StringUtils.hasText(queryLanguage) && queryLanguage.equalsIgnoreCase(supportQueryLanguage);
}
}
@@ -18,29 +18,11 @@
package org.apache.hertzbeat.warehouse.db;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.constants.NetworkConstants;
import org.apache.hertzbeat.common.constants.SignConstants;
import org.apache.hertzbeat.common.util.Base64Util;
import org.apache.hertzbeat.warehouse.store.history.vm.PromQlQueryContent;
import org.apache.hertzbeat.warehouse.store.history.vm.VictoriaMetricsProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
/**
* query executor for victor metrics
@@ -48,71 +30,20 @@ import org.springframework.web.util.UriComponentsBuilder;
@Component
@ConditionalOnProperty(prefix = "warehouse.store.victoria-metrics", name = "enabled", havingValue = "true")
@Slf4j
public class VictoriaMetricsQueryExecutor implements QueryExecutor {
private static final String QUERY_PATH = "/api/v1/query";
public class VictoriaMetricsQueryExecutor extends PromqlQueryExecutor {
private static final String Datasource = "VictoriaMetrics";
private final VictoriaMetricsProperties victoriaMetricsProp;
private final RestTemplate restTemplate;
public VictoriaMetricsQueryExecutor(VictoriaMetricsProperties victoriaMetricsProp, RestTemplate restTemplate) {
super(restTemplate, new HttpPromqlProperties(victoriaMetricsProp.url(),
victoriaMetricsProp.username(), victoriaMetricsProp.password()));
this.victoriaMetricsProp = victoriaMetricsProp;
this.restTemplate = restTemplate;
}
@Override
public List<Map<String, Object>> execute(String query) {
// http run the promql query
List<Map<String, Object>> results = new LinkedList<>();
try {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(List.of(MediaType.APPLICATION_JSON));
if (StringUtils.hasText(victoriaMetricsProp.username())
&& StringUtils.hasText(victoriaMetricsProp.password())) {
String authStr = victoriaMetricsProp.username() + ":" + victoriaMetricsProp.password();
String encodedAuth = Base64Util.encode(authStr);
headers.add(HttpHeaders.AUTHORIZATION, NetworkConstants.BASIC + SignConstants.BLANK + encodedAuth);
}
HttpEntity<Void> httpEntity = new HttpEntity<>(headers);
URI uri = UriComponentsBuilder.fromHttpUrl(victoriaMetricsProp.url() + QUERY_PATH)
.queryParam("query", URLEncoder.encode(query, StandardCharsets.UTF_8))
.build(true).toUri();
ResponseEntity<PromQlQueryContent> responseEntity = restTemplate.exchange(uri,
HttpMethod.GET, httpEntity, PromQlQueryContent.class);
if (responseEntity.getStatusCode().is2xxSuccessful()) {
if (responseEntity.getBody() != null && responseEntity.getBody().getData() != null
&& responseEntity.getBody().getData().getResult() != null) {
List<PromQlQueryContent.ContentData.Content> contents = responseEntity.getBody().getData().getResult();
for (PromQlQueryContent.ContentData.Content content : contents) {
Map<String, String> labels = content.getMetric();
Map<String, Object> queryResult = new HashMap<>(8);
queryResult.putAll(labels);
if (content.getValue() != null && content.getValue().length == 2) {
queryResult.put("__timestamp__", content.getValue()[0]);
queryResult.put("__value__", content.getValue()[1]);
} else if (content.getValues() != null && !content.getValues().isEmpty()) {
List<Object> values = new LinkedList<>();
for (Object[] valueArr : content.getValues()) {
values.add(valueArr[1]);
}
queryResult.put("__value__", values);
}
results.add(queryResult);
}
}
} else {
log.error("query metrics data from victor-metrics failed. {}", responseEntity);
}
} catch (Exception e) {
log.error(e.toString(), e);
}
return results;
public String getDatasource() {
return Datasource;
}
@Override
public boolean support(String datasource) {
return "promql".equals(datasource);
}
}
@@ -15,17 +15,21 @@
* limitations under the License.
*/
package org.apache.hertzbeat.push.service;
package org.apache.hertzbeat.warehouse.service;
import org.apache.hertzbeat.common.entity.push.PushMetricsDto;
import org.springframework.stereotype.Service;
import java.util.List;
import org.apache.hertzbeat.common.entity.dto.query.DatasourceQuery;
import org.apache.hertzbeat.common.entity.dto.query.DatasourceQueryData;
/**
* push metrics
* metrics data query service
*/
@Service
public interface PushService {
void pushMetricsData(PushMetricsDto pushMetricsData);
public interface DatasourceQueryService {
PushMetricsDto getPushMetricData(Long monitorId, Long time);
/**
* Query metrics data
* @param queries query expr
* @return data
*/
List<DatasourceQueryData> query(List<DatasourceQuery> queries);
}
@@ -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.warehouse.service.impl;
import org.apache.hertzbeat.common.entity.dto.query.DatasourceQuery;
import org.apache.hertzbeat.common.entity.dto.query.DatasourceQueryData;
import org.apache.hertzbeat.warehouse.db.QueryExecutor;
import org.apache.hertzbeat.warehouse.service.DatasourceQueryService;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* datasource query service impl
*/
@Service
public class DatasourceQueryServiceImpl implements DatasourceQueryService {
Map<String, QueryExecutor> executorMap;
DatasourceQueryServiceImpl(List<QueryExecutor> executors) {
executorMap = executors.stream().collect(Collectors.toMap(QueryExecutor::getDatasource, executor -> executor));
}
@Override
public List<DatasourceQueryData> query(List<DatasourceQuery> queries) {
if (queries == null) {
throw new IllegalArgumentException("No query found");
}
List<DatasourceQueryData> datasourceQueryDataList = new ArrayList<>();
for (DatasourceQuery datasourceQuery : queries) {
QueryExecutor executor = executorMap.get(datasourceQuery.getDatasource());
if (executor == null) {
throw new IllegalArgumentException("Unsupported datasource: " + datasourceQuery.getDatasource());
}
datasourceQueryDataList.add(executor.query(datasourceQuery));
}
return datasourceQueryDataList;
}
}
@@ -0,0 +1,106 @@
---
title: "Behind the Scenes of HertzBeat: How Metric Collection Works"
author: JuJinPark
author_title: JuJin
author_url: https://github.com/JuJinPark
tags: [opensource, practice]
keywords: [open source monitoring system]
---
## Behind the Scenes of HertzBeat: How Metric Collection Works
HertzBeat is an open-source, real-time monitoring system designed for flexibility and ease of use. But how exactly does it collect, process, and store metrics from various systems?
In this post, well walk through the internal architecture behind **HertzBeats metric collection pipeline** — from job distribution to alerting and storage — with the help of a high-level system diagram.
---
### HertzBeats Metric Collection Architecture
![HertzBeat Architecture](/img/docs/hertzbeat-metrics-collection-arch.png)
> **Figure:** High-level architecture of HertzBeat's metric collection system. The Manager handles job scheduling, alerting, and storage, while Collectors (external or internal) perform the actual metric collection. Communication between the Manager and Collectors uses a custom Netty TCP protocol.
---
### 1. Job Distribution: Assigning What to Monitor
When the **Manager** component starts, it loads monitoring targets from the database. These targets define the host, collection interval, and other parameters.
To distribute the workload, the Manager sends jobs to **external Collectors** over a custom **Netty-based TCP protocol**. The `CollectJobScheduling` module handles this logic using **consistent hashing**, ensuring jobs are evenly distributed across collectors.
> 💡 HertzBeat also includes a built-in **main collector** (identified as `MAIN_COLLECTOR_NODE`) that runs directly inside the Manager. This allows HertzBeat to operate in **standalone mode** without requiring any external collectors.
---
### 2. Task Scheduling: When to Monitor
Once a Collector receives a job, it registers it with the **`TimerDispatch`** system.
- For **external collectors**, the Manager sends the task via the TCP connection.
- For the **main collector**, the Manager directly invokes `CollectJobService` within the same process.
Each Collector runs a **`Timer`** in a background thread, which schedules tasks according to their configured intervals. When the time is up, the timer triggers a `TimerTask` to begin metric collection.
---
### 3. Task Execution: How Metrics Are Collected
When a `TimerTask` is triggered, it creates a `MetricsCollect` task and passes it to `MetricsTaskDispatch`, which places it in the **`MetricsCollectorQueue`**.
- A dedicated thread (`CommonDispatcher`) continuously polls this queue.
- Tasks are executed by a **worker thread pool**, allowing multiple metric collections to run concurrently.
- Each task uses a specific **collector strategy** (e.g., HTTP, JDBC, SSH) to fetch metrics from the target system.
---
### 4. Result Processing: What Happens to Collected Data
Once metrics are collected, the results are processed by the **`CollectDataDispatch`** module.
- If the task is recurring, it is rescheduled via `TimerDispatch`.
- Results are added to a **`CommonDataQueue`** for further handling.
For external collectors, results are sent **back to the Manager** via the Netty TCP connection. For the main collector, results are forwarded **directly** to the next processing stage without network overhead.
---
### 5. Alerting & Storage: Making Metrics Useful
The Manager receives metric data and pushes it into the `MetricsDataToAlertQueue`, where it is processed through two main pipelines:
#### 🔔 Alerting
- The `RealTimeAlertCalculator` consumes metrics from the alert queue.
- It checks each metric against user-defined alert rules and triggers alerts if conditions are met.
#### 🧠 Storage
- After alert evaluation, metrics are added to the `MetricsDataToStorageQueue`.
- A background thread (`DataStorageDispatch`) processes this queue and stores the metrics in a database for long-term analysis and dashboard visualization.
---
### Standalone Mode: No External Collectors Required
Thanks to the built-in **main collector**, HertzBeat can operate entirely in standalone mode. This is especially useful for testing, small deployments, or quick setup. All core components — job scheduling, collection, alerting, and storage — run within a single process.
---
### 🧠 Conclusion
HertzBeats metric collection system is designed for **performance, scalability, and flexibility**. With its:
- **Queue-based, multi-threaded architecture**
- **Persistent TCP connections** for reliable job/result flow
- **Built-in main collector** for standalone operation
it handles large-scale monitoring workloads with minimal overhead and high efficiency.
---
### 🙌 Whats Next?
If you're curious to explore more:
- ⭐️ [Star the project on GitHub](https://github.com/apache/hertzbeat)
- 🤝 [Contribute or open an issue](https://github.com/apache/hertzbeat/issues)
+70
View File
@@ -0,0 +1,70 @@
---
title: From Commiter to PMC The Journey of Growth at Apache Hertzbeat
author: zhangshenghang
author_title: zhangshenghang
author_url: https://github.com/zhangshenghang
author_image_url: https://avatars.githubusercontent.com/u/29418975?s=400&v=4
tags: [opensource, practice]
keywords: [open source monitoring system, alerting system]
---
## From Committer to PMC: The Transformation and Growth of Roles
A year ago, when I received the notification of becoming an Apache HertzBeat **Committer**, the joy and sense of mission I felt are still vivid in my memory. At that time, I was more focused on specific technical implementations and feature developments. Now, being invited to join the **PMC** means that I need to view the project's development from a more comprehensive perspective. This is not only an acknowledgment of my participation in open source but also a call for future responsibilities.
### In-depth Participation: Accumulation during the Committer Period
After becoming a Committer, my daily work changed significantly:
- **Code review became the norm**: From the initial simple PR reviews to the ability to systematically evaluate code quality, the rationality of the architecture, and consistency with the overall project direction.
- **Tighter community collaboration**: I started to regularly participate in community meetings, assist new contributors in solving problems, and even take the lead in the development and implementation of certain features.
- **Participation in technical decision-making**: On key issues such as monitoring protocol support and storage engine optimization, I began to put forward my own opinions and jointly develop implementation plans with the core team.
During this period, I deeply realized that **the vitality of an open source project lies not only in the code but also in community collaboration and trust**. Every code merge and every problem discussion are minor adjustments to the project's direction, and the role of the Committer gave me the opportunity to be involved.
## Becoming a PMC: The Upgrade of Responsibilities and Challenges
The responsibilities of the PMC go far beyond the code level. It requires members to have a deeper understanding and thinking about the project's **technical direction, community governance, and long-term development**. When I received the PMC invitation, I felt excited but also realized that I needed to face new challenges:
### 1. Participation in Technical Strategy
As a PMC, I need to jointly plan the long-term roadmap of HertzBeat with other members. For example:
- **Performance optimization**: In the face of large-scale monitoring scenarios, how to optimize storage and query efficiency.
- **Enhanced scalability**: How to design a more flexible plugin mechanism to facilitate the community to contribute new monitoring types.
These issues are no longer simple code implementations but involve in-depth discussions on technical selection, community resource allocation, and even project positioning.
### 2. Community Governance and Health
The PMC needs to pay attention to the long-term healthy development of the community, including:
- **Contributor experience**: How to optimize the documentation and lower the entry threshold for new members.
- **Community culture**: Ensure a friendly discussion atmosphere and that conflicts can be properly resolved.
- **Sustainable development**: Motivate long-term contributors while attracting new blood.
## Personal Growth: The Transformation of Skills and Mentality
This year's experience has improved me in multiple dimensions:
- **Technical breadth**: From focusing on specific functions to understanding the overall architecture of the distributed monitoring system.
- **Soft skills**: Learned how to communicate effectively, coordinate different opinions, and promote community consensus.
- **Project management**: Understood the operation mode of open source projects and balanced the ideal and practical constraints.
The most profound realization is that **in the open source community, technical ability is just the foundation, and the real value lies in whether you can create achievements greater than the individual through collaboration**.
## Future Prospects: Moving Forward with HertzBeat
As a newly appointed PMC, I have several key directions for the future:
1. **Promote HertzBeat to become an important choice in the field of cloud-native monitoring**, especially to form a differentiated advantage in terms of lightweight and ease of use.
2. **Build a more active contributor community** and cultivate core contributors through mentorship programs, regular activities, and other means.
3. **Improve the project governance process** to make decision-making more transparent and participation smoother.
## Acknowledgments and Encouragement
I would like to especially thank **Tom** for his guidance and all community partners for their support. The Apache Way emphasizes that "community is more important than code", and this concept has deeply influenced my way of working.
Finally, I want to share with you who are reading this: **Open source is a long-lasting and warm journey. There is no need to pursue quick success. Just keep contributing, and the rewards will come naturally**. I look forward to meeting more like-minded friends in the HertzBeat community!
As we often say: **"Participating in open source is to make technology better, not to make life busier"** —— Let's encourage each other 😊
+316
View File
@@ -0,0 +1,316 @@
---
title: Announcement of Apache Hertzbeat 1.7.0 Release
author: tomsun28
author_title: tomsun28
author_url: https://github.com/zhangshenghang
author_image_url: https://avatars.githubusercontent.com/u/24788200?s=400&v=4
tags: [opensource, release]
keywords: [open source monitoring system, alerting system, Hertzbeat, release]
---
Dear Community Members,
We are thrilled to announce the official release of Apache Hertzbeat version 1.7.0!
## Downloads and Documentation
- **Apache Hertzbeat 1.7.0 Download Link**: <https://hertzbeat.apache.org/docs/download>
- **Apache Hertzbeat Documentation**: <https://hertzbeat.apache.org/docs/>
## Major Updates
### New Features and Enhancements
- **Custom Refresh Interval**: Supports custom refresh intervals for each group of metrics to meet monitoring needs in different scenarios.
- **Task Auto Discovery**: Supports automatic task discovery via `http_sd`, enhancing task flexibility and manageability.
- **New Alarm Module**: Supports real-time threshold and scheduled threshold, grouped convergence, alarm suppression, alarm silencing, and more.
- **Kafka Monitoring Enhancement**: Optimized Kafka monitoring features, including improved Kafka chart displays and added Kafka consumer group monitoring metrics.
- **Support for Multiple Protocols and Monitoring Types**: Added support for monitoring `Plc` protocol, further expanding the monitoring scope.
- **Alarm Function Enhancement**: Supports replacing Tencent Cloud SDK with HTTP API for sending SMS notifications, increasing the flexibility and scalability of alarm notifications. Additionally, it supports multi-query expression threshold alarms and periodic alarm thresholds.
- **Multilingual Support**: Added support for languages such as Japanese and Traditional Chinese, enhancing the international user experience.
- **Monitoring Function Enhancement**: Supports monitoring for more types such as `StarRocks FE`, providing users with more monitoring options.
- **E2E Testing Enhancement**: Added multiple E2E test codes, including for Kafka, SSH, and API, improving test coverage and stability.
- **Data Storage Optimization**: Updated `VictoriaMetrics` and `Greptime` storage to improve data storage performance and stability.
- **More New Features**
### Bug Fixes
- **Fix Docker Build Errors**: Fixed the collector Docker build errors, ensuring normal Docker image builds.
- **Fix Linux Process Monitoring Issue**: Fixed an issue where Linux process monitoring failed without warning when a process exited abnormally, improving monitoring stability.
- **Fix Windows Chinese Encoding Issue**: Fixed the issue with Chinese encoding on Windows, ensuring normal operation in Windows environments.
- **Fix Grafana Configuration Issue**: Fixed issues related to Grafana configuration, improving the Grafana integration experience.
- **Fix Windows Metric Configuration Issue**: Updated Windows metric YAML files to resolve related issues.
- **Fix Flyway Location Detection Issue**: Fixed the issue where Flyway could not automatically detect vendor locations, improving database migration reliability.
- **Fix Data Storage Issues**: Fixed issues related to data storage, including Prometheus data storage issues and data update logic issues, ensuring data accuracy and integrity.
- **Fix Alarm Notification Issues**: Fixed issues related to alarm notifications, including duplicate sending and configuration problems, improving alarm notification accuracy and reliability.
- **Fix Monitoring Status Update Issue**: Fixed the issue where monitoring status wasn't updated, ensuring real-time and accurate monitoring status.
- **And Other Bug Fixes**
### Refactoring and Optimization
- **Memory Structure Optimization**: Used `Apache Arrow` as the in-memory data structure, improving memory usage efficiency and performance.
- **Code Standard Optimization**: Optimized the code according to coding standards, improving code quality and readability.
- **Cache Optimization**: Added an `LRU` local cache based on the Singleton pattern, improving cache efficiency and performance.
- **Memory Leak Fix**: Fixed potential memory leak issues, improving system stability and reliability.
- **And Other Optimizations**
### Documentation Enhancements
- **Updated Deployment Documentation**: Updated deployment documentation with more detailed deployment guidance.
- **Updated Security Model Documentation**: Updated the security model documentation.
- **Updated Grafana Configuration Documentation**: Updated Grafana configuration methods and documentation for exposing URLs, enhancing user experience.
- **Updated Windows Monitoring Documentation**: Updated Windows system monitoring documentation with more detailed monitoring guidance.
- **Updated Monitoring Metrics Documentation**: Updated documentation for multiple monitoring metrics, including Kafka, Linux processes, etc., improving the accuracy and completeness of the documentation.
- **Updated Developer Documentation**: Added documentation for custom collector development, helping developers with secondary development.
- **More Documentation Updates**
## Acknowledgements
Special thanks to the following community members for their collaborative efforts:
> @ghyghoo8 @kerwin612 @pjfanning @helei1030 @shinestare @simonsigre @myangle1120 @MasamiYui @Craaaaazy77 @tomsun28 @Aias00 @zhangshenghang @wanhao23 @zqr10159 @LiuTianyou
> @hasimmollah @lixiaobaivv @LL-LIN @JuJinPark @ponfee @starryCoder @NikhilMurugesan @leo-934 @Rancho-7 @MonsterChenzhuo @zuobiao-zhou @pwallk @bigcyy @ZY945 @sarthakeash
> @All-The-Best-for @TJxiaobao @yyahang @yunfan24 @a-little-fool @yasminvo @Yanshuming1 @ayu-v0 @jonasHanhan @Calvin979 @Suvrat1629 @Vedant7789 @notbugggg @lctking @po-168 @doveLin0818
## What's Changed
```markdown
* [doc](download): update for v1.6.1 release by @zqr10159 in https://github.com/apache/hertzbeat/pull/2794
* [Doc] improve website by @zhangshenghang in https://github.com/apache/hertzbeat/pull/2795
* [doc] update deploy doc by @tomsun28 in https://github.com/apache/hertzbeat/pull/2796
* [Task][OSPP] HertzBeat Official Template Marketplace by @All-The-Best-for in https://github.com/apache/hertzbeat/pull/2641
* [improve]:Improve the way Ai is entered and requested by @Yanshuming1 in https://github.com/apache/hertzbeat/pull/2762
* [bugfix] fix collector docker build error by @tomsun28 in https://github.com/apache/hertzbeat/pull/2799
* [fix]Remove the duplicate declaration of commons-net by @shinestare in https://github.com/apache/hertzbeat/pull/2801
* [doc] update new contributors by @tomsun28 in https://github.com/apache/hertzbeat/pull/2802
* [Improve] Improve module name by @zhangshenghang in https://github.com/apache/hertzbeat/pull/2805
* [improve] code according to code specifications by @po-168 in https://github.com/apache/hertzbeat/pull/2809
* [feature] Support custom refresh intervals for each group of metrics by @zuobiao-zhou in https://github.com/apache/hertzbeat/pull/2718
* [improve] Fix error links caused by module name changes. by @zuobiao-zhou in https://github.com/apache/hertzbeat/pull/2807
* [fix] fix the Linux process monitoring process exits abnormally without warning by @LiuTianyou in https://github.com/apache/hertzbeat/pull/2810
* [Doc] Add blog by @zhangshenghang in https://github.com/apache/hertzbeat/pull/2812
* [Improve] improve kafka monitor by @zhangshenghang in https://github.com/apache/hertzbeat/pull/2813
* [Feature] add e2e code by @zhangshenghang in https://github.com/apache/hertzbeat/pull/2811
* [improve] modify e2e test by @zhangshenghang in https://github.com/apache/hertzbeat/pull/2814
* [improve] update windows metrics yml by @tomsun28 in https://github.com/apache/hertzbeat/pull/2816
* [improve] update grafana auth method and add expose url by @tomsun28 in https://github.com/apache/hertzbeat/pull/2818
* Fixed the omissions in #2805 by @kerwin612 in https://github.com/apache/hertzbeat/pull/2826
* [refactor] change name from http_sd to registry by @Calvin979 in https://github.com/apache/hertzbeat/pull/2827
* [fix]fix windows chinese encoding by @starryCoder in https://github.com/apache/hertzbeat/pull/2831
* [doc] Added custom development collector documentation by @zhangshenghang in https://github.com/apache/hertzbeat/pull/2833
* [improve] update and fix template yml priority by @tomsun28 in https://github.com/apache/hertzbeat/pull/2829
* [chore] Delete redundant Spaces by @ayu-v0 in https://github.com/apache/hertzbeat/pull/2834
* [doc]: update sidebar category label and plugin documentation by @zqr10159 in https://github.com/apache/hertzbeat/pull/2837
* [fix] bugfix flyway location can not auto detect vendor when not h2 by @tomsun28 in https://github.com/apache/hertzbeat/pull/2835
* [improve] update victoriametrics and greptime store by @tomsun28 in https://github.com/apache/hertzbeat/pull/2836
* [feature] support managing tasks by using http_sd by @Calvin979 in https://github.com/apache/hertzbeat/pull/2830
* [fix] auto generated by protocol buffer by @tomsun28 in https://github.com/apache/hertzbeat/pull/2842
* [Feature] Add ssh e2e code by @zhangshenghang in https://github.com/apache/hertzbeat/pull/2843
* [bugfix]Fix wrong app name by @zqr10159 in https://github.com/apache/hertzbeat/pull/2845
* [doc] add security model doc and update contributors by @tomsun28 in https://github.com/apache/hertzbeat/pull/2846
* [improve] improve dependency by @zhangshenghang in https://github.com/apache/hertzbeat/pull/2855
* [doc] update security doc and some by @tomsun28 in https://github.com/apache/hertzbeat/pull/2856
* A bug fix by @TJxiaobao in https://github.com/apache/hertzbeat/pull/2853
* [doc] Add ',' separator between monitoring types by @Rancho-7 in https://github.com/apache/hertzbeat/pull/2865
* [doc]improve-windows-monitoring:Update Windows system monitoring docu… by @starryCoder in https://github.com/apache/hertzbeat/pull/2869
* [Optimize] Add a reminder about potential collection issues caused by the Docker deployment method of collector. by @zuobiao-zhou in https://github.com/apache/hertzbeat/pull/2844
* [improve]Add more helpful messages when adding a Kafka monitor by @Rancho-7 in https://github.com/apache/hertzbeat/pull/2876
* modified:add a small change. by @TJxiaobao in https://github.com/apache/hertzbeat/pull/2878
* [Fix] fix clickhouse monitor by @LiuTianyou in https://github.com/apache/hertzbeat/pull/2874
* [chore] Delete the redundant else by @ayu-v0 in https://github.com/apache/hertzbeat/pull/2881
* [improve]Remove stack property from line charts by @zqr10159 in https://github.com/apache/hertzbeat/pull/2888
* [improve]improve linux process by @LiuTianyou in https://github.com/apache/hertzbeat/pull/2889
* [Improve]Beautify Charts by @zqr10159 in https://github.com/apache/hertzbeat/pull/2891
* [doc] Add more hints when users are switching data source. by @Rancho-7 in https://github.com/apache/hertzbeat/pull/2880
* [collector]feature:Add monitoring metrics for consumer groups in Kafka client by @doveLin0818 in https://github.com/apache/hertzbeat/pull/2887
* [Improve] Improve Kafka chart display by @zhangshenghang in https://github.com/apache/hertzbeat/pull/2894
* [collector]bugfix:fix the issue of reusing the `adminClient` in the Kafka client. by @doveLin0818 in https://github.com/apache/hertzbeat/pull/2895
* [improve]add Plc protocol , Modbus monitor by @ZY945 in https://github.com/apache/hertzbeat/pull/2850
* [Improve] add notification when port number changes automatically due to HTTPS toggle.(#2779) by @yunfan24 in https://github.com/apache/hertzbeat/pull/2896
* [feature] integrate with Apache Arrow by @Calvin979 in https://github.com/apache/hertzbeat/pull/2864
* [Doc] update doc by @zhangshenghang in https://github.com/apache/hertzbeat/pull/2900
* [Imporve] Support Kafka internal topic configuration by @zhangshenghang in https://github.com/apache/hertzbeat/pull/2901
* [fix](flink): update calculate metrics definitions by @zqr10159 in https://github.com/apache/hertzbeat/pull/2905
* [feature] Add a new Singleton-pattern-based LRU local cache by @doveLin0818 in https://github.com/apache/hertzbeat/pull/2907
* add an online parser for prometheus. by @leo-934 in https://github.com/apache/hertzbeat/pull/2851
* [Improve] Improve OBS by @zhangshenghang in https://github.com/apache/hertzbeat/pull/2909
* [improve] fix import of CollectRep by @Calvin979 in https://github.com/apache/hertzbeat/pull/2910
* [feature](web-app): Add Alarm Voice Alerts by @zqr10159 in https://github.com/apache/hertzbeat/pull/2906
* [bugfix] Fix the bug where canceling an edit on a record still updates the page values. by @yunfan24 in https://github.com/apache/hertzbeat/pull/2911
* [bugfix] Fix docker container name unable to display problem by @zhangshenghang in https://github.com/apache/hertzbeat/pull/2914
* [improve] Optimize CacheService and add relevant unit test by @lctking in https://github.com/apache/hertzbeat/pull/2912
* [improve] Add required field indicators and form validation prompts for convergence strategies and silent strategies in the form. by @yunfan24 in https://github.com/apache/hertzbeat/pull/2913
* [Feture]Add docker e2e test by @zhangshenghang in https://github.com/apache/hertzbeat/pull/2916
* [bugfix]: fix setColumns method in CollectRep class by @zqr10159 in https://github.com/apache/hertzbeat/pull/2918
* [improve](warehouse): replace empty json object key with empty string by @zqr10159 in https://github.com/apache/hertzbeat/pull/2919
* [bugfix] Bug fix for alarm voice. by @yunfan24 in https://github.com/apache/hertzbeat/pull/2920
* Update app-windows_script.yml by @simonsigre in https://github.com/apache/hertzbeat/pull/2922
* [bugfix] Fixed the 'java.lang.UnsupportedOperationException' exception caused by getCurrentMetricsData by @lixiaobaivv in https://github.com/apache/hertzbeat/pull/2923
* [Improve] Optimize the e2e code structure by @zhangshenghang in https://github.com/apache/hertzbeat/pull/2926
* [improve] optimize website navbar css(#2928) by @ponfee in https://github.com/apache/hertzbeat/pull/2929
* [feature] Adding CPU Temperature Check Into Default Ubuntu Checks by @simonsigre in https://github.com/apache/hertzbeat/pull/2930
* [improve] Improve the synchronization of the mute status. by @yunfan24 in https://github.com/apache/hertzbeat/pull/2927
* [bugfix] Corrected case 'DashBoard' is a lower case 'B' by @simonsigre in https://github.com/apache/hertzbeat/pull/2935
* [bugfix] Modify the doris_be.md document into an English version by @Craaaaazy77 in https://github.com/apache/hertzbeat/pull/2936
* [improve] Refactor and Split the Message Notification Component. by @yunfan24 in https://github.com/apache/hertzbeat/pull/2924
* [home] updated navbar css #2928 by @Vedant7789 in https://github.com/apache/hertzbeat/pull/2934
* [Improve]Improve e2e code by @zhangshenghang in https://github.com/apache/hertzbeat/pull/2945
* [refactor] refactoring methods replaceCryPlaceholder and replaceSmilingPlace by @hasimmollah in https://github.com/apache/hertzbeat/pull/2832
* [alarm] refactor new alarm by @tomsun28 in https://github.com/apache/hertzbeat/pull/2902
* [bugfix](db): optimize column update. by @zqr10159 in https://github.com/apache/hertzbeat/pull/2947
* [doc] Add Supported MySQL Versions. by @yunfan24 in https://github.com/apache/hertzbeat/pull/2949
* [Feature] Support customized JMX monitoring through the Factory Pattern. by @doveLin0818 in https://github.com/apache/hertzbeat/pull/2932
* [Improve]Modify Chinese comments by @zhangshenghang in https://github.com/apache/hertzbeat/pull/2950
* [improve] fix some alarm relate bug, update alarm center ui by @tomsun28 in https://github.com/apache/hertzbeat/pull/2951
* 【Improve】adjust log level from INFO to WARN. by @Rancho-7 in https://github.com/apache/hertzbeat/pull/2952
* [Doc]:Add English version of documentation for Kafka Consumer Detail by @Rancho-7 in https://github.com/apache/hertzbeat/pull/2953
* [Imporve] Improve Huaweicloud by @zhangshenghang in https://github.com/apache/hertzbeat/pull/2954
* [improve] update alarm inhibit rule and alarm ui by @tomsun28 in https://github.com/apache/hertzbeat/pull/2957
* [bufix] fix collector job scheduler error by @tomsun28 in https://github.com/apache/hertzbeat/pull/2966
* [Improve]:Standardize Kafka metric naming by @Rancho-7 in https://github.com/apache/hertzbeat/pull/2961
* [BUG] "Advanced Settings" is all white in dark mode by @Suvrat1629 in https://github.com/apache/hertzbeat/pull/2965
* [Improve] add no popup option after next login by @LiuTianyou in https://github.com/apache/hertzbeat/pull/2969
* [feature] replace googletagmanager to matomo by @Aias00 in https://github.com/apache/hertzbeat/pull/2877
* Fix the search functionality issue. by @yunfan24 in https://github.com/apache/hertzbeat/pull/2970
* [bugfix](warehouse): add metrics data update logic in memory storage by @zqr10159 in https://github.com/apache/hertzbeat/pull/2973
* [Improve] Add more test cases for Kafka junit tests by @Rancho-7 in https://github.com/apache/hertzbeat/pull/2976
* [feature] alert integration extern source by @tomsun28 in https://github.com/apache/hertzbeat/pull/2978
* [bugfix] Fix NullPointerException by @ayu-v0 in https://github.com/apache/hertzbeat/pull/2849
* [webapp] key-value-input component hover effect fixed by @ghyghoo8 in https://github.com/apache/hertzbeat/pull/2972
* [bugfix] fix alert integration extern source bug by @tomsun28 in https://github.com/apache/hertzbeat/pull/2979
* [Feature] Support copy monitoring by @zhangshenghang in https://github.com/apache/hertzbeat/pull/2981
* [bugfix] fix hbase dashboard display anomalies and turn on HTTPS by @MonsterChenzhuo in https://github.com/apache/hertzbeat/pull/2980
* [improve] update i18n json stru and update search ui by @tomsun28 in https://github.com/apache/hertzbeat/pull/2986
* [MINOR UPDATE] improve xml parsing code by @pjfanning in https://github.com/apache/hertzbeat/pull/2988
* [docs]doc:Added spark Chinese documents and changed the original spar… by @helei1030 in https://github.com/apache/hertzbeat/pull/2987
* [type:improve] fix dependencies vulnerabilites by @Aias00 in https://github.com/apache/hertzbeat/pull/2989
* [feature] Add privateKey passphrase config for linux monitor by @MasamiYui in https://github.com/apache/hertzbeat/pull/2982
* [type:fix] remove matomo ip by @Aias00 in https://github.com/apache/hertzbeat/pull/2990
* [feature] Added multilingual defaults for forms by @wanhao23 in https://github.com/apache/hertzbeat/pull/2991
* [Improve]Add Copy token button by @zqr10159 in https://github.com/apache/hertzbeat/pull/2992
* [MINOR UPDATE] close HttpResponse in HttpCollectImpl by @pjfanning in https://github.com/apache/hertzbeat/pull/2993
* [MINOR UPDATE] close http response in PrometheusAutoCollectImpl by @pjfanning in https://github.com/apache/hertzbeat/pull/2994
* [MINOR UPDATE] fix more instances of unclosed Http Responses by @pjfanning in https://github.com/apache/hertzbeat/pull/2995
* [bugfix] fix wrong http user-agent content by @tomsun28 in https://github.com/apache/hertzbeat/pull/2996
* [feature] Support monitoring for StarRocks FE and StarRocks BE. by @yunfan24 in https://github.com/apache/hertzbeat/pull/2997
* [MINOR UPDATE] refactor base64 code to simplify the conversions by @pjfanning in https://github.com/apache/hertzbeat/pull/2999
* [webapp] bugfix edit monitor http query params error by @tomsun28 in https://github.com/apache/hertzbeat/pull/3001
* [feature] Complete multiple languages by @wanhao23 in https://github.com/apache/hertzbeat/pull/3002
* [feature] Add alerter_zh_TW.properties configuration to adapt to mult… by @jonasHanhan in https://github.com/apache/hertzbeat/pull/3004
* [bugfix] fix some unit tests that failed to run by @NikhilMurugesan in https://github.com/apache/hertzbeat/pull/3007
* [feature](alert): implement drag-and-drop functionality for alert templates by @zqr10159 in https://github.com/apache/hertzbeat/pull/3005
* [improve] Freeze the 'Operate' column on the right side of the list. by @yunfan24 in https://github.com/apache/hertzbeat/pull/3009
* [MINOR UPDATE] always specify the char encoding in getBytes by @pjfanning in https://github.com/apache/hertzbeat/pull/3011
* [bugfix]Fix page not found by @zqr10159 in https://github.com/apache/hertzbeat/pull/3014
* [bugfix] Modify mask issue by @myangle1120 in https://github.com/apache/hertzbeat/pull/3018
* [issue-2998] remove invalid check in isValidLabelValue by @pjfanning in https://github.com/apache/hertzbeat/pull/3015
* [MINOR UPDATE] Use Encode to string when possible (Base64) by @pjfanning in https://github.com/apache/hertzbeat/pull/3016
* [Improve] update english doc by @zhangshenghang in https://github.com/apache/hertzbeat/pull/3028
* [Feature] Add API e2e code by @zhangshenghang in https://github.com/apache/hertzbeat/pull/3029
* [doc] update new contributor wall by @tomsun28 in https://github.com/apache/hertzbeat/pull/3025
* OnlineParserTest doesn't test anything by @pjfanning in https://github.com/apache/hertzbeat/pull/3010
* [feature] periodic alert threshold by @tomsun28 in https://github.com/apache/hertzbeat/pull/3024
* [bugfix] fix and enable some unit tests. by @yunfan24 in https://github.com/apache/hertzbeat/pull/3031
* [feature] Add pagination and name-based search functionality in notification module by @yunfan24 in https://github.com/apache/hertzbeat/pull/2948
* [bugfix] Fixed the bug in the threshold rules search box. by @yunfan24 in https://github.com/apache/hertzbeat/pull/3034
* [improve] Replaced hardcoded text with internationalized string. by @yunfan24 in https://github.com/apache/hertzbeat/pull/3035
* [update] upgrade actions/upload-artifact to v4 by @yunfan24 in https://github.com/apache/hertzbeat/pull/3046
* [improve] Search ignores case sensitivity. by @yunfan24 in https://github.com/apache/hertzbeat/pull/3042
* Add sse support for alert center, no need to manually refresh the page, add slide-in animation by @zqr10159 in https://github.com/apache/hertzbeat/pull/3051
* [alert] support multi query expr threshold by @tomsun28 in https://github.com/apache/hertzbeat/pull/3054
* [improve](alert-center): enhance alert card animations and interactions by @zqr10159 in https://github.com/apache/hertzbeat/pull/3055
* [improve] update theme ui color by @tomsun28 in https://github.com/apache/hertzbeat/pull/3057
* [bugfix] Fix the issue where the monitoring status is not updated. by @yunfan24 in https://github.com/apache/hertzbeat/pull/3056
* [bugfix]style(alert-center): enhance 3D transformation and z-index layers by @zqr10159 in https://github.com/apache/hertzbeat/pull/3059
* [Feature]Add zookeeper e2e code by @zhangshenghang in https://github.com/apache/hertzbeat/pull/3030
* [feature] Add Sftp config for monitor by @MasamiYui in https://github.com/apache/hertzbeat/pull/3038
* [feature] Add Japanese by @wanhao23 in https://github.com/apache/hertzbeat/pull/3013
* [bugfix] fix singleton not support remove, search id error, audio fetch 401 by @tomsun28 in https://github.com/apache/hertzbeat/pull/3062
* [API DOC] Change Swagger description by @pwallk in https://github.com/apache/hertzbeat/pull/3061
* [webapp] update ui theme by @tomsun28 in https://github.com/apache/hertzbeat/pull/3064
* [feature] Support SSH Tunnel by @pwallk in https://github.com/apache/hertzbeat/pull/3060
* [improve] Complete the missing labels in the i18n file. by @yunfan24 in https://github.com/apache/hertzbeat/pull/3065
* [Feature]Add Chinese check by @zhangshenghang in https://github.com/apache/hertzbeat/pull/3066
* [improve] Refactor SMS sending and replace Tencent Cloud SDK with HTTP API. by @yunfan24 in https://github.com/apache/hertzbeat/pull/3063
* [Bugfix] fix when the monitor is modified, the status is erroneously changed by @pwallk in https://github.com/apache/hertzbeat/pull/3067
* [improve](web-app): update monitor chart configuration and springboot GreptimeDB version by @zqr10159 in https://github.com/apache/hertzbeat/pull/3071
* [doc] Update the SMS configuration document. by @yunfan24 in https://github.com/apache/hertzbeat/pull/3073
* [feature] SMS notification supports unisms. by @yunfan24 in https://github.com/apache/hertzbeat/pull/3077
* [webapp] update and fix alert ui when theme dark by @tomsun28 in https://github.com/apache/hertzbeat/pull/3082
* [improve] Improve and unify the search. by @yunfan24 in https://github.com/apache/hertzbeat/pull/3085
* [feature] supports alibaba SMS. by @yunfan24 in https://github.com/apache/hertzbeat/pull/3084
* [improve] optimize kafka collect test by @Rancho-7 in https://github.com/apache/hertzbeat/pull/3093
* correct home's new_committer_process by @a-little-fool in https://github.com/apache/hertzbeat/pull/3094
* [bugfix] kafka client detect error by @Rancho-7 in https://github.com/apache/hertzbeat/pull/3088
* [Doc]Improve openai doc by @zhangshenghang in https://github.com/apache/hertzbeat/pull/3097
* [Improve] Message notification prompt optimization by @zhangshenghang in https://github.com/apache/hertzbeat/pull/3095
* [webapp] fix web oom crash when backend api can not access by @tomsun28 in https://github.com/apache/hertzbeat/pull/3100
* [Feature] Add deepseek Api Monitor by @zhangshenghang in https://github.com/apache/hertzbeat/pull/3096
* feat/adding-ptBR-translation by @yasminvo in https://github.com/apache/hertzbeat/pull/3098
* [bugfix] fix alert sse illegal state exception by @tomsun28 in https://github.com/apache/hertzbeat/pull/3106
* [bugfix] Fix exception thrown when searching for CollectRep.Field in the list by @JuJinPark in https://github.com/apache/hertzbeat/pull/3109
* [type: fix] #3090 garbled characters by @notbugggg in https://github.com/apache/hertzbeat/pull/3113
* [doc]fix link by @zhangshenghang in https://github.com/apache/hertzbeat/pull/3123
* [doc] Add GSOC doc by @zhangshenghang in https://github.com/apache/hertzbeat/pull/3122
* [doc] Add alibaba SMS and unisms documentation. by @yunfan24 in https://github.com/apache/hertzbeat/pull/3114
* [improve] support reuse jdbc connection switch by @tomsun28 in https://github.com/apache/hertzbeat/pull/3101
* [webapp] fix monitor define param wrong placeholder tip by @tomsun28 in https://github.com/apache/hertzbeat/pull/3118
* [bugfix] Fix swagger opening exception that Failed to load API definition. (#3127) by @yyahang in https://github.com/apache/hertzbeat/pull/3129
* [doc] welcome new committer and contributor by @tomsun28 in https://github.com/apache/hertzbeat/pull/3132
* [feature] add smslocal sms notification by @a-little-fool in https://github.com/apache/hertzbeat/pull/3135
* [improve] Optimize the progress display of monitoring imports by @MasamiYui in https://github.com/apache/hertzbeat/pull/3120
* [improve] fix potential memory leakage and content length issues. by @tomsun28 in https://github.com/apache/hertzbeat/pull/3128
* [bugfix] fix overflow arrow buffer index by @tomsun28 in https://github.com/apache/hertzbeat/pull/3137
* [improve] improve plugin upload by @LiuTianyou in https://github.com/apache/hertzbeat/pull/3139
* [doc] Add new committer blog by @yunfan24 in https://github.com/apache/hertzbeat/pull/3140
* Configuring gitpod with java by @kerwin612 in https://github.com/apache/hertzbeat/pull/3141
* Fix the issue of the empty dropdown menu on the Kanban board page. by @kerwin612 in https://github.com/apache/hertzbeat/pull/3142
* [feature] Add AWS sms client by @JuJinPark in https://github.com/apache/hertzbeat/pull/3134
* [feature] Support skywalking alert source by @MasamiYui in https://github.com/apache/hertzbeat/pull/3144
* [feature] support SSH proxy jump connections by @LL-LIN in https://github.com/apache/hertzbeat/pull/3138
* [improve] support bind metrics label and others into alert by @tomsun28 in https://github.com/apache/hertzbeat/pull/3146
* [bugfix] Fixed #3112, disappear left menu tree item when restart service by @notbugggg in https://github.com/apache/hertzbeat/pull/3116
* [bugfix] fix collect dispatch error by @tomsun28 in https://github.com/apache/hertzbeat/pull/3150
* [improve] Merge SMS configuration class. by @yunfan24 in https://github.com/apache/hertzbeat/pull/3148
* [webapp] fix less style file over max build error by @tomsun28 in https://github.com/apache/hertzbeat/pull/3151
* [bugfix] fix nightly docker build github action by @tomsun28 in https://github.com/apache/hertzbeat/pull/3153
* [feature] Supports sending messages to a specific Telegram group topic.(#3079) by @bigcyy in https://github.com/apache/hertzbeat/pull/3143
* Abstract Redundant Input Components into ConfigurableFieldComponent for Unified Management by @kerwin612 in https://github.com/apache/hertzbeat/pull/3152
* [feature] Support TencentCloud alert source by @bigcyy in https://github.com/apache/hertzbeat/pull/3149
* [bugfix]: fix incomplete class documentation in AppServiceImpl by @bigcyy in https://github.com/apache/hertzbeat/pull/3162
* [bugfix] Fix http header being incorrectly encoded. by @yunfan24 in https://github.com/apache/hertzbeat/pull/3108
* [bugfix] retain sorting state after monitor list auto-refresh by @LL-LIN in https://github.com/apache/hertzbeat/pull/3156
* [feature] add twilio sms client support by @sarthakeash in https://github.com/apache/hertzbeat/pull/3159
* [doc] Add alert integration japanese i18 by @MasamiYui in https://github.com/apache/hertzbeat/pull/3164
* [release] update release version 1.7.0 version and docs by @tomsun28 in https://github.com/apache/hertzbeat/pull/3165
* [feature] implement labels-based monitors filtering in bulletin creation flow by @LL-LIN in https://github.com/apache/hertzbeat/pull/3161
* [bugfix] fix postgre mount error, use mariadb instead of mysql in compose by @tomsun28 in https://github.com/apache/hertzbeat/pull/3168
* [bugfix] fix bind Labels are not updated when the Alarm Severity switches by @bigcyy in https://github.com/apache/hertzbeat/pull/3170
* [improve] update git archive export ignore by @tomsun28 in https://github.com/apache/hertzbeat/pull/3172
* [improve] update notice copyright years by @tomsun28 in https://github.com/apache/hertzbeat/pull/3171
```
## Apache Hertzbeat
**Repository URL:**
<https://github.com/apache/hertzbeat>
**Official Website:**
<https://hertzbeat.apache.org/>
**Apache Hertzbeat Download Link:**
<https://hertzbeat.apache.org/docs/download>
**Apache Hertzbeat Docker Images:**
Apache Hertzbeat provides Docker images for each release, available on Docker Hub:
- HertzBeat: <https://hub.docker.com/r/apache/hertzbeat>
- HertzBeat Collector: <https://hub.docker.com/r/apache/hertzbeat-collector>
**How to Contribute to the Apache Hertzbeat Open Source Community?**
<https://hertzbeat.apache.org/docs/community/contribution>
+326
View File
@@ -0,0 +1,326 @@
---
id: extend-http-xmlpath
title: HTTP Protocol XmlPath Parsing Method
sidebar_label: XmlPath Parsing Method
---
> After calling the HTTP API to obtain the response data, use the XmlPath script parsing method to parse the response data.
Note⚠️ The response data must be in XML format.
**Use XPath scripts to parse the response data into data that conforms to the data structure rules specified by HertzBeat.**
### XmlPath Parsing Logic
The XmlPath parsing method in HertzBeat uses a two-step XPath process:
1. **Main XPath Expression (`parseScript`)**: This XPath expression is defined in the `http` configuration section under `parseScript`. It is used to select one or more main XML nodes from the response. Each selected node will correspond to one row of metric data in HertzBeat.
2. **Relative Field XPath Expressions (`xpath`)**: For each metric field defined in the `fields` list, you can specify a relative `xpath`. This XPath expression is evaluated *relative to each main node* selected by the `parseScript` in step 1. It extracts the specific value for that metric field from the current main node.
This allows you to easily parse structured XML data where multiple records or items are present.
**Special Metrics**:
* `responseTime`: This built-in metric represents the HTTP request's response time and is automatically collected. It does not require an `xpath`.
* `keyword`: This built-in metric counts the occurrences of a specified keyword (configured in `http.keyword`) in the raw response body. It does not require an `xpath`.
### Example
Assume the HTTP API returns the following XML data:
```xml
<DeviceStatus xmlns="http://www.isapi.org/ver20/XMLSchema" version="2.0">
<CPUList>
<CPU>
<cpuUtilization>36.400002</cpuUtilization>
</CPU>
</CPUList>
<MemoryList>
<Memory>
<memoryUsage>399640</memoryUsage>
<memoryAvailable>98792</memoryAvailable>
<cacheSize>228492</cacheSize>
</Memory>
</MemoryList>
<NetPortStatusList>
<NetPortStatus>
<id>1</id>
<workSpeed>1000</workSpeed>
</NetPortStatus>
<NetPortStatus>
<id>2</id>
<workSpeed>0</workSpeed>
</NetPortStatus>
</NetPortStatusList>
<bootTime>2025-01-06 10:27:48</bootTime>
<deviceUpTime>87天0时55分59秒</deviceUpTime>
<lastCalibrationTime>2025-04-03 11:09:18</lastCalibrationTime>
<lastCalibrationTimeDiff>1</lastCalibrationTimeDiff>
<uploadTimeConsumingList>
<avgTime>16</avgTime>
<maxTime>23</maxTime>
<minTime>12</minTime>
</uploadTimeConsumingList>
<lastCalibrationTimeMode>NTP</lastCalibrationTimeMode>
<lastCalibrationTimeAddress>34.191.45.101</lastCalibrationTimeAddress>
</DeviceStatus>
```
We want to monitor the device status and extract various metrics.
Here's how you would configure the monitoring template YML:
```yaml
category: server
# The monitoring type eg: linux windows tomcat mysql aws...
app: hikvision_isapi
# The monitoring i18n name
name:
zh-CN: 海康威视 ISAPI
en-US: Hikvision ISAPI
# The description and help of this monitoring type
help:
zh-CN: 通过ISAPI接口监控海康威视设备状态,获取设备健康数据。
en-US: Monitor Hikvision devices through ISAPI interface to collect health data.
# Input params define for monitoring(render web ui by the definition)
params:
- field: host
name:
zh-CN: 主机Host
en-US: Host
type: host
required: true
- field: port
name:
zh-CN: 端口
en-US: Port
type: number
range: '[0,65535]'
required: true
defaultValue: 80
- field: timeout
name:
zh-CN: 超时时间(ms)
en-US: Timeout(ms)
type: number
range: '[1000,60000]'
required: true
defaultValue: 5000
- field: username
name:
zh-CN: 用户名
en-US: Username
type: text
required: true
- field: password
name:
zh-CN: 密码
en-US: Password
type: password
required: true
- field: ssl
name:
zh-CN: 启用HTTPS
en-US: SSL
type: boolean
required: false
defaultValue: false
# collect metrics config list
metrics:
- name: system_info
i18n:
zh-CN: 系统信息
en-US: System Info
priority: 0
protocol: http
http:
host: ^_^host^_^
port: ^_^port^_^
ssl: ^_^ssl^_^
url: /ISAPI/System/deviceInfo
method: GET
timeout: ^_^timeout^_^
authorization:
type: Digest Auth
digestAuthUsername: ^_^username^_^
digestAuthPassword: ^_^password^_^
parseType: xmlPath
parseScript: //DeviceInfo
fields:
- field: deviceName
type: 1
i18n:
zh-CN: 设备名称
en-US: Device Name
- field: deviceID
type: 1
i18n:
zh-CN: 设备ID
en-US: Device ID
- field: firmwareVersion
type: 1
i18n:
zh-CN: 固件版本
en-US: Firmware Version
- field: model
type: 1
i18n:
zh-CN: 设备型号
en-US: Device Model
- field: macAddress
type: 1
i18n:
zh-CN: mac地址
en-US: Mac Address
- name: status
i18n:
zh-CN: 设备状态
en-US: Status
priority: 0
protocol: http
http:
host: ^_^host^_^
port: ^_^port^_^
ssl: ^_^ssl^_^
url: /ISAPI/System/status
method: GET
timeout: ^_^timeout^_^
authorization:
type: Digest Auth
digestAuthUsername: ^_^username^_^
digestAuthPassword: ^_^password^_^
parseType: xmlPath
parseScript: //DeviceStatus
fields:
- field: CPU_utilization
i18n:
zh-CN: CPU 利用率
en-US: CPU Utilization
type: 0
unit: '%'
- field: memory_usage
i18n:
zh-CN: 内存使用量
en-US: Memory Usage
type: 0
unit: MB
- field: memory_available
i18n:
zh-CN: 可用内存
en-US: Memory Available
type: 0
unit: MB
- field: cache_size
i18n:
zh-CN: 缓存大小
en-US: Cache Size
type: 0
unit: MB
- field: net_port_1_speed
i18n:
zh-CN: 网口1速度
en-US: Net Port 1 Speed
type: 0
unit: Mbps
- field: net_port_2_speed
i18n:
zh-CN: 网口2速度
en-US: Net Port 2 Speed
type: 0
unit: Mbps
- field: boot_time
i18n:
zh-CN: 启动时间
en-US: Boot Time
type: 1
- field: device_uptime
i18n:
zh-CN: 运行时长
en-US: Device Uptime
type: 1
- field: last_calibration_time
i18n:
zh-CN: 上次校时时间
en-US: Last Calibration Time
type: 1
- field: last_calibration_time_diff
i18n:
zh-CN: 上次校时时间差
en-US: Last Calibration Time Diff
type: 0
unit: s
- field: avg_upload_time
i18n:
zh-CN: 平均上传耗时
en-US: Avg Upload Time
type: 0
unit: ms
- field: max_upload_time
i18n:
zh-CN: 最大上传耗时
en-US: Max Upload Time
type: 0
unit: ms
- field: min_upload_time
i18n:
zh-CN: 最小上传耗时
en-US: Min Upload Time
type: 0
unit: ms
- field: last_calibration_mode
i18n:
zh-CN: 上次校时模式
en-US: Last Calibration Mode
type: 1
- field: last_calibration_address
i18n:
zh-CN: 上次校时地址
en-US: Last Calibration Address
type: 1
- field: response_time
i18n:
zh-CN: 响应时间
en-US: Response Time
type: 0
unit: ms
aliasFields:
- CPUList/CPU/cpuUtilization
- MemoryList/Memory/memoryUsage
- MemoryList/Memory/memoryAvailable
- MemoryList/Memory/cacheSize
- NetPortStatusList/NetPortStatus[id='1']/workSpeed
- NetPortStatusList/NetPortStatus[id='2']/workSpeed
- bootTime
- deviceUpTime
- lastCalibrationTime
- lastCalibrationTimeDiff
- uploadTimeConsumingList/avgTime
- uploadTimeConsumingList/maxTime
- uploadTimeConsumingList/minTime
- lastCalibrationTimeMode
- lastCalibrationTimeAddress
- responseTime
calculates:
- CPU_utilization=CPUList/CPU/cpuUtilization
- memory_usage=MemoryList/Memory/memoryUsage
- memory_available=MemoryList/Memory/memoryAvailable
- cache_size=MemoryList/Memory/cacheSize
- net_port_1_speed=NetPortStatusList/NetPortStatus[id='1']/workSpeed
- net_port_2_speed=NetPortStatusList/NetPortStatus[id='2']/workSpeed
- boot_time=bootTime
- device_uptime=deviceUpTime
- last_calibration_time=lastCalibrationTime
- last_calibration_time_diff=lastCalibrationTimeDiff
- avg_upload_time=uploadTimeConsumingList/avgTime
- max_upload_time=uploadTimeConsumingList/maxTime
- min_upload_time=uploadTimeConsumingList/minTime
- last_calibration_mode=lastCalibrationTimeMode
- last_calibration_address=lastCalibrationTimeAddress
- response_time=responseTime
units:
- memory_usage=KB->MB
- memory_available=KB->MB
- cache_size=KB->MB
@@ -64,8 +64,8 @@ limitations under the License.
### 2.2 Document style check
1. Install `markdownlint-cli2` and run `npm install markdownlint-cli2 --global`
2. Run `markdownlint "home/**/*.md"` in the project to automatically detect the Markdown file format.
3. Run `markdownlint --fix "home/**/*.md"` in the project to automatically format the Markdown file format to ensure that all documents meet the specifications.
2. Run `markdownlint-cli2 "home/**/*.md"` in the project to automatically detect the Markdown file format.
3. Run `markdownlint-cli2 --fix "home/**/*.md"` in the project to automatically format the Markdown file format to ensure that all documents meet the specifications.
Error code description:
+2 -2
View File
@@ -62,8 +62,8 @@ The HertzBeat install package will at `dist/hertzbeat-{version}.tar.gz`
2. Execute under the project root directory: `mvn clean install`
3. Cd to the `collector` directory: `cd collector`
3. Cd to the `hertzbeat-collector` directory: `cd hertzbeat-collector`
4. Execute under `collector` directory: `mvn clean package -Pcluster`
4. Execute under `hertzbeat-collector` directory: `mvn clean package -Pcluster`
The HertzBeat collector package will at `dist/hertzbeat-collector-{version}.tar.gz`
+1 -1
View File
@@ -341,7 +341,7 @@ svn commit -m "release for HertzBeat 1.6.0"
- Check Apache SVN Commit Results
> Visit the address <https://dist.apache.org/repos/dist/dev/incubator/hertzbeat/1.6.0-RC1/> in the browser, check if existed the new material package
> Visit the address <https://dist.apache.org/repos/dist/dev/incubator/hertzbeat/> in the browser, check if existed the new material package
## 4. Enter the community voting stage
+5 -4
View File
@@ -20,10 +20,11 @@ sidebar_label: Download
Previous releases of HertzBeat may be affected by security issues, please use the latest one.
:::
| Version | Date | Download | Release Notes |
|---------|------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------|
| v1.6.1 | 2024.10.29 | [apache-hertzbeat-1.6.1-incubating-bin.tar.gz](https://dist.apache.org/repos/dist/release/incubator/hertzbeat/1.6.1/apache-hertzbeat-1.6.1-incubating-bin.tar.gz) (HertzBeat) ( [signature](https://downloads.apache.org/incubator/hertzbeat/1.6.1/apache-hertzbeat-1.6.1-incubating-bin.tar.gz.asc) , [sha512](https://downloads.apache.org/incubator/hertzbeat/1.6.1/apache-hertzbeat-1.6.1-incubating-bin.tar.gz.sha512) ) <br/> [apache-hertzbeat-collector-1.6.1-incubating-bin.tar.gz](https://downloads.apache.org/incubator/hertzbeat/1.6.1/apache-hertzbeat-collector-1.6.1-incubating-bin.tar.gz) (HertzBeat Collector) ( [signature](https://downloads.apache.org/incubator/hertzbeat/1.6.1/apache-hertzbeat-collector-1.6.1-incubating-bin.tar.gz.asc) , [sha512](https://downloads.apache.org/incubator/hertzbeat/1.6.1/apache-hertzbeat-collector-1.6.1-incubating-bin.tar.gz.sha512) ) <br/> [apache-hertzbeat-1.6.1-incubating-src.tar.gz](https://downloads.apache.org/incubator/hertzbeat/1.6.1/apache-hertzbeat-1.6.1-incubating-src.tar.gz) (HertzBeat Source) ( [signature](https://downloads.apache.org/incubator/hertzbeat/1.6.1/apache-hertzbeat-1.6.1-incubating-src.tar.gz.asc) , [sha512](https://downloads.apache.org/incubator/hertzbeat/1.6.1/apache-hertzbeat-1.6.1-incubating-src.tar.gz.sha512) ) <br/> [apache-hertzbeat-1.6.1-incubating-docker-compose.tar.gz](https://dist.apache.org/repos/dist/release/incubator/hertzbeat/1.6.1/apache-hertzbeat-1.6.1-incubating-docker-compose.tar.gz) (docker-compose) ( [signature](https://dist.apache.org/repos/dist/release/incubator/hertzbeat/1.6.1/apache-hertzbeat-1.6.1-incubating-docker-compose.tar.gz.asc) , [sha512](https://dist.apache.org/repos/dist/release/incubator/hertzbeat/1.6.1/apache-hertzbeat-1.6.1-incubating-docker-compose.tar.gz.sha512) ) | [release note](https://github.com/apache/hertzbeat/releases/tag/v1.6.1)|
| v1.6.0 | 2024.06.10 | [apache-hertzbeat-1.6.0-incubating-bin.tar.gz](https://downloads.apache.org/incubator/hertzbeat/1.6.0/apache-hertzbeat-1.6.0-incubating-bin.tar.gz) (HertzBeat) ( [signature](https://downloads.apache.org/incubator/hertzbeat/1.6.0/apache-hertzbeat-1.6.0-incubating-bin.tar.gz.asc) , [sha512](https://downloads.apache.org/incubator/hertzbeat/1.6.0/apache-hertzbeat-1.6.0-incubating-bin.tar.gz.sha512) ) <br/> [apache-hertzbeat-collector-1.6.0-incubating-bin.tar.gz](https://downloads.apache.org/incubator/hertzbeat/1.6.0/apache-hertzbeat-collector-1.6.0-incubating-bin.tar.gz) (HertzBeat Collector) ( [signature](https://downloads.apache.org/incubator/hertzbeat/1.6.0/apache-hertzbeat-collector-1.6.0-incubating-bin.tar.gz.asc) , [sha512](https://downloads.apache.org/incubator/hertzbeat/1.6.0/apache-hertzbeat-collector-1.6.0-incubating-bin.tar.gz.sha512) ) <br/> [apache-hertzbeat-1.6.0-incubating-src.tar.gz](https://downloads.apache.org/incubator/hertzbeat/1.6.0/apache-hertzbeat-1.6.0-incubating-src.tar.gz) (HertzBeat Source) ( [signature](https://downloads.apache.org/incubator/hertzbeat/1.6.0/apache-hertzbeat-1.6.0-incubating-src.tar.gz.asc) , [sha512](https://downloads.apache.org/incubator/hertzbeat/1.6.0/apache-hertzbeat-1.6.0-incubating-src.tar.gz.sha512) ) | [release note](https://github.com/apache/hertzbeat/releases/tag/v1.6.0) |
| Version | Date | Download | Release |
| ------- | ---------- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------|
| v1.7.0 | 2025.04.02 | [apache-hertzbeat-1.7.0-incubating-bin.tar.gz](https://www.apache.org/dyn/closer.lua/incubator/hertzbeat/1.7.0/apache-hertzbeat-1.7.0-incubating-bin.tar.gz) (Server) ( [signature](https://downloads.apache.org/incubator/hertzbeat/1.7.0/apache-hertzbeat-1.7.0-incubating-bin.tar.gz.asc) , [sha512](https://downloads.apache.org/incubator/hertzbeat/1.7.0/apache-hertzbeat-1.7.0-incubating-bin.tar.gz.sha512) ) <br/> [apache-hertzbeat-collector-1.7.0-incubating-bin.tar.gz](https://www.apache.org/dyn/closer.lua/incubator/hertzbeat/1.7.0/apache-hertzbeat-collector-1.7.0-incubating-bin.tar.gz) (Collector) ( [signature](https://downloads.apache.org/incubator/hertzbeat/1.7.0/apache-hertzbeat-collector-1.7.0-incubating-bin.tar.gz.asc) , [sha512](https://downloads.apache.org/incubator/hertzbeat/1.7.0/apache-hertzbeat-collector-1.7.0-incubating-bin.tar.gz.sha512) ) <br/> [apache-hertzbeat-1.7.0-incubating-src.tar.gz](https://www.apache.org/dyn/closer.lua/incubator/hertzbeat/1.7.0/apache-hertzbeat-1.7.0-incubating-src.tar.gz) (Source Code) ( [signature](https://downloads.apache.org/incubator/hertzbeat/1.7.0/apache-hertzbeat-1.7.0-incubating-src.tar.gz.asc) , [sha512](https://downloads.apache.org/incubator/hertzbeat/1.7.0/apache-hertzbeat-1.7.0-incubating-src.tar.gz.sha512) ) <br/> [apache-hertzbeat-1.7.0-incubating-docker-compose.tar.gz](https://www.apache.org/dyn/closer.lua/incubator/hertzbeat/1.7.0/apache-hertzbeat-1.7.0-incubating-docker-compose.tar.gz) (Docker Compose) ( [signature](https://downloads.apache.org/incubator/hertzbeat/1.7.0/apache-hertzbeat-1.7.0-incubating-docker-compose.tar.gz.asc) , [sha512](https://downloads.apache.org/incubator/hertzbeat/1.7.0/apache-hertzbeat-1.7.0-incubating-docker-compose.tar.gz.sha512) ) | [note](https://github.com/apache/hertzbeat/releases/tag/v1.7.0) |
| v1.6.1 | 2024.10.29 | [apache-hertzbeat-1.6.1-incubating-bin.tar.gz](https://www.apache.org/dyn/closer.lua/incubator/hertzbeat/1.6.1/apache-hertzbeat-1.6.1-incubating-bin.tar.gz) (Server) ( [signature](https://downloads.apache.org/incubator/hertzbeat/1.6.1/apache-hertzbeat-1.6.1-incubating-bin.tar.gz.asc) , [sha512](https://downloads.apache.org/incubator/hertzbeat/1.6.1/apache-hertzbeat-1.6.1-incubating-bin.tar.gz.sha512) ) <br/> [apache-hertzbeat-collector-1.6.1-incubating-bin.tar.gz](https://www.apache.org/dyn/closer.lua/incubator/hertzbeat/1.6.1/apache-hertzbeat-collector-1.6.1-incubating-bin.tar.gz) (Collector) ( [signature](https://downloads.apache.org/incubator/hertzbeat/1.6.1/apache-hertzbeat-collector-1.6.1-incubating-bin.tar.gz.asc) , [sha512](https://downloads.apache.org/incubator/hertzbeat/1.6.1/apache-hertzbeat-collector-1.6.1-incubating-bin.tar.gz.sha512) ) <br/> [apache-hertzbeat-1.6.1-incubating-src.tar.gz](https://www.apache.org/dyn/closer.lua/incubator/hertzbeat/1.6.1/apache-hertzbeat-1.6.1-incubating-src.tar.gz) (Source Code) ( [signature](https://downloads.apache.org/incubator/hertzbeat/1.6.1/apache-hertzbeat-1.6.1-incubating-src.tar.gz.asc) , [sha512](https://downloads.apache.org/incubator/hertzbeat/1.6.1/apache-hertzbeat-1.6.1-incubating-src.tar.gz.sha512) ) <br/> [apache-hertzbeat-1.6.1-incubating-docker-compose.tar.gz](https://www.apache.org/dyn/closer.lua/incubator/hertzbeat/1.6.1/apache-hertzbeat-1.6.1-incubating-docker-compose.tar.gz) (Docker Compose) ( [signature](https://downloads.apache.org/incubator/hertzbeat/1.6.1/apache-hertzbeat-1.6.1-incubating-docker-compose.tar.gz.asc) , [sha512](https://downloads.apache.org/incubator/hertzbeat/1.6.1/apache-hertzbeat-1.6.1-incubating-docker-compose.tar.gz.sha512) ) | [note](https://github.com/apache/hertzbeat/releases/tag/v1.6.1) |
| v1.6.0 | 2024.06.10 | [apache-hertzbeat-1.6.0-incubating-bin.tar.gz](https://www.apache.org/dyn/closer.lua/incubator/hertzbeat/1.6.0/apache-hertzbeat-1.6.0-incubating-bin.tar.gz) (Server) ( [signature](https://downloads.apache.org/incubator/hertzbeat/1.6.0/apache-hertzbeat-1.6.0-incubating-bin.tar.gz.asc) , [sha512](https://downloads.apache.org/incubator/hertzbeat/1.6.0/apache-hertzbeat-1.6.0-incubating-bin.tar.gz.sha512) ) <br/> [apache-hertzbeat-collector-1.6.0-incubating-bin.tar.gz](https://www.apache.org/dyn/closer.lua/incubator/hertzbeat/1.6.0/apache-hertzbeat-collector-1.6.0-incubating-bin.tar.gz) (Collector) ( [signature](https://downloads.apache.org/incubator/hertzbeat/1.6.0/apache-hertzbeat-collector-1.6.0-incubating-bin.tar.gz.asc) , [sha512](https://downloads.apache.org/incubator/hertzbeat/1.6.0/apache-hertzbeat-collector-1.6.0-incubating-bin.tar.gz.sha512) ) <br/> [apache-hertzbeat-1.6.0-incubating-src.tar.gz](https://www.apache.org/dyn/closer.lua/incubator/hertzbeat/1.6.0/apache-hertzbeat-1.6.0-incubating-src.tar.gz) (Source Code) ( [signature](https://downloads.apache.org/incubator/hertzbeat/1.6.0/apache-hertzbeat-1.6.0-incubating-src.tar.gz.asc) , [sha512](https://downloads.apache.org/incubator/hertzbeat/1.6.0/apache-hertzbeat-1.6.0-incubating-src.tar.gz.sha512) ) | [note](https://github.com/apache/hertzbeat/releases/tag/v1.6.0) |
## Release Docker Image
+57
View File
@@ -0,0 +1,57 @@
---
id: alarm_center
title: Alarm Center
sidebar_label: Alarm Center
keywords:
[open-source monitoring system, alarm center, alarm management, alarm display]
---
> The Alarm Center serves as a comprehensive visualization platform that displays all alarms after undergoing grouping, consolidation, suppression, and silencing processes. It encompasses both internally triggered threshold-based alarms and integrated third-party notifications.
## Alarm Sources
The HertzBeat Alarm Center manages notifications from two primary sources:
1. Internal Threshold-Triggered Alarms
- Generated when monitoring metrics exceed predefined thresholds
- Directly correlated with monitoring tasks and threshold rules configured within the system
- Manageable through adjustment of monitoring parameters and threshold configurations
2. Third-Party Integrated Alarms
- Received through API interfaces from external systems
- Compatible with various monitoring systems and alarm platforms
- Processed through identical workflow as internal alarms
## Alarm Processing Mechanism
Before appearing in the Alarm Center, all notifications undergo several processing stages:
1. Grouping
- Categorizes related alarms based on source, type, severity, and other attributes (labels)
- Facilitates efficient management of high-volume alarms
- Supports customizable grouping rules for diverse scenarios
2. Consolidation
- Mitigates notification fatigue from multiple similar alarms within short intervals
- Presents consolidated alarms in a streamlined format, eliminating redundancy
3. Suppression
- Manages alarm dependencies
- Suppresses secondary alarms when primary alarms are triggered
- Supports configurable suppression rules based on alarm dependencies
4. Silencing
- Temporarily mutes specific alarms during designated periods
- Ideal for system maintenance windows and known issue handling
- Enables time-based silence rule configuration
## Alarm Center Interface
![alarm_center](/img/docs/help/alarm-center-1.png)
The Alarm Center provides a comprehensive view of all system alarms:
1. Alarm Display
- Lists all alarms with crucial information including status, source, labels, and timestamps
- Offers detailed view functionality for comprehensive alarm information and context
2. Search Functionality
- Enables rapid alarm identification
- Supports multiple search criteria (labels, annotations, alarm status)
3. Alarm Management
- Alarm Deletion: Removes alarms no longer requiring attention
+24
View File
@@ -0,0 +1,24 @@
---
id: alarm_group
title: Alarm Grouping
sidebar_label: Alarm Grouping
keywords: [Open source monitoring system, alarm reduce, alarm grouping]
---
> Group convergence supports grouping and convergence of alarms for specified packet labels, deduplication and convergence of the same repeated alarms for the time period. When the threshold rule triggers the alarm or external alarm reporting, it will enter the packet convergence to alarm grouping to deduplicate the alarm to avoid a large number of alarm messages causing alarm storms.
## Grouping Policy Parameter Configuration
- Strategy Name: The name that uniquely identifies the grouping policy
- Group Labels: Alarm grouping tag, support up to 10 tags
> Tag source: monitoring, threshold rules, tags carried by external alarms
- Wait Time: Waiting time after a new alarm is generated. The same alarms received during this time will be grouped, with a default of 30 seconds.
> When a new (unable to join an existing group) alarm is generated, the group convergence will wait according to the `wait time`, during which time, the same alarm or the alarm that meets the grouping conditions will be grouped. The alarm after the grouping is sent to the alarm suppression module for subsequent processing until the time interval between the current time and the first alarm generation in the packet exceeds the `wait time`.
- Interval time: The minimum time interval for sending group alarm notifications to avoid excessive alarm notifications, default 5 minutes
- Repeat interval: The minimum notification interval for repeated alarms. For continuously triggered alarms, avoid repeated notifications, default 4 hours
**Note**: Only grouped alarms can be suppressed using suppression rules.
+45
View File
@@ -0,0 +1,45 @@
---
id: alarm_inhibit
title: Alarm Inhibition
sidebar_label: Alarm Inhibition
keywords: [ Open Source Monitoring System, Alarm Convergence, Alarm Inhibition ]
---
> Alarm inhibition is used to configure the inhibition relationship between alarms. When an alarm occurs, other alarms can be suppressed. It can be understood as "important" alarms suppressing "
> unimportant" alarms. For example, the alarm generated by a server crash suppresses the alarms generated by other services on this server.
## Prerequisites
- Correctly configure the alarm grouping rule
## Inhibit rule configuration
- Inhibit Rule Name: The name that uniquely identifies the suppression rule
- Source Labels: When the alarm contains these tags, the target alarm will be suppressed. Multiple tags can be added.
> Identify the tag of the "important" alarm. The alarm tag needs to contain all source tags to suppress the alarm marked by the target tag.
- Target Labels: Alarms matching these tags will be suppressed.
> Identify the label of "unimportant" alarms. Alarm labels need to contain all target labels to be suppressed.
- Equal Labels: Labels for determining alarm correlation. Supports up to 10 labels.
- Enabled: Enable or disable this inhibit rule
## Example
Scenario: Use Hertzbeat to monitor two Centos servers 192.168.1.1, 192.168.1.2, and Redis services Redis-1 and Redis-2 deployed on the two servers.
And configure the following threshold rules:
- Monitor Centos Linux / Monitor availability. Bind label `server-status:down`
- Monitor Redis database / Monitor availability. Bind label `redis-status:down`
If you need to achieve that when the Centos downtime alarm is generated, the Redis alarm will no longer be generated, you can configure the following alarm suppression rules:
- Source label: `server-status:down`
- Target label: `redis-status:down`
- Equal label: `instancehost`
When the Centos 192.168.1.1 downtime alarm is generated, the Redis-1 unavailable alarm will no longer be generated. And at the same time, when Centos 192.168.1.2 is running normally and Redis-2 is
unavailable, the alarm notifying Redis-2 unavailable will be generated normally.
+29
View File
@@ -0,0 +1,29 @@
---
id: alarm_silence
title: Alert Silence
sidebar_label: Alert Silence
keywords: [ Open Source Monitoring System, Alert Silence ]
---
> The alert silence management allows you to configure silence policies to suppress alert notifications during specified time periods, such as during system maintenance or when you dont want to be disturbed by alerts at night or on weekends. Alert silence rules support both one-time and periodic time periods, and can match specific alerts using labels and alert levels.
## One-Time Time Period Silence Configuration
- Silence Strategy Name: A unique name to identify the silence policy;
- Match All: Whether to enable this silence policy for all alerts;
- Label Match: When "Apply to All" is disabled, you can match alerts to be silenced based on specified labels;
- Silence Type: Select "One Time Silence";
- Silence Period: After selecting "One Time Silence", the silence period configuration is shown in the following image, which can be configured as needed
![alarm_silence](/img/docs/help/alert-silence-1-en.png)
- Enable: Enable or disable the silence policy.
## Periodic Time Period Silence Configuration
- Silence Strategy Name: A unique name to identify the silence policy;
- Match All: Whether to enable this silence policy for all alerts;
- Label Match: When "Apply to All" is disabled, you can match alerts to be silenced based on specified labels;
- Silence Type: Select "Periodic Silence";
- Choose Date: After selecting "Periodic Silence", you can configure the dates when alerts should be silenced;
- Silence Period: After selecting "Periodic Silence", the silence period configuration is shown in the following image, which can be configured as needed (e.g., silencing alerts during weekends)
![alarm_silence](/img/docs/help/alert-silence-2-en.png)
- Enable: Enable or disable the silence policy.
+6 -6
View File
@@ -1,15 +1,15 @@
---
id: api
title: Monitoring HTTP API
sidebar_label: HTTP API
keywords: [open source monitoring tool, monitoring http api]
id: api
title: Monitoring HTTP API
sidebar_label: HTTP API
keywords: [ open source monitoring tool, monitoring http api ]
---
> Call HTTP API interface, check whether the interface is available, and monitor its response time and other Metrics.
### Configuration parameter
| Parameter name | Parameter help description |
| Parameter name | Parameter help description |
|---------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Monitoring Host | Monitored IPV4, IPV6 or domain name. Note⚠️Without protocol header (eg: https://, http://) |
| Monitoring name | Identify the name of this monitoring. The name needs to be unique |
@@ -22,7 +22,7 @@ keywords: [open source monitoring tool, monitoring http api]
| Headers | HTTP request headers |
| Params | HTTP query params, support [time expression](time_expression) |
| Content-Type | Set the resource type when carrying the BODY request body data request |
| Request BODY | Set the carry BODY request body data, which is valid when PUT or POST request method is used |
| Request BODY | Set the carry BODY request body data, which is valid when PUT or POST request method is used, support [time expression](time_expression) |
| Collection interval | Interval time of monitor periodic data collection, unit: second, and the minimum interval that can be set is 30 seconds |
| Whether to detect | Whether to detect and check the availability of monitoring before adding monitoring. Adding and modifying operations will continue only after the detection is successful |
| Description remarks | For more information about identifying and describing this monitoring, users can note information here |
+131
View File
@@ -0,0 +1,131 @@
---
id: collector
title: HertzBeat Collector
sidebar_label: Collector
keywords: [monitoring, observability, collector, metrics]
---
> HertzBeat Collector is a lightweight data collection module that enables metrics collection, high availability deployments, and cloud-edge collaboration in Apache HertzBeat (incubating).
## Introduction
HertzBeat Collector is a versatile and lightweight metrics collection module within the Apache HertzBeat monitoring system. It's designed to gather monitoring data from various targets and send the collected metrics to the main HertzBeat server for processing, alerting, and visualization.
With the collector module, you can implement:
- **High Availability**: Deploy multiple collectors to ensure continuous monitoring even if some collector instances fail
- **Load Balancing**: Distribute monitoring tasks across multiple collectors to improve performance
- **Cloud-Edge Collaboration**: Monitor resources in isolated networks while managing everything from a central HertzBeat server
## Collector Architecture
The collector module is built with a modular design to make it easily extensible for various monitoring scenarios. The architecture consists of:
1. **Collector Entry Point**: The main entry point for running the collector module, from which collection tasks are executed after startup.
2. **collector-basic**: Contains implementations for common protocols like HTTP, JDBC, SSH, SNMP, etc. These collectors typically don't require additional proprietary dependencies and can handle most basic monitoring needs.
3. **collector-common**: Provides general utility classes and methods, such as connection pools and caching mechanisms that other modules can reuse.
4. **collector-xxx**: Extension modules for specific services or protocols (MongoDB, RocketMQ, Kafka, NebulaGraph, etc.). These modules often require specific dependencies for their respective services.
## Supported Protocols
HertzBeat Collector supports an extensive list of monitoring protocols:
| Protocol Category | Protocols |
| ----------------- | ------------------------------------------------------------------------------------- |
| Web/API | `http`, `ssl_cert`, `websocket` |
| Databases | `jdbc`, `redis`, `mongodb`, `memcached` |
| Operating Systems | `ssh`, `ipmi` |
| Network | `icmp` (ping), `telnet`, `snmp`, `modbus` |
| Messaging | `mqtt`, `rocketmq`, `kafka` |
| Email | `pop3`, `smtp`, `imap` |
| Cloud Services | `prometheus`, `nebulagraph`, `ngql` |
| Others | `jmx`, `dns`, `ftp`, `ntp`, `udp`, `nginx`, `redfish`, `script`, `registry`, `httpsd` |
## Deployment Options
You can deploy HertzBeat Collector in several ways depending on your environment and needs, once you log in to the HertzBeat web interface and go to the collector, you can see the deployment options.
Parameters explanation:
- `-e IDENTITY=custom-collector-name`: (Optional) Set a unique identifier for this collector. Must be unique across all collectors.
- `-e MODE=public`: Set the running mode (public or private), for public cluster or private cloud-edge mode.
- `-e MANAGER_HOST=192.168.1.100`: Important! Set the IP address of the main HertzBeat server. Replace with your actual server IP.
- `-e MANAGER_PORT=1158`: (Optional) Set the port of the main HertzBeat server, default is 1158.
- `-v $(pwd)/logs:/opt/hertzbeat-collector/logs`: (Optional) Mount the log files to the local host.
## Operating Modes
HertzBeat Collector supports two operating modes:
### Public Mode (Cluster Mode)
In public mode, collectors form a cluster with the main HertzBeat server. Tasks are automatically distributed among collectors, providing high availability and load balancing.
- Set `MODE=public` when deploying the collector
- All collectors must have connectivity to the main HertzBeat server
- Great for horizontal scaling to handle large numbers of monitoring tasks
### Private Mode (Cloud-Edge Mode)
In private mode, collectors operate in isolated networks while still reporting to a central HertzBeat server. This allows monitoring of resources in multiple separate networks.
- Set `MODE=private` when deploying the collector
- Collectors need outbound connectivity to the HertzBeat server, but inbound connectivity is not required
- Ideal for monitoring resources across different data centers, cloud providers, or network segments
## Configuration Parameters
| Parameter | Description | Default |
| -------------- | ----------------------------------- | ------------------------- |
| `identity` | Unique identifier for the collector | Auto-generated if not set |
| `mode` | Operating mode (public/private) | public |
| `manager-host` | IP address of the HertzBeat server | IP |
| `manager-port` | Port of the HertzBeat server | 1158 |
## Collector Management
You can manage collectors through the HertzBeat web interface:
1. Navigate to the Overview page to see all registered collectors
2. Monitor collector status (online/offline), metrics tasks, and system information
3. Enable or disable collectors as needed
## High Availability Setup
To achieve high availability with HertzBeat collectors:
1. Deploy multiple collector instances across different servers or containers
2. Ensure all collectors have the same `mode` setting
3. Connect all collectors to the same HertzBeat server
4. HertzBeat will automatically distribute monitoring tasks and handle failover
If a collector goes offline, its tasks will be reassigned to other available collectors. When the collector comes back online, it will receive new tasks based on the current load distribution.
## Cloud-Edge Collaboration
For monitoring across isolated networks:
1. Deploy HertzBeat Server in your central management network
2. Deploy collectors in each isolated network you need to monitor
3. Configure collectors with:
- `MODE=private`
- `MANAGER_HOST=` pointing to your central HertzBeat server
4. Ensure outbound connectivity from each isolated network to the central server
5. Manage all monitoring tasks from the central HertzBeat dashboard
## Advanced Features
### Custom Protocol Support
HertzBeat's architecture allows for extending the collector with custom protocols. Developers can create new collector modules following the project's modular design.
### Task Scheduling
The collector automatically handles task scheduling based on task priority, available resources, and current system load. Tasks are processed with intelligent prioritization to ensure critical monitoring is performed first.
### Resource Utilization
Collectors are designed to be lightweight and efficient with system resources, making them suitable for deployment on various hardware, from small edge devices to powerful servers.
+60
View File
@@ -0,0 +1,60 @@
---
id: dahua
title: Monitor Dahua Devices
sidebar_label: Dahua
keywords: [ monitor, dahua ]
---
> Monitor Dahua devices through HTTP interface to collect health data.
## Configuration
| Parameter | Description |
| ----------- | ----------- |
| Host | Target IP/Domain |
| Name | Unique monitor name |
| Port | Network port (default 80) |
| Timeout | Request timeout in ms |
| Username | Device username |
| Password | Device password |
| SSL | Enable HTTPS |
| Interval | Collection interval (≥30s) |
## Metrics
### Network Info
- Default Interface
- Domain Name
- Hostname
- eth0 IP Address
- eth0 Gateway
- eth0 MAC
- eth0 Subnet Mask
- eth0 MTU
- DNS Servers
### User Info
- Client Address
- Username
- Login Type
- Login Time
### NTP Info
- NTP Server
- NTP Port
- Sync Interval
## Implementation
Access device APIs via:
1. Network: `/cgi-bin/configManager.cgi?action=getConfig&name=Network`
2. Users: `/cgi-bin/userManager.cgi?action=getActiveUserInfoAll`
3. NTP: `/cgi-bin/configManager.cgi?action=getConfig&name=NTP`
Using Digest Auth and parsing config format responses.
+5 -1
View File
@@ -9,7 +9,7 @@ keywords: [Grafana, Historical Dashboard]
### Prerequisites
- The `Grafana` version 8.1.0 or later is installed and running.
- We recommend using the latest version of Grafana. Earlier versions may not support the exposed API.
- The `Grafana` service is started and the account password is configured.
- The `HertzBeat` service is started and the `VictoriaMetrics` time-series database is configured (note: the `VictoriaMetrics` data source is required).
@@ -22,6 +22,10 @@ keywords: [Grafana, Historical Dashboard]
ref: <https://grafana.com/blog/2023/10/10/how-to-embed-grafana-dashboards-into-web-applications/>
In the `Grafana` configuration file `grafana.ini`, set the `allow_embedding = true`.
In the `Grafana` configuration file `grafana.ini`, set the `[auth.anonymous]` option to `true`.
Or run `Grafana` with the following command via `docker`:
```bash
docker run -itd --name grafana -p 3000:3000 -e "GF_AUTH_PROXY_ENABLED=true" -e "GF_AUTH_ANONYMOUS_ENABLED=true" -e "GF_SECURITY_ALLOW_EMBEDDING=true" grafana/grafana:latest
```ini
allow_embedding = true
+9
View File
@@ -107,6 +107,8 @@ sidebar_label: Help Center
> The triggered alarm information center provides query and filtering of alarm deletion, alarm processing, mark unprocessed, alarm level status, etc.
More details see&emsp;&#x1F449;&emsp;[Alarm center](alarm_center)
### Alarm configuration
> The Metric threshold configuration provides the Metric threshold configuration in the form of expression, which can set the alarm level, trigger times, alarm notification template and whether it is enabled, correlation monitoring and other functions.
@@ -114,6 +116,13 @@ sidebar_label: Help Center
More details see&emsp;&#x1F449;&emsp;[Threshold alarm](alert_threshold) <br />
&emsp;&emsp;&emsp;&#x1F449;&emsp;[Threshold expression](alert_threshold_expr)
### Alarm reduce
> Combine related alarms through alarm grouping, alarm suppression and other functions to reduce the alarm storm caused by one event, reduce alarm noise and improve alarm response efficiency.
More details see&emsp;&#x1F449;&emsp;[Alarm grouping](alarm_group) <br />
&emsp;&emsp;&emsp;&#x1F449;&emsp;[Alarm inhibit](alarm_inhibit)
### Alarm notification
> After triggering the alarm information, in addition to being displayed in the alarm center list, it can also be notified to the designated recipient in a specified way (e-mail, wechat and FeiShu etc.)
+60
View File
@@ -0,0 +1,60 @@
---
id: hikvision_isapi
title: Monitor Hikvision ISAPI
sidebar_label: Hikvision ISAPI
keywords: [ monitor, hikvision_isapi ]
---
> Monitor Hikvision devices through ISAPI interface to collect health data.
## Monitor Configuration
| Parameter | Description |
| ----------- | ----------- |
| Host | The IP or domain name of the monitored device. Note⚠️ Do not include protocol prefix (eg: https://, http://). |
| Name | The unique name that identifies this monitor. |
| Port | Network request port, default is 80. |
| Timeout | Timeout period, in milliseconds, default is 5000ms. |
| Username | Login username for Hikvision device. |
| Password | Login password for Hikvision device. |
| SSL | Whether to enable HTTPS, disabled by default. |
| Collection Interval | The interval time for periodic data collection, in seconds. The minimum interval is 30 seconds. |
## Metrics
### System Info
- Device Name
- Device ID
- Firmware Version
- Device Model
- Mac Address
### Status
- CPU Utilization (%)
- Memory Usage (MB)
- Memory Available (MB)
- Cache Size (MB)
- Net Port 1 Speed (Mbps)
- Net Port 2 Speed (Mbps)
- Boot Time
- Device Uptime
- Last Calibration Time
- Last Calibration Time Diff (s)
- Avg Upload Time (ms)
- Max Upload Time (ms)
- Min Upload Time (ms)
- Last Calibration Mode
- Last Calibration Address
- Response Time (ms)
## Implementation Principle
The monitoring is implemented by accessing the Hikvision device's ISAPI interface:
1. Collect system information through: `/ISAPI/System/deviceInfo`
2. Collect device status through: `/ISAPI/System/status`
It uses HTTP protocol with Digest Authentication to access the interfaces and parses XML response data to extract monitoring metrics.
+1 -1
View File
@@ -28,7 +28,7 @@ management:
web:
exposure:
include: '*'
enabled-by-default: on
enabled-by-default: true
```
*Note: If your project also introduces authentication related dependencies, such as springboot security, the interfaces exposed by SpringBoot Actor may be intercepted. In this case, you need to manually release these interfaces. Taking springboot security as an example, you should add the following code to the Security Configuration class:*
+1 -1
View File
@@ -28,7 +28,7 @@ management:
web:
exposure:
include: '*'
enabled-by-default: on
enabled-by-default: true
```
*Note: If your project also introduces authentication related dependencies, such as springboot security, the interfaces exposed by SpringBoot Actor may be intercepted. In this case, you need to manually release these interfaces. Taking springboot security as an example, you should add the following code to the Security Configuration class:*
+1 -1
View File
@@ -73,4 +73,4 @@ Use advanced settings when adding `SqlServer` monitoring, customize JDBC URL, ad
Example: ```jdbc:sqlserver://127.0.0.1:1433;DatabaseName=demo;encrypt=true;trustServerCertificate=true;```
Reference document: [microsoft pkix-path-building-failed-unable-to-find-valid-certification](<https://techcommunity.microsoft.com/t5/azure-database-support-blog/pkix-path-building-> failed-unable-to-find-valid-certification/ba-p/2591304)
Reference document: [microsoft pkix-path-building-failed-unable-to-find-valid-certification](https://techcommunity.microsoft.com/t5/azure-database-support-blog/pkix-path-building-failed-unable-to-find-valid-certification/ba-p/2591304)
+17 -17
View File
@@ -16,23 +16,23 @@ sidebar_label: Template Marketplace
1. **No filter: displayed in order of upload**
![search.png](template-marketplace-img/search.png)
![search.png](../../static/img/template-marketplace/search.png)
2. **Filtering by category: currently divided into six categories**
> **📋Todo:** develop tag function, subdivided within the category, such as database monitoring template can be divided into MySQL, Oracle, etc.
![search-category.png](template-marketplace-img/search-category.png)
![search-category.png](../../static/img/template-marketplace/search-category.png)
3. **Fuzzy search by Title**
![search-name.png](template-marketplace-img/search-name.png)
![search-name.png](../../static/img/template-marketplace/search-name.png)
4. **Hover window function: download the latest version, view details, favorite/un-favorite**
> Show if the user has favorites after logging in
![img.png](template-marketplace-img/hover-window-guest.png)![img_1.png](template-marketplace-img/hover-window-user.png)
![img.png](../../static/img/template-marketplace/hover-window-guest.png)![img_1.png](../../static/img/template-marketplace/hover-window-user.png)
5. **Sort: Eight Sorting Methods**
@@ -48,19 +48,19 @@ sidebar_label: Template Marketplace
> **📋Todo:** Upgrade to MarkDown format
![img.png](template-marketplace-img/detail-info.png)
![img.png](../../static/img/template-marketplace/detail-info.png)
2. **Version: Historical version download, sharing and basic information display**
> **📋Todo:** Set up a view function for each historical version to display information such as the version description.
![img.png](template-marketplace-img/detail-version.png)
![img.png](../../static/img/template-marketplace/detail-version.png)
3. **FAQ**
> **📋Todo:** Discussion or issue Q&A section
![img.png](template-marketplace-img/detail-faq.png)
![img.png](../../static/img/template-marketplace/detail-faq.png)
4. **Download**
@@ -68,7 +68,7 @@ sidebar_label: Template Marketplace
> The latest version can also be downloaded directly from the template detail page.
> The historical version can be downloaded from the version page.
![img.png](template-marketplace-img/download.png)
![img.png](../../static/img/template-marketplace/download.png)
5. **Share**
@@ -78,7 +78,7 @@ sidebar_label: Template Marketplace
>
> **📋Todo:** Shared template detail page is accessed through the URL of the shared template, and the shared person is free to choose whether to download or not.
![img.png](template-marketplace-img/share.png)
![img.png](../../static/img/template-marketplace/share.png)
### User Center
@@ -94,21 +94,21 @@ sidebar_label: Template Marketplace
>
> **📋Todo:** Function to update template information
![img_1.png](template-marketplace-img/user-center-asset.png)
![img_1.png](../../static/img/template-marketplace/user-center-asset.png)
![img.png](template-marketplace-img/asset-detail.png)
![img.png](../../static/img/template-marketplace/asset-detail.png)
2. **Version Upgrade**
> The user defines the new version number under this template family, updates the version information, and uploads the latest version of the file
![img.png](template-marketplace-img/asset-upgrade.png)
![img.png](../../static/img/template-marketplace/asset-upgrade.png)
3. **Star**
![img.png](template-marketplace-img/user-center-star.png)
![img.png](../../static/img/template-marketplace/user-center-star.png)
![img.png](template-marketplace-img/user-center-star-detail.png)
![img.png](../../static/img/template-marketplace/user-center-star-detail.png)
4. **Upload**
@@ -116,7 +116,7 @@ sidebar_label: Template Marketplace
>
> Fill in the template name, select the template category, fill in the description information and version information, and upload files
![img.png](template-marketplace-img/user-center-upload.png)
![img.png](../../static/img/template-marketplace/user-center-upload.png)
### Sign Up & Login
@@ -128,13 +128,13 @@ sidebar_label: Template Marketplace
>
> **📋Todo:** Captcha function, email verification function
![img.png](template-marketplace-img/sign-up.png)
![img.png](../../static/img/template-marketplace/sign-up.png)
2. **Login**
> **📋Todo:** Captcha function and forgot password function
![img.png](template-marketplace-img/email-login.png)
![img.png](../../static/img/template-marketplace/email-login.png)
## Development Steps
+1
View File
@@ -53,6 +53,7 @@ ${FORMATTER [{ + | - }<DURATION> <TIME_UNIT>]}
#### Where to Use
- Request parameters for HTTP protocol monitoring types
- Request Body for HTTP protocol monitoring types
#### Usage Examples
+47
View File
@@ -0,0 +1,47 @@
---
id: uniview
title: Monitor Uniview Devices
sidebar_label: Uniview
keywords: [ monitor, uniview ]
---
> Monitor Uniview devices through HTTP interface.
## Configuration
| Parameter | Description |
| ----------- | ----------- |
| Host | Device IP/Domain |
| Name | Unique identifier |
| Port | Default 80 |
| Timeout | Milliseconds |
| Username | Auth username |
| Password | Auth password |
| SSL | HTTPS Enable |
| Interval | ≥30 seconds |
## Metrics
### System Info
- Device Name
- Serial Number
- Firmware Version
- Device Model
### NTP Info
- NTP Server IP
- NTP Port
- Sync Interval
- NTP Status
## Implementation
Access device APIs:
1. System: `/LAPI/V1.0/System/DeviceInfo`
2. NTP: `/LAPI/V1.0/System/Time/NTP`
Using Digest Authentication and parsing JSON responses.
@@ -1,6 +1,6 @@
{
"version.label": {
"message": "v1.6.x",
"message": "v1.7.x",
"description": "The label for version current"
},
"sidebar.docs.category.quickstart": {
@@ -51,6 +51,10 @@
"message": "Threshold Alarm Setting",
"description": "The label for category threshold in sidebar docs"
},
"sidebar.docs.category.reduce": {
"message": "Alarm Reduce Setting",
"description": "The label for category reduce in sidebar docs"
},
"sidebar.docs.category.notice": {
"message": "Alarm Notice Setting",
"description": "The label for category notice in sidebar docs"
@@ -1,122 +0,0 @@
{
"version.label": {
"message": "v1.5.x",
"description": "The label for version v1.5.x"
},
"sidebar.docs.category.quickstart": {
"message": "quickstart",
"description": "The label for category quickstart in sidebar docs"
},
"sidebar.docs.category.custom": {
"message": "custom",
"description": "The label for category custom in sidebar docs"
},
"sidebar.docs.category.http": {
"message": "http",
"description": "The label for category http in sidebar docs"
},
"sidebar.docs.category.jdbc": {
"message": "jdbc",
"description": "The label for category jdbc in sidebar docs"
},
"sidebar.docs.category.ssh": {
"message": "ssh",
"description": "The label for category ssh in sidebar docs"
},
"sidebar.docs.category.jmx": {
"message": "jmx",
"description": "The label for category jmx in sidebar docs"
},
"sidebar.docs.category.snmp": {
"message": "snmp",
"description": "The label for category snmp in sidebar docs"
},
"sidebar.docs.category.push": {
"message": "push",
"description": "The label for category push in sidebar docs"
},
"sidebar.docs.category.NGQL": {
"message": "NGQL",
"description": "The label for category NGQL in sidebar docs"
},
"sidebar.docs.category.Community": {
"message": "Community",
"description": "The label for category Community in sidebar docs"
},
"sidebar.docs.category.contribution": {
"message": "contribution",
"description": "The label for category contribution in sidebar docs"
},
"sidebar.docs.category.submit": {
"message": "submit",
"description": "The label for category submit in sidebar docs"
},
"sidebar.docs.category.release": {
"message": "release",
"description": "The label for category release in sidebar docs"
},
"sidebar.docs.category.help": {
"message": "help",
"description": "The label for category help in sidebar docs"
},
"sidebar.docs.category.service": {
"message": "service",
"description": "The label for category service in sidebar docs"
},
"sidebar.docs.category.program": {
"message": "program",
"description": "The label for category program in sidebar docs"
},
"sidebar.docs.category.database": {
"message": "database",
"description": "The label for category database in sidebar docs"
},
"sidebar.docs.category.cache": {
"message": "cache",
"description": "The label for category cache in sidebar docs"
},
"sidebar.docs.category.os": {
"message": "os",
"description": "The label for category os in sidebar docs"
},
"sidebar.docs.category.mid": {
"message": "mid",
"description": "The label for category mid in sidebar docs"
},
"sidebar.docs.category.bigdata": {
"message": "bigdata",
"description": "The label for category bigdata in sidebar docs"
},
"sidebar.docs.category.webserver": {
"message": "webserver",
"description": "The label for category webserver in sidebar docs"
},
"sidebar.docs.category.cloud-native": {
"message": "cloud-native",
"description": "The label for category cloud-native in sidebar docs"
},
"sidebar.docs.category.llm": {
"message": "llm",
"description": "The label for category llm in sidebar docs"
},
"sidebar.docs.category.network": {
"message": "network",
"description": "The label for category network in sidebar docs"
},
"sidebar.docs.category.threshold": {
"message": "threshold",
"description": "The label for category threshold in sidebar docs"
},
"sidebar.docs.category.notice": {
"message": "notice",
"description": "The label for category notice in sidebar docs"
},
"sidebar.docs.category.Others": {
"message": "Others",
"description": "The label for category Others in sidebar docs"
},
"sidebar.docs.link.Install via Helm": {
"message": "Install via Helm",
"description": "The label for link Install via Helm in sidebar docs, linking to https://artifacthub.io/packages/helm/hertzbeat/hertzbeat"
}
}

Some files were not shown because too many files have changed in this diff Show More