diff --git a/docs/_docs/monitoring-metrics/new-metrics.adoc b/docs/_docs/monitoring-metrics/new-metrics.adoc index b055dc4069544..c160fdad76c44 100644 --- a/docs/_docs/monitoring-metrics/new-metrics.adoc +++ b/docs/_docs/monitoring-metrics/new-metrics.adoc @@ -25,6 +25,7 @@ Register name: `sys` [cols="2,1,3",opts="header"] |=== |Name | Type | Description +|AssertionErrorCount | long | Number of AssertionErrors thrown in this JVM. |CpuLoad | double | CPU load. |CurrentThreadCpuTime | long | CPU time of the thread that reads the metric, in nanoseconds. |CurrentThreadUserTime | long | User-mode CPU time of the thread that reads the metric, in nanoseconds. diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/metric/GridMetricManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/metric/GridMetricManager.java index dd7d7759d3b4e..48f2af677c542 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/metric/GridMetricManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/metric/GridMetricManager.java @@ -28,10 +28,13 @@ import java.util.Collection; import java.util.Iterator; import java.util.List; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Consumer; import java.util.function.Supplier; +import jdk.jfr.consumer.RecordedClass; +import jdk.jfr.consumer.RecordingStream; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteException; import org.apache.ignite.internal.GridKernalContext; @@ -45,6 +48,7 @@ import org.apache.ignite.internal.processors.metric.impl.AtomicLongMetric; import org.apache.ignite.internal.processors.metric.impl.DoubleMetricImpl; import org.apache.ignite.internal.processors.metric.impl.HitRateMetric; +import org.apache.ignite.internal.processors.metric.impl.LongAdderMetric; import org.apache.ignite.internal.processors.metric.impl.MaxValueMetric; import org.apache.ignite.internal.processors.metric.impl.MetricUtils; import org.apache.ignite.internal.processors.timeout.GridTimeoutProcessor; @@ -142,6 +146,9 @@ public class GridMetricManager extends GridManagerAdapter imp /** Custom metrics registry name. */ public static final String CUSTOM_METRICS = "custom"; + /** Metric name for the count of AssertionErrors thrown in the JVM. */ + public static final String ASSERTION_ERROR_CNT = "AssertionErrorCount"; + /** JVM interface to memory consumption info */ private static final MemoryMXBean mem = ManagementFactory.getMemoryMXBean(); @@ -202,6 +209,12 @@ public class GridMetricManager extends GridManagerAdapter imp /** */ private final IgniteMetrics customMetrics; + /** AssertionError counter. */ + private final LongAdderMetric assertionErrorCnt; + + /** JFR stream for capturing thrown Errors. */ + private volatile RecordingStream jfrStream; + /** * @param ctx Kernal context. */ @@ -247,6 +260,8 @@ public GridMetricManager(GridKernalContext ctx) { "CPU time of the thread that reads the metric, in nanoseconds."); sysreg.register("CurrentThreadUserTime", threads::getCurrentThreadUserTime, "User-mode CPU time of the thread that reads the metric, in nanoseconds."); + assertionErrorCnt = sysreg.longAdderMetric(ASSERTION_ERROR_CNT, + "Number of AssertionErrors thrown in this JVM."); MetricRegistryImpl pmeReg = registry(PME_METRICS); @@ -273,6 +288,8 @@ public GridMetricManager(GridKernalContext ctx) { startSpi(); + startJfrErrorStream(); + // In case standalone kernal start. if (ctx.internalSubscriptionProcessor() == null) return; @@ -323,6 +340,35 @@ public GridMetricManager(GridKernalContext ctx) { // Stop discovery worker and metrics updater. U.closeQuiet(metricsUpdateTask); + U.closeQuiet(jfrStream); + jfrStream = null; + } + + /** + * Starts a JFR RecordingStream to capture thrown Error events and increment the + * {@link #assertionErrorCnt} metric when an AssertionError is detected. + */ + private void startJfrErrorStream() { + try { + RecordingStream stream = new RecordingStream(); + stream.enable("jdk.JavaErrorThrow"); + stream.onEvent("jdk.JavaErrorThrow", event -> { + try { + RecordedClass cls = event.getClass("thrownClass"); + Objects.requireNonNull(cls, "Java error occurs. Unable to get thrown class"); + if ("java.lang.AssertionError".equals(cls.getName())) + assertionErrorCnt.increment(); + } + catch (RuntimeException cause) { + log.warning("Failed to monitor Java error event.", cause); + } + }); + stream.startAsync(); + jfrStream = stream; + } + catch (RuntimeException cause) { + U.warn(log, "Failed to start JFR error stream: ", cause); + } } /** diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/metric/GridMetricManagerAssertionMetricTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/metric/GridMetricManagerAssertionMetricTest.java new file mode 100644 index 0000000000000..93da51c86fbe3 --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/metric/GridMetricManagerAssertionMetricTest.java @@ -0,0 +1,98 @@ +/* + * 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.ignite.internal.processors.metric; + +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.metric.MetricRegistry; +import org.apache.ignite.spi.metric.LongMetric; +import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; +import org.junit.Test; + +import static org.apache.ignite.internal.processors.metric.GridMetricManager.ASSERTION_ERROR_CNT; +import static org.apache.ignite.internal.processors.metric.GridMetricManager.SYS_METRICS; +import static org.apache.ignite.testframework.GridTestUtils.waitForCondition; + +/** + * Tests the ASSERTION_ERROR_CNT metric in the sys metrics registry. + */ +public class GridMetricManagerAssertionMetricTest extends GridCommonAbstractTest { + + /** + * Verifies that the ASSERTION_ERROR_CNT counter increments + * when an AssertionError is thrown. + * + * @throws Exception If fails. + */ + @Test + public void testAssertionErrorMetric() throws Exception { + try { + IgniteEx grid = startGrid(0); + + MetricRegistry sysReg = grid.context().metric().registry(SYS_METRICS); + + assertNotNull(sysReg); + + LongMetric ctr = sysReg.findMetric(ASSERTION_ERROR_CNT); + + assertNotNull("Metric " + ASSERTION_ERROR_CNT + " not found", ctr); + + long init = ctr.value(); + + // Throw an AssertionError. + try { + throw new AssertionError("test assertion"); + } + catch (AssertionError e) { + // Expected. + } + + // Metric should increment (JFR events are delivered asynchronously). + assertTrue( + "Metric should increment after AssertionError", + waitForCondition(() -> ctr.value() >= init + 1, getTestTimeout())); + } + finally { + stopAllGrids(); + } + } + + /** + * Verifies that the ASSERTION_ERROR_CNT metric exposes proper name and description. + * + * @throws Exception If fails. + */ + @Test + public void testMetricMetadata() throws Exception { + try { + IgniteEx grid = startGrid(0); + + MetricRegistry sysReg = grid.context().metric().registry(SYS_METRICS); + + LongMetric ctr = sysReg.findMetric(ASSERTION_ERROR_CNT); + + assertNotNull(ctr); + + assertEquals(SYS_METRICS + "." + ASSERTION_ERROR_CNT, ctr.name()); + + assertEquals("Number of AssertionErrors thrown in this JVM.", ctr.description()); + } + finally { + stopAllGrids(); + } + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteKernalSelfTestSuite.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteKernalSelfTestSuite.java index dd6dd06042cb6..77d78e4e2e4fb 100644 --- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteKernalSelfTestSuite.java +++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteKernalSelfTestSuite.java @@ -60,6 +60,7 @@ import org.apache.ignite.internal.processors.cluster.ClusterStateChangeOnNodeJoinTest; import org.apache.ignite.internal.processors.cluster.GridAddressResolverSelfTest; import org.apache.ignite.internal.processors.cluster.GridUpdateNotifierSelfTest; +import org.apache.ignite.internal.processors.metric.GridMetricManagerAssertionMetricTest; import org.apache.ignite.internal.processors.port.GridPortProcessorSelfTest; import org.apache.ignite.internal.util.GridMutableLongTest; import org.apache.ignite.internal.util.GridStartupWithUndefinedIgniteHomeSelfTest; @@ -113,6 +114,7 @@ IgniteConnectionConcurrentReserveAndRemoveTest.class, LongJVMPauseDetectorTest.class, ClusterMetricsSelfTest.class, + GridMetricManagerAssertionMetricTest.class, DeploymentRequestOfUnknownClassProcessingTest.class, ThreadNameValidationTest.class, NodeWithFilterRestartTest.class,