diff --git a/collector/src/main/java/com/usthe/collector/collect/AbstractCollect.java b/collector/src/main/java/com/usthe/collector/collect/AbstractCollect.java index 70620de35c5..30d7aa7d360 100644 --- a/collector/src/main/java/com/usthe/collector/collect/AbstractCollect.java +++ b/collector/src/main/java/com/usthe/collector/collect/AbstractCollect.java @@ -21,6 +21,8 @@ import com.usthe.common.entity.job.Metrics; import com.usthe.common.entity.message.CollectRep; +import java.io.IOException; + /** * Specific indicator group collection implementation abstract class * 具体的指标组采集实现抽象类 @@ -40,5 +42,5 @@ public abstract class AbstractCollect { * @param metrics Metric group configuration 指标组配置 * return response builder */ - public abstract void collect(CollectRep.MetricsData.Builder builder, long appId, String app, Metrics metrics); + public abstract void collect(CollectRep.MetricsData.Builder builder, long appId, String app, Metrics metrics) throws IOException; } diff --git a/collector/src/main/java/com/usthe/collector/collect/common/cache/JmxConnect.java b/collector/src/main/java/com/usthe/collector/collect/common/cache/JmxConnect.java new file mode 100644 index 00000000000..92f2cfe0683 --- /dev/null +++ b/collector/src/main/java/com/usthe/collector/collect/common/cache/JmxConnect.java @@ -0,0 +1,43 @@ +package com.usthe.collector.collect.common.cache; + +import lombok.extern.slf4j.Slf4j; + +import javax.management.remote.JMXConnector; + +/** + * jmx链接销毁管理 + * + * @Author huacheng + * @Date 2022/7/3 14:58 + **/ +@Slf4j +public class JmxConnect implements CacheCloseable { + + private JMXConnector connection; + + public JmxConnect(JMXConnector connection) { + this.connection = connection; + } + + + @Override + public void close() { + try { + if (connection != null) { + connection.close(); + } + } catch (Exception e) { + log.error("close redis connect error: {}", e.getMessage()); + } + } + + @Override + protected void finalize() throws Throwable { + close(); + super.finalize(); + } + + public JMXConnector getConnection() { + return connection; + } +} diff --git a/collector/src/main/java/com/usthe/collector/collect/jmx/JmxCollectImpl.java b/collector/src/main/java/com/usthe/collector/collect/jmx/JmxCollectImpl.java new file mode 100644 index 00000000000..01a0d2980d3 --- /dev/null +++ b/collector/src/main/java/com/usthe/collector/collect/jmx/JmxCollectImpl.java @@ -0,0 +1,133 @@ +package com.usthe.collector.collect.jmx; + +import com.usthe.collector.collect.AbstractCollect; +import com.usthe.collector.collect.common.cache.CacheIdentifier; +import com.usthe.collector.collect.common.cache.CommonCache; +import com.usthe.collector.collect.common.cache.JmxConnect; +import com.usthe.common.entity.job.Metrics; +import com.usthe.common.entity.job.protocol.JmxProtocol; +import com.usthe.common.entity.message.CollectRep; +import com.usthe.common.util.CommonConstants; +import lombok.extern.slf4j.Slf4j; + +import javax.management.*; +import javax.management.openmbean.CompositeDataSupport; +import javax.management.remote.*; +import java.io.IOException; +import java.util.*; +import java.util.stream.Collectors; + +/** + * jmx 协议采集实现 - jmx + * jmx protocol acquisition implementation + * + * @author huacheng + * @date 2022/6/21 15:09 + */ +@Slf4j +public class JmxCollectImpl extends AbstractCollect { + + private static final String JMX_URL_PREFIX = "service:jmx:rmi:///jndi/rmi://"; + + private static final String JMX_URL_SUFFIX = "/jmxrmi"; + + private JmxCollectImpl() { + } + + public static JmxCollectImpl getInstance() { + return Singleton.INSTANCE; + } + + @Override + public void collect(CollectRep.MetricsData.Builder builder, long appId, String app, Metrics metrics) throws IOException { + //Get the address and port from the jmx carried by Metrics + //从Metrics携带的jmx中拿到地址 端口 + + try { + JmxProtocol jmxProtocol = metrics.getJmx(); + + //Create a jndi remote connection + //创建一个jndi远程连接 + JMXConnector conn = getConnectSession(jmxProtocol); + + MBeanServerConnection jmxBean = conn.getMBeanServerConnection(); + ObjectName objectName = new ObjectName(jmxProtocol.getObjectName()); + CollectRep.ValueRow.Builder valueRowBuilder = CollectRep.ValueRow.newBuilder(); + + //Whether there is a second level of nesting + //是否存在二级嵌套 + if (jmxProtocol.getAttributeName() != null) { + String attributeName = jmxProtocol.getAttributeName(); + Object attribute = jmxBean.getAttribute(objectName, attributeName); + CompositeDataSupport support = null; + if (attribute instanceof CompositeDataSupport) { + support = (CompositeDataSupport) attribute; + } + CompositeDataSupport finalSupport = support; + metrics.getFields().forEach(field -> { + assert finalSupport != null; + Object fieldValue = finalSupport.get(field.getField()); + if (fieldValue != null) { + valueRowBuilder.addColumns(fieldValue.toString()); + } else { + valueRowBuilder.addColumns(CommonConstants.NULL_VALUE); + } + }); + } else { + String[] attributes = new String[metrics.getAliasFields().size()]; + attributes = metrics.getAliasFields().toArray(attributes); + AttributeList attributeList = jmxBean.getAttributes(objectName, attributes); + Map map = attributeList.asList().stream().collect(Collectors.toMap(Attribute::getName, Attribute::getValue)); + for (String attribute : attributes) { + Object attributeValue = map.get(attribute); + valueRowBuilder.addColumns(attributeValue != null ? attributeValue.toString() : CommonConstants.NULL_VALUE); + } + } + builder.addValues(valueRowBuilder.build()); + } catch (IOException exception) { + log.error("JMX IOException :{}", exception.getMessage()); + builder.setCode(CollectRep.Code.UN_CONNECTABLE); + builder.setMsg(exception.getMessage()); + } catch (Exception e) { + builder.setCode(CollectRep.Code.FAIL); + builder.setMsg(e.getMessage()); + log.error("JMX Error :{}", e.getMessage()); + } + } + + private JMXConnector getConnectSession(JmxProtocol jmxProtocol) throws IOException { + CacheIdentifier identifier = CacheIdentifier.builder().ip(jmxProtocol.getHost()) + .port(jmxProtocol.getPort()).username(jmxProtocol.getUsername()) + .password(jmxProtocol.getPassword()).build(); + Optional cacheOption = CommonCache.getInstance().getCache(identifier, true); + JMXConnector conn = null; + if (cacheOption.isPresent()) { + JmxConnect jmxConnect = (JmxConnect) cacheOption.get(); + conn = jmxConnect.getConnection(); + try { + conn.getMBeanServerConnection(); + } catch (Exception e) { + conn = null; + CommonCache.getInstance().removeCache(identifier); + } + } + if (conn != null) { + return conn; + } + String url; + if (jmxProtocol.getUrl() != null) { + url = jmxProtocol.getUrl(); + } else { + url = JMX_URL_PREFIX + jmxProtocol.getHost() + ":" + jmxProtocol.getPort() + JMX_URL_SUFFIX; + } + JMXServiceURL jmxServiceUrl = new JMXServiceURL(url); + conn = JMXConnectorFactory.connect(jmxServiceUrl); + CommonCache.getInstance().addCache(identifier, new JmxConnect(conn)); + return conn; + } + + private static class Singleton { + private static final JmxCollectImpl INSTANCE = new JmxCollectImpl(); + } + +} diff --git a/collector/src/main/java/com/usthe/collector/dispatch/DispatchConstants.java b/collector/src/main/java/com/usthe/collector/dispatch/DispatchConstants.java index 4443cc217c3..8e17036a70b 100644 --- a/collector/src/main/java/com/usthe/collector/dispatch/DispatchConstants.java +++ b/collector/src/main/java/com/usthe/collector/dispatch/DispatchConstants.java @@ -50,6 +50,10 @@ public interface DispatchConstants { * protocol redis */ String PROTOCOL_REDIS = "redis"; + /** + * protocol jmx + */ + String PROTOCOL_JMX = "jmx"; /** * protocol snmp */ diff --git a/collector/src/main/java/com/usthe/collector/dispatch/MetricsCollect.java b/collector/src/main/java/com/usthe/collector/dispatch/MetricsCollect.java index 78ef622c00e..6a9be05a3a9 100644 --- a/collector/src/main/java/com/usthe/collector/dispatch/MetricsCollect.java +++ b/collector/src/main/java/com/usthe/collector/dispatch/MetricsCollect.java @@ -23,6 +23,7 @@ import com.usthe.collector.collect.database.JdbcCommonCollect; import com.usthe.collector.collect.http.HttpCollectImpl; import com.usthe.collector.collect.icmp.IcmpCollectImpl; +import com.usthe.collector.collect.jmx.JmxCollectImpl; import com.usthe.collector.collect.redis.RedisSingleCollectImpl; import com.usthe.collector.collect.snmp.SnmpCollectImpl; import com.usthe.collector.collect.ssh.SshCollectImpl; @@ -159,6 +160,9 @@ public void run() { case DispatchConstants.PROTOCOL_SNMP: abstractCollect = SnmpCollectImpl.getInstance(); break; + case DispatchConstants.PROTOCOL_JMX: + abstractCollect = JmxCollectImpl.getInstance(); + break; default: break; } diff --git a/common/src/main/java/com/usthe/common/entity/job/Metrics.java b/common/src/main/java/com/usthe/common/entity/job/Metrics.java index 9a6a27ed7e0..6aa8a5e61a8 100644 --- a/common/src/main/java/com/usthe/common/entity/job/Metrics.java +++ b/common/src/main/java/com/usthe/common/entity/job/Metrics.java @@ -17,14 +17,7 @@ package com.usthe.common.entity.job; -import com.usthe.common.entity.job.protocol.HttpProtocol; -import com.usthe.common.entity.job.protocol.IcmpProtocol; -import com.usthe.common.entity.job.protocol.JdbcProtocol; -import com.usthe.common.entity.job.protocol.RedisProtocol; -import com.usthe.common.entity.job.protocol.SnmpProtocol; -import com.usthe.common.entity.job.protocol.SshProtocol; -import com.usthe.common.entity.job.protocol.TcpUdpProtocol; -import com.usthe.common.entity.job.protocol.TelnetProtocol; +import com.usthe.common.entity.job.protocol.*; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -118,6 +111,13 @@ public class Metrics { * 使用公共的redis协议的监控配置信息 */ private RedisProtocol redis; + + /** + * Get monitoring configuration information using public JMX protocol + * 使用公共JMX协议获取监控配置信息 + */ + private JmxProtocol jmx; + /** * Monitoring configuration information using the public snmp protocol * 使用公共的snmp协议的监控配置信息 diff --git a/common/src/main/java/com/usthe/common/entity/job/protocol/JmxProtocol.java b/common/src/main/java/com/usthe/common/entity/job/protocol/JmxProtocol.java new file mode 100644 index 00000000000..a05856f5522 --- /dev/null +++ b/common/src/main/java/com/usthe/common/entity/job/protocol/JmxProtocol.java @@ -0,0 +1,64 @@ +package com.usthe.common.entity.job.protocol; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Jmx协议 Jmx protocol + * + * @ClassName JmxProtocol + * @Description + * @Author huacheng + * @Date 2022/6/21 15:45 + * @Version 1.0 + **/ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class JmxProtocol { + /** + * JMX host ip or domain name + * JMX主机ip或域名 + */ + private String host; + + /** + * The port number + * 端口号 + */ + private String port; + + /** + * Jmx username (optional) + * Jmx用户名(可选) + */ + private String username; + + /** + * Jmx password (optional) + * Jmx密码(可选) + */ + private String password; + + /** + * jmx protocol custom collection indicator address + * jmx协议自定义收集指标地址 + */ + private String url; + + /** + * The name of the type where the outer layer of the jmx indicator is located + * jmx指标外层所在类型名称 + */ + private String objectName; + + /** + * Jmx indicator name + * jmx指标名称 + */ + private String attributeName; + +} diff --git a/manager/src/main/resources/define/app/app-tomcat.yml b/manager/src/main/resources/define/app/app-tomcat.yml new file mode 100644 index 00000000000..5fcd7720a72 --- /dev/null +++ b/manager/src/main/resources/define/app/app-tomcat.yml @@ -0,0 +1,279 @@ +category: mid +app: tomcat +name: + zh-CN: Tomcat应用服务器 + en-US: Tomcat Server +# 参数映射map. type是参数类型: 0-number数字, 1-string明文字符串, 2-secret加密字符串 +# 强制固定必须参数 - host +configmap: + - key: host + type: 1 + - key: port + type: 0 + - key: username + type: 1 + - key: password + type: 2 + - key: url + type: 1 +# 指标组列表 +metrics: + - name: server + priority: 0 + fields: + - field: serverInfo + type: 1 + - field: serverNumber + type: 1 + - field: serverBuilt + type: 1 + - field: port + type: 1 + - field: address + type: 1 + protocol: jmx + jmx: + # 主机host: ipv4 ipv6 域名 + host: ^_^host^_^ + # 端口 + port: ^_^port^_^ + username: ^_^username^_^ + password: ^_^password^_^ + #外层类型 + objectName: Catalina:type=Server + url: ^_^url^_^ + + - name: eden_space + # 指标组调度优先级(0-127)越小优先级越高,优先级低的指标组会等优先级高的指标组采集完成后才会被调度,相同优先级的指标组会并行调度采集 + # 优先级为0的指标组为可用性指标组,即它会被首先调度,采集成功才会继续调度其它指标组,采集失败则中断调度 + priority: 1 + # 指标组中的具体监控指标 + fields: + # 指标信息 包括 field名称 type字段类型:0-number数字,1-string字符串 instance是否为实例主键 unit:指标单位 + - field: committed + type: 0 + - field: init + type: 0 + - field: max + type: 0 + - field: used + type: 0 + protocol: jmx + jmx: + # 主机host: ipv4 ipv6 域名 + host: ^_^host^_^ + # 端口 + port: ^_^port^_^ + username: ^_^username^_^ + password: ^_^password^_^ + #外层类型 + objectName: java.lang:type=MemoryPool,name=Eden Space + attributeName: Usage + url: ^_^url^_^ + + - name: meta_space + priority: 2 + # 指标组中的具体监控指标 + fields: + # 指标信息 包括 field名称 type字段类型:0-number数字,1-string字符串 instance是否为实例主键 unit:指标单位 + - field: committed + type: 0 + - field: init + type: 0 + - field: max + type: 0 + - field: used + type: 0 + protocol: jmx + jmx: + # 主机host: ipv4 ipv6 域名 + host: ^_^host^_^ + # 端口 + port: ^_^port^_^ + username: ^_^username^_^ + password: ^_^password^_^ + #外层类型 + objectName: java.lang:type=MemoryPool,name=Metaspace + attributeName: Usage + url: ^_^url^_^ + + - name: survivor_space + priority: 3 + # 指标组中的具体监控指标 + fields: + # 指标信息 包括 field名称 type字段类型:0-number数字,1-string字符串 instance是否为实例主键 unit:指标单位 + - field: committed + type: 0 + - field: init + type: 0 + - field: max + type: 0 + - field: used + type: 0 + protocol: jmx + jmx: + # 主机host: ipv4 ipv6 域名 + host: ^_^host^_^ + # 端口 + port: ^_^port^_^ + username: ^_^username^_^ + password: ^_^password^_^ + #外层类型 + objectName: java.lang:type=MemoryPool,name=Survivor Space + attributeName: Usage + url: ^_^url^_^ + + - name: tenured_gen + priority: 4 + # 指标组中的具体监控指标 + fields: + # 指标信息 包括 field名称 type字段类型:0-number数字,1-string字符串 instance是否为实例主键 unit:指标单位 + - field: committed + type: 0 + - field: init + type: 0 + - field: max + type: 0 + - field: used + type: 0 + protocol: jmx + jmx: + # 主机host: ipv4 ipv6 域名 + host: ^_^host^_^ + # 端口 + port: ^_^port^_^ + username: ^_^username^_^ + password: ^_^password^_^ + #外层类型 + objectName: java.lang:type=MemoryPool,name=Tenured Gen + attributeName: Usage + url: ^_^url^_^ + + - name: code_cache + priority: 5 + # 指标组中的具体监控指标 + fields: + # 指标信息 包括 field名称 type字段类型:0-number数字,1-string字符串 instance是否为实例主键 unit:指标单位 + - field: committed + type: 0 + - field: init + type: 0 + - field: max + type: 0 + - field: used + type: 0 + protocol: jmx + jmx: + # 主机host: ipv4 ipv6 域名 + host: ^_^host^_^ + # 端口 + port: ^_^port^_^ + username: ^_^username^_^ + password: ^_^password^_^ + #外层类型 + objectName: java.lang:type=MemoryPool,name=Code Cache + attributeName: Usage + url: ^_^url^_^ + + - name: class_loading + priority: 6 + # 指标组中的具体监控指标 + fields: + # 指标信息 包括 field名称 type字段类型:0-number数字,1-string字符串 instance是否为实例主键 unit:指标单位 + - field: LoadedClassCount + type: 0 + - field: TotalLoadedClassCount + type: 0 + - field: UnloadedClassCount + type: 0 + protocol: jmx + jmx: + # 主机host: ipv4 ipv6 域名 + host: ^_^host^_^ + # 端口 + port: ^_^port^_^ + username: ^_^username^_^ + password: ^_^password^_^ + #外层类型 + objectName: java.lang:type=ClassLoading + url: ^_^url^_^ + + - name: thread + priority: 7 + # 指标组中的具体监控指标 + fields: + # 指标信息 包括 field名称 type字段类型:0-number数字,1-string字符串 instance是否为实例主键 unit:指标单位 + - field: TotalStartedThreadCount + type: 0 + - field: ThreadCount + type: 0 + - field: PeakThreadCount + type: 0 + - field: DaemonThreadCount + type: 0 + - field: CurrentThreadUserTime + type: 0 + unit: s + - field: CurrentThreadCpuTime + type: 0 + unit: s + protocol: jmx + jmx: + # 主机host: ipv4 ipv6 域名 + host: ^_^host^_^ + # 端口 + port: ^_^port^_^ + username: ^_^username^_^ + password: ^_^password^_^ + #外层类型 + objectName: java.lang:type=Threading + url: ^_^url^_^ + + - name: application + priority: 8 + fields: + - field: acceptCount + type: 0 + - field: acceptorThreadCount + type: 0 + - field: acceptorThreadPriority + type : 0 + - field: connectionTimeout + type: 1 + unit: ms + - field: currentThreadCount + type: 0 + - field: currentThreadsBusy + type: 0 + - field: keepAliveCount + type: 0 + - field: keepAliveTimeout + type: 0 + unit: ms + - field: maxConnections + type: 0 + - field: maxKeepAliveRequests + type: 0 + - field: maxThreads + type: 0 + - field: minSpareThreads + type: 0 + - field: name + type: 1 + - field: pollerThreadCount + type: 0 + - field: pollerThreadPriority + type: 0 + - field: running + type: 1 + protocol: jmx + jmx: + # 主机host: ipv4 ipv6 域名 + host: ^_^host^_^ + # 端口 + port: ^_^port^_^ + username: ^_^username^_^ + password: ^_^password^_^ + #外层类型 + objectName: Catalina:type=ThreadPool,name="http-nio-8080" + url: ^_^url^_^ diff --git a/manager/src/main/resources/define/param/param-tomcat.yml b/manager/src/main/resources/define/param/param-tomcat.yml new file mode 100644 index 00000000000..7aae7c6f721 --- /dev/null +++ b/manager/src/main/resources/define/param/param-tomcat.yml @@ -0,0 +1,45 @@ +app: tomcat +param: + # field-字段名称标识符 + - field: host + # name-参数字段显示名称 + name: + zh-CN: 主机Host + en-US: Host + # type-字段类型,样式(大部分映射input标签type属性) + type: host + # 是否是必输项 true-必填 false-可选 + required: true + - field: port + name: + zh-CN: 端口 + en-US: Port + type: number + # 当type为number时,用range表示范围 + range: '[0,65535]' + required: true + # 端口默认值 + defaultValue: 9999 + - field: url + name: + zh-CN: JMX URL + en-US: JMX URL + type: text + required: false + hide: true + placeholder: 'service:jmx:rmi:///jndi/rmi://host:port/jmxrmi' + - field: username + name: + zh-CN: 用户名 + en-US: Username + type: text + limit: 20 + required: false + hide: true + - field: password + name: + zh-CN: 密码 + en-US: Password + type: password + required: false + hide: true \ No newline at end of file