Commit 535f25b7 authored by ncampbell's avatar ncampbell

Low level monitoring plugin initial checkin. Include JRobin additions until I...

Low level monitoring plugin initial checkin.  Include JRobin additions until I can clear committing them in the JRobin project.

git-svn-id: http://svn.igniterealtime.org/svn/repos/messenger/branches@2807 b35dd754-fafc-0310-a699-88a17e54d16e
parent c1d7fe7e
initialize.objectname=Loading {0}.
rrdmanager.createoverride=Using override {0}.
rrdmanager.archiveadded=Adding archive {0}.
accumulator.resultnotnumber=Unable to update RRD {2}. Object returned was type {0} with result {1}.
\ No newline at end of file
java.lang\:type\=Threading=org.jivesoftware.messenger.plugin.monitor.ThreadMXBeanSupportInfo
java.lang\:type\=Memory=org.jivesoftware.messenger.plugin.monitor.MemoryMXBeanSupportInfo
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<plugin>
<class>org.jivesoftware.messenger.plugin.monitor.MonitorPlugin</class>
<name>Monitor</name>
<description>Provides in depth monitoring of various runtime components in Messenger.</description>
<author>Noah Campbell</author>
<version>1.0</version>
<date>09/01/2005</date>
<minServerVersion>2.1.5</minServerVersion>
<!--
<adminconsole>
<tab id="tab-users">
<sidebar id="sidebar-users">
<item id="registration-props-form" name="Registration Properties"
url="registration-props-form.jsp"
description="User Registration" />
</sidebar>
</tab>
</adminconsole>
-->
</plugin>
\ No newline at end of file
/**
*
*/
package org.jivesoftware.messenger.plugin.monitor;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.management.openmbean.CompositeData;
import org.jrobin.annotations.Ds;
import org.jrobin.core.Datasource;
import org.jrobin.core.RrdDb;
import org.jrobin.core.RrdDbPool;
import org.jrobin.core.RrdException;
import org.jrobin.core.Sample;
/**
* @author Noah Campbell
* @version 1.0
*/
public class Accumulator implements Runnable {
final static private Logger logger = Logger.getLogger("ACCUMULATOR", "resources");
private final String dbPath;
private final MBeanServer server;
private final ObjectName objectName;
private final RrdDbPool pool;
private final List<Ds> ds;
public Accumulator(MBeanServer server, ObjectName objectName, String db, List<Ds> anonDs, RrdDbPool pool) {
this.dbPath = db;
this.server = server;
this.objectName = objectName;
this.pool = pool;
this.ds = anonDs;
}
/**
* @see java.lang.Runnable#run()
*/
public void run() {
RrdDb db = null;
try {
db = pool.requestRrdDb(dbPath);
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
return;
}
try {
Sample s = db.createSample((System.currentTimeMillis() + 500L)/ 1000L);
for(Ds ds : this.ds) {
String[] calls = ds.expr().split("\\.");
if(calls.length == 0) {
calls = new String[1];
calls[0] = ds.name();
}
int i = 0;
Object result = server.getAttribute(objectName, calls[i++]);
if(result instanceof CompositeData) {
for(int j = i; j < calls.length;j++) {
CompositeData cd = (CompositeData) result;
result = cd.get(calls[j]);
}
}
if(result instanceof Number) {
Number n = (Number)result;
s.setValue(ds.name(), n.doubleValue());
} else {
logger.log(Level.WARNING, "accumulator.resultnotnumber",
new Object[]{result.getClass().getName(),
result.toString(),
db.getPath()});
}
}
s.update();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (AttributeNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstanceNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (MBeanException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ReflectionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (RrdException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
pool.release(db);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
/**
*
*/
package org.jivesoftware.messenger.plugin.monitor;
import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
import javax.imageio.ImageIO;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jrobin.core.RrdDb;
import org.jrobin.core.RrdDbPool;
import org.jrobin.core.RrdException;
import org.jrobin.graph.RrdGraph;
import org.jrobin.graph.RrdGraphDef;
/**
* @author Noah Campbell
* @version 1.0
*/
public class GraphServlet extends HttpServlet {
/**
* @see javax.servlet.http.HttpServlet#service(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("image/png");
ServletOutputStream out = response.getOutputStream();
response.addHeader("Cache-Control", "no-cache");
try {
createPNGGraph(request, out);
} catch (RrdException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Color[] colors = new Color[]{new Color(102, 204, 204),
new Color(102,153,204),
new Color(102,102,204),
new Color(153,102,204),
new Color(102,204,153),
new Color( 61,184,184),
new Color( 46,138,138),
new Color(204,102,204),
new Color(102,204,102),
new Color(138, 46, 46),
new Color(184, 61, 61),
new Color(204,102,153),
new Color(153,204,102),
new Color(204,204,102),
new Color(204,153,102),
new Color(204,102,102)};
/**
* @param request
* @return
* @throws RrdException
*/
private void createPNGGraph(HttpServletRequest request, OutputStream out) throws IOException, RrdException {
int width;
int height;
try {
int w = Integer.parseInt(request.getParameter("width"));
width = w <= 800 ? w : 800;
} catch (NumberFormatException e) {
width = 400;
}
try {
int h = Integer.parseInt(request.getParameter("height"));
height = h <= 600 ? h : 600;
} catch (NumberFormatException e) {
height = 300;
}
try {
String title = request.getParameter("title");
String store = request.getParameter("store");
String objectName = request.getParameter("oname");
Boolean aa = Boolean.parseBoolean(request.getParameter("aa"));
RrdGraphDef def = new RrdGraphDef();
def.setAntiAliasing(aa);
if(title != null)
def.setTitle(title);
RrdDbPool pool = RrdDbPool.getInstance();
RrdManager manager = RrdManager.listStores().get(store);
String filename = manager.getFileName(new ObjectName(objectName));
RrdDb db = pool.requestRrdDb(filename);
String ref = "input";
int count = 0;
for(String ds : db.getDsNames()) {
String id = ref + count++;
def.datasource(id, filename, ds, "AVERAGE");
def.line(id, colors[count], ds);
}
RrdGraph graph = new RrdGraph(def);
byte[] output;
if(request.getParameter("width") == null) {
output = graph.getPNGBytes();
} else {
output = graph.getPNGBytes(width, height);
}
/**
* write out the byte array to the client.
*/
ByteArrayInputStream bis = new ByteArrayInputStream(output);
while(bis.available() > 0) {
out.write(bis.read());
}
} catch (MalformedObjectNameException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NumberFormatException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NullPointerException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (RrdException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = (Graphics2D) bufferedImage.getGraphics();
g2d.setBackground(Color.white);
// g2d.setBackground(new Color(0,0,0,255));
// GradientPaint gpaint = new GradientPaint(0, height / 2, Color.white, width, height / 2, Color.gray);
// Paint originalPaint = g2d.getPaint();
// g2d.setPaint(gpaint);
g2d.fillRect(0,0,width,height);
String error = "An error has occured.";
int errorWidth = g2d.getFontMetrics().stringWidth(error);
// g2d.setPaint(originalPaint);
g2d.setColor(Color.black);
g2d.drawString(error, width / 2 - errorWidth / 2, height / 2);
g2d.dispose();
ImageIO.write(bufferedImage, "png", out);
}
/**
* @see javax.servlet.GenericServlet#init(javax.servlet.ServletConfig)
*/
@Override
public void init(ServletConfig arg0) throws ServletException {
// TODO Auto-generated method stub
super.init(arg0);
}
}
/**
*
*/
package org.jivesoftware.messenger.plugin.monitor;
import org.jrobin.annotations.Arc;
import org.jrobin.annotations.Archives;
import org.jrobin.annotations.ConsolidationFunction;
import org.jrobin.annotations.Ds;
import org.jrobin.annotations.Rrd;
import org.jrobin.annotations.SourceType;
/**
* @author Noah Campbell
* @version 1.0
*/
@Rrd
@Archives({@Arc(consolidationFunction=ConsolidationFunction.AVERAGE, xff=0.5, steps=6, rows=700)})
public interface MemoryMXBeanSupportInfo {
@Ds(name="max", expr="HeapMemoryUsage.max", type=SourceType.COUNTER)
long getMaxHeapMemoryUsage();
@Ds(name="init", expr="HeapMemoryUsage.init")
long getInitHeapMemoryUsage();
@Ds(name="used", expr="HeapMemoryUsage.used")
long getUsedHeapMemoryUsage();
@Ds(name="commited", expr="HeapMemoryUsage.committed")
long getCommittedHeapMemoryUsage();
}
/**
*
*/
package org.jivesoftware.messenger.plugin.monitor;
import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.Preferences;
import javax.management.InstanceNotFoundException;
import javax.management.IntrospectionException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import org.jivesoftware.messenger.container.Plugin;
import org.jivesoftware.messenger.container.PluginManager;
import org.jrobin.annotations.Rrd;
import org.jrobin.core.RrdDb;
import org.jrobin.core.RrdDbPool;
import org.jrobin.core.RrdDef;
import org.jrobin.core.RrdException;
import org.jrobin.core.Sample;
/**
* @author Noah Campbell
* @version 1.0
*/
public class MonitorPlugin implements Plugin {
private File pluginDirectory;
private PluginManager manager;
final static private Logger logger = Logger.getLogger("monitorplugin", "resources");
private MBeanServer server = ManagementFactory.getPlatformMBeanServer();
private Preferences preferences;
private RrdManager rrdManager;
ScheduledExecutorService executors = Executors.newScheduledThreadPool(10);
RrdDbPool pool = RrdDbPool.getInstance();
/**
* @see org.jivesoftware.messenger.container.Plugin#initializePlugin(org.jivesoftware.messenger.container.PluginManager, java.io.File)
*/
public void initializePlugin(PluginManager manager, File pluginDirectory) {
this.manager = manager;
this.pluginDirectory = pluginDirectory;
File store = new File(pluginDirectory, "store");
if(!store.exists()) {
store.mkdirs();
}
try {
rrdManager = new RrdManager(store.getCanonicalFile(), pool);
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
preferences = Preferences.systemNodeForPackage(MonitorPlugin.class);
String monitoredObjectNames = preferences.get("monitoredObjectNames", "java.lang:type=Threading;java.lang:type=Memory");
for(String name : monitoredObjectNames.split("; ?")) {
logger.log(Level.FINER, "initialize.objectname", name);
try {
ObjectName objectName = new ObjectName(name);
Accumulator accum = rrdManager.create(objectName, server.getMBeanInfo(objectName));
/**
* Schedule the accumulator.
*/
executors.scheduleAtFixedRate(accum, 0, 300, TimeUnit.SECONDS);
} catch (MalformedObjectNameException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NullPointerException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstanceNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (RrdException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IntrospectionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ReflectionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/**
* @see org.jivesoftware.messenger.container.Plugin#destroyPlugin()
*/
public void destroyPlugin() {
}
}
/**
*
*/
package org.jivesoftware.messenger.plugin.monitor;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Method;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.management.MBeanInfo;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import org.jrobin.annotations.Arc;
import org.jrobin.annotations.Archives;
import org.jrobin.annotations.Ds;
import org.jrobin.annotations.Rrd;
import org.jrobin.core.RrdDb;
import org.jrobin.core.RrdDbPool;
import org.jrobin.core.RrdDef;
import org.jrobin.core.RrdException;
/**
* @author Noah Campbell
* @version 1.0
*/
public class RrdManager {
/**
* @return storeList
*/
public static Map<String, RrdManager> listStores() {
return Collections.unmodifiableMap(stores);
}
/** The stores. */
private static Map<String, RrdManager> stores = new HashMap<String, RrdManager>();
private final File store;
private final RrdDbPool pool;
private final MBeanServer server = ManagementFactory.getPlatformMBeanServer();
/**
* Construct a new <code>RrdManager</code>.
*
* @param store
* @param pool
* @throws IOException
*/
public RrdManager(File store, RrdDbPool pool) throws IOException {
this.store = store;
this.pool = pool;
if(!stores.containsKey(store.getName())) {
stores.put(store.getName(), this);
} else {
logger.log(Level.WARNING, "rrdmanager.existingstore", store.getName());
}
InputStream is = this.getClass().getClassLoader().getResourceAsStream("typeoverrides.config");
try {
overrides.load(is);
} catch (Exception e) {
logger.log(Level.WARNING, "rrdmanager.unabletoloadoverride");
}
}
Properties overrides = new Properties();
/** The logger. */
final static private Logger logger = Logger.getLogger("RESOURCEMGR", "resources");
/**
* Returns a file name that is unique for the ObjectName.
*
* @param name
* @return fileName
* @throws IOException
*/
public String getFileName(ObjectName name) throws IOException {
return new File(store, "x" + digest(name.getCanonicalName()) + ".rrd").getCanonicalPath();
}
/** The key. */
private static byte[] key = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x04, 0x03, 0x02, 0x01 };
/**
* @param buffer
* @return
*/
private static String digest(String buffer) {
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(buffer.getBytes());
return new String(encodeHex(md5.digest(key)));
} catch (NoSuchAlgorithmException e) {
}
return null;
}
/**
* Used to build output as Hex
*/
private static final char[] DIGITS = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
};
public static char[] encodeHex(byte[] data) {
int l = data.length;
char[] out = new char[l << 1];
// two characters form the hex value.
for (int i = 0, j = 0; i < l; i++) {
out[j++] = DIGITS[(0xF0 & data[i]) >>> 4 ];
out[j++] = DIGITS[ 0x0F & data[i] ];
}
return out;
}
/** The accumulators. */
public Map<ObjectName, Accumulator> accumulators =
new HashMap<ObjectName, Accumulator>();
/**
* Create an accumulator for the Rrd annotation and the ObjectName.
*
* @param objectName
* @param info
* @return accumulator
* @throws Exception
*/
public Accumulator create(ObjectName objectName, MBeanInfo info) throws Exception {
if (accumulators.containsKey(objectName)) {
return accumulators.get(objectName);
} else {
if (exists(objectName)) {
Accumulator acc = new Accumulator(server, objectName, getFileName(objectName),
findDs(Class.forName(info.getClassName())), pool);
accumulators.put(objectName, acc);
return acc;
} else {
Accumulator acc = internalCreate(objectName, info);
accumulators.put(objectName, acc);
return acc;
}
}
}
private Accumulator internalCreate(ObjectName objectName, MBeanInfo info) throws ClassNotFoundException, RrdException, IOException {
Class<?> forName = Class.forName(info.getClassName());
if(!forName.isAnnotationPresent(Rrd.class)) {
forName = Class.forName(overrides.getProperty(objectName.getCanonicalName()));
logger.log(Level.INFO, "rrdmanager.createoverride", forName.getName());
}
Rrd rrd = forName.getAnnotation(Rrd.class);
// handle when it's not there :)
Archives archives = forName.getAnnotation(Archives.class);
RrdDef def = new RrdDef(getFileName(objectName));
def.setStep(rrd.step());
if(archives != null) {
for(Arc arc : archives.value()) {
def.addArchive(arc.consolidationFunction().toString(), arc.xff(), arc.steps(), arc.rows());
logger.log(Level.FINE, "rrdmanager.archiveadded", arc.consolidationFunction());
}
}
List<Ds> anonDs = findDs(forName);
for(Ds ds : anonDs) {
def.addDatasource(ds.name(), ds.type().toString(), ds.heartbeat(), ds.minValue(), ds.maxValue());
logger.log(Level.FINE, "rrdmanager.datasourceadded", new Object[]{ds.expr(), ds.name()});
}
/**
* Store
*/
RrdDb db = pool.requestRrdDb(def);
pool.release(db);
return new Accumulator(server, objectName, db.getPath(), anonDs, pool);
}
private List<Ds> findDs(Class<?> forName) {
List<Ds> anonDs = new ArrayList<Ds>();
Method[] methods = forName.getMethods();
for(Method m : methods) {
Ds ds = m.getAnnotation(Ds.class);
if(ds != null) {
anonDs.add(ds);
}
}
return anonDs;
}
/**
* @param objectName
* @return boolean Does the rrd exist or not.
* @throws IOException
*/
public boolean exists(ObjectName objectName) {
try {
return new File(getFileName(objectName)).exists();
} catch (IOException e) {
logger.log(Level.SEVERE, "rrdmanager.existsfailed");
return false;
}
}
}
/**
*
*/
package org.jivesoftware.messenger.plugin.monitor;
import java.lang.management.ThreadMXBean;
import org.jrobin.annotations.Arc;
import org.jrobin.annotations.Archives;
import org.jrobin.annotations.ConsolidationFunction;
import org.jrobin.annotations.Ds;
import org.jrobin.annotations.Rrd;
/**
* @author Noah Campbell
* @version 1.0
*/
@Rrd
@Archives({@Arc(consolidationFunction=ConsolidationFunction.AVERAGE, xff=0.5, steps=6, rows=700)})
public interface ThreadMXBeanSupportInfo {
@Ds(name="cputime", expr="CurrentThreadCpuTime" )
long getCurrentThreadCpuTime();
}
/**
*
*/
package org.jrobin.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Noah Campbell
* @version 1.0
*/
@Target(value=ElementType.TYPE)
@Retention(value=RetentionPolicy.RUNTIME)
public @interface Arc {
ConsolidationFunction consolidationFunction();
double xff();
int steps();
int rows();
}
/**
*
*/
package org.jrobin.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Noah Campbell
* @version 1.0
*/
@Target(value=ElementType.TYPE)
@Retention(value=RetentionPolicy.RUNTIME)
public @interface Archives {
Arc[] value();
}
/**
*
*/
package org.jrobin.annotations;
/**
* @author Noah Campbell
* @version 1.0
*/
public enum ConsolidationFunction {
AVERAGE,
MIN,
MAX,
LAST
}
/**
*
*/
package org.jrobin.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Noah Campbell
* @version 1.0
*/
@Retention(value=RetentionPolicy.RUNTIME)
@Target(value={ElementType.METHOD})
public @interface Ds {
String name();
SourceType type() default SourceType.COUNTER;
long heartbeat() default 600;
double minValue() default Double.MIN_VALUE;
double maxValue() default Double.MAX_VALUE;
String expr() default "";
}
/**
*
*/
package org.jrobin.annotations;
/**
* Marker interface for Rrd#override()
*
* @author Noah Campbell
* @version 1.0
*/
public interface None {
}
/**
*
*/
package org.jrobin.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Noah Campbell
* @version 1.0
*/
@Target(value=ElementType.TYPE)
@Retention(value=RetentionPolicy.RUNTIME)
public @interface Rrd {
long step() default 300;
Class override() default None.class;
}
/**
*
*/
package org.jrobin.annotations;
/**
* @author Noah Campbell
* @version 1.0
*/
public enum SourceType {
/**
* COUNTER
*/
COUNTER,
/**
* GAUGE
*/
GAUGE,
/**
* The DERIVE.
*/
DERIVE,
/**
* The ABSOLUTE.
*/
ABSOLUTE;
}
<?xml version='1.0' encoding='ISO-8859-1'?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<!-- Servlets -->
<servlet>
<servlet-name>GraphServlet</servlet-name>
<servlet-class>org.jivesoftware.messenger.plugin.monitor.GraphServlet</servlet-class>
</servlet>
<!-- Servlet mappings -->
<servlet-mapping>
<servlet-name>GraphServlet</servlet-name>
<url-pattern>/monitor/*</url-pattern>
</servlet-mapping>
</web-app>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment