diff --git a/pom.xml b/pom.xml index c46c48cd..13380094 100755 --- a/pom.xml +++ b/pom.xml @@ -164,7 +164,13 @@ sentinel-adapter ${project.version} - + + + com.alibaba.csp + sentinel-metric-exporter + ${project.version} + + com.alibaba fastjson diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleManager.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleManager.java index 10d34c1d..0fb7f4fe 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleManager.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleManager.java @@ -15,6 +15,16 @@ */ package com.alibaba.csp.sentinel.slots.block.flow; +import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; +import com.alibaba.csp.sentinel.config.SentinelConfig; +import com.alibaba.csp.sentinel.log.RecordLog; +import com.alibaba.csp.sentinel.node.metric.MetricTimerListener; +import com.alibaba.csp.sentinel.property.DynamicSentinelProperty; +import com.alibaba.csp.sentinel.property.PropertyListener; +import com.alibaba.csp.sentinel.property.SentinelProperty; +import com.alibaba.csp.sentinel.util.AssertUtil; +import com.alibaba.csp.sentinel.util.StringUtil; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -23,16 +33,6 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; -import com.alibaba.csp.sentinel.config.SentinelConfig; -import com.alibaba.csp.sentinel.log.RecordLog; -import com.alibaba.csp.sentinel.util.AssertUtil; -import com.alibaba.csp.sentinel.util.StringUtil; -import com.alibaba.csp.sentinel.node.metric.MetricTimerListener; -import com.alibaba.csp.sentinel.property.DynamicSentinelProperty; -import com.alibaba.csp.sentinel.property.PropertyListener; -import com.alibaba.csp.sentinel.property.SentinelProperty; - /** *

* One resources can have multiple rules. And these rules take effects in the following order: @@ -53,6 +53,7 @@ public class FlowRuleManager { private static final FlowPropertyListener LISTENER = new FlowPropertyListener(); private static SentinelProperty> currentProperty = new DynamicSentinelProperty>(); + /** the corePool size of SCHEDULER must be set at 1, so the two task ({@link #startMetricTimerListener()} can run orderly by the SCHEDULER **/ @SuppressWarnings("PMD.ThreadPoolCreationRule") private static final ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(1, new NamedThreadFactory("sentinel-metrics-record-task", true)); diff --git a/sentinel-extension/pom.xml b/sentinel-extension/pom.xml index 8e336793..ad3c8d07 100755 --- a/sentinel-extension/pom.xml +++ b/sentinel-extension/pom.xml @@ -24,6 +24,7 @@ sentinel-datasource-etcd sentinel-datasource-eureka sentinel-annotation-cdi-interceptor + sentinel-metric-exporter diff --git a/sentinel-extension/sentinel-metric-exporter/pom.xml b/sentinel-extension/sentinel-metric-exporter/pom.xml new file mode 100644 index 00000000..e733c094 --- /dev/null +++ b/sentinel-extension/sentinel-metric-exporter/pom.xml @@ -0,0 +1,27 @@ + + + + sentinel-extension + com.alibaba.csp + 1.8.2 + + 4.0.0 + + sentinel-metric-exporter + jar + + + + com.alibaba.csp + sentinel-core + + + + junit + junit + test + + + \ No newline at end of file diff --git a/sentinel-extension/sentinel-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/MetricExporterInit.java b/sentinel-extension/sentinel-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/MetricExporterInit.java new file mode 100644 index 00000000..a8d30cfa --- /dev/null +++ b/sentinel-extension/sentinel-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/MetricExporterInit.java @@ -0,0 +1,76 @@ +/* + * Copyright 1999-2021 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.csp.sentinel.metric; + +import com.alibaba.csp.sentinel.init.InitFunc; +import com.alibaba.csp.sentinel.log.RecordLog; +import com.alibaba.csp.sentinel.metric.collector.MetricCollector; +import com.alibaba.csp.sentinel.metric.exporter.MetricExporter; +import com.alibaba.csp.sentinel.metric.exporter.jmx.JMXMetricExporter; + +import java.util.ArrayList; +import java.util.List; + +/** + * The{@link MetricExporterInit} work on load Metric exporters. + * + * @author chenglu + * @date 2021-07-01 19:58 + * @since 1.8.3 + */ +public class MetricExporterInit implements InitFunc { + + /** + * the list of metric exporters. + */ + private static List metricExporters = new ArrayList<>(); + + /* + load metric exporters. + */ + static { + // now we use this simple way to load MetricExporter. + metricExporters.add(new JMXMetricExporter()); + } + + @Override + public void init() throws Exception { + RecordLog.info("[MetricExporterInit] MetricExporter start init."); + // start the metric exporters. + for (MetricExporter metricExporter : metricExporters) { + try { + metricExporter.start(); + } catch (Exception e) { + RecordLog.warn("[MetricExporterInit] MetricExporterInit start the metricExport[{}] failed, will ignore it.", + metricExporter.getClass().getName(), e); + } + } + + // add shutdown hook. + Runtime.getRuntime().addShutdownHook(new Thread( + () -> metricExporters.forEach(metricExporter -> { + try { + metricExporter.shutdown(); + } catch (Exception e) { + RecordLog.warn("[MetricExporterInit] MetricExporterInit shutdown the metricExport[{}] failed, will ignore it.", + metricExporter.getClass().getName(), e); + } + }) + )); + } +} diff --git a/sentinel-extension/sentinel-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/collector/MetricCollector.java b/sentinel-extension/sentinel-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/collector/MetricCollector.java new file mode 100644 index 00000000..ee940e60 --- /dev/null +++ b/sentinel-extension/sentinel-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/collector/MetricCollector.java @@ -0,0 +1,94 @@ +/* + * Copyright 1999-2021 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.csp.sentinel.metric.collector; + +import com.alibaba.csp.sentinel.Constants; +import com.alibaba.csp.sentinel.node.ClusterNode; +import com.alibaba.csp.sentinel.node.metric.MetricNode; +import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; +import com.alibaba.csp.sentinel.util.TimeUtil; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * The {@link MetricCollector} work on collecting metrics in {@link MetricNode}. + * + * @author chenglu + * @date 2021-07-01 20:01 + * @since 1.8.3 + */ +public class MetricCollector { + + /** + * collect the metrics in {@link MetricNode}. + * + * @return the metric grouped by resource name. + */ + public Map collectMetric() { + final long currentTime = TimeUtil.currentTimeMillis(); + final long maxTime = currentTime - currentTime % 1000; + final long minTime = maxTime - 1000; + Map metricNodeMap = new HashMap<>(); + for (Map.Entry e : ClusterBuilderSlot.getClusterNodeMap().entrySet()) { + ClusterNode node = e.getValue(); + List metrics = getLastMetrics(node, minTime, maxTime); + aggregate(metricNodeMap, metrics, node); + } + aggregate(metricNodeMap, getLastMetrics(Constants.ENTRY_NODE, minTime, maxTime), Constants.ENTRY_NODE); + return metricNodeMap; + } + + + /** + * Get the last second {@link MetricNode} of {@link ClusterNode} + * @param node {@link ClusterNode} + * @param minTime the min time. + * @param maxTime the max time. + * @return the list of {@link MetricNode} + */ + private List getLastMetrics(ClusterNode node, long minTime, long maxTime) { + return node.rawMetricsInMin(time -> time >= minTime && time < maxTime); + } + + + /** + * aggregate the metrics, the metrics under the same resource will left the lasted value + * @param metricNodeMap metrics map + * @param metrics metrics info group by timestamp + * @param node the node + */ + private void aggregate(Map metricNodeMap, List metrics, ClusterNode node) { + if (metrics == null || metrics.size() == 0) { + return; + } + for (MetricNode metricNode : metrics) { + String resource = node.getName(); + metricNode.setResource(resource); + metricNode.setClassification(node.getResourceType()); + MetricNode existMetricNode = metricNodeMap.get(resource); + // always keep the MetricNode is the last + if (existMetricNode != null && existMetricNode.getTimestamp() > metricNode.getTimestamp()) { + continue; + } + metricNodeMap.put(resource, metricNode); + } + } +} diff --git a/sentinel-extension/sentinel-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/exporter/MetricExporter.java b/sentinel-extension/sentinel-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/exporter/MetricExporter.java new file mode 100644 index 00000000..f08eb1d5 --- /dev/null +++ b/sentinel-extension/sentinel-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/exporter/MetricExporter.java @@ -0,0 +1,49 @@ +/* + * Copyright 1999-2021 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.csp.sentinel.metric.exporter; + +/** + * {@link MetricExporter} work on export metric to target monitor. + * you can implement your export ways by this class. + * + * @author chenglu + * @date 2021-07-01 21:16 + */ +public interface MetricExporter { + + /** + * start the {@link MetricExporter}. + * + * @throws Exception start exception. + */ + void start() throws Exception; + + /** + * export the data to target monitor by the implement. + * + * @throws Exception export exception. + */ + void export() throws Exception; + + /** + * shutdown the {@link MetricExporter}. + * + * @throws Exception shutdown exception. + */ + void shutdown() throws Exception; +} diff --git a/sentinel-extension/sentinel-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/exporter/jmx/JMXMetricExporter.java b/sentinel-extension/sentinel-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/exporter/jmx/JMXMetricExporter.java new file mode 100644 index 00000000..cab682c5 --- /dev/null +++ b/sentinel-extension/sentinel-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/exporter/jmx/JMXMetricExporter.java @@ -0,0 +1,88 @@ +/* + * Copyright 1999-2021 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.csp.sentinel.metric.exporter.jmx; + +import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; +import com.alibaba.csp.sentinel.log.RecordLog; +import com.alibaba.csp.sentinel.metric.collector.MetricCollector; +import com.alibaba.csp.sentinel.metric.exporter.MetricExporter; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * The JMX metric exporter, mainly for write metric datas to JMX bean. It implement {@link MetricExporter}, provide method + * start, export and shutdown. The mainly design for the jmx is refresh the JMX bean data scheduled. + * {@link JMXExportTask} work on export data to {@link MetricBean}. + * + * @author chenglu + * @date 2021-07-01 20:02 + * @since 1.8.3 + */ +public class JMXMetricExporter implements MetricExporter { + + /** + * schedule executor. + */ + private final ScheduledExecutorService jmxExporterSchedule; + + /** + * JMX metric writer, write metric datas to {@link MetricBean}. + */ + private final MetricBeanWriter metricBeanWriter = new MetricBeanWriter(); + + /** + * global metrics collector. + */ + private final MetricCollector metricCollector = new MetricCollector(); + + public JMXMetricExporter() { + jmxExporterSchedule = new ScheduledThreadPoolExecutor(1, new NamedThreadFactory("sentinel-metrics-jmx-exporter-task", true)); + } + + @Override + public void start() throws Exception { + jmxExporterSchedule.scheduleAtFixedRate(new JMXExportTask(), 1, 1, TimeUnit.SECONDS); + } + + @Override + public void export() throws Exception { + metricBeanWriter.write(metricCollector.collectMetric()); + } + + @Override + public void shutdown() throws Exception { + jmxExporterSchedule.shutdown(); + } + + /** + * JMXExportTask mainly work on execute the JMX metric export. + */ + class JMXExportTask implements Runnable { + + @Override + public void run() { + try { + export(); + } catch (Exception e) { + RecordLog.warn("[JMX Metric Exporter] export to JMX MetricBean failed.", e); + } + } + } +} diff --git a/sentinel-extension/sentinel-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/exporter/jmx/MBeanRegistry.java b/sentinel-extension/sentinel-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/exporter/jmx/MBeanRegistry.java new file mode 100644 index 00000000..a0168d1a --- /dev/null +++ b/sentinel-extension/sentinel-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/exporter/jmx/MBeanRegistry.java @@ -0,0 +1,121 @@ +/* + * Copyright 1999-2021 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.csp.sentinel.metric.exporter.jmx; + +import com.alibaba.csp.sentinel.log.RecordLog; + +import javax.management.JMException; +import javax.management.MBeanServer; +import javax.management.MBeanServerFactory; +import javax.management.ObjectName; +import java.lang.management.ManagementFactory; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * This class provides a unified interface for registering/unregistering of Metric MBean. + * + * @author chenglu + * @date 2021-07-01 20:02 + * @since 1.8.3 + */ +public class MBeanRegistry { + + private static volatile MBeanRegistry instance = new MBeanRegistry(); + + private Map mapBean2Name= new ConcurrentHashMap<>(8); + + private Map mapName2Bean = new ConcurrentHashMap<>(8); + + private MBeanServer mBeanServer; + + public static MBeanRegistry getInstance() { + return instance; + } + + public MBeanRegistry() { + try { + mBeanServer = ManagementFactory.getPlatformMBeanServer(); + } catch (Error e) { + // Account for running within IKVM and create a new MBeanServer + // if the PlatformMBeanServer does not exist. + mBeanServer = MBeanServerFactory.createMBeanServer(); + } + } + + /** + * Registers a new MBean with the platform MBean server. + * @param bean the bean being registered + * @param mBeanName the mBeanName + * @throws JMException MBean can not register exception + */ + public void register(MetricBean bean, String mBeanName) throws JMException { + assert bean != null; + try { + ObjectName oname = new ObjectName(mBeanName); + mBeanServer.registerMBean(bean, oname); + mapBean2Name.put(bean, mBeanName); + mapName2Bean.put(mBeanName, bean); + } catch (JMException e) { + RecordLog.warn("[MBeanRegistry] Failed to register MBean " + mBeanName, e); + throw e; + } + } + + /** + * unregister the MetricBean + * @param bean MetricBean + */ + public void unRegister(MetricBean bean) { + assert bean != null; + String beanName = mapBean2Name.get(bean); + if (beanName == null) { + return; + } + try { + ObjectName objectName = new ObjectName(beanName); + mBeanServer.unregisterMBean(objectName); + mapBean2Name.remove(bean); + mapName2Bean.remove(beanName); + } catch (JMException e) { + RecordLog.warn("[MBeanRegistry] UnRegister the MetricBean fail", e); + } + } + + /** + * find the MBean by BeanName + * @param mBeanName mBeanName + * @return MetricMBean + */ + public MetricBean findMBean(String mBeanName) { + if (mBeanName == null) { + return null; + } + return mapName2Bean.get(mBeanName); + } + + /** + * list all MBeans which is registered into MBeanRegistry + * @return MetricBeans + */ + public List listAllMBeans() { + return new ArrayList<>(mapName2Bean.values()); + } +} diff --git a/sentinel-extension/sentinel-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/exporter/jmx/MetricBean.java b/sentinel-extension/sentinel-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/exporter/jmx/MetricBean.java new file mode 100644 index 00000000..2c169c72 --- /dev/null +++ b/sentinel-extension/sentinel-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/exporter/jmx/MetricBean.java @@ -0,0 +1,154 @@ +/* + * Copyright 1999-2021 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.csp.sentinel.metric.exporter.jmx; + +import com.alibaba.csp.sentinel.node.metric.MetricNode; + +/** + * the MetricBean for JMX expose. + * + * @author chenglu + * @date 2021-07-01 20:02 + * @since 1.8.3 + */ +public class MetricBean implements MetricMXBean { + + private String resource; + + /** + * Resource classification (e.g. SQL or RPC) + */ + private int classification; + + private long timestamp; + + private long passQps; + + private long blockQps; + + private long successQps; + + private long exceptionQps; + + private long rt; + + private long occupiedPassQps; + + private int concurrency; + + private long version; + + @Override + public String getResource() { + return resource; + } + + @Override + public int getClassification() { + return classification; + } + + @Override + public long getTimestamp() { + return timestamp; + } + + @Override + public long getPassQps() { + return passQps; + } + + @Override + public long getBlockQps() { + return blockQps; + } + + @Override + public long getSuccessQps() { + return successQps; + } + + @Override + public long getExceptionQps() { + return exceptionQps; + } + + @Override + public long getRt() { + return rt; + } + + @Override + public long getOccupiedPassQps() { + return occupiedPassQps; + } + + @Override + public int getConcurrency() { + return concurrency; + } + + @Override + public long getVersion() { + return version; + } + + /** + * set the version to current Mbean. + * + * @param version current version. + */ + public void setVersion(long version) { + this.version = version; + } + + /** + * reset the MBean value to the initialized value. + */ + public void reset() { + this.blockQps = 0; + this.passQps = 0; + this.timestamp = System.currentTimeMillis(); + this.exceptionQps = 0; + this.occupiedPassQps = 0; + this.successQps = 0; + this.rt = 0; + this.concurrency = 0; + } + + /** + * set the MetricBean's value which from MetricNode. + * + * @param metricNode metric Node for write file + */ + public void setValueFromNode(MetricNode metricNode) { + if (metricNode == null) { + return; + } + this.successQps = metricNode.getSuccessQps(); + this.blockQps = metricNode.getBlockQps(); + this.passQps = metricNode.getPassQps(); + this.occupiedPassQps = metricNode.getOccupiedPassQps(); + this.exceptionQps = metricNode.getExceptionQps(); + this.timestamp = metricNode.getTimestamp(); + this.classification = metricNode.getClassification(); + this.concurrency = metricNode.getConcurrency(); + this.resource = metricNode.getResource(); + this.rt = metricNode.getRt(); + } +} diff --git a/sentinel-extension/sentinel-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/exporter/jmx/MetricBeanWriter.java b/sentinel-extension/sentinel-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/exporter/jmx/MetricBeanWriter.java new file mode 100644 index 00000000..974d0692 --- /dev/null +++ b/sentinel-extension/sentinel-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/exporter/jmx/MetricBeanWriter.java @@ -0,0 +1,97 @@ +/* + * Copyright 1999-2021 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.csp.sentinel.metric.exporter.jmx; + +import com.alibaba.csp.sentinel.config.SentinelConfig; +import com.alibaba.csp.sentinel.log.RecordLog; +import com.alibaba.csp.sentinel.node.metric.MetricNode; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * the metric bean writer, it provides {@link MetricBeanWriter#write} method for register the + * MetricBean in {@link MBeanRegistry} or update the value of MetricBean + * + * @author chenglu + * @date 2021-07-01 20:02 + * @since 1.8.3 + */ +public class MetricBeanWriter { + + private final MBeanRegistry mBeanRegistry = MBeanRegistry.getInstance(); + + private static final String DEFAULT_APP_NAME = "sentinel-application"; + + /** + * write the MetricNode value to MetricBean + * if the MetricBean is not registered into {@link MBeanRegistry}, + * it will be created and registered into {@link MBeanRegistry}. + * else it will update the value of MetricBean. + * Notes. if the MetricNode is null, then {@link MetricBean} will be reset. + * @param map metricNode value group by resource + * @throws Exception write failed exception + */ + public synchronized void write(Map map) throws Exception { + if (map == null || map.isEmpty()) { + List metricNodes = mBeanRegistry.listAllMBeans(); + if (metricNodes == null || metricNodes.isEmpty()) { + return; + } + for (MetricBean metricNode : metricNodes) { + metricNode.reset(); + } + return; + } + String appName = SentinelConfig.getAppName(); + if (appName == null) { + appName = DEFAULT_APP_NAME; + } + long version = System.currentTimeMillis(); + // set or update the new value + for (MetricNode metricNode : map.values()) { + final String mBeanName = "Sentinel:type=" + appName + ",name=\"" + metricNode.getResource() + +"\",classification=\"" + metricNode.getClassification() +"\""; + MetricBean metricBean = mBeanRegistry.findMBean(mBeanName); + if (metricBean != null) { + metricBean.setValueFromNode(metricNode); + metricBean.setVersion(version); + } else { + metricBean = new MetricBean(); + metricBean.setValueFromNode(metricNode); + metricBean.setVersion(version); + mBeanRegistry.register(metricBean, mBeanName); + RecordLog.info("[MetricBeanWriter] Registering with JMX as Metric MBean [{}]", mBeanName); + } + } + // reset the old value + List metricBeans = mBeanRegistry.listAllMBeans(); + if (metricBeans == null || metricBeans.isEmpty()) { + return; + } + for (MetricBean metricBean : metricBeans) { + if (!Objects.equals(metricBean.getVersion(), version)) { + metricBean.reset(); + mBeanRegistry.unRegister(metricBean); + } + } + } +} diff --git a/sentinel-extension/sentinel-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/exporter/jmx/MetricMXBean.java b/sentinel-extension/sentinel-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/exporter/jmx/MetricMXBean.java new file mode 100644 index 00000000..dc6697ea --- /dev/null +++ b/sentinel-extension/sentinel-metric-exporter/src/main/java/com/alibaba/csp/sentinel/metric/exporter/jmx/MetricMXBean.java @@ -0,0 +1,50 @@ +/* + * Copyright 1999-2021 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.csp.sentinel.metric.exporter.jmx; + +/** + * the Metric JMX Bean interface. + * + * @author chenglu + * @date 2021-07-01 20:02 + * @since 1.8.3 + */ +public interface MetricMXBean { + + long getTimestamp(); + + long getOccupiedPassQps(); + + long getSuccessQps(); + + long getPassQps(); + + long getExceptionQps(); + + long getBlockQps(); + + long getRt(); + + String getResource(); + + int getClassification(); + + int getConcurrency(); + + long getVersion(); +} diff --git a/sentinel-extension/sentinel-metric-exporter/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc b/sentinel-extension/sentinel-metric-exporter/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc new file mode 100644 index 00000000..9774e1f0 --- /dev/null +++ b/sentinel-extension/sentinel-metric-exporter/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc @@ -0,0 +1 @@ +com.alibaba.csp.sentinel.metric.MetricExporterInit \ No newline at end of file diff --git a/sentinel-extension/sentinel-metric-exporter/src/test/java/com/alibaba/cps/sentinel/metric/exporter/MBeanRegistryTest.java b/sentinel-extension/sentinel-metric-exporter/src/test/java/com/alibaba/cps/sentinel/metric/exporter/MBeanRegistryTest.java new file mode 100644 index 00000000..d07e0726 --- /dev/null +++ b/sentinel-extension/sentinel-metric-exporter/src/test/java/com/alibaba/cps/sentinel/metric/exporter/MBeanRegistryTest.java @@ -0,0 +1,34 @@ +package com.alibaba.cps.sentinel.metric.exporter; + +import com.alibaba.csp.sentinel.metric.exporter.jmx.MBeanRegistry; +import com.alibaba.csp.sentinel.metric.exporter.jmx.MetricBean; +import org.junit.Assert; +import org.junit.Test; + +import javax.management.JMException; + +/** + * {@link com.alibaba.csp.sentinel.metric.exporter.jmx.MBeanRegistry} unit test. + * + * @author chenglu + * @date 2021-07-01 23:07 + */ +public class MBeanRegistryTest { + + @Test + public void testMBeanRegistry() throws JMException { + MBeanRegistry mBeanRegistry = MBeanRegistry.getInstance(); + + MetricBean metricBean = new MetricBean(); + String mBeanName = "Sentinel:type=test,name=test"; + mBeanRegistry.register(metricBean, mBeanName); + + MetricBean mb1 = mBeanRegistry.findMBean(mBeanName); + Assert.assertEquals(mb1, metricBean); + + mBeanRegistry.unRegister(metricBean); + + MetricBean mb2 = mBeanRegistry.findMBean(mBeanName); + Assert.assertNull(mb2); + } +}