diff --git a/instrumentation/tomcat-jmx/src/main/java/com/nr/agent/instrumentation/tomcat/TomcatUtils.java b/instrumentation/tomcat-jmx/src/main/java/com/nr/agent/instrumentation/tomcat/TomcatUtils.java index 83a304819c..c239f584d4 100644 --- a/instrumentation/tomcat-jmx/src/main/java/com/nr/agent/instrumentation/tomcat/TomcatUtils.java +++ b/instrumentation/tomcat-jmx/src/main/java/com/nr/agent/instrumentation/tomcat/TomcatUtils.java @@ -7,22 +7,50 @@ package com.nr.agent.instrumentation.tomcat; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.logging.Level; - import com.newrelic.agent.bridge.AgentBridge; import com.newrelic.api.agent.NewRelic; +import javax.management.MBeanServer; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; +import java.lang.management.ManagementFactory; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; + public class TomcatUtils { private static final String JMX_PREFIX = "Catalina"; + private static final String JMX_EMBEDDED_PREFIX = "Tomcat"; + private static final String JMX_EMBEDDED_DATASOURCE_PREFIX = "org.apache.tomcat.jdbc.pool.jmx"; + private static final AtomicBoolean addedJmx = new AtomicBoolean(false); public static void addJmx() { if (System.getProperty("com.sun.aas.installRoot") == null) { if (!addedJmx.getAndSet(true)) { - AgentBridge.jmxApi.addJmxMBeanGroup(JMX_PREFIX); - NewRelic.getAgent().getLogger().log(Level.FINER, "Added JMX for Tomcat"); + MBeanServer server = ManagementFactory.getPlatformMBeanServer(); + //The ObjectName is different if embedded tomcat is used. Checking that this object has been registered + //will let us know that we are in an embedded tomcat. The Set returned by queryNames will return size 0 if the ObjectName is not + //found + try { + if (server.queryNames(new ObjectName("Tomcat:type=Server"), null).size() == 1) { + AgentBridge.jmxApi.addJmxMBeanGroup(JMX_EMBEDDED_PREFIX); + NewRelic.getAgent().getLogger().log(Level.FINER, "Added JMX for Tomcat"); + + } + if(server.queryNames(new ObjectName("org.apache.tomcat.jdbc.pool.jmx:name=dataSourceMBean,type=ConnectionPool"), null) + .size() == 1){ + AgentBridge.jmxApi.addJmxMBeanGroup(JMX_EMBEDDED_DATASOURCE_PREFIX); + NewRelic.getAgent().getLogger().log(Level.FINER, "Added JMX for Tomcat dataSourceMbean ConnectionPool"); + + } else { + // It is safe to assume we are in a non embedded Tomcat (Catalina) which uses Catalina for the ObjectName, no need to query. + AgentBridge.jmxApi.addJmxMBeanGroup(JMX_PREFIX); + NewRelic.getAgent().getLogger().log(Level.FINER, "Added JMX for Catalina"); + } + } catch (MalformedObjectNameException e) { + NewRelic.getAgent().getLogger().log(Level.FINEST, e, e.getMessage()); + } } } } diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/jmx/JmxApiImpl.java b/newrelic-agent/src/main/java/com/newrelic/agent/jmx/JmxApiImpl.java index f0d43f720f..12f26e274e 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/jmx/JmxApiImpl.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/jmx/JmxApiImpl.java @@ -10,6 +10,8 @@ import com.newrelic.agent.Agent; import com.newrelic.agent.bridge.JmxApi; import com.newrelic.agent.jmx.metrics.JmxFrameworkValues; +import com.newrelic.agent.jmx.values.EmbeddedTomcatDataSourceJmxValues; +import com.newrelic.agent.jmx.values.EmbeddedTomcatJmxValues; import com.newrelic.agent.jmx.values.GlassfishJmxValues; import com.newrelic.agent.jmx.values.Jboss7UpJmxValues; import com.newrelic.agent.jmx.values.JettyJmxMetrics; @@ -71,6 +73,10 @@ private JmxFrameworkValues getJmxFrameworkValues(String prefixName) { return new WebsphereLibertyJmxValues(); case TomcatJmxValues.PREFIX: return new TomcatJmxValues(); + case EmbeddedTomcatJmxValues.PREFIX: + return new EmbeddedTomcatJmxValues(); + case EmbeddedTomcatDataSourceJmxValues.PREFIX: + return new EmbeddedTomcatDataSourceJmxValues(); case JettyJmxMetrics.PREFIX: return new JettyJmxMetrics(); case Jboss7UpJmxValues.PREFIX: diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/jmx/values/EmbeddedTomcatDataSourceJmxValues.java b/newrelic-agent/src/main/java/com/newrelic/agent/jmx/values/EmbeddedTomcatDataSourceJmxValues.java new file mode 100644 index 0000000000..c8bbe27317 --- /dev/null +++ b/newrelic-agent/src/main/java/com/newrelic/agent/jmx/values/EmbeddedTomcatDataSourceJmxValues.java @@ -0,0 +1,65 @@ +/* + * + * * Copyright 2022 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.newrelic.agent.jmx.values; + +import com.newrelic.agent.MetricNames; +import com.newrelic.agent.jmx.metrics.BaseJmxValue; +import com.newrelic.agent.jmx.metrics.DataSourceJmxMetricGenerator; +import com.newrelic.agent.jmx.metrics.JmxFrameworkValues; +import com.newrelic.agent.jmx.metrics.JmxMetric; + +import java.util.ArrayList; +import java.util.List; + +public class EmbeddedTomcatDataSourceJmxValues extends JmxFrameworkValues { + + /** + * The Mbean group namespaces are different between standalone Tomcat and Embedded Tomcat + * Standalone uses the prefix (aka type) Catalina for queries whereas embedded Tomcat uses the prefix (aka type) Tomcat. + * Our TomcatJmxValues instrumentation only queries MBeans with the Catalina prefix thus queries initiated by embedded Tomcat + * do not provide metrics. Additionally, datasource metrics + * were broken out into type: org.apache.tomcat.pool.jmx for embedded tomcat, hence this Class. See (@EmbeddedTomcatJmxValues) + */ + + public static final String PREFIX = "org.apache.tomcat.jdbc.pool.jmx"; + + private static final int METRIC_COUNT = 1; + + private static final JmxMetric CONNECTIONS_ACTIVE = DataSourceJmxMetricGenerator.CONNECTIONS_ACTIVE.createMetric("NumActive"); + private static final JmxMetric CONNECTIONS_IDLE = DataSourceJmxMetricGenerator.CONNECTIONS_IDLE.createMetric("NumIdle"); + private static final JmxMetric CONNECTIONS_MAX = DataSourceJmxMetricGenerator.CONNECTIONS_MAX.createMetric("MaxActive"); + private static final JmxMetric CONNECTIONS_CREATED = DataSourceJmxMetricGenerator.CONNECTIONS_CREATED.createMetric("CreatedCount"); + + private final List metrics = new ArrayList<>(METRIC_COUNT); + + public EmbeddedTomcatDataSourceJmxValues() { + createMetrics("*"); + } + + public EmbeddedTomcatDataSourceJmxValues(String name) { + createMetrics(name); + + } + + private void createMetrics(String name) { + + metrics.add(new BaseJmxValue("org.apache.tomcat.jdbc.pool.jmx:name=*,type=ConnectionPool", MetricNames.JMX_DATASOURCES + "{name}/", + new JmxMetric[] { CONNECTIONS_ACTIVE, CONNECTIONS_IDLE, CONNECTIONS_MAX, CONNECTIONS_CREATED })); + } + + @Override + public List getFrameworkMetrics() { + return metrics; + } + + @Override + public String getPrefix() { + return PREFIX; + } + +} \ No newline at end of file diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/jmx/values/EmbeddedTomcatJmxValues.java b/newrelic-agent/src/main/java/com/newrelic/agent/jmx/values/EmbeddedTomcatJmxValues.java new file mode 100644 index 0000000000..62183eb5b8 --- /dev/null +++ b/newrelic-agent/src/main/java/com/newrelic/agent/jmx/values/EmbeddedTomcatJmxValues.java @@ -0,0 +1,84 @@ +/* + * + * * Copyright 2022 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.newrelic.agent.jmx.values; + +import com.newrelic.agent.MetricNames; +import com.newrelic.agent.jmx.JmxType; +import com.newrelic.agent.jmx.metrics.BaseJmxValue; +import com.newrelic.agent.jmx.metrics.JmxAction; +import com.newrelic.agent.jmx.metrics.JmxFrameworkValues; +import com.newrelic.agent.jmx.metrics.JmxMetric; +import com.newrelic.agent.jmx.metrics.ServerJmxMetricGenerator; + +import java.util.ArrayList; +import java.util.List; + +public class EmbeddedTomcatJmxValues extends JmxFrameworkValues { + + /** + * The Mbean group namespaces are different between standalone Tomcat and Embedded Tomcat + * Standalone uses the prefix (aka type) Catalina for queries whereas embedded Tomcat uses the prefix (aka type) Tomcat. + * Our TomcatJmxValues instrumentation only queries MBeans with the Catalina prefix thus queries initiated by embedded Tomcat + * do not provide metrics. Additionally, datasource metrics + * were broken out into type: org.apache.tomcat.pool.jmx for embedded tomcat. See (@EmbeddedTomcatDataSourceJmxValues) + */ + + public static final String PREFIX = "Tomcat"; + + private static final int METRIC_COUNT = 2; + + // SESSION METRICS (Manager) + private static final JmxMetric ACTIVE_SESSIONS = ServerJmxMetricGenerator.SESSION_ACTIVE_COUNT.createMetric("activeSessions"); + private static final JmxMetric EXPIRED_SESSIONS = ServerJmxMetricGenerator.SESSION_EXPIRED_COUNT.createMetric("expiredSessions"); + private static final JmxMetric REJECTED_SESSIONS = ServerJmxMetricGenerator.SESSION_REJECTED_COUNT.createMetric("rejectedSessions"); + private static final JmxMetric SESSION_ALIVE_TIME = ServerJmxMetricGenerator.SESSION_AVG_ALIVE_TIME.createMetric("sessionAverageAliveTime"); + + // THREAD POOL METRICS + private static final JmxMetric CURRENT_MAX_COUNT = ServerJmxMetricGenerator.MAX_THREAD_POOL_COUNT.createMetric("maxThreads"); + private static final JmxMetric CURRENT_ACTIVE_COUNT = ServerJmxMetricGenerator.ACTIVE_THREAD_POOL_COUNT.createMetric("currentThreadsBusy"); + private static final JmxMetric CURRENT_IDLE_COUNT = JmxMetric.create(new String[] { "currentThreadCount", + "currentThreadsBusy" }, MetricNames.JMX_THREAD_POOL_IDLE, JmxAction.SUBTRACT_ALL_FROM_FIRST, JmxType.SIMPLE); + + private final List metrics = new ArrayList<>(METRIC_COUNT); + + public EmbeddedTomcatJmxValues() { + createMetrics("*"); + } + + public EmbeddedTomcatJmxValues(String name) { + createMetrics(name); + + } + + private void createMetrics(String name) { + /* + * Only used by 7.0+. The manager bean provides information about sessions. sessionCounter is the total number of + * sessions created by this manager. ActiveSessions is the number of active sessions at this moment. + * expiredSesions is the number of sessions that have expired. RejectedSessions is the number of sessions + * rejected due to maxActive being reached. SessionAverageAliveTime is the average time an expired session had + * been alive. + */ + metrics.add(new BaseJmxValue(name + ":type=Manager,context=*,host=*,*", MetricNames.JMX_SESSION + "{context}/", + new JmxMetric[] { ACTIVE_SESSIONS, EXPIRED_SESSIONS, REJECTED_SESSIONS, SESSION_ALIVE_TIME })); + + metrics.add(new BaseJmxValue(name + ":type=ThreadPool,name=*", MetricNames.JMX_THREAD_POOL + "{name}/", + new JmxMetric[] { CURRENT_ACTIVE_COUNT, CURRENT_IDLE_COUNT, CURRENT_MAX_COUNT })); + + } + + @Override + public List getFrameworkMetrics() { + return metrics; + } + + @Override + public String getPrefix() { + return PREFIX; + } + +} \ No newline at end of file diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/jmx/values/TomcatJmxValues.java b/newrelic-agent/src/main/java/com/newrelic/agent/jmx/values/TomcatJmxValues.java index 788144a91c..c6395e9ec8 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/jmx/values/TomcatJmxValues.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/jmx/values/TomcatJmxValues.java @@ -27,7 +27,7 @@ public class TomcatJmxValues extends JmxFrameworkValues { */ public static final String PREFIX = "Catalina"; - private static final int METRIC_COUNT = 3; + private static final int METRIC_COUNT = 5; // SESSION METRICS private static final JmxMetric ACTIVE_SESSIONS = ServerJmxMetricGenerator.SESSION_ACTIVE_COUNT.createMetric("activeSessions");