HADOOP-18954. Filter NaN values from JMX json interface. (#6229).
Reviewed-by: Ferenc Erdelyi Signed-off-by: He Xiaoqiao <hexiaoqiao@apache.org>
This commit is contained in:
parent
f58945d7d1
commit
a32097a921
@ -1076,5 +1076,13 @@ public class CommonConfigurationKeysPublic {
|
|||||||
public static final String IPC_SERVER_METRICS_UPDATE_RUNNER_INTERVAL =
|
public static final String IPC_SERVER_METRICS_UPDATE_RUNNER_INTERVAL =
|
||||||
"ipc.server.metrics.update.runner.interval";
|
"ipc.server.metrics.update.runner.interval";
|
||||||
public static final int IPC_SERVER_METRICS_UPDATE_RUNNER_INTERVAL_DEFAULT = 5000;
|
public static final int IPC_SERVER_METRICS_UPDATE_RUNNER_INTERVAL_DEFAULT = 5000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see
|
||||||
|
* <a href="{@docRoot}/../hadoop-project-dist/hadoop-common/core-default.xml">
|
||||||
|
* core-default.xml</a>
|
||||||
|
*/
|
||||||
|
public static final String JMX_NAN_FILTER = "hadoop.http.jmx.nan-filter.enabled";
|
||||||
|
public static final boolean JMX_NAN_FILTER_DEFAULT = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,6 +56,7 @@
|
|||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.apache.hadoop.classification.VisibleForTesting;
|
import org.apache.hadoop.classification.VisibleForTesting;
|
||||||
|
import org.apache.hadoop.jmx.JMXJsonServletNaNFiltered;
|
||||||
import org.apache.hadoop.util.Preconditions;
|
import org.apache.hadoop.util.Preconditions;
|
||||||
import org.apache.hadoop.thirdparty.com.google.common.collect.ImmutableMap;
|
import org.apache.hadoop.thirdparty.com.google.common.collect.ImmutableMap;
|
||||||
import com.sun.jersey.spi.container.servlet.ServletContainer;
|
import com.sun.jersey.spi.container.servlet.ServletContainer;
|
||||||
@ -117,6 +118,9 @@
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.JMX_NAN_FILTER;
|
||||||
|
import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.JMX_NAN_FILTER_DEFAULT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a Jetty embedded server to answer http requests. The primary goal is
|
* Create a Jetty embedded server to answer http requests. The primary goal is
|
||||||
* to serve up status information for the server. There are three contexts:
|
* to serve up status information for the server. There are three contexts:
|
||||||
@ -785,7 +789,7 @@ private void initializeWebServer(String name, String hostName,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addDefaultServlets();
|
addDefaultServlets(conf);
|
||||||
addPrometheusServlet(conf);
|
addPrometheusServlet(conf);
|
||||||
addAsyncProfilerServlet(contexts, conf);
|
addAsyncProfilerServlet(contexts, conf);
|
||||||
}
|
}
|
||||||
@ -976,12 +980,17 @@ private void setContextAttributes(ServletContextHandler context,
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Add default servlets.
|
* Add default servlets.
|
||||||
|
* @param configuration the hadoop configuration
|
||||||
*/
|
*/
|
||||||
protected void addDefaultServlets() {
|
protected void addDefaultServlets(Configuration configuration) {
|
||||||
// set up default servlets
|
// set up default servlets
|
||||||
addServlet("stacks", "/stacks", StackServlet.class);
|
addServlet("stacks", "/stacks", StackServlet.class);
|
||||||
addServlet("logLevel", "/logLevel", LogLevel.Servlet.class);
|
addServlet("logLevel", "/logLevel", LogLevel.Servlet.class);
|
||||||
addServlet("jmx", "/jmx", JMXJsonServlet.class);
|
addServlet("jmx", "/jmx",
|
||||||
|
configuration.getBoolean(JMX_NAN_FILTER, JMX_NAN_FILTER_DEFAULT)
|
||||||
|
? JMXJsonServletNaNFiltered.class
|
||||||
|
: JMXJsonServlet.class
|
||||||
|
);
|
||||||
addServlet("conf", "/conf", ConfServlet.class);
|
addServlet("conf", "/conf", ConfServlet.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,12 +17,12 @@
|
|||||||
|
|
||||||
package org.apache.hadoop.jmx;
|
package org.apache.hadoop.jmx;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonFactory;
|
import java.io.IOException;
|
||||||
import com.fasterxml.jackson.core.JsonGenerator;
|
import java.io.PrintWriter;
|
||||||
import org.apache.hadoop.http.HttpServer2;
|
import java.lang.management.ManagementFactory;
|
||||||
import org.slf4j.Logger;
|
import java.lang.reflect.Array;
|
||||||
import org.slf4j.LoggerFactory;
|
import java.util.Iterator;
|
||||||
|
import java.util.Set;
|
||||||
import javax.management.AttributeNotFoundException;
|
import javax.management.AttributeNotFoundException;
|
||||||
import javax.management.InstanceNotFoundException;
|
import javax.management.InstanceNotFoundException;
|
||||||
import javax.management.IntrospectionException;
|
import javax.management.IntrospectionException;
|
||||||
@ -42,12 +42,14 @@
|
|||||||
import javax.servlet.http.HttpServlet;
|
import javax.servlet.http.HttpServlet;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PrintWriter;
|
import com.fasterxml.jackson.core.JsonFactory;
|
||||||
import java.lang.management.ManagementFactory;
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
import java.lang.reflect.Array;
|
import org.slf4j.Logger;
|
||||||
import java.util.Iterator;
|
import org.slf4j.LoggerFactory;
|
||||||
import java.util.Set;
|
|
||||||
|
import org.apache.commons.lang3.NotImplementedException;
|
||||||
|
import org.apache.hadoop.http.HttpServer2;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This servlet is based off of the JMXProxyServlet from Tomcat 7.0.14. It has
|
* This servlet is based off of the JMXProxyServlet from Tomcat 7.0.14. It has
|
||||||
@ -136,6 +138,7 @@ public class JMXJsonServlet extends HttpServlet {
|
|||||||
* Json Factory to create Json generators for write objects in json format
|
* Json Factory to create Json generators for write objects in json format
|
||||||
*/
|
*/
|
||||||
protected transient JsonFactory jsonFactory;
|
protected transient JsonFactory jsonFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize this servlet.
|
* Initialize this servlet.
|
||||||
*/
|
*/
|
||||||
@ -386,10 +389,10 @@ private void writeAttribute(JsonGenerator jg, ObjectName oname, MBeanAttributeIn
|
|||||||
|
|
||||||
private void writeAttribute(JsonGenerator jg, String attName, Object value) throws IOException {
|
private void writeAttribute(JsonGenerator jg, String attName, Object value) throws IOException {
|
||||||
jg.writeFieldName(attName);
|
jg.writeFieldName(attName);
|
||||||
writeObject(jg, value);
|
writeObject(jg, value, attName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeObject(JsonGenerator jg, Object value) throws IOException {
|
private void writeObject(JsonGenerator jg, Object value, String attName) throws IOException {
|
||||||
if(value == null) {
|
if(value == null) {
|
||||||
jg.writeNull();
|
jg.writeNull();
|
||||||
} else {
|
} else {
|
||||||
@ -399,9 +402,11 @@ private void writeObject(JsonGenerator jg, Object value) throws IOException {
|
|||||||
int len = Array.getLength(value);
|
int len = Array.getLength(value);
|
||||||
for (int j = 0; j < len; j++) {
|
for (int j = 0; j < len; j++) {
|
||||||
Object item = Array.get(value, j);
|
Object item = Array.get(value, j);
|
||||||
writeObject(jg, item);
|
writeObject(jg, item, attName);
|
||||||
}
|
}
|
||||||
jg.writeEndArray();
|
jg.writeEndArray();
|
||||||
|
} else if (extraCheck(value)) {
|
||||||
|
extraWrite(value, attName, jg);
|
||||||
} else if(value instanceof Number) {
|
} else if(value instanceof Number) {
|
||||||
Number n = (Number)value;
|
Number n = (Number)value;
|
||||||
jg.writeNumber(n.toString());
|
jg.writeNumber(n.toString());
|
||||||
@ -421,7 +426,7 @@ private void writeObject(JsonGenerator jg, Object value) throws IOException {
|
|||||||
TabularData tds = (TabularData)value;
|
TabularData tds = (TabularData)value;
|
||||||
jg.writeStartArray();
|
jg.writeStartArray();
|
||||||
for(Object entry : tds.values()) {
|
for(Object entry : tds.values()) {
|
||||||
writeObject(jg, entry);
|
writeObject(jg, entry, attName);
|
||||||
}
|
}
|
||||||
jg.writeEndArray();
|
jg.writeEndArray();
|
||||||
} else {
|
} else {
|
||||||
@ -429,4 +434,18 @@ private void writeObject(JsonGenerator jg, Object value) throws IOException {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In case you need to modify the logic, how java objects transforms to json,
|
||||||
|
* you can overwrite this method to return true in case special handling
|
||||||
|
* @param value the object what should be judged
|
||||||
|
* @return true, if it needs special transformation
|
||||||
|
*/
|
||||||
|
protected boolean extraCheck(Object value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void extraWrite(Object value, String attName, JsonGenerator jg) throws IOException {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.hadoop.jmx;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For example in case of MutableGauge we are using numbers,
|
||||||
|
* but not implementing Number interface,
|
||||||
|
* so we skip class check here because we can not be sure NaN values are wrapped
|
||||||
|
* with classes which implements the Number interface
|
||||||
|
*/
|
||||||
|
public class JMXJsonServletNaNFiltered extends JMXJsonServlet {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
LoggerFactory.getLogger(JMXJsonServletNaNFiltered.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean extraCheck(Object value) {
|
||||||
|
return Objects.equals("NaN", Objects.toString(value).trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void extraWrite(Object value, String attName, JsonGenerator jg) throws IOException {
|
||||||
|
LOG.debug("The {} attribute with value: {} was identified as NaN "
|
||||||
|
+ "and will be replaced with 0.0", attName, value);
|
||||||
|
jg.writeNumber(0.0);
|
||||||
|
}
|
||||||
|
}
|
@ -65,6 +65,18 @@
|
|||||||
</description>
|
</description>
|
||||||
</property>
|
</property>
|
||||||
|
|
||||||
|
<property>
|
||||||
|
<name>hadoop.http.jmx.nan-filter.enabled</name>
|
||||||
|
<value>false</value>
|
||||||
|
<description>
|
||||||
|
The REST API of the JMX interface can return with NaN values
|
||||||
|
if the attribute represent a 0.0/0.0 value.
|
||||||
|
Some JSON parser by default can not parse json attributes like foo:NaN.
|
||||||
|
If this filter is enabled the NaN values will be converted to 0.0 values,
|
||||||
|
to make json parse less complicated.
|
||||||
|
</description>
|
||||||
|
</property>
|
||||||
|
|
||||||
<!--- security properties -->
|
<!--- security properties -->
|
||||||
|
|
||||||
<property>
|
<property>
|
||||||
|
@ -62,10 +62,15 @@ public static void assertReFind(String re, String value) {
|
|||||||
result = readOutput(new URL(baseUrl, "/jmx?qry=java.lang:type=Memory"));
|
result = readOutput(new URL(baseUrl, "/jmx?qry=java.lang:type=Memory"));
|
||||||
assertReFind("\"name\"\\s*:\\s*\"java.lang:type=Memory\"", result);
|
assertReFind("\"name\"\\s*:\\s*\"java.lang:type=Memory\"", result);
|
||||||
assertReFind("\"modelerType\"", result);
|
assertReFind("\"modelerType\"", result);
|
||||||
|
|
||||||
|
System.setProperty("THE_TEST_OF_THE_NAN_VALUES", String.valueOf(Float.NaN));
|
||||||
result = readOutput(new URL(baseUrl, "/jmx"));
|
result = readOutput(new URL(baseUrl, "/jmx"));
|
||||||
assertReFind("\"name\"\\s*:\\s*\"java.lang:type=Memory\"", result);
|
assertReFind("\"name\"\\s*:\\s*\"java.lang:type=Memory\"", result);
|
||||||
|
assertReFind(
|
||||||
|
"\"key\"\\s*:\\s*\"THE_TEST_OF_THE_NAN_VALUES\"\\s*,\\s*\"value\"\\s*:\\s*\"NaN\"",
|
||||||
|
result
|
||||||
|
);
|
||||||
|
|
||||||
// test to get an attribute of a mbean
|
// test to get an attribute of a mbean
|
||||||
result = readOutput(new URL(baseUrl,
|
result = readOutput(new URL(baseUrl,
|
||||||
"/jmx?get=java.lang:type=Memory::HeapMemoryUsage"));
|
"/jmx?get=java.lang:type=Memory::HeapMemoryUsage"));
|
||||||
|
@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* 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.hadoop.jmx;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.http.HttpServer2;
|
||||||
|
import org.apache.hadoop.http.HttpServerFunctionalTest;
|
||||||
|
|
||||||
|
import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.JMX_NAN_FILTER;
|
||||||
|
|
||||||
|
public class TestJMXJsonServletNaNFiltered extends HttpServerFunctionalTest {
|
||||||
|
private static HttpServer2 server;
|
||||||
|
private static URL baseUrl;
|
||||||
|
|
||||||
|
@BeforeClass public static void setup() throws Exception {
|
||||||
|
Configuration configuration = new Configuration();
|
||||||
|
configuration.setBoolean(JMX_NAN_FILTER, true);
|
||||||
|
server = createTestServer(configuration);
|
||||||
|
server.start();
|
||||||
|
baseUrl = getServerURL(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass public static void cleanup() throws Exception {
|
||||||
|
server.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void assertReFind(String re, String value) {
|
||||||
|
Pattern p = Pattern.compile(re);
|
||||||
|
Matcher m = p.matcher(value);
|
||||||
|
assertTrue("'"+p+"' does not match "+value, m.find());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void testQuery() throws Exception {
|
||||||
|
System.setProperty("THE_TEST_OF_THE_NAN_VALUES", String.valueOf(Float.NaN));
|
||||||
|
String result = readOutput(new URL(baseUrl, "/jmx"));
|
||||||
|
assertReFind("\"name\"\\s*:\\s*\"java.lang:type=Memory\"", result);
|
||||||
|
assertReFind(
|
||||||
|
"\"key\"\\s*:\\s*\"THE_TEST_OF_THE_NAN_VALUES\"\\s*,\\s*\"value\"\\s*:\\s*0.0",
|
||||||
|
result
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -634,5 +634,7 @@ public void testMutableGaugeFloat() {
|
|||||||
assertEquals(3.2f, mgf.value(), 0.0);
|
assertEquals(3.2f, mgf.value(), 0.0);
|
||||||
mgf.incr();
|
mgf.incr();
|
||||||
assertEquals(4.2f, mgf.value(), 0.0);
|
assertEquals(4.2f, mgf.value(), 0.0);
|
||||||
|
mgf.set(Float.NaN);
|
||||||
|
assertEquals(Float.NaN, mgf.value(), 0.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user