From 42f52106b9dd3c4cffcb8bb600eab33ec76fd0d0 Mon Sep 17 00:00:00 2001 From: Jeff Mesnil Date: Wed, 14 Nov 2018 14:45:23 +0100 Subject: [PATCH] [WFLY-11351] Add resource address and attribute to the metric tags 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 --- .../MicroProfile_Metrics.adoc | 19 +++++ .../metrics/MetricsRegistrationService.java | 43 +++++------ .../deployment/DeploymentMetricService.java | 17 ++--- ...icroProfileMetricsApplicationTestCase.java | 71 ++++++++++++++++++- 4 files changed, 117 insertions(+), 33 deletions(-) diff --git a/docs/src/main/asciidoc/_admin-guide/subsystem-configuration/MicroProfile_Metrics.adoc b/docs/src/main/asciidoc/_admin-guide/subsystem-configuration/MicroProfile_Metrics.adoc index 69c2c9a29331..459ed8da4df4 100644 --- a/docs/src/main/asciidoc/_admin-guide/subsystem-configuration/MicroProfile_Metrics.adoc +++ b/docs/src/main/asciidoc/_admin-guide/subsystem-configuration/MicroProfile_Metrics.adoc @@ -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=/subsystem=undertow/servlet=` 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. diff --git a/microprofile/metrics-smallrye/src/main/java/org/wildfly/extension/microprofile/metrics/MetricsRegistrationService.java b/microprofile/metrics-smallrye/src/main/java/org/wildfly/extension/microprofile/metrics/MetricsRegistrationService.java index 0ebcc3967d29..d8a7cc09fda6 100644 --- a/microprofile/metrics-smallrye/src/main/java/org/wildfly/extension/microprofile/metrics/MetricsRegistrationService.java +++ b/microprofile/metrics-smallrye/src/main/java/org/wildfly/extension/microprofile/metrics/MetricsRegistrationService.java @@ -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; @@ -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; @@ -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 @@ -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 registerMetrics(Resource rootResource, ImmutableManagementResourceRegistration managementResourceRegistration, MetricRegistry metricRegistry, - MetricNameCreator metricNameCreator) { + Function resourceAddressResolver) { Map> metrics = findMetrics(rootResource, managementResourceRegistration); - Set registeredMetrics = registerMetrics(metrics, metricRegistry, metricNameCreator); + Set registeredMetrics = registerMetrics(metrics, metricRegistry, resourceAddressResolver); return registeredMetrics; } @@ -206,20 +200,29 @@ private void collectMetrics(final Resource current, ImmutableManagementResourceR } } - public Set registerMetrics(Map> metrics, MetricRegistry registry, MetricNameCreator metricNameCreator) { + public Set registerMetrics(Map> metrics, MetricRegistry registry, Function resourceAddressResolver) { Set registeredMetricNames = new HashSet<>(); for (Map.Entry> entry : metrics.entrySet()) { - PathAddress address = entry.getKey(); + PathAddress resourceAddress = resourceAddressResolver.apply(entry.getKey()); for (Map.Entry 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= tag + HashMap 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(); @@ -241,7 +244,7 @@ public Set registerMetrics(Map> 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() { @@ -249,14 +252,14 @@ public Set registerMetrics(Map> metr 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()) { @@ -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); } } }; diff --git a/microprofile/metrics-smallrye/src/main/java/org/wildfly/extension/microprofile/metrics/deployment/DeploymentMetricService.java b/microprofile/metrics-smallrye/src/main/java/org/wildfly/extension/microprofile/metrics/deployment/DeploymentMetricService.java index 83998faa2265..53d01615cab9 100644 --- a/microprofile/metrics-smallrye/src/main/java/org/wildfly/extension/microprofile/metrics/deployment/DeploymentMetricService.java +++ b/microprofile/metrics-smallrye/src/main/java/org/wildfly/extension/microprofile/metrics/deployment/DeploymentMetricService.java @@ -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 diff --git a/testsuite/integration/basic/src/test/java/org/wildfly/test/integration/microprofile/metrics/application/MicroProfileMetricsApplicationTestCase.java b/testsuite/integration/basic/src/test/java/org/wildfly/test/integration/microprofile/metrics/application/MicroProfileMetricsApplicationTestCase.java index 598468958d78..9c83944a96f7 100644 --- a/testsuite/integration/basic/src/test/java/org/wildfly/test/integration/microprofile/metrics/application/MicroProfileMetricsApplicationTestCase.java +++ b/testsuite/integration/basic/src/test/java/org/wildfly/test/integration/microprofile/metrics/application/MicroProfileMetricsApplicationTestCase.java @@ -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; @@ -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; @@ -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; @@ -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") @@ -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 { @@ -136,6 +167,16 @@ 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"); @@ -143,8 +184,36 @@ public void tesApplicationMetricAfterUndeployment() throws Exception { 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); + } + }