Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
17 changes: 16 additions & 1 deletion checkstyle-suppressions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,20 @@
"https://checkstyle.org/dtds/suppressions_1_2.dtd">

<suppressions>
<suppress checks="Javadoc" files="."/>
<!-- Suppress Javadoc paragraph formatting (empty line before <p> tag) globally. This is a
stylistic check that would require fixing hundreds of locations. We still enforce that
public APIs have documentation via other Javadoc checks. -->
<suppress checks="JavadocParagraph" files="."/>

<!-- Suppress all Javadoc checks for internal implementation packages -->
<suppress checks="Javadoc" files=".*[/\\]internal[/\\].*"/>

<!-- Suppress all Javadoc checks for example code -->
<suppress checks="Javadoc" files=".*[/\\]examples[/\\].*"/>

<!-- Suppress all Javadoc checks for benchmark code -->
<suppress checks="Javadoc" files=".*[/\\]benchmarks[/\\].*"/>

<!-- Suppress all Javadoc checks for integration tests -->
<suppress checks="Javadoc" files=".*[/\\]integration-tests[/\\].*"/>
</suppressions>
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,8 @@
<excludes>
<exclude>**/generated/**</exclude>
<exclude>**/*BlockingRejectedExecutionHandler*</exclude>
<exclude>**/*AllocationCountingNotificationListener*</exclude>
<exclude>**/*MapperConfig*</exclude>
</excludes>
</configuration>
<executions>
Expand Down Expand Up @@ -264,6 +266,11 @@
<value>COVEREDRATIO</value>
<minimum>${jacoco.line-coverage}</minimum>
</limit>
<limit>
<counter>BRANCH</counter>
<value>COVEREDRATIO</value>
<minimum>0.50</minimum>
</limit>
</limits>
</rule>
</rules>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.prometheus.metrics.config;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;

import java.util.HashMap;
import java.util.Map;
Expand Down Expand Up @@ -68,4 +69,40 @@ void builder() {
.build();
assertValues(properties);
}

@Test
void builderWithHttpProtobuf() {
ExporterOpenTelemetryProperties properties =
ExporterOpenTelemetryProperties.builder().protocol("http/protobuf").build();
assertThat(properties.getProtocol()).isEqualTo("http/protobuf");
}

@Test
void builderWithInvalidProtocol() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> ExporterOpenTelemetryProperties.builder().protocol("invalid"))
.withMessage("invalid: Unsupported protocol. Expecting grpc or http/protobuf");
}

@Test
void builderWithInvalidIntervalSeconds() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> ExporterOpenTelemetryProperties.builder().intervalSeconds(0))
.withMessage("0: Expecting intervalSeconds > 0");

assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> ExporterOpenTelemetryProperties.builder().intervalSeconds(-1))
.withMessage("-1: Expecting intervalSeconds > 0");
}

@Test
void builderWithInvalidTimeoutSeconds() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> ExporterOpenTelemetryProperties.builder().timeoutSeconds(0))
.withMessage("0: Expecting timeoutSeconds > 0");

assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> ExporterOpenTelemetryProperties.builder().timeoutSeconds(-1))
.withMessage("-1: Expecting timeoutSeconds > 0");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,35 @@ void builder() {
.build();
assertThat(properties.getIncludeCreatedTimestamps()).isTrue();
assertThat(properties.getExemplarsOnAllMetricTypes()).isTrue();
assertThat(properties.getPrometheusTimestampsInMs()).isFalse();
}

@Test
void defaultValues() {
ExporterProperties properties = ExporterProperties.builder().build();
assertThat(properties.getIncludeCreatedTimestamps()).isFalse();
assertThat(properties.getExemplarsOnAllMetricTypes()).isFalse();
assertThat(properties.getPrometheusTimestampsInMs()).isFalse();
}

@Test
void prometheusTimestampsInMs() {
ExporterProperties properties =
ExporterProperties.builder().prometheusTimestampsInMs(true).build();
assertThat(properties.getPrometheusTimestampsInMs()).isTrue();

properties =
load(new HashMap<>(Map.of("io.prometheus.exporter.prometheus_timestamps_in_ms", "true")));
assertThat(properties.getPrometheusTimestampsInMs()).isTrue();

assertThatExceptionOfType(PrometheusPropertiesException.class)
.isThrownBy(
() ->
load(
new HashMap<>(
Map.of("io.prometheus.exporter.prometheus_timestamps_in_ms", "invalid"))))
.withMessage(
"io.prometheus.exporter.prometheus_timestamps_in_ms: Expecting 'true' or 'false'. Found:"
+ " invalid");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,50 @@ void load() {
+ " Found: foo");
}

@Test
void loadWithHttps() {
ExporterPushgatewayProperties properties =
load(Map.of("io.prometheus.exporter.pushgateway.scheme", "https"));
assertThat(properties.getScheme()).isEqualTo("https");
}

@Test
void loadWithEscapingSchemes() {
ExporterPushgatewayProperties properties =
load(Map.of("io.prometheus.exporter.pushgateway.escaping_scheme", "allow-utf-8"));
assertThat(properties.getEscapingScheme()).isEqualTo(EscapingScheme.ALLOW_UTF8);

properties = load(Map.of("io.prometheus.exporter.pushgateway.escaping_scheme", "values"));
assertThat(properties.getEscapingScheme()).isEqualTo(EscapingScheme.VALUE_ENCODING_ESCAPING);

properties = load(Map.of("io.prometheus.exporter.pushgateway.escaping_scheme", "underscores"));
assertThat(properties.getEscapingScheme()).isEqualTo(EscapingScheme.UNDERSCORE_ESCAPING);

properties = load(Map.of("io.prometheus.exporter.pushgateway.escaping_scheme", "dots"));
assertThat(properties.getEscapingScheme()).isEqualTo(EscapingScheme.DOTS_ESCAPING);
}

@Test
void loadWithInvalidEscapingScheme() {
assertThatExceptionOfType(PrometheusPropertiesException.class)
.isThrownBy(
() -> load(Map.of("io.prometheus.exporter.pushgateway.escaping_scheme", "invalid")))
.withMessage(
"io.prometheus.exporter.pushgateway.escaping_scheme: Illegal value. Expecting"
+ " 'allow-utf-8', 'values', 'underscores', or 'dots'. Found: invalid");
}

@Test
void loadWithTimeouts() {
ExporterPushgatewayProperties properties =
load(
Map.of(
"io.prometheus.exporter.pushgateway.connect_timeout_seconds", "5",
"io.prometheus.exporter.pushgateway.read_timeout_seconds", "10"));
assertThat(properties.getConnectTimeout()).isEqualTo(Duration.ofSeconds(5));
assertThat(properties.getReadTimeout()).isEqualTo(Duration.ofSeconds(10));
}

private static ExporterPushgatewayProperties load(Map<String, String> map) {
Map<Object, Object> regularProperties = new HashMap<>(map);
PropertySource propertySource = new PropertySource(regularProperties);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,62 @@ void testBuilder() {
assertThat(result.getMetricProperties("unknown_metric")).isNull();
assertThat(result.getExporterProperties()).isSameAs(defaults.getExporterProperties());
}

@Test
void testMetricNameNormalization() {
PrometheusProperties.Builder builder = PrometheusProperties.builder();
MetricsProperties customProps =
MetricsProperties.builder().histogramClassicUpperBounds(0.1, 0.5).build();

// Test that metric names with dots are normalized to underscores
builder.putMetricProperty("my.metric.name", customProps);
PrometheusProperties result = builder.build();

// Should be able to retrieve with dots
assertThat(result.getMetricProperties("my.metric.name")).isSameAs(customProps);
// Should also be able to retrieve with underscores
assertThat(result.getMetricProperties("my_metric_name")).isSameAs(customProps);
}

@Test
void testMetricNameWithInvalidCharacters() {
PrometheusProperties.Builder builder = PrometheusProperties.builder();
MetricsProperties customProps =
MetricsProperties.builder().histogramClassicUpperBounds(0.1, 0.5).build();

// Test that invalid characters are converted to underscores
builder.putMetricProperty("metric-name@with#invalid$chars", customProps);
PrometheusProperties result = builder.build();

// Should normalize invalid characters to underscores
assertThat(result.getMetricProperties("metric-name@with#invalid$chars")).isSameAs(customProps);
assertThat(result.getMetricProperties("metric_name_with_invalid_chars")).isSameAs(customProps);
}

@Test
void testMetricNameWithValidCharacters() {
PrometheusProperties.Builder builder = PrometheusProperties.builder();
MetricsProperties customProps =
MetricsProperties.builder().histogramClassicUpperBounds(0.1, 0.5).build();

// Test valid characters: letters, numbers (not at start), underscore, colon
builder.putMetricProperty("my_metric:name123", customProps);
PrometheusProperties result = builder.build();

assertThat(result.getMetricProperties("my_metric:name123")).isSameAs(customProps);
}

@Test
void testMetricNameStartingWithNumber() {
PrometheusProperties.Builder builder = PrometheusProperties.builder();
MetricsProperties customProps =
MetricsProperties.builder().histogramClassicUpperBounds(0.1, 0.5).build();

// First digit is invalid (i=0), but subsequent digits are valid (i>0)
builder.putMetricProperty("123metric", customProps);
PrometheusProperties result = builder.build();

assertThat(result.getMetricProperties("123metric")).isSameAs(customProps);
assertThat(result.getMetricProperties("_23metric")).isSameAs(customProps);
}
}
2 changes: 1 addition & 1 deletion prometheus-metrics-exporter-httpserver/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

<properties>
<automatic.module.name>io.prometheus.metrics.exporter.httpserver</automatic.module.name>
<jacoco.line-coverage>0.45</jacoco.line-coverage>
<jacoco.line-coverage>0.60</jacoco.line-coverage>
</properties>

<dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,29 @@ public Result authenticate(HttpExchange exchange) {
run(server, "/", 204, "");
}

@Test
void testSubjectDoAsWithInvalidSubject() throws Exception {
Authenticator authenticator =
new Authenticator() {
@Override
public Result authenticate(HttpExchange exchange) {
exchange.setAttribute("aa", "not-a-subject");
return new Success(new HttpPrincipal("user", "/"));
}
};

HttpHandler handler = exchange -> exchange.sendResponseHeaders(204, -1);
HTTPServer server =
HTTPServer.builder()
.port(0)
.authenticator(authenticator)
.defaultHandler(handler)
.authenticatedSubjectAttributeName("aa")
.buildAndStart();

run(server, "/", 403, "");
}

@Test
void defaultHandler() throws Exception {
run(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package io.prometheus.metrics.exporter.httpserver;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import java.net.URI;
import java.util.List;
import org.junit.jupiter.api.Test;

class HttpExchangeAdapterTest {

@Test
void getRequestPath() {
HttpExchange httpExchange = mock(HttpExchange.class);
when(httpExchange.getRequestURI()).thenReturn(URI.create("/metrics?name=test"));
HttpExchangeAdapter adapter = new HttpExchangeAdapter(httpExchange);
assertThat(adapter.getRequest().getRequestPath()).isEqualTo("/metrics");
}

@Test
void getRequestPathWithoutQueryString() {
HttpExchange httpExchange = mock(HttpExchange.class);
when(httpExchange.getRequestURI()).thenReturn(URI.create("/metrics"));
HttpExchangeAdapter adapter = new HttpExchangeAdapter(httpExchange);
assertThat(adapter.getRequest().getRequestPath()).isEqualTo("/metrics");
}

@Test
void getHeadersWhenPresent() {
HttpExchange httpExchange = mock(HttpExchange.class);
Headers headers = new Headers();
headers.put("Accept", List.of("text/plain"));
when(httpExchange.getRequestHeaders()).thenReturn(headers);
HttpExchangeAdapter adapter = new HttpExchangeAdapter(httpExchange);
assertThat(adapter.getRequest().getHeaders("Accept").nextElement()).isEqualTo("text/plain");
}

@Test
void getHeadersWhenNotPresent() {
HttpExchange httpExchange = mock(HttpExchange.class);
Headers headers = new Headers();
when(httpExchange.getRequestHeaders()).thenReturn(headers);
HttpExchangeAdapter adapter = new HttpExchangeAdapter(httpExchange);
assertThat(adapter.getRequest().getHeaders("Accept").hasMoreElements()).isFalse();
}
}
Loading