mirror of https://github.com/apache/nifi.git
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:
parent
340b5fcb00
commit
5811a9c579
|
@ -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";
|
||||
|
|
|
@ -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
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 -->
|
||||
<!--
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue