Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
cz.pumpitup.pn5.reporting.prometheus.PrometheusReporter Maven / Gradle / Ivy
package cz.pumpitup.pn5.reporting.prometheus;
import cz.pumpitup.pn5.config.ConfigHelper;
import cz.pumpitup.pn5.core.LogLevel;
import cz.pumpitup.pn5.reporting.spi.AbstractReporterSPI;
import io.prometheus.client.CollectorRegistry;
import io.prometheus.client.Gauge;
import io.prometheus.client.exporter.BasicAuthHttpConnectionFactory;
import io.prometheus.client.exporter.PushGateway;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.junit.jupiter.api.extension.ExtensionContext;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import static java.util.Objects.requireNonNull;
public class PrometheusReporter extends AbstractReporterSPI {
public static final String CONFIG_PREFIX = "pn5.reporting.prometheus.";
public static final String CONFIG_KEY_PROMETHEUS_ENABLED = "enabled";
public static final String CONFIG_KEY_PROMETHEUS_ENDPOINT = "endpoint";
public static final String CONFIG_KEY_PROMETHEUS_USERNAME = "username";
public static final String CONFIG_KEY_PROMETHEUS_PASSWORD = "password";
public static final String CONFIG_KEY_PROMETHEUS_JOB_NAME = "job";
public static final String CONFIG_KEY_CUSTOM_LABELS = "labels";
public static final String KEY_SUITE_NAME = "suite";
public static final String CONFIG_KEY_PROMETHEUS_TRACK_TESTCASES = "testcases.track";
public static final String SUITE_LABEL = "suite_name";
public static final String SUITE_METRIC = "suite_execution_gauge";
public static final String SUITE_FINISH_METRIC = "suite_execution_finish_time";
public static final String SUITE_DURATION_METRIC = "suite_execution_duration_seconds";
public static final String TEST_CASE_LABEL = "test_case_name";
public static final String TEST_EXECUTION_TOTAL = "test_execution_total";
public static final String TEST_EXECUTION_FINISH_TIME = "test_execution_finish_time";
public static final String TEST_EXECUTION_DURATION_SECONDS = "test_execution_duration_seconds";
ExtensionContext extensionContext;
PushGateway pushGateway = null;
private CollectorRegistry registry = null;
Gauge testExecutionTotalGauge = null;
Gauge testExecutionDurationGauge = null;
Gauge testExecutionFinishGauge = null;
Gauge classExecutionGauge = null;
Gauge classExecutionFinishGauge = null;
Gauge classExecutionDurationGauge = null;
private String jobName;
private boolean trackTestcases;
String[] classLabelKeys;
String[] testLabelKeys;
String[] classLabelValues;
String[] testLabelValues;
Map> testCaseStarted = new HashMap<>();
Map testCaseCompleted = new HashMap<>();
String displayId;
String testSuiteName;
String testCaseName;
int total;
int success;
long start;
long testStart;
private final Lock lock = new ReentrantLock();
private static final Map reporters = new HashMap<>();
public static PrometheusReporter resolve(ExtensionContext extensionContext) {
final Optional> testClass = extensionContext.getTestClass();
if (testClass.isEmpty()) {
return null;
}
String key = testClass.get().getName();
if (!reporters.containsKey(key)) {
final PrometheusReporter reporter = new PrometheusReporter().init(extensionContext);
reporters.put(key, reporter);
}
return reporters.get(key);
}
protected PrometheusReporter init(ExtensionContext extensionContext) {
super.init(extensionContext, CONFIG_PREFIX);
this.extensionContext = extensionContext;
if (!this.config.getOrDefault(CONFIG_KEY_PROMETHEUS_ENABLED, false)) {
return null;
}
if (!config.containsKey(CONFIG_KEY_PROMETHEUS_ENDPOINT)) {
logger.log(LogLevel.DEBUG, "PrometheusReporter is configured, but pn5.reporting.prometheus.endpoint is not. Will not push any metrics.");
} else {
String endpoint =
requireNonNull(config.get(CONFIG_KEY_PROMETHEUS_ENDPOINT),
"Missing endpoint in configuration under parameter \"pn5.reporting.prometheus.endpoint\". Please, check your configuration and try again.");
try {
this.pushGateway = new PushGateway(new URL(endpoint));
} catch (MalformedURLException e) {
throw new AssertionError("Endpoint must be a valid URL", e);
}
}
this.jobName = config.get(CONFIG_KEY_PROMETHEUS_JOB_NAME);
if (config.containsKey(CONFIG_KEY_PROMETHEUS_USERNAME)) {
String username =
requireNonNull(config.get(CONFIG_KEY_PROMETHEUS_USERNAME),
"Missing username in configuration under parameter \"pn5.reporting.prometheus.username\". Please, check your configuration and try again.");
String password =
requireNonNull(config.get(CONFIG_KEY_PROMETHEUS_PASSWORD),
"Missing password in configuration under parameter \"pn5.reporting.prometheus.password\". Please, check your configuration and try again.");
this.pushGateway.setConnectionFactory(new BasicAuthHttpConnectionFactory(username, password));
}
this.trackTestcases = config.getBoolean(CONFIG_KEY_PROMETHEUS_TRACK_TESTCASES);
this.registry = new CollectorRegistry();
ArrayList classLabelKeys = new ArrayList<>();
ArrayList classLabelValues = new ArrayList<>();
classLabelKeys.add(SUITE_LABEL);
classLabelValues.add("");
ArrayList testLabelKeys = new ArrayList<>();
ArrayList testLabelValues = new ArrayList<>();
testLabelKeys.add(SUITE_LABEL);
testLabelValues.add("");
testLabelKeys.add(TEST_CASE_LABEL);
testLabelValues.add("");
if (config.containsKey(CONFIG_KEY_CUSTOM_LABELS)) {
final StringTokenizer tokenizer = new StringTokenizer(config.get(CONFIG_KEY_CUSTOM_LABELS), ",");
while (tokenizer.hasMoreTokens()) {
final String token = tokenizer.nextToken();
final String[] keyAndValue = token.split("=");
if (keyAndValue.length != 2) {
logger.log(LogLevel.WARN, "Invalid custom label: {}, will ignore it. Make sure you use format 'key1=value1,key2=value2'", token);
continue;
}
String key = keyAndValue[0].trim();
String value = keyAndValue[1].trim();
if (key.isEmpty() || value.isEmpty()) {
logger.log(LogLevel.WARN, "Invalid custom label: {}, will ignore it. Make sure you use format 'key1=value1,key2=value2'", token);
continue;
}
logger.log(LogLevel.DEBUG, "Adding custom label: {}", token);
classLabelKeys.add(key);
classLabelValues.add(value);
testLabelKeys.add(key);
testLabelValues.add(value);
}
}
this.testLabelKeys = testLabelKeys.toArray(new String[0]);
this.testLabelValues = testLabelValues.toArray(new String[0]);
this.classLabelKeys = classLabelKeys.toArray(new String[0]);
this.classLabelValues = classLabelValues.toArray(new String[0]);
this.classExecutionGauge = Gauge.build()
.name(SUITE_METRIC)
.help("Test suite execution status")
.labelNames(this.classLabelKeys)
.register(registry);
this.classExecutionFinishGauge = Gauge.build()
.name(SUITE_FINISH_METRIC)
.help("Timestamp of finish of last execution of test suite")
.labelNames(this.classLabelKeys)
.register(registry);
this.classExecutionDurationGauge = Gauge.build()
.name(SUITE_DURATION_METRIC)
.help("Duration in seconds of last execution of test suite")
.labelNames(this.classLabelKeys)
.register(registry);
this.displayId = extensionContext.getDisplayName();
return this;
}
@Override
public void startTestcase(String testCaseName) {
lock.lock();
this.total++;
if (this.trackTestcases) {
try {
if (this.testExecutionTotalGauge == null
&& this.testExecutionFinishGauge == null
&& this.testExecutionDurationGauge == null) {
this.testExecutionTotalGauge = Gauge.build()
.name(TEST_EXECUTION_TOTAL)
.help("Test case execution status")
.labelNames("suite_name", "test_name", "result")
.register(registry);
this.testExecutionFinishGauge = Gauge.build()
.name(TEST_EXECUTION_FINISH_TIME)
.help("Timestamp of finish of last execution of test case")
.labelNames("suite_name", "test_name")
.register(registry);
this.testExecutionDurationGauge = Gauge.build()
.name(TEST_EXECUTION_DURATION_SECONDS)
.help("Duration in seconds of last execution of test case")
.labelNames("suite_name", "test_name")
.register(registry);
}
Map mapTestCase = new HashMap<>();
mapTestCase.put("started", Instant.now().getEpochSecond());
mapTestCase.put("order", (long) total);
this.testCaseStarted.put(testCaseName, mapTestCase);
this.testCaseCompleted.put(testCaseName, false);
} finally {
lock.unlock();
}
}
}
@Override
public void completeTestcase(String testCaseName) {
lock.lock();
try {
// This method in only called by JUnitReportingExtension if the test case passed
// In case the test fails, the 'success' counter just stays the same
success++;
if (this.trackTestcases) {
this.testLabelValues = new String[3];
this.testLabelValues[0] = testSuiteName;
this.testLabelValues[1] = testCaseName;
this.testLabelValues[2] = "passed";
this.testExecutionTotalGauge.labels(this.testLabelValues).set(this.testCaseStarted.get(testCaseName).get("order"));
this.logger.log(LogLevel.INFO, "Pushing metric {} with labels {} to Prometheus (complete)", TEST_EXECUTION_TOTAL, Arrays.toString(this.testLabelValues));
pushSafely(this.jobName);
pushGenericTestCaseMetrics(testCaseName);
this.testCaseCompleted.put(testCaseName, true);
}
} finally {
lock.unlock();
}
}
@Override
public void completeTestcaseFailed(String testCaseName) {
lock.lock();
try {
if (this.trackTestcases) {
this.testLabelValues = new String[3];
this.testLabelValues[0] = testSuiteName;
this.testLabelValues[1] = testCaseName;
this.testLabelValues[2] = "failed";
this.testExecutionTotalGauge.labels(this.testLabelValues).set(this.testCaseStarted.get(testCaseName).get("order"));
this.logger.log(LogLevel.INFO, "Pushing metric {} with labels {} to Prometheus (complete)", TEST_EXECUTION_TOTAL, Arrays.toString(this.testLabelValues));
pushSafely(this.jobName);
pushGenericTestCaseMetrics(testCaseName);
this.testCaseCompleted.put(testCaseName, true);
}
} finally {
lock.unlock();
}
}
private void pushGenericTestCaseMetrics(String testCaseName) {
final Instant end = Instant.now();
this.testLabelValues = new String[2];
this.testLabelValues[0] = testSuiteName;
this.testLabelValues[1] = testCaseName;
this.testExecutionFinishGauge.labels(this.testLabelValues).set(end.getEpochSecond());
this.logger.log(LogLevel.INFO, "Pushing metric {} with labels {} to Prometheus (complete)", TEST_EXECUTION_FINISH_TIME, Arrays.toString(this.testLabelValues));
pushSafely(this.jobName);
this.testLabelValues = new String[2];
this.testLabelValues[0] = testSuiteName;
this.testLabelValues[1] = testCaseName;
long duration = end.minus(this.testCaseStarted.get(testCaseName).get("started"), ChronoUnit.SECONDS).getEpochSecond();
this.testExecutionDurationGauge.labels(this.testLabelValues).set(duration);
this.logger.log(LogLevel.INFO, "Pushing metric {} with labels {} to Prometheus (complete)", TEST_EXECUTION_DURATION_SECONDS, Arrays.toString(this.testLabelValues));
pushSafely(this.jobName);
}
@Override
public void startTestSuite() {
String testSuiteName = config.get(KEY_SUITE_NAME);
if(testSuiteName == null) {
testSuiteName = extensionContext.getDisplayName();
}
this.testSuiteName = testSuiteName;
if(this.jobName == null) {
this.jobName = testSuiteName;
}
success = 0;
total = 0;
start = Instant.now().getEpochSecond();
}
@Override
public void completeTestSuite() {
this.classLabelValues[0] = testSuiteName;
final Instant end = Instant.now();
classExecutionFinishGauge.labels(this.classLabelValues).set(end.getEpochSecond());
final long duration = end.minus(start, ChronoUnit.SECONDS).getEpochSecond();
classExecutionDurationGauge.labels(this.classLabelValues).set(duration);
double successRatio = 0;
if (total != 0) {
successRatio = Math.round(success / (double) total * 100.0) / 100.0;
}
classExecutionGauge.labels(this.classLabelValues).set(successRatio);
this.logger.log(LogLevel.INFO, "Pushing metric {} with labels {} to Prometheus", SUITE_FINISH_METRIC, Arrays.toString(this.classLabelValues));
this.logger.log(LogLevel.INFO, "Pushing metric {} with labels {} to Prometheus", SUITE_DURATION_METRIC, Arrays.toString(this.classLabelValues));
this.logger.log(LogLevel.INFO, "Pushing metric {} with labels {} to Prometheus", SUITE_METRIC, Arrays.toString(this.classLabelValues));
pushSafely(this.jobName);
}
private void pushSafely(String jobName) {
if (pushGateway != null) {
try {
this.pushGateway.pushAdd(this.registry, jobName);
} catch (IOException e) {
logger.log(LogLevel.WARN, "Exception pushing metrics to gateway: {}", ExceptionUtils.getStackTrace(e));
}
}
}
@Override
public String getTestcaseKey() {
return null;
}
@Override
public boolean markTestAsStarted() {
return true;
}
@Override
public void markTestAsPassed() {
}
@Override
public void markTestAsFailed() {
}
@Override
public void markStepAsStarted(int step) {
}
@Override
public void markStepAsSkipped(int step) {
}
@Override
public void markStepAsPassed(int step) {
}
@Override
public void markStepAsFailed(int step) {
}
@Override
public void addComment(String comment, Integer step) {
}
@Override
public void addThrowable(Throwable throwable, Integer step) {
}
@Override
public void attach(String name, String fileExtension, String type, InputStream stream, Integer step) {
}
}