mirror of
https://github.com/dromara/hertzbeat.git
synced 2026-09-17 18:19:02 +00:00
Compare commits
54
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7f688ef9c | ||
|
|
4b5a98ea26 | ||
|
|
066d74629a | ||
|
|
95852ca2bc | ||
|
|
9619638cff | ||
|
|
b91f849594 | ||
|
|
e1806f856f | ||
|
|
0da2835b24 | ||
|
|
73ce238cad | ||
|
|
95654e49c5 | ||
|
|
2763c264ad | ||
|
|
148cca0e6e | ||
|
|
9ceb6e6400 | ||
|
|
64cc15fabb | ||
|
|
27da850ca4 | ||
|
|
1ad52321ea | ||
|
|
ff037a6a3a | ||
|
|
acda0a9097 | ||
|
|
433f09a4e3 | ||
|
|
706baffd5e | ||
|
|
0b6fb353f1 | ||
|
|
1c64e8022f | ||
|
|
e5d2aedfe0 | ||
|
|
e70633c256 | ||
|
|
5e69374456 | ||
|
|
6acf76c910 | ||
|
|
6d8e6f949a | ||
|
|
e6f91cae42 | ||
|
|
73e7d0ade1 | ||
|
|
79673834f1 | ||
|
|
e3a458786c | ||
|
|
0385892674 | ||
|
|
04e2321175 | ||
|
|
8373f0a247 | ||
|
|
5ad03687b2 | ||
|
|
7127159063 | ||
|
|
51715797ef | ||
|
|
47a1b13781 | ||
|
|
d5fc492b98 | ||
|
|
bbc30cd3db | ||
|
|
6e50d1f3a7 | ||
|
|
b18412254e | ||
|
|
fc0ef7773c | ||
|
|
adf71441f8 | ||
|
|
e36842420f | ||
|
|
a2c1123b09 | ||
|
|
b4151a4e73 | ||
|
|
1c1683d9c6 | ||
|
|
46d4fc2dd9 | ||
|
|
ffa2487587 | ||
|
|
30c7b22c4c | ||
|
|
7a3e23ec59 | ||
|
|
da6556641a | ||
|
|
b008f16b67 |
+1
-1
@@ -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;
|
||||
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.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."));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+1
-1
@@ -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>
|
||||
|
||||
+250
-52
@@ -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) {
|
||||
|
||||
-432
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
-252
@@ -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;
|
||||
}
|
||||
}
|
||||
-52
@@ -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;
|
||||
}
|
||||
}
|
||||
+12
-23
@@ -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();
|
||||
|
||||
+88
-70
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-1
@@ -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
|
||||
*/
|
||||
|
||||
+6
-6
@@ -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);
|
||||
|
||||
+95
@@ -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();
|
||||
}
|
||||
}
|
||||
+23
-31
@@ -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
|
||||
|
||||
+96
-1
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
-79
@@ -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());
|
||||
}
|
||||
}
|
||||
+30
-7
@@ -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();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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);
|
||||
|
||||
+2
-1
@@ -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
|
||||
+9
@@ -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";
|
||||
}
|
||||
|
||||
+3
-7
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -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();
|
||||
|
||||
+59
-21
@@ -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
|
||||
|
||||
+15
@@ -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;
|
||||
}
|
||||
|
||||
+2
@@ -65,6 +65,8 @@ public interface ConfigConstants {
|
||||
String INFO = "info";
|
||||
|
||||
String GRAFANA = "grafana";
|
||||
|
||||
String LOG = "log";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+59
@@ -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;
|
||||
}
|
||||
+103
@@ -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
|
||||
*/
|
||||
|
||||
+59
@@ -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)
|
||||
|
||||
+1
-9
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
@@ -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);
|
||||
|
||||
+5
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
+13
-11
@@ -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
|
||||
@@ -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);
|
||||
|
||||
+1
@@ -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)
|
||||
|
||||
+1
-1
@@ -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 category:service-application service monitoring db-database monitoring custom-custom monitoring os-operating system monitoring
|
||||
category: server
|
||||
# The monitoring type eg: linux windows tomcat mysql aws...
|
||||
app: 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 category:service-application service monitoring db-database monitoring custom-custom monitoring os-operating system monitoring
|
||||
category: server
|
||||
# The monitoring type eg: linux windows tomcat mysql aws...
|
||||
app: 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 category:service-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 category:service-application service monitoring db-database monitoring custom-custom monitoring os-operating system monitoring
|
||||
category: server
|
||||
# The monitoring type eg: linux windows tomcat mysql aws...
|
||||
app: 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>
|
||||
@@ -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>
|
||||
|
||||
+42
@@ -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;
|
||||
}
|
||||
}
|
||||
+82
@@ -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() {}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.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;
|
||||
}
|
||||
}
|
||||
+15
-16
@@ -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);
|
||||
}
|
||||
|
||||
+10
-3
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
+96
-3
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-143
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
-93
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+9
-12
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
+8
@@ -56,4 +56,12 @@ public interface WarehouseConstants {
|
||||
String MEMORY = "memory";
|
||||
}
|
||||
|
||||
String PROMQL = "promql";
|
||||
|
||||
String SQL = "sql";
|
||||
|
||||
String RANGE = "range";
|
||||
|
||||
String INSTANT = "instant";
|
||||
|
||||
}
|
||||
|
||||
+18
-27
@@ -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)));
|
||||
}
|
||||
}
|
||||
+9
-74
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+217
@@ -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);
|
||||
uriComponentsBuilder.queryParam(HTTP_QUERY_PARAM, queryString);
|
||||
URI uri = uriComponentsBuilder.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;
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
}
|
||||
+9
-2
@@ -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);
|
||||
}
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.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);
|
||||
}
|
||||
|
||||
}
|
||||
+6
-75
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+12
-8
@@ -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);
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.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, we’ll walk through the internal architecture behind **HertzBeat’s metric collection pipeline** — from job distribution to alerting and storage — with the help of a high-level system diagram.
|
||||
|
||||
---
|
||||
|
||||
### HertzBeat’s Metric Collection Architecture
|
||||
|
||||

|
||||
|
||||
> **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
|
||||
|
||||
HertzBeat’s 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.
|
||||
|
||||
---
|
||||
|
||||
### 🙌 What’s 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)
|
||||
@@ -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 😊
|
||||
@@ -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>
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||

|
||||
|
||||
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
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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 don’t 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
|
||||

|
||||
- 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)
|
||||

|
||||
- Enable: Enable or disable the silence policy.
|
||||
@@ -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 |
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -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 👉 [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 👉 [Threshold alarm](alert_threshold) <br />
|
||||
   👉 [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 👉 [Alarm grouping](alarm_group) <br />
|
||||
   👉 [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.)
|
||||
|
||||
@@ -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.
|
||||
@@ -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:*
|
||||
|
||||
@@ -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:*
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -16,23 +16,23 @@ sidebar_label: Template Marketplace
|
||||
|
||||
1. **No filter: displayed in order of upload**
|
||||
|
||||

|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||

|
||||
|
||||
3. **Fuzzy search by Title**
|
||||
|
||||

|
||||

|
||||
|
||||
4. **Hover window function: download the latest version, view details, favorite/un-favorite**
|
||||
|
||||
> Show if the user has favorites after logging in
|
||||
|
||||

|
||||

|
||||
|
||||
5. **Sort: Eight Sorting Methods**
|
||||
|
||||
@@ -48,19 +48,19 @@ sidebar_label: Template Marketplace
|
||||
|
||||
> **📋Todo:** Upgrade to MarkDown format
|
||||
|
||||

|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||

|
||||
|
||||
3. **FAQ**
|
||||
|
||||
> **📋Todo:** Discussion or issue Q&A section
|
||||
|
||||

|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||

|
||||
|
||||
### User Center
|
||||
|
||||
@@ -94,21 +94,21 @@ sidebar_label: Template Marketplace
|
||||
>
|
||||
> **📋Todo:** Function to update template information
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
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
|
||||
|
||||

|
||||

|
||||
|
||||
3. **Star**
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
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
|
||||
|
||||

|
||||

|
||||
|
||||
### Sign Up & Login
|
||||
|
||||
@@ -128,13 +128,13 @@ sidebar_label: Template Marketplace
|
||||
>
|
||||
> **📋Todo:** Captcha function, email verification function
|
||||
|
||||

|
||||

|
||||
|
||||
2. **Login**
|
||||
|
||||
> **📋Todo:** Captcha function and forgot password function
|
||||
|
||||

|
||||

|
||||
|
||||
## Development Steps
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@
|
||||
"description": "The label of footer link with label=contact linking to /docs/community/contact"
|
||||
},
|
||||
"copyright": {
|
||||
"message": "\n <div style=\"text-align: left;margin-top:30px\">\n <div style=\"align-items: center; display: flex\">\n <div style=\"width: 1200px; background-color: #282c77; padding: 10px; border-radius: 6px\">\n <a href=\"https://incubator.apache.org/\">\n <img src=\"/img/icons/apache-incubator.svg\" alt=\"Apache Incubator logo\">\n </a>\n </div>\n <div style=\"margin-left: 40px\">\n <p style=\"font-size: 14px;line-height: 25px;\">\n Apache HertzBeat is an effort undergoing incubation at The Apache Software Foundation (ASF), sponsored by the Apache Incubator. Incubation is required of all newly accepted projects until a further review indicates that the infrastructure, communications, and decision making process have stabilized in a manner consistent with other successful ASF projects. While incubation status is not necessarily a reflection of the completeness or stability of the code, it does indicate that the project has yet to be fully endorsed by the ASF.\n </p>\n </div>\n </div>\n\n <div style=\"border-top: 1px solid #525252;min-height: 60px;line-height: 25px;text-align: left;font-size: 14px;display: flex;align-items: center;\">\n <span>\n Copyright © 2024 The Apache Software Foundation. Apache HertzBeat, HertzBeat, and its feather logo are trademarks of The Apache Software Foundation.\n </span>\n </div>\n </div>",
|
||||
"message": "\n <div style=\"text-align: left;margin-top:30px\">\n <div style=\"align-items: center; display: flex\">\n <div style=\"width: 1200px; background-color: #282c77; padding: 10px; border-radius: 6px\">\n <a href=\"https://incubator.apache.org/\">\n <img src=\"/img/icons/apache-incubator.svg\" alt=\"Apache Incubator logo\">\n </a>\n </div>\n <div style=\"margin-left: 40px\">\n <p style=\"font-size: 14px;line-height: 25px;\">\n Apache HertzBeat is an effort undergoing incubation at The Apache Software Foundation (ASF), sponsored by the Apache Incubator. Incubation is required of all newly accepted projects until a further review indicates that the infrastructure, communications, and decision making process have stabilized in a manner consistent with other successful ASF projects. While incubation status is not necessarily a reflection of the completeness or stability of the code, it does indicate that the project has yet to be fully endorsed by the ASF.\n </p>\n </div>\n </div>\n\n <div style=\"border-top: 1px solid #525252;min-height: 60px;line-height: 25px;text-align: left;font-size: 14px;display: flex;align-items: center;\">\n <span>\n Copyright © 2024-2025 The Apache Software Foundation. Apache HertzBeat, HertzBeat, and its feather logo are trademarks of The Apache Software Foundation.\n </span>\n </div>\n </div>",
|
||||
"description": "The footer copyright"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
---
|
||||
title: 从 Committer 到 PMC:在 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]
|
||||
---
|
||||
|
||||
## 从 Committer 到 PMC:角色的转变与成长
|
||||
|
||||
一年前,当我收到成为 Apache HertzBeat **Committer** 的通知时,那种喜悦和使命感至今记忆犹新。那时的我,更多是专注于具体的技术实现和功能开发。而如今,被邀请加入 **PMC**,意味着我需要以更全局的视角来看待项目的发展,这既对我参与开源的认可,更是对未来责任的召唤。
|
||||
|
||||
### 深入参与:Committer 时期的积累
|
||||
|
||||
成为 Committer 后,我的日常工作发生了显著变化:
|
||||
|
||||
- **代码审查成为常态**:从最初的简单 PR 审核,到后来能够系统性评估代码质量、架构合理性以及与项目整体方向的一致性。
|
||||
- **社区协作更加紧密**:我开始定期参与社区例会,协助新贡献者解决问题,甚至主导某些功能的开发与落地。
|
||||
- **技术决策的参与**:在监控协议支持、存储引擎优化等关键议题上,我开始提出自己的见解,并与核心团队共同制定实施方案。
|
||||
|
||||
这段时间让我深刻体会到,**开源项目的生命力不仅在于代码,更在于社区的协作与信任**。每一次代码合并、每一次问题讨论,都是对项目方向的微小调整,而 Committer 的角色让我有机会参与其中。
|
||||
|
||||
## 成为 PMC:责任与挑战的升级
|
||||
|
||||
PMC 的职责远超代码层面,它要求成员对项目的**技术方向、社区治理、长期发展**有更深入的思考。当我收到 PMC 邀请时,既感到兴奋,也意识到需要迎接新的挑战:
|
||||
|
||||
### 1. **技术战略的参与**
|
||||
|
||||
作为 PMC,我需要与其他成员共同规划 HertzBeat 的长期路线图。例如:
|
||||
|
||||
- **性能优化**:面对大规模监控场景,如何优化存储和查询效率。
|
||||
- **可扩展性增强**:如何设计更灵活的插件机制,方便社区贡献新的监控类型。
|
||||
|
||||
这些议题不再是单纯的代码实现,而是涉及技术选型、社区资源分配甚至项目定位的深层次讨论。
|
||||
|
||||
### 2. **社区治理与健康**
|
||||
|
||||
PMC 需要关注社区的长期健康发展,包括:
|
||||
|
||||
- **贡献者体验**:如何优化文档、降低新人的参与门槛。
|
||||
- **社区文化**:确保讨论氛围友好,冲突能够得到妥善解决。
|
||||
- **可持续发展**:激励长期贡献者,同时吸引新鲜血液。
|
||||
|
||||
## 个人成长:技能与心态的蜕变
|
||||
|
||||
这一年的经历让我在多个维度得到提升:
|
||||
|
||||
- **技术广度**:从专注具体功能到理解分布式监控系统的整体架构。
|
||||
- **软技能**:学会如何有效沟通、协调不同意见,推动社区共识。
|
||||
- **项目管理**:理解开源项目的运作模式,平衡理想与现实约束。
|
||||
|
||||
最深刻的感悟是:**在开源社区,技术能力只是基础,真正的价值在于能否通过协作创造大于个人的成果**。
|
||||
|
||||
## 未来展望:与 HertzBeat 共同前行
|
||||
|
||||
作为新晋 PMC,我对未来有几个重点方向:
|
||||
|
||||
1. **推动 HertzBeat 成为云原生监控领域的重要选择**,特别是在轻量级、易用性方面形成差异化优势。
|
||||
2. **建设更活跃的贡献者社区**,通过导师计划、定期活动等方式培养核心贡献者。
|
||||
3. **完善项目治理流程**,使决策更透明,参与更顺畅。
|
||||
|
||||
## 致谢与共勉
|
||||
|
||||
特别感谢 **Tom** 的指导,以及所有社区伙伴的支持。Apache 之道强调"社区重于代码",这一理念已深深影响我的工作方式。
|
||||
|
||||
最后,想对正在阅读的你分享:**开源是一场持久而温暖的旅程,不必追求速成,只需持续贡献,收获自会水到渠成**。期待在 HertzBeat 社区见到更多志同道合的朋友!
|
||||
|
||||
(正如我们常说的:**"参与开源是为了让技术更美好,而不是让生活更忙碌"** —— 共勉 😊)
|
||||
@@ -0,0 +1,316 @@
|
||||
---
|
||||
title: Apache Hertzbeat 1.7.0 发布公告
|
||||
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]
|
||||
---
|
||||
|
||||
亲爱的社区小伙伴们,
|
||||
|
||||
我们很高兴地宣布 Apache Hertzbeat 1.7.0 版本正式发布!
|
||||
|
||||
## Downloads and Documentation
|
||||
|
||||
- **Apache Hertzbeat 1.7.0 Download Link**: <https://hertzbeat.apache.org/zh-cn/docs/download>
|
||||
- **Apache Hertzbeat Documentation**: <https://hertzbeat.apache.org/zh-cn/docs/>
|
||||
|
||||
## Major Updates
|
||||
|
||||
### New Features and Enhancements
|
||||
|
||||
- **自定义刷新间隔**:支持为每组指标设置自定义刷新间隔,满足不同场景下的监控需求。
|
||||
- **任务自动发现**:通过 http_sd 支持自动发现任务,提升了任务的灵活性和可管理性。
|
||||
- **新的告警模块**:支持实时阈值和计划阈值,分组收敛,告警抑制,告警静默等。
|
||||
- **Kafka 监控增强**:优化了 Kafka 监控功能,包括改进 Kafka 图表显示、添加 Kafka 消费者组监控指标等。
|
||||
- **支持多种协议和监控类型**:新增了对 Plc 协议监控的支持,进一步丰富了监控范围。
|
||||
- **报警功能增强**:支持通过 HTTP API 替换腾讯云 SDK 发送短信通知,增强了报警通知的灵活性和扩展性。同时,还支持了多查询表达式阈值报警、周期性报警阈值等功能。
|
||||
- **多语言支持**:新增了日语、繁体中文等多语言支持,提升了国际化体验。
|
||||
- **监控功能增强**:支持 StarRocks FE 等多种类型的监控,为用户提供了更多的监控选项。
|
||||
- **E2E 测试增强**:新增了多个 E2E 测试代码,包括 Kafka、SSH、API 等,提升了测试覆盖率和稳定性。
|
||||
- **数据存储优化**:更新了 VictoriaMetrics 和 Greptime 存储,提升了数据存储的性能和稳定性。
|
||||
- **更多的新功能特性**
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **修复 Docker 构建错误**:修复了 collector Docker 构建错误,确保了 Docker 镜像的正常构建。
|
||||
- **修复 Linux 进程监控问题**:修复了 Linux 进程监控进程异常退出且无警告的问题,提升了监控的稳定性。
|
||||
- **修复 Windows 中文编码问题**:修复了 Windows 中文编码问题,确保了在 Windows 环境下的正常运行。
|
||||
- **修复 Grafana 配置问题**:修复了 Grafana 配置相关的问题,提升了 Grafana 的集成体验。
|
||||
- **修复 Windows 指标配置问题**:更新了 Windows 指标 yml 文件,解决了相关问题。
|
||||
- **修复 flyway 位置检测问题**:修复了 flyway 位置无法自动检测供应商的问题,提升了数据库迁移的可靠性。
|
||||
- **修复数据存储问题**:修复了数据存储相关的问题,包括修复了 Prometheus 数据存储问题、修复了数据存储更新逻辑问题等,确保了数据的准确性和完整性。
|
||||
- **修复报警通知问题**:修复了报警通知相关的问题,包括修复了报警通知重复发送问题、修复了报警通知配置问题等,提升了报警通知的准确性和可靠性。
|
||||
- **修复监控状态更新问题**:修复了监控状态未更新的问题,确保了监控状态的实时性和准确性。
|
||||
- **和其它的BUG修复**
|
||||
|
||||
### Refactoring and Optimization
|
||||
|
||||
- **内存结构优化**:使用Apache Arrow作为数据内存数据结构,提升了内存使用效率和性能。
|
||||
- **代码规范优化**:根据代码规范对代码进行了优化,提升了代码质量和可读性。
|
||||
- **缓存优化**:新增了基于 Singleton 模式的 LRU 本地缓存,提升了缓存的效率和性能。
|
||||
- **内存泄漏修复**:修复了潜在的内存泄漏问题,提升了系统的稳定性和可靠性。
|
||||
- **和其它的优化**
|
||||
|
||||
### Documentation Enhancements
|
||||
|
||||
- **更新部署文档**:更新了部署文档,提供了更详细的部署指导。
|
||||
- **更新安全模型文档**:更新了安全模型文档。
|
||||
- **更新 Grafana 配置文档**:更新了 Grafana 配置方法和添加暴露 URL 的文档,提升了用户体验。
|
||||
- **更新 Windows 监控文档**:更新了 Windows 系统监控文档,提供了更详细的监控指导。
|
||||
- **更新监控指标文档**:更新了多个监控指标的文档,包括 Kafka、Linux 进程等,提升了文档的准确性和完整性。
|
||||
- **更新开发者文档**:新增了自定义开发采集器的文档,方便开发者进行二次开发。
|
||||
- **更多的文档更新**
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
感谢以下社区成员的共同努力:
|
||||
|
||||
> @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
|
||||
|
||||
**仓库地址:**
|
||||
|
||||
<https://github.com/apache/hertzbeat>
|
||||
|
||||
**网址:**
|
||||
|
||||
<https://hertzbeat.apache.org/>
|
||||
|
||||
**Apache Hertzbeat 下载地址:**
|
||||
|
||||
<https://hertzbeat.apache.org/zh-cn/docs/download>
|
||||
|
||||
**Apache Hertzbeat Docker 镜像版本:**
|
||||
|
||||
> Apache HertzBeat 为每个版本制作了 Docker 镜像. 你可以从 Docker Hub 拉取使用.
|
||||
|
||||
- HertzBeat <https://hub.docker.com/r/apache/hertzbeat>
|
||||
- HertzBeat Collector <https://hub.docker.com/r/apache/hertzbeat-collector>
|
||||
|
||||
**Apache Hertzbeat 开源社区如何参与?**
|
||||
|
||||
<https://hertzbeat.apache.org/zh-cn/docs/community/contribution>
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version.label": {
|
||||
"message": "v1.6.x",
|
||||
"message": "v1.7.x",
|
||||
"description": "The label for version current"
|
||||
},
|
||||
"sidebar.docs.category.quickstart": {
|
||||
@@ -55,6 +55,10 @@
|
||||
"message": "阈值告警配置",
|
||||
"description": "The label for category threshold in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.reduce": {
|
||||
"message": "告警收敛配置",
|
||||
"description": "The label for category reduce in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.notice": {
|
||||
"message": "告警通知配置",
|
||||
"description": "The label for category notice in sidebar docs"
|
||||
@@ -128,11 +132,11 @@
|
||||
"description": "The label for category NGQL in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.llm": {
|
||||
"message": "Ai大模型监控",
|
||||
"message": "AI大模型监控",
|
||||
"description": "The label for category llm in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.aiConfig": {
|
||||
"message": "Ai大模型配置",
|
||||
"message": "AI大模型配置",
|
||||
"description": "The label for category aiConfig in sidebar docs"
|
||||
},
|
||||
"sidebar.docs.category.install": {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user