NIFI-11385 Added JMX Metrics REST Resource for Diagnostics

This closes #7124

Co-authored-by: David Handermann <exceptionfactory@apache.org>
Signed-off-by: David Handermann <exceptionfactory@apache.org>
This commit is contained in:
Timea Barna 2023-04-05 14:42:47 +02:00 committed by exceptionfactory
parent 340b5fcb00
commit 5811a9c579
No known key found for this signature in database
GPG Key ID: 29B6A52D2AAE8DBA
15 changed files with 766 additions and 3 deletions

View File

@ -245,6 +245,7 @@ public class NiFiProperties extends ApplicationProperties {
public static final String WEB_REQUEST_IP_WHITELIST = "nifi.web.request.ip.whitelist";
public static final String WEB_SHOULD_SEND_SERVER_VERSION = "nifi.web.should.send.server.version";
public static final String WEB_REQUEST_LOG_FORMAT = "nifi.web.request.log.format";
public static final String WEB_JMX_METRICS_ALLOWED_FILTER_PATTERN = "nifi.web.jmx.metrics.allowed.filter.pattern";
// ui properties
public static final String UI_BANNER_TEXT = "nifi.ui.banner.text";

View File

@ -4174,6 +4174,8 @@ request headers. The default value is:
The CustomRequestLog writes formatted messages using the following SLF4J logger:
`org.apache.nifi.web.server.RequestLog`
|`nifi.web.jmx.metrics.allowed.filter.pattern`|The regular expression controlling the JMX MBean names that the REST API
is allowed to return. The default value is empty, blocking all MBeans. Configuring `.*` allows all registered MBeans.
|====
[[security_properties]]
@ -4844,3 +4846,37 @@ nifi.diagnostics.on.shutdown.max.directory.size=10 MB
```
In the case of a lengthy diagnostic, NiFi may terminate before the command execution ends. In this case, the `graceful.shutdown.seconds` property should be set to a higher value in the `bootstrap.conf` configuration file.
[[jmx_metrics]]
== JMX Metrics
It is possible to get JMX metrics using the REST API with read permissions on system diagnostics resources.
The information available depends on the registered MBeans. Metrics can contain data related to performance indicators.
Listing of MBeans is controlled using a regular expression pattern in application properties. Leaving the
property empty means no MBeans will be returned. The default value blocks all MBeans and must be changed to return
information.
nifi.web.jmx.metrics.allowed.filter.pattern=.*
An optionally provided query parameter using a regular expression pattern, will display only MBeans with matching names.
Leaving this parameter empty means listing all MBeans except those filtered out by the blocked filter pattern.
https://localhost:8443/nifi-api/system-diagnostics/jmx-metrics?beanNameFilter=bean.name.1|bean.name.2
An example output would look like this:
[
{
"beanName" : "bean.name.1,type=type1",
"attributeName" : “attribute-name",
"attributeValue" : “attribute-value”
},
{
"beanName" : "bean.name.2, type=type2",
"attributeName" : "attribute-name",
"attributeValue" : integer-value
}
]

View File

@ -0,0 +1,49 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.nifi.web.api.dto;
import io.swagger.annotations.ApiModelProperty;
import javax.xml.bind.annotation.XmlType;
@XmlType(name = "jmxMetricsResults")
public class JmxMetricsResultDTO {
final private String beanName;
final private String attributeName;
final private Object attributeValue;
public JmxMetricsResultDTO(final String beanName, final String attributeName, final Object attributeValue) {
this.beanName = beanName;
this.attributeName = attributeName;
this.attributeValue = attributeValue;
}
@ApiModelProperty("The bean name of the metrics bean.")
public String getBeanName() {
return beanName;
}
@ApiModelProperty("The attribute name of the metrics bean's attribute.")
public String getAttributeName() {
return attributeName;
}
@ApiModelProperty("The attribute value of the the metrics bean's attribute")
public Object getAttributeValue() {
return attributeValue;
}
}

View File

@ -0,0 +1,43 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.nifi.web.api.entity;
import org.apache.nifi.web.api.dto.JmxMetricsResultDTO;
import javax.xml.bind.annotation.XmlRootElement;
import java.util.Collection;
/**
* A serialized representation of this class can be placed in the entity body of a request or response to or from the API. This particular entity holds a reference to a list of JmxMetricsResult.
*/
@XmlRootElement(name = "jmxMetricsResult")
public class JmxMetricsResultsEntity extends Entity {
private Collection<JmxMetricsResultDTO> jmxMetricsResults;
/**
* A collection of JmxMetricsResultDTO objects that is being serialized.
*
* @return The collection of JmxMetricsResultDTO objects
*/
public Collection<JmxMetricsResultDTO> getJmxMetricsResults() {
return jmxMetricsResults;
}
public void setJmxMetricsResults(final Collection<JmxMetricsResultDTO> jmxMetricsResults) {
this.jmxMetricsResults = jmxMetricsResults;
}
}

View File

@ -140,6 +140,7 @@
<nifi.web.request.ip.whitelist />
<nifi.web.should.send.server.version>true</nifi.web.should.send.server.version>
<nifi.web.request.log.format>%{client}a - %u %t "%r" %s %O "%{Referer}i" "%{User-Agent}i"</nifi.web.request.log.format>
<nifi.web.jmx.metrics.allowed.filter.pattern />
<!-- nifi.properties: security properties -->
<nifi.security.autoreload.enabled>false</nifi.security.autoreload.enabled>
<nifi.security.autoreload.interval>10 secs</nifi.security.autoreload.interval>

View File

@ -178,6 +178,9 @@ nifi.web.request.ip.whitelist=${nifi.web.request.ip.whitelist}
nifi.web.should.send.server.version=${nifi.web.should.send.server.version}
nifi.web.request.log.format=${nifi.web.request.log.format}
# Filter JMX MBeans available through the System Diagnostics REST API
nifi.web.jmx.metrics.allowed.filter.pattern=${nifi.web.jmx.metrics.allowed.filter.pattern}
# Include or Exclude TLS Cipher Suites for HTTPS
nifi.web.https.ciphersuites.include=
nifi.web.https.ciphersuites.exclude=
@ -341,8 +344,6 @@ nifi.analytics.connection.model.score.threshold=${nifi.analytics.connection.mode
nifi.monitor.long.running.task.schedule=
nifi.monitor.long.running.task.threshold=
# Create automatic diagnostics when stopping/restarting NiFi.
# Enable automatic diagnostic at shutdown.
nifi.diagnostics.on.shutdown.enabled=false

View File

@ -28,8 +28,11 @@ import org.apache.nifi.authorization.resource.Authorizable;
import org.apache.nifi.authorization.user.NiFiUserUtils;
import org.apache.nifi.cluster.manager.NodeResponse;
import org.apache.nifi.web.NiFiServiceFacade;
import org.apache.nifi.web.api.dto.JmxMetricsResultDTO;
import org.apache.nifi.web.api.dto.SystemDiagnosticsDTO;
import org.apache.nifi.web.api.entity.JmxMetricsResultsEntity;
import org.apache.nifi.web.api.entity.SystemDiagnosticsEntity;
import org.apache.nifi.web.api.metrics.jmx.JmxMetricsService;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
@ -40,6 +43,7 @@ import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.Collection;
/**
* RESTful endpoint for retrieving system diagnostics.
@ -50,7 +54,7 @@ import javax.ws.rs.core.Response;
description = "Endpoint for accessing system diagnostics."
)
public class SystemDiagnosticsResource extends ApplicationResource {
private JmxMetricsService jmxMetricsService;
private NiFiServiceFacade serviceFacade;
private Authorizer authorizer;
@ -138,6 +142,56 @@ public class SystemDiagnosticsResource extends ApplicationResource {
return generateOkResponse(entity).build();
}
/**
* Retrieves the JMX metrics.
*
* @return A jmxMetricsResult list.
*/
@Path("jmx-metrics")
@GET
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(
value = "Retrieve available JMX metrics",
notes = NON_GUARANTEED_ENDPOINT,
response = JmxMetricsResultsEntity.class,
authorizations = {
@Authorization(value = "Read - /system")
}
)
@ApiResponses(
value = {
@ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@ApiResponse(code = 401, message = "Client could not be authenticated."),
@ApiResponse(code = 403, message = "Client is not authorized to make this request."),
@ApiResponse(code = 404, message = "The specified resource could not be found."),
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response getJmxMetrics(
@ApiParam(
value = "Regular Expression Pattern to be applied against the ObjectName")
@QueryParam("beanNameFilter") final String beanNameFilter
) {
authorizeJmxMetrics();
final Collection<JmxMetricsResultDTO> results = jmxMetricsService.getFilteredMBeanMetrics(beanNameFilter);
final JmxMetricsResultsEntity entity = new JmxMetricsResultsEntity();
entity.setJmxMetricsResults(results);
return generateOkResponse(entity)
.type(MediaType.APPLICATION_JSON_TYPE)
.build();
}
private void authorizeJmxMetrics() {
serviceFacade.authorizeAccess(lookup -> {
final Authorizable system = lookup.getSystem();
system.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
});
}
// setters
public void setServiceFacade(NiFiServiceFacade serviceFacade) {
@ -147,4 +201,8 @@ public class SystemDiagnosticsResource extends ApplicationResource {
public void setAuthorizer(Authorizer authorizer) {
this.authorizer = authorizer;
}
public void setJmxMetricsService(final JmxMetricsService jmxMetricsService) {
this.jmxMetricsService = jmxMetricsService;
}
}

View File

@ -0,0 +1,82 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.nifi.web.api.metrics.jmx;
import org.apache.nifi.web.api.dto.JmxMetricsResultDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.IntrospectionException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.management.RuntimeMBeanException;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;
public class JmxMetricsCollector {
private static final Logger LOGGER = LoggerFactory.getLogger(JmxMetricsCollector.class);
private final static String PATTERN_FOR_ALL_OBJECT_NAMES = "*:*";
private final JmxMetricsResultConverter resultConverter;
public JmxMetricsCollector(final JmxMetricsResultConverter metricsResultConverter) {
this.resultConverter = metricsResultConverter;
}
public Collection<JmxMetricsResultDTO> getBeanMetrics() {
final MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
final Set<ObjectInstance> instances;
try {
instances = mBeanServer.queryMBeans(new ObjectName(PATTERN_FOR_ALL_OBJECT_NAMES), null);
} catch (MalformedObjectNameException e) {
throw new RuntimeException("Invalid ObjectName pattern", e);
}
final Collection<JmxMetricsResultDTO> results = new ArrayList<>();
for (final ObjectInstance instance : instances) {
final MBeanInfo info;
try {
info = mBeanServer.getMBeanInfo(instance.getObjectName());
} catch (InstanceNotFoundException | ReflectionException | IntrospectionException e) {
continue;
}
for (MBeanAttributeInfo attribute : info.getAttributes()) {
try {
final String beanName = instance.getObjectName().getCanonicalName();
final String attributeName = attribute.getName();
final Object attributeValue = resultConverter.convert(mBeanServer.getAttribute(instance.getObjectName(), attribute.getName()));
results.add(new JmxMetricsResultDTO(beanName, attributeName, attributeValue));
} catch (final MBeanException | RuntimeMBeanException | ReflectionException | InstanceNotFoundException | AttributeNotFoundException e) {
//Empty or invalid attributes should not stop the loop.
LOGGER.debug("MBean Object [{}] invalid attribute [{}] found", instance.getObjectName(), attribute.getName());
}
}
}
return results;
}
}

View File

@ -0,0 +1,59 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.nifi.web.api.metrics.jmx;
import org.apache.nifi.web.api.dto.JmxMetricsResultDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import java.util.stream.Collectors;
public class JmxMetricsFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(JmxMetricsFilter.class);
private final static String MATCH_NOTHING = "~^";
private final static String MATCH_ALL = "";
private final Pattern allowedNameFilter;
private final Pattern beanNameFilter;
public JmxMetricsFilter(final String allowedNameFilter, final String beanNameFilter) {
this.allowedNameFilter = createPattern(allowedNameFilter, MATCH_NOTHING);
this.beanNameFilter = createPattern(beanNameFilter, MATCH_ALL);
}
private Pattern createPattern(final String filter, final String defaultValue) {
try {
if (filter == null || filter.isEmpty()) {
return Pattern.compile(defaultValue);
} else {
return Pattern.compile(filter);
}
} catch (PatternSyntaxException e) {
LOGGER.warn("Invalid JMX MBean filter pattern ignored [{}]", filter);
return Pattern.compile(defaultValue);
}
}
public Collection<JmxMetricsResultDTO> filter(final Collection<JmxMetricsResultDTO> results) {
return results.stream()
.filter(result -> allowedNameFilter.asPredicate().test(result.getBeanName()))
.filter(result -> beanNameFilter.asPredicate().test(result.getBeanName()))
.collect(Collectors.toList());
}
}

View File

@ -0,0 +1,66 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.nifi.web.api.metrics.jmx;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.TabularData;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class JmxMetricsResultConverter {
private static final String COMPOSITE_DATA_KEY = "CompositeData%s";
public Object convert(final Object attributeValue) {
if (attributeValue instanceof CompositeData[]) {
final CompositeData[] valueArray = (CompositeData[]) attributeValue;
final Map<String, Object> values = new LinkedHashMap<>();
for (int i = 0; i < valueArray.length; i++) {
final Map<String, Object> subValues = new LinkedHashMap<>();
convertCompositeData(valueArray[i], subValues);
values.put(String.format(COMPOSITE_DATA_KEY, i), subValues);
}
return values;
} else if (attributeValue instanceof CompositeData) {
final Map<String, Object> values = new LinkedHashMap<>();
convertCompositeData(((CompositeData) attributeValue), values);
return values;
} else if (attributeValue instanceof TabularData) {
final Map<String, Object> values = new LinkedHashMap<>();
convertTabularData((TabularData) attributeValue, values);
return values;
} else {
return attributeValue;
}
}
private void convertCompositeData(CompositeData attributeValue, Map<String, Object> values) {
for (String key : attributeValue.getCompositeType().keySet()) {
values.put(key, convert(attributeValue.get(key)));
}
}
private void convertTabularData(TabularData attributeValue, Map<String, Object> values) {
final Set<List<?>> keys = (Set<List<?>>) attributeValue.keySet();
for (List<?> key : keys) {
Object value = convert(attributeValue.get(key.toArray()));
values.put(key.toString(), value);
}
}
}

View File

@ -0,0 +1,30 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.nifi.web.api.metrics.jmx;
import org.apache.nifi.web.api.dto.JmxMetricsResultDTO;
import java.util.Collection;
public interface JmxMetricsService {
/**
* @return a filtered mBean metric collection
*
* @param beanNameFilter regular expression pattern for bean name filtering
*/
Collection<JmxMetricsResultDTO> getFilteredMBeanMetrics(final String beanNameFilter);
}

View File

@ -0,0 +1,42 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.nifi.web.api.metrics.jmx;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.api.dto.JmxMetricsResultDTO;
import java.util.Collection;
public class StandardJmxMetricsService implements JmxMetricsService {
private NiFiProperties properties;
private JmxMetricsCollector metricsCollector;
@Override
public Collection<JmxMetricsResultDTO> getFilteredMBeanMetrics(final String beanNameFilter) {
final String allowedFilterPattern = properties.getProperty(NiFiProperties.WEB_JMX_METRICS_ALLOWED_FILTER_PATTERN);
final JmxMetricsFilter metricsFilter = new JmxMetricsFilter(allowedFilterPattern, beanNameFilter);
return metricsFilter.filter(metricsCollector.getBeanMetrics());
}
public void setProperties(final NiFiProperties properties) {
this.properties = properties;
}
public void setMetricsCollector(final JmxMetricsCollector metricsCollector) {
this.metricsCollector = metricsCollector;
}
}

View File

@ -608,6 +608,7 @@
<property name="requestReplicator" ref="requestReplicator" />
<property name="authorizer" ref="authorizer"/>
<property name="flowController" ref="flowController" />
<property name="jmxMetricsService" ref="jmxMetricsService" />
</bean>
<bean id="accessResource" class="org.apache.nifi.web.api.AccessResource" scope="singleton">
<property name="logoutRequestManager" ref="logoutRequestManager" />
@ -655,6 +656,15 @@
<property name="clusterComponentLifecycle" ref="clusterComponentLifecycle" />
<property name="localComponentLifecycle" ref="localComponentLifecycle" />
</bean>
<bean id="jmxMetricsService" class="org.apache.nifi.web.api.metrics.jmx.StandardJmxMetricsService">
<property name="properties" ref="nifiProperties"/>
<property name="metricsCollector" ref="metricsCollector"/>
</bean>
<bean id="metricsResultConverter" class="org.apache.nifi.web.api.metrics.jmx.JmxMetricsResultConverter">
</bean>
<bean id="metricsCollector" class="org.apache.nifi.web.api.metrics.jmx.JmxMetricsCollector">
<constructor-arg ref="metricsResultConverter"/>
</bean>
<!-- enable aop -->
<!--

View File

@ -0,0 +1,127 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.nifi.web.api.metrics.jmx;
import org.apache.nifi.web.api.dto.JmxMetricsResultDTO;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class JmxMetricsFilterTest {
private static final String ALLOW_ALL_PATTERN = ".*";
private static final String EMPTY_STRING_PATTERN = "";
private static final String BEAN_NAME_FILTER = "%s|%s";
private static final String INVALID_REGEX = "(";
private static final String TEST_BEAN_NAME_ONE = "testBean1";
private static final String TEST_BEAN_NAME_TWO = "testBean2";
private static final JmxMetricsResultDTO RESULT_ONE = new JmxMetricsResultDTO(TEST_BEAN_NAME_ONE, null, null);
private static final JmxMetricsResultDTO RESULT_TWO = new JmxMetricsResultDTO(TEST_BEAN_NAME_TWO, null, null);
private static List<JmxMetricsResultDTO> results;
@BeforeAll
public static void init() {
results = new ArrayList<>();
results.add(RESULT_ONE);
results.add(RESULT_TWO);
}
@Test
public void testNotProvidingFiltersReturnsAllMBeans() {
final JmxMetricsFilter metricsFilter = new JmxMetricsFilter(ALLOW_ALL_PATTERN, EMPTY_STRING_PATTERN);
final Collection<JmxMetricsResultDTO> actual = metricsFilter.filter(results);
assertEquals(actual.size(), 2);
assertTrue(actual.containsAll(results));
}
@Test
public void testAllowedNameFiltersRemovesMBeanFromResult() {
final JmxMetricsFilter metricsFilter = new JmxMetricsFilter(TEST_BEAN_NAME_ONE, EMPTY_STRING_PATTERN);
final Collection<JmxMetricsResultDTO> actual = metricsFilter.filter(results);
assertEquals(actual.size(), 1);
assertTrue(actual.contains(RESULT_ONE));
assertFalse(actual.contains(RESULT_TWO));
}
@Test
public void testBeanNameFiltersReturnsTheSpecifiedMBeansOnly() {
final JmxMetricsFilter metricsFilter = new JmxMetricsFilter(ALLOW_ALL_PATTERN, String.format(BEAN_NAME_FILTER, TEST_BEAN_NAME_ONE, TEST_BEAN_NAME_TWO));
final Collection<JmxMetricsResultDTO> actual = metricsFilter.filter(results);
assertEquals(actual.size(), 2);
assertTrue(actual.containsAll(results));
}
@Test
public void testInvalidAllowedNameFilterRevertingBackToDefaultFiltering() {
final JmxMetricsFilter metricsFilter = new JmxMetricsFilter(INVALID_REGEX, EMPTY_STRING_PATTERN);
final Collection<JmxMetricsResultDTO> actual = metricsFilter.filter(results);
assertTrue(actual.isEmpty());
}
@Test
public void testInvalidBeanNameFilteringRevertingBackToDefaultFiltering() {
final JmxMetricsFilter metricsFilter = new JmxMetricsFilter(ALLOW_ALL_PATTERN, INVALID_REGEX);
final Collection<JmxMetricsResultDTO> actual = metricsFilter.filter(results);
assertEquals(actual.size(), 2);
assertTrue(actual.containsAll(results));
}
@Test
public void testInvalidFiltersRevertingBackToDefaultFiltering() {
final JmxMetricsFilter metricsFilter = new JmxMetricsFilter(INVALID_REGEX, INVALID_REGEX);
final Collection<JmxMetricsResultDTO> actual = metricsFilter.filter(results);
assertTrue(actual.isEmpty());
}
@Test
public void testAllowedNameFilterHasPriority() {
final JmxMetricsFilter metricsFilter = new JmxMetricsFilter(TEST_BEAN_NAME_TWO, TEST_BEAN_NAME_ONE);
final Collection<JmxMetricsResultDTO> actual = metricsFilter.filter(results);
assertTrue(actual.isEmpty());
}
@Test
public void testAllowedNameFilterHasPriorityWhenTheSameFiltersApplied() {
final JmxMetricsFilter metricsFilter = new JmxMetricsFilter(TEST_BEAN_NAME_TWO, String.format(BEAN_NAME_FILTER, TEST_BEAN_NAME_ONE, TEST_BEAN_NAME_TWO));
final Collection<JmxMetricsResultDTO> actual = metricsFilter.filter(results);
assertEquals(actual.size(), 1);
assertFalse(actual.contains(RESULT_ONE));
assertTrue(actual.contains(RESULT_TWO));
}
}

View File

@ -0,0 +1,158 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.nifi.web.api.metrics.jmx;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.CompositeDataSupport;
import javax.management.openmbean.CompositeType;
import javax.management.openmbean.OpenDataException;
import javax.management.openmbean.OpenType;
import javax.management.openmbean.SimpleType;
import javax.management.openmbean.TabularData;
import javax.management.openmbean.TabularDataSupport;
import javax.management.openmbean.TabularType;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
class JmxMetricsResultConverterTest {
private static final String COMPOSITE_DATA_KEY = "CompositeData%s";
private static final String TABLE_DATA_KEY = "Test String %s";
private static final String CONVERTED_TABLE_DATA_KEY = "[Test String %s]";
private static final String ATTRIBUTE_NAME_STRING = "string";
private static final String ATTRIBUTE_NAME_INT = "int";
private static final String ATTRIBUTE_NAME_BOOLEAN = "boolean";
private static final String TABLE_DESCRIPTION = "Table for all tests";
private static final String TABLE_NAME = "Test table";
private static final String METRIC_TYPE_DESCRIPTION = "Metric type for testing";
private static final String METRIC_TYPE_NAME = "Metric type";
private static JmxMetricsResultConverter metricsResultConverter;
private static CompositeData compositeDataOne;
private static CompositeData compositeDataTwo;
private static CompositeData compositeDataThree;
private static TabularType tableType;
@BeforeAll
public static void init() throws OpenDataException {
metricsResultConverter = new JmxMetricsResultConverter();
final CompositeType compositeType = new CompositeType(METRIC_TYPE_NAME,
METRIC_TYPE_DESCRIPTION,
new String[]{ATTRIBUTE_NAME_STRING, ATTRIBUTE_NAME_INT, ATTRIBUTE_NAME_BOOLEAN},
new String[]{ATTRIBUTE_NAME_STRING, ATTRIBUTE_NAME_INT, ATTRIBUTE_NAME_BOOLEAN},
new OpenType[]{SimpleType.STRING, SimpleType.INTEGER, SimpleType.BOOLEAN});
tableType = new TabularType(TABLE_NAME,
TABLE_DESCRIPTION,
compositeType,
new String[] {ATTRIBUTE_NAME_STRING});
compositeDataOne = new CompositeDataSupport(compositeType,
new String[] {ATTRIBUTE_NAME_STRING, ATTRIBUTE_NAME_INT, ATTRIBUTE_NAME_BOOLEAN},
new Object[] {"Test String 1", 1, Boolean.FALSE}
);
compositeDataTwo = new CompositeDataSupport(compositeType,
new String[] {ATTRIBUTE_NAME_STRING, ATTRIBUTE_NAME_INT, ATTRIBUTE_NAME_BOOLEAN},
new Object[] {"Test String 2", 2, Boolean.TRUE}
);
compositeDataThree = new CompositeDataSupport(compositeType,
new String[] {ATTRIBUTE_NAME_STRING, ATTRIBUTE_NAME_INT, ATTRIBUTE_NAME_BOOLEAN},
new Object[] {"Test String 3", 3, Boolean.FALSE}
);
}
@Test
public void testSimpleTypeKeptOriginalType() {
final String expectedString = "Test String";
final int expectedInt = 1;
final boolean expectedBoolean = Boolean.TRUE;
final Object actualString = metricsResultConverter.convert(expectedString);
final Object actualInt = metricsResultConverter.convert(expectedInt);
final Object actualBoolean = metricsResultConverter.convert(expectedBoolean);
assertEquals(expectedString, actualString);
assertEquals(expectedInt, actualInt);
assertEquals(expectedBoolean, actualBoolean);
assertEquals(SimpleType.STRING.getTypeName(), actualString.getClass().getName());
assertEquals(SimpleType.INTEGER.getTypeName(), actualInt.getClass().getName());
assertEquals(SimpleType.BOOLEAN.getTypeName(), actualBoolean.getClass().getName());
}
@Test
public void testCompositeDataConvertedToMap() {
//CompositeData consists of a compositeType and a content which is a Map of attribute name and value pairs.
//The content will be concerted to a LinkedHashMap.
final CompositeData expected = compositeDataOne;
final Map<String, Object> actual = castToMap(metricsResultConverter.convert(expected));
assertEquals(expected.get(ATTRIBUTE_NAME_STRING), actual.get(ATTRIBUTE_NAME_STRING));
assertEquals(expected.get(ATTRIBUTE_NAME_STRING).getClass().getName(), actual.get(ATTRIBUTE_NAME_STRING).getClass().getName());
assertEquals(expected.get(ATTRIBUTE_NAME_INT), actual.get(ATTRIBUTE_NAME_INT));
assertEquals(expected.get(ATTRIBUTE_NAME_INT).getClass().getName(), actual.get(ATTRIBUTE_NAME_INT).getClass().getName());
assertEquals(expected.get(ATTRIBUTE_NAME_BOOLEAN), actual.get(ATTRIBUTE_NAME_BOOLEAN));
assertEquals(expected.get(ATTRIBUTE_NAME_BOOLEAN).getClass().getName(), actual.get(ATTRIBUTE_NAME_BOOLEAN).getClass().getName());
}
@Test
public void testCompositeDataListConvertedToMaps() {
//A CompositeData array consists of a collection of CompositeData objects.
//The content will be concerted to a LinkedHashMap where the key is 'CompositeData<array_index>'
// and the value is a LinkedHashmap of the CompositeData content.
final CompositeData[] expected = new CompositeData[] {
compositeDataOne,
compositeDataTwo,
compositeDataThree
};
final Map<String, Object> actual = castToMap(metricsResultConverter.convert(expected));
assertTrue(expected[0].values().containsAll(castToMap(actual.get(String.format(COMPOSITE_DATA_KEY, 0))).values()));
assertTrue(expected[1].values().containsAll(castToMap(actual.get(String.format(COMPOSITE_DATA_KEY, 1))).values()));
assertTrue(expected[2].values().containsAll(castToMap(actual.get(String.format(COMPOSITE_DATA_KEY, 2))).values()));
assertEquals(expected.length, actual.size());
}
@Test
public void testTabularDataConvertedToMaps() {
//A TabularData consists of a Collection where key is a String array of attribute names and value is a CompositeData.
//The content will be concerted to a LinkedHashMap where the key is String output of the Tabular key array
// and the value is a LinkedHashmap of the CompositeData content.
final TabularData expected = new TabularDataSupport(tableType);
expected.put(compositeDataOne);
expected.put(compositeDataTwo);
expected.put(compositeDataThree);
final Map<String, Object> actual = castToMap(metricsResultConverter.convert(expected));
assertTrue(expected.get(new String[]{String.format(TABLE_DATA_KEY, 1)}).values().containsAll(castToMap(actual.get(String.format(CONVERTED_TABLE_DATA_KEY, 1))).values()));
assertTrue(expected.get(new String[]{String.format(TABLE_DATA_KEY, 2)}).values().containsAll(castToMap(actual.get(String.format(CONVERTED_TABLE_DATA_KEY, 2))).values()));
assertTrue(expected.get(new String[]{String.format(TABLE_DATA_KEY, 3)}).values().containsAll(castToMap(actual.get(String.format(CONVERTED_TABLE_DATA_KEY, 3))).values()));
assertEquals(expected.values().size(), actual.size());
}
private Map<String, Object> castToMap(final Object object) {
return (Map<String, Object>) object;
}
}