Skip to content

Commit

Permalink
create test for http metrics (#197)
Browse files Browse the repository at this point in the history
Change exporter for jvm metrics
  • Loading branch information
benjamin-confino authored and yasmin-aumeeruddy committed Jun 25, 2024
1 parent ed3c69b commit fa54dbd
Show file tree
Hide file tree
Showing 11 changed files with 447 additions and 184 deletions.
3 changes: 2 additions & 1 deletion tck/logs/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,13 @@ To test that the OpenTelemetry instance is set properly at runtime initializatio
----

== Logging File Configuration
OpenTelemetry logs are sent to stdout in the tests. Set the system property `log.file.path` to the file containing the log output when running the logs TCK. For example:
OpenTelemetry logs are sent to stdout in the tests. Ensure logs written to stdout are captured in a file and set the system property `log.file.path` to the file containing the log output when running the logs TCK. For example:

[source, xml]
----
log.file.path=console.log
----

== Configuration in Apache Maven pom.xml
If you use Apache Maven then the tests are run via the `maven-surefire-plugin`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,20 @@ void julWarnTest() throws IOException {

public boolean checkMessage(String logMessage) throws IOException {
try {
BufferedReader reader = new BufferedReader(new FileReader(logFilePath));
String line;
while ((line = reader.readLine()) != null) {
if (line.contains(logMessage)) {
return true;
try {
Thread.sleep(5000);
BufferedReader reader = new BufferedReader(new FileReader(logFilePath));
String line;
while ((line = reader.readLine()) != null) {
if (line.contains(logMessage)) {
return true;
}
}
return false;
} catch (IOException e) {
return false;
}
return false;
} catch (IOException e) {
} catch (InterruptedException e) {
return false;
}
}
Expand Down
14 changes: 13 additions & 1 deletion tck/metrics/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,19 @@ To enable the tests in your project you need to add the following dependency to
----

If you want to run the metrics tests, you can specify all tests in the `tck-suite.xml`. E.g.
== Running the tests

The jvm metrics tests require runtime configuration to enable metric reading at a runtime level. The metrics must be sent to stdout in the tests. Ensure logs written to stdout are captured in a file and set the system property `log.file.path` to the file containing the log output when running the logs TCK. For example:


[source, xml]
----
otel.sdk.disabled=false
otel.metrics.exporter=logging
log.file.path=console.log
----

To run the tests, include the following content in the `tck-suite.xml` in your project:

[source, xml]
----
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* Copyright (c) 2022-2023 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* 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 org.eclipse.microprofile.telemetry.metrics.tck;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;

import org.jboss.arquillian.test.api.ArquillianResource;

/**
* A really basic client for doing Http requests
* <p>
* For use when we don't want to use JAX-RS client or something else which has integration with telemetry
*/
public class BasicHttpClient {

private URI baseUri;

/**
* @param baseUrl
* The base URL. Any path requested through this client will be appended to this URL. This should usually
* be a URL injected using {@link ArquillianResource}
*/
public BasicHttpClient(URL baseUrl) {
try {
baseUri = baseUrl.toURI();
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}

/**
* Makes a GET request to a path and returns the response code
*
* @param path
* the path to request, relative to the baseUrl
* @return the response code
*/
public int get(String path) {
if (path.startsWith("/")) {
path = path.substring(1);
}
try {
URL spanUrl = baseUri.resolve(path).toURL();
HttpURLConnection connection = (HttpURLConnection) spanUrl.openConnection();
try {
return connection.getResponseCode();
} catch (Exception e) {
throw new RuntimeException("Exception retrieving " + spanUrl, e);
} finally {
connection.disconnect();
}
} catch (Exception e) {
throw new RuntimeException("Exception retrieving path " + path, e);
}
}

/**
* Makes a GET request to a path and returns the response code
*
* @param path
* the path to request, relative to the baseUrl
* @return the response message
*/
public String getResponseMessage(String path) {
if (path.startsWith("/")) {
path = path.substring(1);
}
try {
URL spanUrl = baseUri.resolve(path).toURL();
HttpURLConnection connection = (HttpURLConnection) spanUrl.openConnection();
try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
String inputLine;
StringBuilder response = new StringBuilder();

while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
return response.toString();
} catch (Exception e) {
throw new RuntimeException("Exception retrieving " + spanUrl, e);
} finally {
connection.disconnect();
}
} catch (Exception e) {
throw new RuntimeException("Exception retrieving path " + path, e);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/*
**********************************************************************
* Copyright (c) 2024 Contributors to the Eclipse Foundation
*
* See the NOTICES file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
**********************************************************************/
package org.eclipse.microprofile.telemetry.metrics.tck.http;

import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.eclipse.microprofile.telemetry.metrics.tck.BasicHttpClient;
import org.eclipse.microprofile.telemetry.metrics.tck.TestLibraries;
import org.eclipse.microprofile.telemetry.metrics.tck.exporter.InMemoryMetricExporter;
import org.eclipse.microprofile.telemetry.metrics.tck.exporter.InMemoryMetricExporterProvider;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.arquillian.testng.Arquillian;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider;
import io.opentelemetry.sdk.metrics.data.MetricData;
import jakarta.inject.Inject;
import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Application;
import jakarta.ws.rs.core.Response;

public class HttpHistogramTest extends Arquillian {

private static final AttributeKey<String> HTTP_REQUEST_METHOD = AttributeKey.stringKey("http.request.method");
private static final AttributeKey<String> URL_SCHEME = AttributeKey.stringKey("url.scheme");
private static final AttributeKey<String> HTTP_RESPONSE_STATUS_CODE =
AttributeKey.stringKey("http.response.status_code");
private static final AttributeKey<String> NETWORK_PROTOCOL_NAME =
AttributeKey.stringKey("network.protocol.name");
private static final AttributeKey<String> HTTP_ROUTE = AttributeKey.stringKey("http.route");
private static final AttributeKey<String> ERROR_TYPE = AttributeKey.stringKey("error.type");

@Inject
OpenTelemetry openTelemetry;

@Deployment
public static WebArchive createTestArchive() {
return ShrinkWrap.create(WebArchive.class)
.addClasses(InMemoryMetricExporter.class, InMemoryMetricExporterProvider.class,
BasicHttpClient.class)
.addAsLibrary(TestLibraries.AWAITILITY_LIB)
.addAsServiceProvider(ConfigurableMetricExporterProvider.class, InMemoryMetricExporterProvider.class)
.addAsResource(new StringAsset(
"otel.sdk.disabled=false\notel.metrics.exporter=in-memory\notel.logs.exporter=none\notel.traces.exporter=none\notel.metric.export.interval=3000"),
"META-INF/microprofile-config.properties")
.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml");
}

@ArquillianResource
private URL url;
@Inject
private InMemoryMetricExporter metricExporter;

private BasicHttpClient basicClient;

@BeforeMethod
void setUp() {
if (metricExporter != null) {
metricExporter.reset();
basicClient = new BasicHttpClient(url);
}
}

@Test
void collectsHttpRouteFromEndAttributes() {

basicClient.get("/fail"); // Ensure we have metrics from both a successful (entering this method) and failing
// HTTP call.

try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
Assert.fail("The test thread was interrupted");
}

List<MetricData> items = metricExporter.getFinishedMetricItems();

Map<AttributeKey<String>, String> successfulHTTPMethod = new HashMap<AttributeKey<String>, String>();
successfulHTTPMethod.put(HTTP_REQUEST_METHOD, "GET");
successfulHTTPMethod.put(URL_SCHEME, "http");
successfulHTTPMethod.put(HTTP_RESPONSE_STATUS_CODE, "200");
successfulHTTPMethod.put(NETWORK_PROTOCOL_NAME, "HTTP");
successfulHTTPMethod.put(HTTP_ROUTE, url.getPath().replaceAll("ArquillianServletRunner.*", ""));

Map<AttributeKey<String>, String> failingHTTPMethod = new HashMap<AttributeKey<String>, String>();
failingHTTPMethod.put(HTTP_REQUEST_METHOD, "GET");
failingHTTPMethod.put(URL_SCHEME, "http");
failingHTTPMethod.put(HTTP_RESPONSE_STATUS_CODE, "500");
failingHTTPMethod.put(NETWORK_PROTOCOL_NAME, "HTTP");
failingHTTPMethod.put(HTTP_ROUTE, url.getPath() + "fail");
failingHTTPMethod.put(ERROR_TYPE, "500");

testMetricItem(successfulHTTPMethod, items);
testMetricItem(failingHTTPMethod, items);

}

private void testMetricItem(Map<AttributeKey<String>, String> keyAndExpectedValue, List<MetricData> items) {

Assert.assertTrue(
items.stream()
.flatMap(md -> md.getHistogramData().getPoints().stream())
.anyMatch(point -> {
return keyAndExpectedValue.entrySet().stream()
.allMatch(entry -> {
String attribute = point.getAttributes().get(entry.getKey());
return attribute != null &&
(attribute.equals(entry.getValue())
|| entry.getKey().equals(HTTP_ROUTE) && entry.getValue()
.contains(keyAndExpectedValue.get(HTTP_ROUTE))
// special case for Path to exclude "/ArquillianServletRunnerEE9"
);
});
}),
"failed to find a metric with all items in this attribute map: " + dumpTestedMap(keyAndExpectedValue)
+ "\n Dumping all attributes: "
+ dumpMetricItems(items));
}

private String dumpTestedMap(Map<AttributeKey<String>, String> keyAndExpectedValue) {
return keyAndExpectedValue.entrySet().stream()
.map(entry -> entry.getKey().toString() + "=" + entry.getValue())
.collect(Collectors.joining(", "));
}

private String dumpMetricItems(List<MetricData> items) {

return items.stream()
.flatMap(md -> md.getHistogramData().getPoints().stream())
.map(point -> point.getAttributes().toString())
.collect(Collectors.joining(", "));
}

@Path("/")
public static class SpanResource {
@GET
@Path("/fail")
public Response span() {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
}
}

@ApplicationPath("/")
public static class RestApplication extends Application {

}

}
Loading

0 comments on commit fa54dbd

Please sign in to comment.