Skip to content

Commit

Permalink
[WFLY-11351] Add resource address and attribute to the metric tags
Browse files Browse the repository at this point in the history
To improve query and aggregation of the metrics exposed by the
MicroProfile Metrics HTTP endpoints, the resource address' elements and
the attribute name are added to the tags in the metric' metadata.

Add test in MicroProfileMetricsApplicationTestCase to check WildFly
deployment metrics (using Undertow's request-count)

JIRA: https://issues.jboss.org/browse/WFLY-11351
  • Loading branch information
jmesnil committed Nov 14, 2018
1 parent bada8cb commit 42f5210
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 33 deletions.
Expand Up @@ -73,6 +73,25 @@ The HTTP endpoint exposes the following metrics:
* Vendor metrics - Metrics from WildFly subsystems are exposed in the `vendor` scope
* Application metrics - Metrics from the application and from the deployment's subsystems are exposed in the `application` scope.

=== WildFly Metrics Description

WildFly metrics can appears in two scopes:

* `vendor` for metrics defined in subsystem resources (e.g. the `bytes-received` attribute of the `/subsystem=undertow/server=default-server/http-listener=default` resource)
* `applications` for metrics defined in deployment resources (e.g. the `request-count` attribute on the `/deployment=<deployment name>/subsystem=undertow/servlet=<servlet name>` resource).

The name of the MicroProfile metric is composed of the whole resource address appended by the attribute name.
For example, `subsystem/undertow/server/default-server/http-listener/default/request-count` and `deployment/example.war/subsystem/undertow/servlet/org.example.MyServlet/request-count` for the two attributes above.

==== Tags

WildFly metrics provides tags corresponding to the resource address elements as well as a tag with the attribute name.
This allows to be able to query all MicroProfile metrics for a subsystem (e.g. `subsystem="undertow"`) or a deployment (e.g. `deployment="example.war`).

For example, the MicroProfile metric registered for the `request-count` attribute on the `/deployment=example.war/subsystem=undertow/servlet=org.example.MyServlet` resource will have the following tags:

* `subsystem="undertow", servlet="org.example.MyServlet", deployment="example.war", attribute="request-count"` (note that the tag order does not preserve order of the resource address)

== Component Reference

The Eclipse MicroProfile Health is implemented by the SmallRye Health project.
Expand Down
Expand Up @@ -21,6 +21,7 @@
*/
package org.wildfly.extension.microprofile.metrics;

import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ATTRIBUTE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ATTRIBUTES;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DESCRIPTION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILURE_DESCRIPTION;
Expand All @@ -46,6 +47,7 @@
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Function;
import java.util.function.Supplier;

import io.smallrye.metrics.ExtendedMetadata;
Expand Down Expand Up @@ -122,7 +124,7 @@ public void start(StartContext context) throws StartException {
registerMetrics(rootResource,
rootResourceRegistration,
MetricRegistries.get(MetricRegistry.Type.VENDOR),
(address, attributeName) -> address.toPathStyleString().substring(1) + "/" + attributeName);
Function.identity());
}

@Override
Expand All @@ -144,20 +146,12 @@ public MetricsRegistrationService getValue() {
return this;
}

public interface MetricNameCreator {
String createName(PathAddress address, String attributeName);

default PathAddress getResourceAddress(PathAddress address) {
return address;
}
}

public Set<String> registerMetrics(Resource rootResource,
ImmutableManagementResourceRegistration managementResourceRegistration,
MetricRegistry metricRegistry,
MetricNameCreator metricNameCreator) {
Function<PathAddress, PathAddress> resourceAddressResolver) {
Map<PathAddress, Map<String, ModelNode>> metrics = findMetrics(rootResource, managementResourceRegistration);
Set<String> registeredMetrics = registerMetrics(metrics, metricRegistry, metricNameCreator);
Set<String> registeredMetrics = registerMetrics(metrics, metricRegistry, resourceAddressResolver);
return registeredMetrics;
}

Expand Down Expand Up @@ -206,20 +200,29 @@ private void collectMetrics(final Resource current, ImmutableManagementResourceR
}
}

public Set<String> registerMetrics(Map<PathAddress, Map<String, ModelNode>> metrics, MetricRegistry registry, MetricNameCreator metricNameCreator) {
public Set<String> registerMetrics(Map<PathAddress, Map<String, ModelNode>> metrics, MetricRegistry registry, Function<PathAddress, PathAddress> resourceAddressResolver) {
Set<String> registeredMetricNames = new HashSet<>();

for (Map.Entry<PathAddress, Map<String, ModelNode>> entry : metrics.entrySet()) {
PathAddress address = entry.getKey();
PathAddress resourceAddress = resourceAddressResolver.apply(entry.getKey());
for (Map.Entry<String, ModelNode> wildflyMetric : entry.getValue().entrySet()) {
String attributeName = wildflyMetric.getKey();
ModelNode attributeDescription = wildflyMetric.getValue();

String metricName = metricNameCreator.createName(address, attributeName);
String metricName = resourceAddress.toPathStyleString().substring(1) + "/" + attributeName;
String unit = attributeDescription.get(UNIT).asString(MetricUnits.NONE).toLowerCase();
String description = attributeDescription.get(DESCRIPTION).asStringOrNull();

Metadata metadata = new ExtendedMetadata(metricName, attributeName + " for " + address.toHttpStyleString(), description, MetricType.GAUGE, unit);
// fill the MP Metric tags with the address key/value pairs (that will be not preserve order)
// and an attribute=<attributeName> tag
HashMap<String, String> tags = new HashMap<>();
for (PathElement element: resourceAddress) {
tags.put(element.getKey(), element.getValue());
}
tags.put(ATTRIBUTE, attributeName);

Metadata metadata = new ExtendedMetadata(metricName, attributeName + " for " + resourceAddress.toHttpStyleString(), description, MetricType.GAUGE, unit);
metadata.setTags(tags);

ModelType type = attributeDescription.get(TYPE).asType();

Expand All @@ -241,22 +244,22 @@ public Set<String> registerMetrics(Map<PathAddress, Map<String, ModelNode>> metr
case TYPE:
case UNDEFINED:
default:
LOGGER.debugf("Type %s is not supported for MicroProfile Metrics, the attribute %s on %s will not be registered.", type, attributeName, address);
LOGGER.debugf("Type %s is not supported for MicroProfile Metrics, the attribute %s on %s will not be registered.", type, attributeName, resourceAddress);
continue;
}
Metric metric = new Gauge() {
@Override
public Number getValue() {
final ModelNode readAttributeOp = new ModelNode();
readAttributeOp.get(OP).set(READ_ATTRIBUTE_OPERATION);
readAttributeOp.get(OP_ADDR).set(metricNameCreator.getResourceAddress(address).toModelNode());
readAttributeOp.get(OP_ADDR).set(resourceAddress.toModelNode());
readAttributeOp.get(ModelDescriptionConstants.INCLUDE_UNDEFINED_METRIC_VALUES).set(true);
readAttributeOp.get(NAME).set(attributeName);
ModelNode response = modelControllerClient.execute(readAttributeOp);
String error = getFailureDescription(response);
if (error != null) {
registry.remove(metricName);
throw LOGGER.unableToReadAttribute(attributeName, address, error);
throw LOGGER.unableToReadAttribute(attributeName, resourceAddress, error);
}
ModelNode result = response.get(RESULT);
if (result.isDefined()) {
Expand All @@ -271,11 +274,11 @@ public Number getValue() {
return result.asDouble();
}
} catch (Exception e) {
throw LOGGER.unableToConvertAttribute(attributeName, address, e);
throw LOGGER.unableToConvertAttribute(attributeName, resourceAddress, e);
}
} else {
registry.remove(metricName);
throw LOGGER.undefinedMetric(attributeName, address);
throw LOGGER.undefinedMetric(attributeName, resourceAddress);
}
}
};
Expand Down
Expand Up @@ -57,18 +57,11 @@ private DeploymentMetricService(Resource rootResource, ManagementResourceRegistr
@Override
public void start(StartContext startContext) {
MetricRegistry applicationRegistry = MetricRegistries.get(APPLICATION);
registeredMetrics = registrationService.get().registerMetrics(rootResource, managementResourceRegistration, applicationRegistry,
new MetricsRegistrationService.MetricNameCreator() {
@Override
public String createName(PathAddress address, String attributeName) {
return deploymentAddress.append(address).toPathStyleString().substring(1) + "/" + attributeName;
}

@Override
public PathAddress getResourceAddress(PathAddress address) {
return deploymentAddress.append(address);
}
});
registeredMetrics = registrationService.get().registerMetrics(rootResource,
managementResourceRegistration,
applicationRegistry,
// prepend the deployment address to the subsystem resource address
address -> deploymentAddress.append(address));
}

@Override
Expand Down
Expand Up @@ -22,6 +22,9 @@

package org.wildfly.test.integration.microprofile.metrics.application;

import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.STATISTICS_ENABLED;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUBSYSTEM;
import static org.jboss.as.test.shared.ServerReload.reloadIfRequired;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
Expand All @@ -30,6 +33,7 @@
import static org.wildfly.test.integration.microprofile.metrics.MetricsHelper.getMetricValueFromPrometheusOutput;
import static org.wildfly.test.integration.microprofile.metrics.MetricsHelper.getPrometheusMetrics;

import java.io.IOException;
import java.net.URL;
import java.util.concurrent.TimeUnit;

Expand All @@ -41,8 +45,12 @@
import org.jboss.arquillian.junit.InSequence;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.as.arquillian.api.ContainerResource;
import org.jboss.as.arquillian.api.ServerSetup;
import org.jboss.as.arquillian.api.ServerSetupTask;
import org.jboss.as.arquillian.container.ManagementClient;
import org.jboss.as.controller.client.helpers.Operations;
import org.jboss.as.test.integration.common.HttpRequest;
import org.jboss.dmr.ModelNode;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
Expand All @@ -57,8 +65,29 @@
*/
@RunWith(Arquillian.class)
@RunAsClient
@ServerSetup({MicroProfileMetricsApplicationTestCase.EnablesUndertowStatistics.class})
public class MicroProfileMetricsApplicationTestCase {

static class EnablesUndertowStatistics implements ServerSetupTask {

@Override
public void setup(ManagementClient managementClient, String containerId) throws Exception {
managementClient.getControllerClient().execute(enableStatistics(true));
reloadIfRequired(managementClient);
}

@Override
public void tearDown(ManagementClient managementClient, String containerId) throws Exception {
managementClient.getControllerClient().execute(enableStatistics(false));
reloadIfRequired(managementClient);
}

private ModelNode enableStatistics(boolean enabled) {
final ModelNode address = Operations.createAddress(SUBSYSTEM, "undertow");
return Operations.createWriteAttributeOperation(address, STATISTICS_ENABLED, enabled);
}
}

@Deployment(name = "MicroProfileMetricsApplicationTestCase", managed = false)
public static Archive<?> deploy() {
WebArchive war = ShrinkWrap.create(WebArchive.class, "MicroProfileMetricsApplicationTestCase.war")
Expand All @@ -73,6 +102,8 @@ public static Archive<?> deploy() {
@ArquillianResource
private Deployer deployer;

private static int requestCalled = 0;

@Test
@InSequence(1)
public void testApplicationMetricBeforeDeployment() throws Exception {
Expand Down Expand Up @@ -136,15 +167,53 @@ public void testApplicationMetricWithJSONAfterDeployment(@ArquillianResource URL

@Test
@InSequence(4)
@OperateOnDeployment("MicroProfileMetricsApplicationTestCase")
public void testDeploymentWildFlyMetrics(@ArquillianResource URL url) throws Exception {
// test the request-count metric on the deployment's undertow resources
checkRequestCount(requestCalled);
performCall(url);
checkRequestCount(requestCalled);
}

@Test
@InSequence(5)
public void tesApplicationMetricAfterUndeployment() throws Exception {
deployer.undeploy("MicroProfileMetricsApplicationTestCase");

getPrometheusMetrics(managementClient, "application", false);
getJSONMetrics(managementClient, "application", false);
}

private String performCall(URL url) throws Exception {
private static String performCall(URL url) throws Exception {
requestCalled++;
URL appURL = new URL(url.toExternalForm() + "microprofile-metrics-app/hello");
return HttpRequest.get(appURL.toExternalForm(), 10, TimeUnit.SECONDS);
}

private void checkRequestCount(int expectedCount) throws IOException {
// Prometheus conversion of the metric deployment/MicroProfileMetricsApplicationTestCase.war/subsystem/undertow/servlet/org.wildfly.test.integration.microprofile.metrics.application.TestApplication/request-count
String prometheusMetricName = "deployment_micro_profile_metrics_application_test_case_war_subsystem_undertow_servlet_org_wildfly_test_integration_microprofile_metrics_application_test_application_request_count";

String tags = null;
Double value = null;
String metrics = getPrometheusMetrics(managementClient, "application", true);
for (String line : metrics.split("\\R")) {
if (line.startsWith("application:" + prometheusMetricName)) {
String[] split = line.split("\\s+");
tags = split[0].substring(("application:" + prometheusMetricName).length());
value = Double.valueOf(split[1]);
}
}

assertNotNull(tags);
assertNotNull(value);

assertTrue(tags.contains("subsystem=\"undertow\""));
assertTrue(tags.contains("deployment=\"MicroProfileMetricsApplicationTestCase.war\""));
assertTrue(tags.contains("servlet=\"org.wildfly.test.integration.microprofile.metrics.application.TestApplication\""));
assertTrue(tags.contains("attribute=\"request-count\""));

assertEquals(Integer.valueOf(expectedCount).doubleValue(), value, 0);
}

}

0 comments on commit 42f5210

Please sign in to comment.