com.limemojito.test.prometheus.Parser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of test-utilities Show documentation
Show all versions of test-utilities Show documentation
Test utilities for various development work. Json, reflection, getter/setter testing, DTO, Canonical form, etc.
AWS support for Dynamo DB, SQS, SNS, S3. Prometheus metrics reader and asserter. Synthetic S3 Event generation.
The newest version!
/*
* Copyright 2011-2024 Lime Mojito Pty Ltd
*
* Licensed 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 com.limemojito.test.prometheus;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.io.LineNumberReader;
import java.io.StringReader;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.lang.String.format;
/**
* The Parser class is responsible for parsing Prometheus metrics data and converting it into a map of Metric objects.
* It provides methods for parsing different formats of metrics data and extracting metric names, tags, and values.
* The class uses regular expressions and string manipulation to extract the necessary information from the metrics.
*
* The Parser class has the following public methods:
* - parsePrometheusData: Parses the given Prometheus metrics data and returns a map of metrics.
* - isComment: Checks if a given string starts with a comment symbol.
* - parse: Parses metrics data to a Metric object representation.
*
* Example Usage:
*
{@code
* Parser parser = new Parser();
* String metricsData = "metric_name{tag1=value1,tag2=value2} 10.0";
* Map> metricMap = parser.parsePrometheusData(metricsData);
* }
*/
@Slf4j
public class Parser {
private final Pattern tagExtractor;
/**
* Creates a new parser.
*/
public Parser() {
tagExtractor = Pattern.compile("(.+?)\\{(.+?)} (.+)");
}
/**
* Parses the given Prometheus metrics data and returns a map of metrics.
*
* @param metricsData the Prometheus metrics data to be parsed
* @return a map with metric names as keys and lists of metrics as values
*/
@SneakyThrows
public Map> parsePrometheusData(String metricsData) {
try (StringReader reader = new StringReader(metricsData); LineNumberReader lineReader = new LineNumberReader(
reader)) {
final Map> metricMap = new LinkedHashMap<>();
String line;
do {
line = lineReader.readLine();
if (line != null) {
if (isComment(line)) {
log.trace("Read line {} as comment", lineReader.getLineNumber());
} else {
log.trace("Parsing line {} as metric", lineReader.getLineNumber());
Metric metric = parse(line);
log.trace("Read line {} as {}", lineReader.getLineNumber(), metric);
final List metrics = metricMap.computeIfAbsent(metric.getName(),
(key) -> new ArrayList<>());
metrics.add(metric);
metricMap.put(metric.getName(), metrics);
}
}
} while (line != null);
log.debug("Loaded {} metrics from prometheus scrape", metricMap.size());
return metricMap;
}
}
/**
* Checks if the given string starts with a comment symbol.
*
* @param data the string to check
* @return true if the string starts with a comment symbol, false otherwise
*/
public boolean isComment(String data) {
return data.startsWith("#");
}
/**
* Parses metrics to a class representation.
* trade_event_account_info_seconds_count{class="com.limemojito.cloud.tradeeventprocessor.AccountInfoEventHandler",exception="none",method="process",} 2.0
*
* @param metric content to parse: name{tag="tag-value"} value
* @return a metric object.
*/
public Metric parse(String metric) {
try {
if (metric.contains("{")) {
return parseTagFormat(metric);
}
return parseSimpleFormat(metric);
} catch (NumberFormatException e) {
log.warn("Could not parse metric value [{}]", metric);
throw e;
}
}
private Metric parseSimpleFormat(String metric) {
final String[] split = metric.split(" ");
return Metric.builder()
.name(split[0])
.value(parseValue(split[1]))
.build();
}
private Metric parseTagFormat(String metric) {
final Matcher matcher = tagExtractor.matcher(metric);
if (matcher.matches()) {
final int valueIndex = 3;
return Metric.builder()
.name(matcher.group(1))
.tags(parseTagMap(matcher.group(2)))
.value(parseValue(matcher.group(valueIndex)))
.build();
} else {
throw new IllegalArgumentException(format("Can not parse [%s]", metric));
}
}
private BigDecimal parseValue(String value) {
try {
return new BigDecimal(value);
} catch (NumberFormatException e) {
if ("NaN".equalsIgnoreCase(value)) {
return new BigDecimal(Double.MAX_VALUE);
} else {
throw e;
}
}
}
private Map parseTagMap(String tags) {
final Map tagMap = new LinkedHashMap<>();
final String[] split = tags.split(",");
for (String keyValue : split) {
final String[] keyValueSet = keyValue.split("=");
final String rawValue = keyValueSet[1];
tagMap.put(keyValueSet[0], rawValue.substring(1, rawValue.length() - 1));
}
return tagMap;
}
}