Skip to content

Commit

Permalink
add a online prometheus parser and a prometheus-like push style. (#1644)
Browse files Browse the repository at this point in the history
  • Loading branch information
leo-934 authored Mar 14, 2024
1 parent a808700 commit 4e826e2
Show file tree
Hide file tree
Showing 6 changed files with 367 additions and 0 deletions.
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
*
*
*/
@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
*
*
*/
@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
*
*
*/
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
@@ -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"));
}
}

}
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
*
*
*/

@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
*
*
*/

@Slf4j
@Service
public class PushGatewayServiceImpl implements PushGatewayService {

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

0 comments on commit 4e826e2

Please sign in to comment.