Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add a online prometheus parser and a prometheus-like push style. #1644

Merged
merged 5 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
*
* @author vinci
*/
leo-934 marked this conversation as resolved.
Show resolved Hide resolved
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Label {
private String name;
private String value;
}
Original file line number Diff line number Diff line change
@@ -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
*
* @author vinci
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Metric {
private String metricName;
private List<Label> labelList;
private Double value;
private Long timestamp;
}
Original file line number Diff line number Diff line change
@@ -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
*
* @author vinci
*/
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;
}
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
/**
* push controller
*
*
* @author vinci
*/
@Tag(name = "Metrics Push API | 监控数据推送API")
@RestController
Expand Down
Original file line number Diff line number Diff line change
@@ -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
*
* @author vinci
*/
@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"));
}
}

}
Original file line number Diff line number Diff line change
@@ -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
*
* @author vinci
*/

@Service
public interface PushGatewayService {

boolean pushMetricsData(InputStream inputStream) throws IOException;

}
Original file line number Diff line number Diff line change
@@ -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
*
* @author vinci
*/

@Slf4j
@Service
public class PushGatewayServiceImpl implements PushGatewayService {

@Override
public boolean pushMetricsData(InputStream inputStream) throws IOException {
List<Metric> metrics = PrometheusUtil.parseMetrics(inputStream);
return metrics != null;
}
}
Loading