mirror of
https://github.com/dromara/hertzbeat.git
synced 2026-09-17 09:40:58 +00:00
add a online prometheus parser and a prometheus-like push style. (#1644)
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
package org.dromara.hertzbeat.common.util.prometheus;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* prometheus label entity
|
||||
*
|
||||
*
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Label {
|
||||
private String name;
|
||||
private String value;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.dromara.hertzbeat.common.util.prometheus;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* prometheus metric line entity
|
||||
*
|
||||
*
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Metric {
|
||||
private String metricName;
|
||||
private List<Label> labelList;
|
||||
private Double value;
|
||||
private Long timestamp;
|
||||
}
|
||||
+233
@@ -0,0 +1,233 @@
|
||||
package org.dromara.hertzbeat.common.util.prometheus;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* prometheus metric sparser
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class PrometheusUtil {
|
||||
|
||||
private static final int ERROR_FORMAT = -1; //解析过程中出现了未知格式数据,因为无法继续解析或已经到达输入流的末尾
|
||||
|
||||
private static final int NORMAL_END = -2; //输入流正常结束
|
||||
|
||||
private static final int COMMENT_LINE = -3;
|
||||
|
||||
|
||||
private static int parseMetricName(InputStream inputStream, Metric.MetricBuilder metricBuilder) throws IOException {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
int i;
|
||||
i = inputStream.read();
|
||||
if (i == -1) {
|
||||
return NORMAL_END;
|
||||
}
|
||||
else if (i == '#') {
|
||||
return COMMENT_LINE;
|
||||
}
|
||||
|
||||
while (i != -1) {
|
||||
if (i == ' ' || i == '{') {
|
||||
metricBuilder.metricName(stringBuilder.toString());
|
||||
return i;
|
||||
}
|
||||
stringBuilder.append((char) i);
|
||||
i = inputStream.read();
|
||||
}
|
||||
|
||||
return ERROR_FORMAT;
|
||||
}
|
||||
|
||||
private static int parseLabel(InputStream inputStream, List<Label> labelList) throws IOException {
|
||||
Label.LabelBuilder labelBuilder = new Label.LabelBuilder();
|
||||
int i;
|
||||
|
||||
StringBuilder labelName = new StringBuilder();
|
||||
i = inputStream.read();
|
||||
while (i != -1 && i != '=') {
|
||||
labelName.append((char) i);
|
||||
i = inputStream.read();
|
||||
}
|
||||
if (i == -1) {
|
||||
return ERROR_FORMAT;
|
||||
}
|
||||
labelBuilder.name(labelName.toString());
|
||||
|
||||
if (inputStream.read() != '\"') {
|
||||
return ERROR_FORMAT;
|
||||
}
|
||||
|
||||
StringBuilder labelValue = new StringBuilder();
|
||||
i = inputStream.read();
|
||||
while (i != -1 && i != ',' && i != '}') {
|
||||
labelValue.append((char) i);
|
||||
i = inputStream.read();
|
||||
}
|
||||
if (i == -1 || labelValue.charAt(labelValue.length() - 1) != '\"') {
|
||||
return ERROR_FORMAT;
|
||||
}
|
||||
|
||||
// skip space only in this condition
|
||||
if (i == '}' && inputStream.read() != ' ') {
|
||||
return ERROR_FORMAT;
|
||||
}
|
||||
|
||||
labelValue.deleteCharAt(labelValue.length() - 1);
|
||||
labelBuilder.value(labelValue.toString());
|
||||
|
||||
labelList.add(labelBuilder.build());
|
||||
return i;
|
||||
}
|
||||
|
||||
private static int parseLabelList(InputStream inputStream, Metric.MetricBuilder metricBuilder) throws IOException {
|
||||
List<Label> labelList = new ArrayList<>();
|
||||
int i;
|
||||
|
||||
i = parseLabel(inputStream, labelList);
|
||||
while (i == ',') {
|
||||
i = parseLabel(inputStream, labelList);
|
||||
}
|
||||
if (i == -1) {
|
||||
return ERROR_FORMAT;
|
||||
}
|
||||
|
||||
metricBuilder.labelList(labelList);
|
||||
return i;
|
||||
}
|
||||
|
||||
private static int parseValue(InputStream inputStream, Metric.MetricBuilder metricBuilder) throws IOException {
|
||||
int i;
|
||||
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
i = inputStream.read();
|
||||
while (i != -1 && i != ' ' && i != '\n') {
|
||||
stringBuilder.append((char) i);
|
||||
i = inputStream.read();
|
||||
}
|
||||
|
||||
String string = stringBuilder.toString();
|
||||
|
||||
switch (string) {
|
||||
case "NaN":
|
||||
metricBuilder.value(Double.NaN);
|
||||
break;
|
||||
case "+Inf":
|
||||
metricBuilder.value(Double.POSITIVE_INFINITY);
|
||||
break;
|
||||
case "-Inf":
|
||||
metricBuilder.value(Double.NEGATIVE_INFINITY);
|
||||
break;
|
||||
default:
|
||||
try {
|
||||
BigDecimal bigDecimal = new BigDecimal(string);
|
||||
metricBuilder.value(bigDecimal.doubleValue());
|
||||
} catch (NumberFormatException e) {
|
||||
return ERROR_FORMAT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == -1) {
|
||||
return NORMAL_END;
|
||||
}
|
||||
else {
|
||||
return i; // ' ' or \n'
|
||||
}
|
||||
}
|
||||
|
||||
private static int parseTimestamp(InputStream inputStream, Metric.MetricBuilder metricBuilder) throws IOException {
|
||||
int i;
|
||||
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
i = inputStream.read();
|
||||
while (i != -1 && i != '\n') {
|
||||
stringBuilder.append((char) i);
|
||||
i = inputStream.read();
|
||||
}
|
||||
|
||||
String string = stringBuilder.toString();
|
||||
try {
|
||||
metricBuilder.timestamp(Long.parseLong(string));
|
||||
} catch (NumberFormatException e) {
|
||||
return ERROR_FORMAT;
|
||||
}
|
||||
|
||||
if (i == -1) {
|
||||
return NORMAL_END;
|
||||
}
|
||||
else {
|
||||
return i; // '\n'
|
||||
}
|
||||
}
|
||||
|
||||
// return value:
|
||||
// -1: error format
|
||||
// -2: normal end
|
||||
// '\n': more lines
|
||||
private static int parseMetric(InputStream inputStream, List<Metric> metrics) throws IOException {
|
||||
Metric.MetricBuilder metricBuilder = new Metric.MetricBuilder();
|
||||
|
||||
int i = parseMetricName(inputStream, metricBuilder); // RET: -1, -2, -3, '{', ' '
|
||||
if (i == ERROR_FORMAT || i == NORMAL_END || i == COMMENT_LINE) {
|
||||
return i;
|
||||
}
|
||||
|
||||
if (i == '{') {
|
||||
i = parseLabelList(inputStream, metricBuilder); // RET: -1, '}'
|
||||
if (i == ERROR_FORMAT) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
i = parseValue(inputStream, metricBuilder); // RET: -1, -2, '\n', ' '
|
||||
if (i != ' ') {
|
||||
metrics.add(metricBuilder.build());
|
||||
return i;
|
||||
}
|
||||
|
||||
i = parseTimestamp(inputStream, metricBuilder); // RET: -1, -2, '\n'
|
||||
|
||||
metrics.add(metricBuilder.build());
|
||||
return i;
|
||||
|
||||
}
|
||||
|
||||
private static int skipCommentLine(InputStream inputStream) throws IOException {
|
||||
int i = inputStream.read();
|
||||
while (i != -1 && i != '\n') {
|
||||
i = inputStream.read();
|
||||
}
|
||||
if (i == -1) {
|
||||
return NORMAL_END;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
public static List<Metric> parseMetrics(InputStream inputStream) throws IOException {
|
||||
List<Metric> metricList= new ArrayList<>();
|
||||
int i = parseMetric(inputStream, metricList);
|
||||
while (i == '\n' || i == COMMENT_LINE) {
|
||||
if (i == COMMENT_LINE) {
|
||||
if (skipCommentLine(inputStream) == NORMAL_END) {
|
||||
return metricList;
|
||||
}
|
||||
|
||||
}
|
||||
i = parseMetric(inputStream, metricList);
|
||||
}
|
||||
if (i == NORMAL_END) {
|
||||
return metricList;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.dromara.hertzbeat.push.controller;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.dromara.hertzbeat.common.entity.dto.Message;
|
||||
import org.dromara.hertzbeat.push.service.PushGatewayService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* push gateway controller
|
||||
*
|
||||
*
|
||||
*/
|
||||
@Tag(name = "Metrics Push Gateway API | 监控数据推送网关API")
|
||||
@RestController
|
||||
@RequestMapping(value = "/api/push/pushgateway")
|
||||
public class PushGatewayController {
|
||||
|
||||
@Autowired
|
||||
private PushGatewayService pushGatewayService;
|
||||
|
||||
@PostMapping()
|
||||
@Operation(summary = "Push metric data to hertzbeat pushgateway", description = "推送监控数据到hertzbeat推送网关")
|
||||
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"));
|
||||
}
|
||||
else {
|
||||
return ResponseEntity.ok(Message.success("Push failed"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.dromara.hertzbeat.push.service;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* push gateway metrics
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
@Service
|
||||
public interface PushGatewayService {
|
||||
|
||||
boolean pushMetricsData(InputStream inputStream) throws IOException;
|
||||
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package org.dromara.hertzbeat.push.service.impl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.hertzbeat.common.util.prometheus.Metric;
|
||||
import org.dromara.hertzbeat.common.util.prometheus.PrometheusUtil;
|
||||
import org.dromara.hertzbeat.push.service.PushGatewayService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* push gateway service impl
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class PushGatewayServiceImpl implements PushGatewayService {
|
||||
|
||||
@Override
|
||||
public boolean pushMetricsData(InputStream inputStream) throws IOException {
|
||||
List<Metric> metrics = PrometheusUtil.parseMetrics(inputStream);
|
||||
return metrics != null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user