You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by ja...@apache.org on 2026/09/29 05:59:51 UTC
(iotdb) branch master updated: Fix metrics lost after metric service restart and conflicting metric series (#18748)
This is an automated email from the ASF dual-hosted git repository.
JackieTien97 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/master by this push:
new b2cc8b52126 Fix metrics lost after metric service restart and conflicting metric series (#18748)
b2cc8b52126 is described below
commit b2cc8b521262aec0993d3d223b59502904031c82
Author: Jackie Tien <ja...@gmail.com>
AuthorDate: Tue Sep 29 13:59:44 2026 +0800
Fix metrics lost after metric service restart and conflicting metric series (#18748)
---
.../iotdb/confignode/i18n/ManagerMessages.java | 8 -
.../iotdb/confignode/i18n/ManagerMessages.java | 8 -
.../PipeConfigNodeRemainingTimeMetrics.java | 20 +-
.../PipeTemporaryMetaInCoordinatorMetrics.java | 18 +-
.../metric/sink/PipeConfigRegionSinkMetrics.java | 17 +-
.../source/PipeConfigRegionSourceMetrics.java | 17 +-
.../metric/PipeConfigNodeMetricsRestartTest.java | 184 +++++++++
iotdb-core/consensus/pom.xml | 5 +
.../logdispatcher/LogDispatcherThreadMetrics.java | 104 +++--
.../consensus/pipe/IoTConsensusV2ServerImpl.java | 5 +
.../pipe/metric/IoTConsensusV2ServerMetrics.java | 3 -
.../consensus/ratis/metrics/CounterProxy.java | 8 +-
.../ratis/metrics/IoTDBMetricRegistry.java | 34 +-
.../ratis/metrics/MetricRegistryManager.java | 14 +
.../consensus/ratis/metrics/RatisMetricSet.java | 8 +-
.../iotdb/consensus/ratis/metrics/TimerProxy.java | 7 +-
.../LogDispatcherThreadMetricsTest.java | 146 +++++++
.../metric/IoTConsensusV2ServerMetricsTest.java | 107 ++++++
.../ratis/metrics/RatisMetricSetTest.java | 134 +++++++
.../apache/iotdb/db/i18n/DataNodePipeMessages.java | 24 --
.../apache/iotdb/db/i18n/DataNodePipeMessages.java | 23 --
.../overview/PipeDataNodeSinglePipeMetrics.java | 20 +-
.../overview/PipeTsFileToTabletsMetrics.java | 16 +-
.../metric/processor/PipeProcessorMetrics.java | 19 +-
.../schema/PipeSchemaRegionListenerMetrics.java | 16 +-
.../metric/schema/PipeSchemaRegionSinkMetrics.java | 16 +-
.../schema/PipeSchemaRegionSourceMetrics.java | 16 +-
.../metric/sink/PipeDataRegionSinkMetrics.java | 11 +-
.../db/pipe/metric/source/PipeAssignerMetrics.java | 13 +-
.../metric/source/PipeDataRegionSourceMetrics.java | 20 +-
.../protocol/airgap/IoTDBDataRegionAirGapSink.java | 3 +-
.../thrift/async/IoTDBDataRegionAsyncSink.java | 3 +-
.../thrift/sync/IoTDBDataRegionSyncSink.java | 3 +-
.../handler/RPCServiceThriftHandlerMetrics.java | 37 +-
.../analyze/cache/partition/PartitionCache.java | 2 +-
.../schemaengine/metric/SchemaEngineMemMetric.java | 7 +-
.../schemaengine/metric/SchemaRegionMemMetric.java | 7 +-
.../rescon/MemSchemaEngineStatistics.java | 4 +
.../iotdb/db/service/metrics/CacheMetrics.java | 17 +
.../db/service/metrics/DataNodeMetricsHelper.java | 1 +
.../iotdb/db/service/metrics/WritingMetrics.java | 80 +++-
.../db/service/metrics/file/TsFileMetrics.java | 22 ++
.../dataregion/DataRegionMetrics.java | 49 ++-
.../memtable/TsFileProcessorInfoMetrics.java | 55 ++-
.../load/memory/LoadTsFileMemoryBlock.java | 20 +-
.../load/metrics/ActiveLoadingFilesMetricsSet.java | 6 +
.../ActiveLoadingFilesNumberMetricsSet.java | 16 +-
.../load/metrics/LoadTsFileMemMetricSet.java | 19 +-
...nsensusSubscriptionPrefetchingQueueMetrics.java | 12 +-
.../SubscriptionPrefetchingQueueMetrics.java | 55 ++-
.../db/pipe/metric/PipeMetricsRestartTest.java | 426 +++++++++++++++++++++
.../PipeSchemaRegionListenerMetricsTest.java | 116 ++++++
.../metric/SchemaMemMetricTableTest.java | 133 +++++++
.../metrics/DataNodeMetricsRestartTest.java | 290 ++++++++++++++
.../db/service/metrics/file/TsFileMetricsTest.java | 148 +++++++
.../dataregion/DatabaseMemMetricsTest.java | 144 +++++++
.../metric/SubscriptionMetricsRestartTest.java | 188 +++++++++
.../iotdb/metrics/metricsets/jvm/JvmGcMetrics.java | 86 ++---
.../metrics/metricsets/system/SystemMetrics.java | 13 +-
.../apache/iotdb/commons/i18n/PipeMessages.java | 2 -
.../apache/iotdb/commons/i18n/ServiceMessages.java | 2 +
.../apache/iotdb/commons/i18n/PipeMessages.java | 2 -
.../apache/iotdb/commons/i18n/ServiceMessages.java | 2 +
.../commons/concurrent/ThreadPoolMetrics.java | 74 ++--
.../WrappedScheduledExecutorService.java | 4 +-
.../WrappedSingleThreadExecutorService.java | 4 +-
.../WrappedSingleThreadScheduledExecutor.java | 4 +-
.../threadpool/WrappedThreadPoolExecutor.java | 4 +-
.../pipe/metric/PipeEventCommitMetrics.java | 20 +-
.../service/metric/JvmGcMonitorMetrics.java | 38 +-
.../commons/service/metric/MetricService.java | 13 +-
.../iotdb/commons/service/metric/enums/Metric.java | 3 +
.../commons/concurrent/ThreadPoolMetricsTest.java | 157 ++++++++
.../pipe/metric/PipeEventCommitMetricsTest.java | 99 +++++
.../service/metric/JvmGcMonitorMetricsTest.java | 72 ++++
.../service/metric/MetricServiceRestartTest.java | 218 +++++++++++
76 files changed, 3235 insertions(+), 486 deletions(-)
diff --git a/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ManagerMessages.java b/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ManagerMessages.java
index 6a36ac71302..48e1a390d18 100644
--- a/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ManagerMessages.java
+++ b/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ManagerMessages.java
@@ -204,14 +204,6 @@ public final class ManagerMessages {
"Failed to sync template {} extension info to DataNode {}";
public static final String FAILED_TO_SYNC_TOPIC_META_RESULT_STATUS =
"Failed to sync topic meta. Result status: {}.";
- public static final String FAILED_TO_UNBIND_FROM_PIPE_CONFIG_REGION_CONNECTOR_METRICS_CONNECTOR =
- "Failed to unbind from pipe config region connector metrics, connector map not empty";
- public static final String FAILED_TO_UNBIND_FROM_PIPE_CONFIG_REGION_EXTRACTOR_METRICS_EXTRACTOR =
- "Failed to unbind from pipe config region extractor metrics, extractor map not empty";
- public static final String FAILED_TO_UNBIND_FROM_PIPE_REMAINING_TIME_METRICS_REMAININGTIMEOPERATOR_MAP =
- "Failed to unbind from pipe remaining time metrics, RemainingTimeOperator map not empty";
- public static final String FAILED_TO_UNBIND_FROM_PIPE_TEMPORARY_META_METRICS_PIPETEMPORARYMETA_MAP =
- "Failed to unbind from pipe temporary meta metrics, PipeTemporaryMeta map not empty";
public static final String FAILED_TO_UPDATE_PIPE_PROCEDURE_TIMER_PIPEPROCEDURE_DOES_NOT_EXIST =
"Failed to update pipe procedure timer, PipeProcedure({}) does not exist";
public static final String FAILED_TO_UPDATE_THE_LAST_EXECUTION_TIME_OF_CQ_BECAUSE =
diff --git a/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ManagerMessages.java b/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ManagerMessages.java
index 3c35171dd06..a06ae420eac 100644
--- a/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ManagerMessages.java
+++ b/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ManagerMessages.java
@@ -202,14 +202,6 @@ public final class ManagerMessages {
"将模板 {} 的扩展信息同步到 DataNode {} 失败";
public static final String FAILED_TO_SYNC_TOPIC_META_RESULT_STATUS =
"同步 topic 元数据失败。结果状态:{}。";
- public static final String FAILED_TO_UNBIND_FROM_PIPE_CONFIG_REGION_CONNECTOR_METRICS_CONNECTOR =
- "从 pipe config region connector 指标解绑失败,connector map 不为空";
- public static final String FAILED_TO_UNBIND_FROM_PIPE_CONFIG_REGION_EXTRACTOR_METRICS_EXTRACTOR =
- "从 pipe config region extractor 指标解绑失败,extractor map 不为空";
- public static final String FAILED_TO_UNBIND_FROM_PIPE_REMAINING_TIME_METRICS_REMAININGTIMEOPERATOR_MAP =
- "从 pipe remaining time 指标解绑失败,RemainingTimeOperator map 不为空";
- public static final String FAILED_TO_UNBIND_FROM_PIPE_TEMPORARY_META_METRICS_PIPETEMPORARYMETA_MAP =
- "从 pipe temporary meta 指标解绑失败,PipeTemporaryMeta map 不为空";
public static final String FAILED_TO_UPDATE_PIPE_PROCEDURE_TIMER_PIPEPROCEDURE_DOES_NOT_EXIST =
"更新 pipe procedure timer 失败,PipeProcedure({}) 不存在";
public static final String FAILED_TO_UPDATE_THE_LAST_EXECUTION_TIME_OF_CQ_BECAUSE =
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/metric/overview/PipeConfigNodeRemainingTimeMetrics.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/metric/overview/PipeConfigNodeRemainingTimeMetrics.java
index 0e9c44f05d2..bd585a98a8c 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/metric/overview/PipeConfigNodeRemainingTimeMetrics.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/metric/overview/PipeConfigNodeRemainingTimeMetrics.java
@@ -51,7 +51,7 @@ public class PipeConfigNodeRemainingTimeMetrics implements IMetricSet {
//////////////////////////// bindTo & unbindFrom (metric framework) ////////////////////////////
@Override
- public void bindTo(final AbstractMetricService metricService) {
+ public synchronized void bindTo(final AbstractMetricService metricService) {
this.metricService = metricService;
ImmutableSet.copyOf(remainingTimeOperatorMap.keySet()).forEach(this::createMetrics);
}
@@ -74,13 +74,11 @@ public class PipeConfigNodeRemainingTimeMetrics implements IMetricSet {
}
@Override
- public void unbindFrom(final AbstractMetricService metricService) {
- ImmutableSet.copyOf(remainingTimeOperatorMap.keySet()).forEach(this::deregister);
- if (!remainingTimeOperatorMap.isEmpty()) {
- LOGGER.warn(
- ManagerMessages
- .FAILED_TO_UNBIND_FROM_PIPE_REMAINING_TIME_METRICS_REMAININGTIMEOPERATOR_MAP);
- }
+ public synchronized void unbindFrom(final AbstractMetricService metricService) {
+ // Keep the operators registered: they hold the states of the pipes and register only once, so
+ // a metric service restart must be able to bind them again.
+ // Synchronized with the (de)registrations, which may remove a registration being unbound.
+ ImmutableSet.copyOf(remainingTimeOperatorMap.keySet()).forEach(this::removeMetrics);
}
private void removeMetrics(final String pipeID) {
@@ -96,12 +94,11 @@ public class PipeConfigNodeRemainingTimeMetrics implements IMetricSet {
operator.getPipeName(),
Tag.CREATION_TIME.toString(),
String.valueOf(operator.getCreationTime()));
- remainingTimeOperatorMap.remove(pipeID);
}
//////////////////////////// register & deregister (pipe integration) ////////////////////////////
- public void register(final IoTDBConfigRegionSource extractor) {
+ public synchronized void register(final IoTDBConfigRegionSource extractor) {
// The metric is global thus the regionId is omitted
final String pipeID = extractor.getPipeName() + "_" + extractor.getCreationTime();
remainingTimeOperatorMap
@@ -132,7 +129,7 @@ public class PipeConfigNodeRemainingTimeMetrics implements IMetricSet {
remainingTimeOperatorMap.get(pipeID).freezeRate(true);
}
- public void deregister(final String pipeID) {
+ public synchronized void deregister(final String pipeID) {
if (!remainingTimeOperatorMap.containsKey(pipeID)) {
LOGGER.warn(
ManagerMessages
@@ -143,6 +140,7 @@ public class PipeConfigNodeRemainingTimeMetrics implements IMetricSet {
if (Objects.nonNull(metricService)) {
removeMetrics(pipeID);
}
+ remainingTimeOperatorMap.remove(pipeID);
}
public void markRegionCommit(final String pipeID, final boolean isDataRegion) {
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/metric/overview/PipeTemporaryMetaInCoordinatorMetrics.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/metric/overview/PipeTemporaryMetaInCoordinatorMetrics.java
index 19604e0d6c1..c4e3ca285e2 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/metric/overview/PipeTemporaryMetaInCoordinatorMetrics.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/metric/overview/PipeTemporaryMetaInCoordinatorMetrics.java
@@ -58,7 +58,7 @@ public class PipeTemporaryMetaInCoordinatorMetrics implements IMetricSet {
//////////////////////////// bindTo & unbindFrom (metric framework) ////////////////////////////
@Override
- public void bindTo(final AbstractMetricService metricService) {
+ public synchronized void bindTo(final AbstractMetricService metricService) {
this.metricService = metricService;
ImmutableSet.copyOf(pipeTemporaryMetaMap.keySet()).forEach(this::createMetrics);
}
@@ -91,12 +91,10 @@ public class PipeTemporaryMetaInCoordinatorMetrics implements IMetricSet {
}
@Override
- public void unbindFrom(final AbstractMetricService metricService) {
- ImmutableSet.copyOf(pipeTemporaryMetaMap.keySet()).forEach(this::deregister);
- if (!pipeTemporaryMetaMap.isEmpty()) {
- LOGGER.warn(
- ManagerMessages.FAILED_TO_UNBIND_FROM_PIPE_TEMPORARY_META_METRICS_PIPETEMPORARYMETA_MAP);
- }
+ public synchronized void unbindFrom(final AbstractMetricService metricService) {
+ // Keep the pipes registered so that a metric service restart can bind them again.
+ // Synchronized with the (de)registrations, which may remove a registration being unbound.
+ ImmutableSet.copyOf(pipeTemporaryMetaMap.keySet()).forEach(this::removeMetrics);
}
private void removeMetrics(final String pipeID) {
@@ -119,12 +117,11 @@ public class PipeTemporaryMetaInCoordinatorMetrics implements IMetricSet {
pipeNameAndCreationTime[0],
Tag.CREATION_TIME.toString(),
pipeNameAndCreationTime[1]);
- pipeTemporaryMetaMap.remove(pipeID);
}
//////////////////////////// register & deregister (pipe integration) ////////////////////////////
- public void register(final PipeMeta pipeMeta) {
+ public synchronized void register(final PipeMeta pipeMeta) {
final String taskID =
pipeMeta.getStaticMeta().getPipeName() + "_" + pipeMeta.getStaticMeta().getCreationTime();
pipeTemporaryMetaMap.putIfAbsent(
@@ -134,7 +131,7 @@ public class PipeTemporaryMetaInCoordinatorMetrics implements IMetricSet {
}
}
- public void deregister(final String pipeID) {
+ public synchronized void deregister(final String pipeID) {
if (!pipeTemporaryMetaMap.containsKey(pipeID)) {
LOGGER.warn(
ManagerMessages
@@ -145,6 +142,7 @@ public class PipeTemporaryMetaInCoordinatorMetrics implements IMetricSet {
if (Objects.nonNull(metricService)) {
removeMetrics(pipeID);
}
+ pipeTemporaryMetaMap.remove(pipeID);
}
public void handleTemporaryMetaChanges(final Iterable<PipeMeta> pipeMetaList) {
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/metric/sink/PipeConfigRegionSinkMetrics.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/metric/sink/PipeConfigRegionSinkMetrics.java
index 0ca45d4c364..3b36730cafc 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/metric/sink/PipeConfigRegionSinkMetrics.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/metric/sink/PipeConfigRegionSinkMetrics.java
@@ -50,7 +50,7 @@ public class PipeConfigRegionSinkMetrics implements IMetricSet {
//////////////////////////// bindTo & unbindFrom (metric framework) ////////////////////////////
@Override
- public void bindTo(final AbstractMetricService metricService) {
+ public synchronized void bindTo(final AbstractMetricService metricService) {
this.metricService = metricService;
ImmutableSet.copyOf(subtaskMap.keySet()).forEach(this::createMetrics);
}
@@ -74,12 +74,11 @@ public class PipeConfigRegionSinkMetrics implements IMetricSet {
}
@Override
- public void unbindFrom(final AbstractMetricService metricService) {
- ImmutableSet.copyOf(subtaskMap.keySet()).forEach(this::deregister);
- if (!subtaskMap.isEmpty()) {
- LOGGER.warn(
- ManagerMessages.FAILED_TO_UNBIND_FROM_PIPE_CONFIG_REGION_CONNECTOR_METRICS_CONNECTOR);
- }
+ public synchronized void unbindFrom(final AbstractMetricService metricService) {
+ // Keep the subtasks registered: they register only once, so a metric service restart
+ // must be able to bind them again.
+ // Synchronized with the (de)registrations, which may remove a registration being unbound.
+ ImmutableSet.copyOf(subtaskMap.keySet()).forEach(this::removeMetrics);
}
private void removeMetrics(final String taskID) {
@@ -101,7 +100,7 @@ public class PipeConfigRegionSinkMetrics implements IMetricSet {
//////////////////////////// register & deregister (pipe integration) ////////////////////////////
- public void register(final PipeConfigNodeSubtask pipeConfigNodeSubtask) {
+ public synchronized void register(final PipeConfigNodeSubtask pipeConfigNodeSubtask) {
final String taskID = pipeConfigNodeSubtask.getTaskID();
subtaskMap.putIfAbsent(taskID, pipeConfigNodeSubtask);
if (Objects.nonNull(metricService)) {
@@ -109,7 +108,7 @@ public class PipeConfigRegionSinkMetrics implements IMetricSet {
}
}
- public void deregister(final String taskID) {
+ public synchronized void deregister(final String taskID) {
if (!subtaskMap.containsKey(taskID)) {
LOGGER.warn(ManagerMessages.FAILED_TO_DEREGISTER_PIPE_CONFIG_REGION_CONNECTOR, taskID);
return;
diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/metric/source/PipeConfigRegionSourceMetrics.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/metric/source/PipeConfigRegionSourceMetrics.java
index 428b4b40263..44dcdf2c522 100644
--- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/metric/source/PipeConfigRegionSourceMetrics.java
+++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/metric/source/PipeConfigRegionSourceMetrics.java
@@ -47,7 +47,7 @@ public class PipeConfigRegionSourceMetrics implements IMetricSet {
//////////////////////////// bindTo & unbindFrom (metric framework) ////////////////////////////
@Override
- public void bindTo(final AbstractMetricService metricService) {
+ public synchronized void bindTo(final AbstractMetricService metricService) {
this.metricService = metricService;
ImmutableSet.copyOf(extractorMap.keySet()).forEach(this::createMetrics);
}
@@ -70,12 +70,11 @@ public class PipeConfigRegionSourceMetrics implements IMetricSet {
}
@Override
- public void unbindFrom(final AbstractMetricService metricService) {
- ImmutableSet.copyOf(extractorMap.keySet()).forEach(this::deregister);
- if (!extractorMap.isEmpty()) {
- LOGGER.warn(
- ManagerMessages.FAILED_TO_UNBIND_FROM_PIPE_CONFIG_REGION_EXTRACTOR_METRICS_EXTRACTOR);
- }
+ public synchronized void unbindFrom(final AbstractMetricService metricService) {
+ // Keep the extractors registered: they register only once, so a metric service restart
+ // must be able to bind them again.
+ // Synchronized with the (de)registrations, which may remove a registration being unbound.
+ ImmutableSet.copyOf(extractorMap.keySet()).forEach(this::removeMetrics);
}
private void removeMetrics(final String taskID) {
@@ -96,7 +95,7 @@ public class PipeConfigRegionSourceMetrics implements IMetricSet {
//////////////////////////// pipe integration ////////////////////////////
- public void register(final IoTDBConfigRegionSource extractor) {
+ public synchronized void register(final IoTDBConfigRegionSource extractor) {
final String taskID = extractor.getTaskID();
extractorMap.putIfAbsent(taskID, extractor);
if (Objects.nonNull(metricService)) {
@@ -104,7 +103,7 @@ public class PipeConfigRegionSourceMetrics implements IMetricSet {
}
}
- public void deregister(final String taskID) {
+ public synchronized void deregister(final String taskID) {
if (!extractorMap.containsKey(taskID)) {
LOGGER.warn(ManagerMessages.FAILED_TO_DEREGISTER_PIPE_CONFIG_REGION_EXTRACTOR, taskID);
return;
diff --git a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/pipe/metric/PipeConfigNodeMetricsRestartTest.java b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/pipe/metric/PipeConfigNodeMetricsRestartTest.java
new file mode 100644
index 00000000000..0bd5e52aff1
--- /dev/null
+++ b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/pipe/metric/PipeConfigNodeMetricsRestartTest.java
@@ -0,0 +1,184 @@
+/*
+ * 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.iotdb.confignode.manager.pipe.metric;
+
+import org.apache.iotdb.commons.pipe.agent.task.meta.PipeMeta;
+import org.apache.iotdb.commons.pipe.agent.task.meta.PipeStaticMeta;
+import org.apache.iotdb.commons.pipe.agent.task.meta.PipeTemporaryMetaInCoordinator;
+import org.apache.iotdb.commons.service.metric.MetricService;
+import org.apache.iotdb.commons.service.metric.enums.Metric;
+import org.apache.iotdb.confignode.manager.pipe.agent.task.PipeConfigNodeSubtask;
+import org.apache.iotdb.confignode.manager.pipe.metric.overview.PipeConfigNodeRemainingTimeMetrics;
+import org.apache.iotdb.confignode.manager.pipe.metric.overview.PipeTemporaryMetaInCoordinatorMetrics;
+import org.apache.iotdb.confignode.manager.pipe.metric.sink.PipeConfigRegionSinkMetrics;
+import org.apache.iotdb.confignode.manager.pipe.metric.source.PipeConfigRegionSourceMetrics;
+import org.apache.iotdb.confignode.manager.pipe.source.IoTDBConfigRegionSource;
+import org.apache.iotdb.metrics.config.MetricConfig;
+import org.apache.iotdb.metrics.config.MetricConfigDescriptor;
+import org.apache.iotdb.metrics.metricsets.IMetricSet;
+import org.apache.iotdb.metrics.type.IMetric;
+import org.apache.iotdb.metrics.type.Rate;
+import org.apache.iotdb.metrics.utils.MetricInfo;
+import org.apache.iotdb.metrics.utils.MetricLevel;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+
+/** Pipe metrics must survive a metric service restart, which drops and rebinds all metrics. */
+public class PipeConfigNodeMetricsRestartTest {
+
+ private static final String PIPE = "pipe";
+ private static final long CREATION_TIME = 1L;
+ private static final String PIPE_ID = PIPE + "_" + CREATION_TIME;
+ private static final String TASK_ID = "task";
+
+ private final MetricService service = MetricService.getInstance();
+ private final MetricConfig config = MetricConfigDescriptor.getInstance().getMetricConfig();
+ private final List<IMetricSet> boundMetricSets = new ArrayList<>();
+ private MetricLevel originalLevel;
+ private String originalReporters;
+
+ @Before
+ public void setUp() {
+ originalLevel = config.getMetricLevel();
+ originalReporters =
+ config.getMetricReporterList().stream().map(Enum::name).collect(Collectors.joining(","));
+ config.setMetricLevel(MetricLevel.IMPORTANT);
+ config.setMetricReporterList("");
+ service.startService();
+ }
+
+ @After
+ public void tearDown() {
+ boundMetricSets.forEach(service::removeMetricSet);
+ service.stopService();
+ config.setMetricLevel(originalLevel);
+ config.setMetricReporterList(originalReporters);
+ }
+
+ @Test
+ public void testConfigRegionSourceMetrics() {
+ final PipeConfigRegionSourceMetrics metrics = PipeConfigRegionSourceMetrics.getInstance();
+ bind(metrics);
+ metrics.register(mockConfigRegionSource());
+ try {
+ service.restartService();
+ assertEquals(1, count(Metric.UNTRANSFERRED_CONFIG_COUNT));
+ } finally {
+ metrics.deregister(TASK_ID);
+ }
+ assertEquals(0, count(Metric.UNTRANSFERRED_CONFIG_COUNT));
+ }
+
+ @Test
+ public void testConfigRegionSinkMetrics() {
+ final PipeConfigRegionSinkMetrics metrics = PipeConfigRegionSinkMetrics.getInstance();
+ bind(metrics);
+ final PipeConfigNodeSubtask subtask = Mockito.mock(PipeConfigNodeSubtask.class);
+ when(subtask.getTaskID()).thenReturn(TASK_ID);
+ when(subtask.getPipeName()).thenReturn(PIPE);
+ when(subtask.getCreationTime()).thenReturn(CREATION_TIME);
+ metrics.register(subtask);
+ try {
+ service.restartService();
+ metrics.markConfigEvent(TASK_ID);
+ assertEquals(1, ((Rate) get(Metric.PIPE_CONNECTOR_CONFIG_TRANSFER)).getCount());
+ } finally {
+ metrics.deregister(TASK_ID);
+ }
+ assertEquals(0, count(Metric.PIPE_CONNECTOR_CONFIG_TRANSFER));
+ }
+
+ @Test
+ public void testRemainingTimeMetrics() {
+ final PipeConfigNodeRemainingTimeMetrics metrics =
+ PipeConfigNodeRemainingTimeMetrics.getInstance();
+ bind(metrics);
+ metrics.register(mockConfigRegionSource());
+ try {
+ service.restartService();
+ assertEquals(1, count(Metric.PIPE_CONFIGNODE_REMAINING_TIME));
+ } finally {
+ metrics.deregister(PIPE_ID);
+ }
+ assertEquals(0, count(Metric.PIPE_CONFIGNODE_REMAINING_TIME));
+ }
+
+ @Test
+ public void testTemporaryMetaInCoordinatorMetrics() {
+ final PipeTemporaryMetaInCoordinatorMetrics metrics =
+ PipeTemporaryMetaInCoordinatorMetrics.getInstance();
+ bind(metrics);
+ final PipeStaticMeta staticMeta = Mockito.mock(PipeStaticMeta.class);
+ when(staticMeta.getPipeName()).thenReturn(PIPE);
+ when(staticMeta.getCreationTime()).thenReturn(CREATION_TIME);
+ final PipeMeta pipeMeta = Mockito.mock(PipeMeta.class);
+ when(pipeMeta.getStaticMeta()).thenReturn(staticMeta);
+ final PipeTemporaryMetaInCoordinator temporaryMeta =
+ Mockito.mock(PipeTemporaryMetaInCoordinator.class);
+ when(pipeMeta.getTemporaryMeta()).thenReturn(temporaryMeta);
+ metrics.register(pipeMeta);
+ try {
+ service.restartService();
+ assertEquals(1, count(Metric.PIPE_GLOBAL_REMAINING_EVENT_COUNT));
+ } finally {
+ metrics.deregister(PIPE_ID);
+ }
+ assertEquals(0, count(Metric.PIPE_GLOBAL_REMAINING_EVENT_COUNT));
+ }
+
+ private void bind(final IMetricSet metricSet) {
+ service.addMetricSet(metricSet);
+ boundMetricSets.add(metricSet);
+ }
+
+ private static IoTDBConfigRegionSource mockConfigRegionSource() {
+ final IoTDBConfigRegionSource source = Mockito.mock(IoTDBConfigRegionSource.class);
+ when(source.getTaskID()).thenReturn(TASK_ID);
+ when(source.getPipeName()).thenReturn(PIPE);
+ when(source.getCreationTime()).thenReturn(CREATION_TIME);
+ return source;
+ }
+
+ private long count(final Metric metric) {
+ return service.getAllMetrics().keySet().stream()
+ .filter(info -> metric.toString().equals(info.getName()))
+ .count();
+ }
+
+ private IMetric get(final Metric metric) {
+ for (final Map.Entry<MetricInfo, IMetric> entry : service.getAllMetrics().entrySet()) {
+ if (metric.toString().equals(entry.getKey().getName())) {
+ return entry.getValue();
+ }
+ }
+ throw new AssertionError(metric + " is not registered");
+ }
+}
diff --git a/iotdb-core/consensus/pom.xml b/iotdb-core/consensus/pom.xml
index 5d4b785ef73..7130af74828 100644
--- a/iotdb-core/consensus/pom.xml
+++ b/iotdb-core/consensus/pom.xml
@@ -124,6 +124,11 @@
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
diff --git a/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/iot/logdispatcher/LogDispatcherThreadMetrics.java b/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/iot/logdispatcher/LogDispatcherThreadMetrics.java
index f30f8c23094..9a5dd6c4b28 100644
--- a/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/iot/logdispatcher/LogDispatcherThreadMetrics.java
+++ b/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/iot/logdispatcher/LogDispatcherThreadMetrics.java
@@ -28,7 +28,20 @@ import org.apache.iotdb.metrics.type.Timer;
import org.apache.iotdb.metrics.utils.MetricLevel;
import org.apache.iotdb.metrics.utils.MetricType;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
public class LogDispatcherThreadMetrics implements IMetricSet {
+ // The stage timers are shared by the dispatcher threads of all peers of a region, so only the
+ // last of them to unbind may remove the timers, otherwise the others keep recording into timers
+ // that are no longer exported, e.g. after the metric service restarts. A metric set may be bound
+ // twice without being unbound in between, e.g. when it is added before the metric service
+ // starts, so track the owners rather than count the bindings.
+ private static final Map<String, Set<LogDispatcherThreadMetrics>> REGION_TO_STAGE_TIMER_OWNERS =
+ new HashMap<>();
+
private final LogDispatcher.LogDispatcherThread logDispatcherThread;
private final String peerGroupId;
@@ -91,47 +104,62 @@ public class LogDispatcherThreadMetrics implements IMetricSet {
}
private void bindStageTimer(AbstractMetricService metricService) {
- constructBatchTimer =
- metricService.getOrCreateTimer(
- Metric.IOT_SEND_LOG.toString(),
- MetricLevel.IMPORTANT,
- Tag.NAME.toString(),
- Metric.IOT_CONSENSUS.toString(),
- Tag.STAGE.toString(),
- "constructBatch",
- Tag.REGION.toString(),
- peerGroupId);
- syncLogTimePerRequestTimer =
- metricService.getOrCreateTimer(
- Metric.IOT_SEND_LOG.toString(),
- MetricLevel.IMPORTANT,
- Tag.NAME.toString(),
- Metric.IOT_CONSENSUS.toString(),
- Tag.STAGE.toString(),
- "syncLogTimePerRequest",
- Tag.REGION.toString(),
- peerGroupId);
+ synchronized (REGION_TO_STAGE_TIMER_OWNERS) {
+ REGION_TO_STAGE_TIMER_OWNERS
+ .computeIfAbsent(peerGroupId, region -> new HashSet<>())
+ .add(this);
+ constructBatchTimer =
+ metricService.getOrCreateTimer(
+ Metric.IOT_SEND_LOG.toString(),
+ MetricLevel.IMPORTANT,
+ Tag.NAME.toString(),
+ Metric.IOT_CONSENSUS.toString(),
+ Tag.STAGE.toString(),
+ "constructBatch",
+ Tag.REGION.toString(),
+ peerGroupId);
+ syncLogTimePerRequestTimer =
+ metricService.getOrCreateTimer(
+ Metric.IOT_SEND_LOG.toString(),
+ MetricLevel.IMPORTANT,
+ Tag.NAME.toString(),
+ Metric.IOT_CONSENSUS.toString(),
+ Tag.STAGE.toString(),
+ "syncLogTimePerRequest",
+ Tag.REGION.toString(),
+ peerGroupId);
+ }
}
private void unbindStageTimer(AbstractMetricService metricService) {
- metricService.remove(
- MetricType.TIMER,
- Metric.IOT_SEND_LOG.toString(),
- Tag.NAME.toString(),
- Metric.IOT_CONSENSUS.toString(),
- Tag.STAGE.toString(),
- "constructBatch",
- Tag.REGION.toString(),
- peerGroupId);
- metricService.remove(
- MetricType.TIMER,
- Metric.IOT_SEND_LOG.toString(),
- Tag.NAME.toString(),
- Metric.IOT_CONSENSUS.toString(),
- Tag.STAGE.toString(),
- "syncLogTimePerRequest",
- Tag.REGION.toString(),
- peerGroupId);
+ synchronized (REGION_TO_STAGE_TIMER_OWNERS) {
+ final Set<LogDispatcherThreadMetrics> owners = REGION_TO_STAGE_TIMER_OWNERS.get(peerGroupId);
+ if (owners != null) {
+ owners.remove(this);
+ if (!owners.isEmpty()) {
+ return;
+ }
+ REGION_TO_STAGE_TIMER_OWNERS.remove(peerGroupId);
+ }
+ metricService.remove(
+ MetricType.TIMER,
+ Metric.IOT_SEND_LOG.toString(),
+ Tag.NAME.toString(),
+ Metric.IOT_CONSENSUS.toString(),
+ Tag.STAGE.toString(),
+ "constructBatch",
+ Tag.REGION.toString(),
+ peerGroupId);
+ metricService.remove(
+ MetricType.TIMER,
+ Metric.IOT_SEND_LOG.toString(),
+ Tag.NAME.toString(),
+ Metric.IOT_CONSENSUS.toString(),
+ Tag.STAGE.toString(),
+ "syncLogTimePerRequest",
+ Tag.REGION.toString(),
+ peerGroupId);
+ }
}
@Override
diff --git a/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/pipe/IoTConsensusV2ServerImpl.java b/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/pipe/IoTConsensusV2ServerImpl.java
index 415326cbf52..52df1138161 100644
--- a/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/pipe/IoTConsensusV2ServerImpl.java
+++ b/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/pipe/IoTConsensusV2ServerImpl.java
@@ -54,6 +54,7 @@ import org.apache.iotdb.consensus.iotconsensusv2.thrift.TWaitReleaseAllRegionRel
import org.apache.iotdb.consensus.pipe.consensuspipe.ConsensusPipeName;
import org.apache.iotdb.consensus.pipe.consensuspipe.ReplicateProgressManager;
import org.apache.iotdb.consensus.pipe.metric.IoTConsensusV2ServerMetrics;
+import org.apache.iotdb.consensus.pipe.metric.IoTConsensusV2SyncLagManager;
import org.apache.iotdb.pipe.api.exception.PipeException;
import org.apache.iotdb.rpc.RpcUtils;
@@ -122,12 +123,16 @@ public class IoTConsensusV2ServerImpl {
public synchronized void stop() {
MetricService.getInstance().removeMetricSet(this.iotConsensusV2ServerMetrics);
+ // Release it here rather than when unbinding the metrics, which also happens when the metric
+ // service restarts
+ IoTConsensusV2SyncLagManager.release(consensusGroupId);
stateMachine.stop();
isStarted.set(false);
}
public synchronized void clear() {
MetricService.getInstance().removeMetricSet(this.iotConsensusV2ServerMetrics);
+ IoTConsensusV2SyncLagManager.release(consensusGroupId);
peerManager.clear();
stateMachine.stop();
isStarted.set(false);
diff --git a/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/pipe/metric/IoTConsensusV2ServerMetrics.java b/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/pipe/metric/IoTConsensusV2ServerMetrics.java
index 2c10d434b51..0f39e14206d 100644
--- a/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/pipe/metric/IoTConsensusV2ServerMetrics.java
+++ b/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/pipe/metric/IoTConsensusV2ServerMetrics.java
@@ -68,9 +68,6 @@ public class IoTConsensusV2ServerMetrics implements IMetricSet {
unbindAutoGauge(metricService);
unbindGauge(metricService);
unbindStageTimer(metricService);
-
- // release corresponding resource
- IoTConsensusV2SyncLagManager.release(impl.getConsensusGroupId());
}
public void bindGauge(AbstractMetricService metricService) {
diff --git a/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/ratis/metrics/CounterProxy.java b/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/ratis/metrics/CounterProxy.java
index 33d2470af20..04c8441eba3 100644
--- a/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/ratis/metrics/CounterProxy.java
+++ b/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/ratis/metrics/CounterProxy.java
@@ -26,13 +26,17 @@ import org.apache.ratis.metrics.LongCounter;
/** CounterProxy will route Ratis' internal counter metrics to our IoTDB {@link Counter} */
public class CounterProxy implements LongCounter {
- /** IoTDB Counter. */
- private final Counter counter;
+ /** IoTDB Counter, replaced when the metric service restarts. */
+ private volatile Counter counter;
CounterProxy(Counter counter) {
this.counter = counter;
}
+ void setCounter(Counter counter) {
+ this.counter = counter;
+ }
+
@Override
public void inc() {
inc(1L);
diff --git a/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/ratis/metrics/IoTDBMetricRegistry.java b/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/ratis/metrics/IoTDBMetricRegistry.java
index 41619b28e63..2288e37ef6f 100644
--- a/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/ratis/metrics/IoTDBMetricRegistry.java
+++ b/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/ratis/metrics/IoTDBMetricRegistry.java
@@ -43,7 +43,7 @@ public class IoTDBMetricRegistry implements RatisMetricRegistry {
private final Map<String, String> metricNameCache = new ConcurrentHashMap<>();
private final Map<String, CounterProxy> counterCache = new ConcurrentHashMap<>();
private final Map<String, TimerProxy> timerCache = new ConcurrentHashMap<>();
- private final Map<String, Boolean> gaugeCache = new ConcurrentHashMap<>();
+ private final Map<String, GaugeProxy<?>> gaugeCache = new ConcurrentHashMap<>();
/** Time taken to flush log. */
public static final String RAFT_LOG_FLUSH_TIME = "flushTime";
@@ -149,7 +149,11 @@ public class IoTDBMetricRegistry implements RatisMetricRegistry {
metricService.remove(MetricType.AUTO_GAUGE, getMetricName(name));
} catch (IllegalArgumentException ignored) {
}
-
+ // Forget the metric so that rebinding does not restore it and Ratis can create it again.
+ final String fullName = getMetricName(name);
+ counterCache.remove(fullName);
+ timerCache.remove(fullName);
+ gaugeCache.remove(fullName);
return true;
}
@@ -161,7 +165,7 @@ public class IoTDBMetricRegistry implements RatisMetricRegistry {
fn -> {
final GaugeProxy<T> gauge = new GaugeProxy<>(supplier);
metricService.createAutoGauge(fn, getMetricLevel(fn), gauge, GaugeProxy::getDoubleValue);
- return true;
+ return gauge;
});
}
@@ -170,10 +174,32 @@ public class IoTDBMetricRegistry implements RatisMetricRegistry {
return info;
}
- void removeAll() {
+ /**
+ * Registers all metrics again. Ratis creates its metrics once and keeps using them, so they must
+ * be restored after the metric service drops them, e.g. when it restarts.
+ */
+ void bindMetrics() {
+ counterCache.forEach(
+ (name, counter) ->
+ counter.setCounter(metricService.getOrCreateCounter(name, getMetricLevel(name))));
+ timerCache.forEach(
+ (name, timer) ->
+ timer.setTimer(metricService.getOrCreateTimer(name, getMetricLevel(name))));
+ gaugeCache.forEach(
+ (name, gauge) ->
+ metricService.createAutoGauge(
+ name, getMetricLevel(name), gauge, GaugeProxy::getDoubleValue));
+ }
+
+ /** Removes all metrics from the metric service but keeps them for {@link #bindMetrics()}. */
+ void unbindMetrics() {
counterCache.forEach((name, counter) -> metricService.remove(MetricType.COUNTER, name));
gaugeCache.forEach((name, gauge) -> metricService.remove(MetricType.AUTO_GAUGE, name));
timerCache.forEach((name, timer) -> metricService.remove(MetricType.TIMER, name));
+ }
+
+ void removeAll() {
+ unbindMetrics();
metricNameCache.clear();
counterCache.clear();
gaugeCache.clear();
diff --git a/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/ratis/metrics/MetricRegistryManager.java b/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/ratis/metrics/MetricRegistryManager.java
index 2a8ca243295..104b8faaf7e 100644
--- a/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/ratis/metrics/MetricRegistryManager.java
+++ b/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/ratis/metrics/MetricRegistryManager.java
@@ -52,6 +52,20 @@ public class MetricRegistryManager extends MetricRegistries {
this.registries.clear();
}
+ /** Registers the metrics of all registries again, e.g. after the metric service restarts. */
+ void bindMetrics() {
+ registries.values().stream()
+ .map(IoTDBMetricRegistry.class::cast)
+ .forEach(IoTDBMetricRegistry::bindMetrics);
+ }
+
+ /** Removes the metrics of all registries from the metric service without dropping them. */
+ void unbindMetrics() {
+ registries.values().stream()
+ .map(IoTDBMetricRegistry.class::cast)
+ .forEach(IoTDBMetricRegistry::unbindMetrics);
+ }
+
@Override
public RatisMetricRegistry create(MetricRegistryInfo metricRegistryInfo) {
return registries.put(
diff --git a/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/ratis/metrics/RatisMetricSet.java b/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/ratis/metrics/RatisMetricSet.java
index c13efd591dc..9903cff2813 100644
--- a/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/ratis/metrics/RatisMetricSet.java
+++ b/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/ratis/metrics/RatisMetricSet.java
@@ -100,6 +100,9 @@ public class RatisMetricSet implements IMetricSet {
@Override
public void bindTo(AbstractMetricService metricService) {
manager = MetricRegistries.global();
+ if (manager instanceof MetricRegistryManager) {
+ ((MetricRegistryManager) manager).bindMetrics();
+ }
for (MetricInfo metricInfo : metricInfoMap.values()) {
metricService.getOrCreateTimer(
metricInfo.getName(), MetricLevel.CORE, metricInfo.getTagsInArray());
@@ -108,7 +111,10 @@ public class RatisMetricSet implements IMetricSet {
@Override
public void unbindFrom(AbstractMetricService metricService) {
- manager.clear();
+ // Keep the registries: Ratis still holds their metrics and never creates them again.
+ if (manager instanceof MetricRegistryManager) {
+ ((MetricRegistryManager) manager).unbindMetrics();
+ }
for (MetricInfo metricInfo : metricInfoMap.values()) {
metricService.remove(MetricType.TIMER, metricInfo.getName(), metricInfo.getTagsInArray());
}
diff --git a/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/ratis/metrics/TimerProxy.java b/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/ratis/metrics/TimerProxy.java
index 07a01abe04a..8aff57a9bc7 100644
--- a/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/ratis/metrics/TimerProxy.java
+++ b/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/ratis/metrics/TimerProxy.java
@@ -46,12 +46,17 @@ public class TimerProxy implements Timekeeper {
}
}
- private final Timer timer;
+ /** IoTDB Timer, replaced when the metric service restarts. */
+ private volatile Timer timer;
TimerProxy(Timer timer) {
this.timer = timer;
}
+ void setTimer(Timer timer) {
+ this.timer = timer;
+ }
+
@Override
public Context time() {
return new TimerContext(timer);
diff --git a/iotdb-core/consensus/src/test/java/org/apache/iotdb/consensus/iot/logdispatcher/LogDispatcherThreadMetricsTest.java b/iotdb-core/consensus/src/test/java/org/apache/iotdb/consensus/iot/logdispatcher/LogDispatcherThreadMetricsTest.java
new file mode 100644
index 00000000000..4da003936a9
--- /dev/null
+++ b/iotdb-core/consensus/src/test/java/org/apache/iotdb/consensus/iot/logdispatcher/LogDispatcherThreadMetricsTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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.iotdb.consensus.iot.logdispatcher;
+
+import org.apache.iotdb.common.rpc.thrift.TEndPoint;
+import org.apache.iotdb.commons.consensus.DataRegionId;
+import org.apache.iotdb.commons.service.metric.MetricService;
+import org.apache.iotdb.commons.service.metric.enums.Metric;
+import org.apache.iotdb.commons.service.metric.enums.Tag;
+import org.apache.iotdb.consensus.common.Peer;
+import org.apache.iotdb.metrics.config.MetricConfig;
+import org.apache.iotdb.metrics.config.MetricConfigDescriptor;
+import org.apache.iotdb.metrics.type.IMetric;
+import org.apache.iotdb.metrics.type.Timer;
+import org.apache.iotdb.metrics.utils.MetricInfo;
+import org.apache.iotdb.metrics.utils.MetricLevel;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+public class LogDispatcherThreadMetricsTest {
+
+ private static final DataRegionId REGION = new DataRegionId(1);
+
+ private final MetricService service = MetricService.getInstance();
+ private final MetricConfig config = MetricConfigDescriptor.getInstance().getMetricConfig();
+ private MetricLevel originalLevel;
+ private String originalReporters;
+
+ @Before
+ public void setUp() {
+ originalLevel = config.getMetricLevel();
+ originalReporters =
+ config.getMetricReporterList().stream().map(Enum::name).collect(Collectors.joining(","));
+ config.setMetricLevel(MetricLevel.IMPORTANT);
+ config.setMetricReporterList("");
+ service.startService();
+ }
+
+ @After
+ public void tearDown() {
+ service.stopService();
+ config.setMetricLevel(originalLevel);
+ config.setMetricReporterList(originalReporters);
+ }
+
+ /** The dispatcher threads of all peers of a region record into the same stage timers. */
+ @Test
+ public void testStageTimersAreSharedByThePeersOfARegion() {
+ final LogDispatcherThreadMetrics first = new LogDispatcherThreadMetrics(mockThread(2));
+ final LogDispatcherThreadMetrics second = new LogDispatcherThreadMetrics(mockThread(3));
+ service.addMetricSet(first);
+ service.addMetricSet(second);
+ try {
+ service.restartService();
+ first.recordConstructBatchTime(1);
+ second.recordConstructBatchTime(1);
+ assertEquals(2, getConstructBatchTimer().getCount());
+
+ // A peer leaving the region must not remove the timers still used by the other peers
+ service.removeMetricSet(second);
+ first.recordConstructBatchTime(1);
+ assertEquals(3, getConstructBatchTimer().getCount());
+ } finally {
+ service.removeMetricSet(first);
+ service.removeMetricSet(second);
+ }
+ assertNull(findConstructBatchTimer());
+ }
+
+ /**
+ * The dispatchers recovered at startup register their metrics before the metric service starts,
+ * which binds them again, so a metric set may be bound twice without being unbound in between.
+ */
+ @Test
+ public void testStageTimersAreRemovedAfterBindingTwice() {
+ service.stopService();
+ final LogDispatcherThreadMetrics first = new LogDispatcherThreadMetrics(mockThread(2));
+ final LogDispatcherThreadMetrics second = new LogDispatcherThreadMetrics(mockThread(3));
+ service.addMetricSet(first);
+ service.addMetricSet(second);
+ service.startService();
+ try {
+ first.recordConstructBatchTime(1);
+ second.recordConstructBatchTime(1);
+ assertEquals(2, getConstructBatchTimer().getCount());
+ } finally {
+ service.removeMetricSet(first);
+ service.removeMetricSet(second);
+ }
+ assertNull(findConstructBatchTimer());
+ }
+
+ private static LogDispatcher.LogDispatcherThread mockThread(final int peerNodeId) {
+ final LogDispatcher.LogDispatcherThread thread =
+ Mockito.mock(LogDispatcher.LogDispatcherThread.class);
+ Mockito.when(thread.getPeer())
+ .thenReturn(new Peer(REGION, peerNodeId, new TEndPoint("127.0.0.1", 10000 + peerNodeId)));
+ return thread;
+ }
+
+ private Timer getConstructBatchTimer() {
+ final IMetric timer = findConstructBatchTimer();
+ if (timer == null) {
+ throw new AssertionError("The constructBatch timer of " + REGION + " is not registered");
+ }
+ return (Timer) timer;
+ }
+
+ private IMetric findConstructBatchTimer() {
+ for (final Map.Entry<MetricInfo, IMetric> entry : service.getAllMetrics().entrySet()) {
+ final Map<String, String> tags = entry.getKey().getTags();
+ if (Metric.IOT_SEND_LOG.toString().equals(entry.getKey().getName())
+ && "constructBatch".equals(tags.get(Tag.STAGE.toString()))
+ && REGION.toString().equals(tags.get(Tag.REGION.toString()))) {
+ return entry.getValue();
+ }
+ }
+ return null;
+ }
+}
diff --git a/iotdb-core/consensus/src/test/java/org/apache/iotdb/consensus/pipe/metric/IoTConsensusV2ServerMetricsTest.java b/iotdb-core/consensus/src/test/java/org/apache/iotdb/consensus/pipe/metric/IoTConsensusV2ServerMetricsTest.java
new file mode 100644
index 00000000000..86ef55e58a8
--- /dev/null
+++ b/iotdb-core/consensus/src/test/java/org/apache/iotdb/consensus/pipe/metric/IoTConsensusV2ServerMetricsTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.iotdb.consensus.pipe.metric;
+
+import org.apache.iotdb.commons.consensus.DataRegionId;
+import org.apache.iotdb.commons.service.metric.MetricService;
+import org.apache.iotdb.commons.service.metric.enums.Metric;
+import org.apache.iotdb.commons.service.metric.enums.Tag;
+import org.apache.iotdb.consensus.pipe.IoTConsensusV2ServerImpl;
+import org.apache.iotdb.consensus.pipe.consensuspipe.ConsensusPipeName;
+import org.apache.iotdb.consensus.pipe.consensuspipe.ConsensusPipeSink;
+import org.apache.iotdb.metrics.config.MetricConfig;
+import org.apache.iotdb.metrics.config.MetricConfigDescriptor;
+import org.apache.iotdb.metrics.type.AutoGauge;
+import org.apache.iotdb.metrics.type.IMetric;
+import org.apache.iotdb.metrics.utils.MetricInfo;
+import org.apache.iotdb.metrics.utils.MetricLevel;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+public class IoTConsensusV2ServerMetricsTest {
+
+ private static final DataRegionId GROUP_ID = new DataRegionId(1);
+
+ private final MetricService service = MetricService.getInstance();
+ private final MetricConfig config = MetricConfigDescriptor.getInstance().getMetricConfig();
+ private MetricLevel originalLevel;
+ private String originalReporters;
+
+ @Before
+ public void setUp() {
+ originalLevel = config.getMetricLevel();
+ originalReporters =
+ config.getMetricReporterList().stream().map(Enum::name).collect(Collectors.joining(","));
+ config.setMetricLevel(MetricLevel.IMPORTANT);
+ config.setMetricReporterList("");
+ service.startService();
+ IoTConsensusV2SyncLagManager.build();
+ }
+
+ @After
+ public void tearDown() {
+ IoTConsensusV2SyncLagManager.release(GROUP_ID.toString());
+ service.stopService();
+ config.setMetricLevel(originalLevel);
+ config.setMetricReporterList(originalReporters);
+ }
+
+ @Test
+ public void testSyncLagSurvivesMetricServiceRestart() {
+ final IoTConsensusV2ServerImpl impl = Mockito.mock(IoTConsensusV2ServerImpl.class);
+ Mockito.when(impl.getConsensusGroupId()).thenReturn(GROUP_ID.toString());
+ final ConsensusPipeSink sink = Mockito.mock(ConsensusPipeSink.class);
+ Mockito.when(sink.getLeaderReplicateProgress()).thenReturn(10L);
+ Mockito.when(sink.getFollowerApplyProgress()).thenReturn(3L);
+ IoTConsensusV2SyncLagManager.getInstance(GROUP_ID.toString())
+ .addConsensusPipeConnector(new ConsensusPipeName(GROUP_ID, 1, 2), sink);
+
+ final IoTConsensusV2ServerMetrics metrics = new IoTConsensusV2ServerMetrics(impl);
+ service.addMetricSet(metrics);
+ try {
+ assertEquals(7, getSyncLag(), 0);
+ service.restartService();
+ assertEquals(7, getSyncLag(), 0);
+ } finally {
+ service.removeMetricSet(metrics);
+ }
+ }
+
+ private double getSyncLag() {
+ AutoGauge gauge = null;
+ for (final Map.Entry<MetricInfo, IMetric> entry : service.getAllMetrics().entrySet()) {
+ if (Metric.IOT_CONSENSUS_V2.toString().equals(entry.getKey().getName())
+ && "syncLag".equals(entry.getKey().getTags().get(Tag.TYPE.toString()))) {
+ gauge = (AutoGauge) entry.getValue();
+ }
+ }
+ assertNotNull(gauge);
+ return gauge.getValue();
+ }
+}
diff --git a/iotdb-core/consensus/src/test/java/org/apache/iotdb/consensus/ratis/metrics/RatisMetricSetTest.java b/iotdb-core/consensus/src/test/java/org/apache/iotdb/consensus/ratis/metrics/RatisMetricSetTest.java
new file mode 100644
index 00000000000..4fcf57f0ca6
--- /dev/null
+++ b/iotdb-core/consensus/src/test/java/org/apache/iotdb/consensus/ratis/metrics/RatisMetricSetTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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.iotdb.consensus.ratis.metrics;
+
+import org.apache.iotdb.commons.service.metric.MetricService;
+import org.apache.iotdb.metrics.config.MetricConfig;
+import org.apache.iotdb.metrics.config.MetricConfigDescriptor;
+import org.apache.iotdb.metrics.type.AutoGauge;
+import org.apache.iotdb.metrics.type.Counter;
+import org.apache.iotdb.metrics.type.IMetric;
+import org.apache.iotdb.metrics.type.Timer;
+import org.apache.iotdb.metrics.utils.MetricInfo;
+import org.apache.iotdb.metrics.utils.MetricLevel;
+
+import org.apache.ratis.metrics.LongCounter;
+import org.apache.ratis.metrics.MetricRegistries;
+import org.apache.ratis.metrics.MetricRegistryInfo;
+import org.apache.ratis.metrics.RatisMetricRegistry;
+import org.apache.ratis.metrics.Timekeeper;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+public class RatisMetricSetTest {
+
+ private static final String COMPONENT = "ratis_metric_set_test";
+
+ private final MetricRegistryInfo info =
+ new MetricRegistryInfo("test", "ratis", COMPONENT, "RatisMetricSetTest");
+ private final RatisMetricSet metricSet = new RatisMetricSet();
+ private final MetricService service = MetricService.getInstance();
+ private final MetricConfig config = MetricConfigDescriptor.getInstance().getMetricConfig();
+ private MetricLevel originalLevel;
+ private String originalReporters;
+ private RatisMetricRegistry registry;
+
+ @Before
+ public void setUp() {
+ originalLevel = config.getMetricLevel();
+ originalReporters =
+ config.getMetricReporterList().stream().map(Enum::name).collect(Collectors.joining(","));
+ config.setMetricLevel(MetricLevel.IMPORTANT);
+ config.setMetricReporterList("");
+ service.startService();
+ service.addMetricSet(metricSet);
+ registry = MetricRegistries.global().create(info);
+ }
+
+ @After
+ public void tearDown() {
+ MetricRegistries.global().remove(info);
+ service.removeMetricSet(metricSet);
+ service.stopService();
+ config.setMetricLevel(originalLevel);
+ config.setMetricReporterList(originalReporters);
+ }
+
+ @Test
+ public void testRatisMetricsSurviveMetricServiceRestart() {
+ // Ratis creates these metrics once and keeps using the returned instances.
+ final LongCounter counter = registry.counter("counter");
+ final Timekeeper timer = registry.timer("timer");
+ registry.gauge("gauge", () -> () -> 42);
+ counter.inc();
+
+ for (final MetricLevel level :
+ new MetricLevel[] {MetricLevel.ALL, MetricLevel.OFF, MetricLevel.IMPORTANT}) {
+ config.setMetricLevel(level);
+ service.restartService();
+ if (level == MetricLevel.OFF) {
+ assertNull(getMetric("counter"));
+ assertNull(getMetric("timer"));
+ assertNull(getMetric("gauge"));
+ } else {
+ assertNotNull(getMetric("counter"));
+ assertNotNull(getMetric("timer"));
+ assertEquals(42, ((AutoGauge) getMetric("gauge")).getValue(), 0);
+ }
+ }
+
+ counter.inc();
+ timer.time().stop();
+ assertEquals(1, ((Counter) getMetric("counter")).getCount());
+ assertEquals(1, ((Timer) getMetric("timer")).getCount());
+ }
+
+ @Test
+ public void testRemovedGaugeIsNotRestoredAndCanBeAddedAgain() {
+ registry.gauge("gauge", () -> () -> 1);
+ registry.remove("gauge");
+ assertNull(getMetric("gauge"));
+ service.restartService();
+ assertNull(getMetric("gauge"));
+
+ // Ratis adds a gauge again with a new supplier, e.g. when a server becomes leader again.
+ registry.gauge("gauge", () -> () -> 2);
+ assertEquals(2, ((AutoGauge) getMetric("gauge")).getValue(), 0);
+ service.restartService();
+ assertEquals(2, ((AutoGauge) getMetric("gauge")).getValue(), 0);
+ }
+
+ private IMetric getMetric(final String name) {
+ for (final Map.Entry<MetricInfo, IMetric> entry : service.getAllMetrics().entrySet()) {
+ if (entry.getKey().getName().endsWith(COMPONENT + name)) {
+ return entry.getValue();
+ }
+ }
+ return null;
+ }
+}
diff --git a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodePipeMessages.java b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
index 59f58407dc4..8fd63338892 100644
--- a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
+++ b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
@@ -1524,25 +1524,6 @@ public final class DataNodePipeMessages {
public static final String FAILED_TO_SET_RECENT_PROCESSED_TSFILE_EPOCH =
"Failed to set recent processed tsfile epoch state, PipeRealtimeDataRegionExtractor({}) "
+ "does not exist";
- public static final String FAILED_TO_UNBIND_FROM_PIPE_ASSIGNER_METRICS =
- "Failed to unbind from pipe assigner metrics, assigner map not empty";
- public static final String FAILED_TO_UNBIND_FROM_PIPE_DATA_REGION =
- "Failed to unbind from pipe data region sink metrics, sink map not empty";
- public static final String FAILED_TO_UNBIND_FROM_PIPE_EXTRACTOR_METRICS =
- "Failed to unbind from pipe extractor metrics, extractor map not empty";
- public static final String FAILED_TO_UNBIND_FROM_PIPE_PROCESSOR_METRICS =
- "Failed to unbind from pipe processor metrics, processor map not empty";
- public static final String FAILED_TO_UNBIND_FROM_PIPE_REMAINING_EVENT =
- "Failed to unbind from pipe remaining event and time metrics, "
- + "RemainingEventAndTimeOperator map not empty";
- public static final String FAILED_TO_UNBIND_FROM_PIPE_SCHEMA_REGION =
- "Failed to unbind from pipe schema region connector metrics, connector map not empty";
- public static final String FAILED_TO_UNBIND_FROM_PIPE_SCHEMA_REGION_1 =
- "Failed to unbind from pipe schema region extractor metrics, extractor map not empty";
- public static final String FAILED_TO_UNBIND_FROM_PIPE_SCHEMA_REGION_2 =
- "Failed to unbind from pipe schema region listener metrics, listening queue map not empty";
- public static final String FAILED_TO_UNBIND_FROM_PIPE_TSFILE_TO =
- "Failed to unbind from pipe tsfile to tablets metrics, pipe map is not empty, pipe: {}";
// ---------------------------------------------------------------------------
// pipe – PipeRow
@@ -1872,16 +1853,11 @@ public final class DataNodePipeMessages {
"Subscription: consumer {} poll excessive payload {} for event {} (outdated: {}) with "
+ "request: {}, something unexpected happened with parameter configuration or payload "
+ "control...";
- public static final String PIPE_LOG_FAILED_TO_UNBIND_FROM_SUBSCRIPTION_PREFETCHING_QUEUE_METRICS_6614388C =
- "Failed to unbind from subscription prefetching queue metrics, prefetching queue map not "
- + "empty";
public static final String PIPE_LOG_FAILED_TO_DEREGISTER_SUBSCRIPTION_PREFETCHING_QUEUE_METRICS_F08479A7 =
"Failed to deregister subscription prefetching queue metrics, "
+ "SubscriptionPrefetchingQueue({}) does not exist";
public static final String PIPE_LOG_FAILED_TO_MARK_TRANSFER_EVENT_RATE_SUBSCRIPTIONPREFETCHINGQUEUE_7DEF95B5 =
"Failed to mark transfer event rate, SubscriptionPrefetchingQueue({}) does not exist";
- public static final String PIPE_LOG_FAILED_TO_UNBIND_FROM_CONSENSUS_SUBSCRIPTION_PREFETCHING_A8F920D9 =
- "Failed to unbind from consensus subscription prefetching queue metrics, queue map not empty";
public static final String PIPE_LOG_FAILED_TO_DEREGISTER_CONSENSUS_SUBSCRIPTION_PREFETCHING_8B180091 =
"Failed to deregister consensus subscription prefetching queue metrics, "
+ "ConsensusPrefetchingQueue({}) does not exist";
diff --git a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodePipeMessages.java b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
index 48ec49342f3..62918788834 100644
--- a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
+++ b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
@@ -1423,25 +1423,6 @@ public final class DataNodePipeMessages {
public static final String FAILED_TO_SET_RECENT_PROCESSED_TSFILE_EPOCH =
"设置 recent processed tsfile epoch state, PipeRealtimeDataRegionExtractor({}) does not "
+ "exist 失败";
- public static final String FAILED_TO_UNBIND_FROM_PIPE_ASSIGNER_METRICS =
- "解绑 from pipe assigner metrics, assigner map not empty 失败";
- public static final String FAILED_TO_UNBIND_FROM_PIPE_DATA_REGION =
- "解绑 from pipe data region sink metrics, sink map not empty 失败";
- public static final String FAILED_TO_UNBIND_FROM_PIPE_EXTRACTOR_METRICS =
- "解绑 from pipe extractor metrics, extractor map not empty 失败";
- public static final String FAILED_TO_UNBIND_FROM_PIPE_PROCESSOR_METRICS =
- "解绑 from pipe processor metrics, processor map not empty 失败";
- public static final String FAILED_TO_UNBIND_FROM_PIPE_REMAINING_EVENT =
- "解绑 from pipe remaining event and time metrics, RemainingEventAndTimeOperator map not "
- + "empty 失败";
- public static final String FAILED_TO_UNBIND_FROM_PIPE_SCHEMA_REGION =
- "解绑 from pipe schema region connector metrics, connector map not empty 失败";
- public static final String FAILED_TO_UNBIND_FROM_PIPE_SCHEMA_REGION_1 =
- "解绑 from pipe schema region extractor metrics, extractor map not empty 失败";
- public static final String FAILED_TO_UNBIND_FROM_PIPE_SCHEMA_REGION_2 =
- "解绑 from pipe schema region listener metrics, listening queue map not empty 失败";
- public static final String FAILED_TO_UNBIND_FROM_PIPE_TSFILE_TO =
- "解绑 from pipe tsfile to tablets metrics, pipe map is not empty, pipe: {} 失败";
// ---------------------------------------------------------------------------
// pipe – PipeRow
@@ -1740,14 +1721,10 @@ public final class DataNodePipeMessages {
public static final String PIPE_LOG_SUBSCRIPTION_CONSUMER_POLL_EXCESSIVE_PAYLOAD_FOR_EVENT_OUTDATED_2BFF690B =
"Subscription:consumer {} poll 到过大的 payload {},event={}(outdated:{}),请求:{},参数配置或 payload "
+ "控制可能出现意外情况……";
- public static final String PIPE_LOG_FAILED_TO_UNBIND_FROM_SUBSCRIPTION_PREFETCHING_QUEUE_METRICS_6614388C =
- "解绑 subscription prefetching queue metrics 失败,prefetching queue map 非空";
public static final String PIPE_LOG_FAILED_TO_DEREGISTER_SUBSCRIPTION_PREFETCHING_QUEUE_METRICS_F08479A7 =
"注销 subscription prefetching queue metrics 失败,SubscriptionPrefetchingQueue({}) 不存在";
public static final String PIPE_LOG_FAILED_TO_MARK_TRANSFER_EVENT_RATE_SUBSCRIPTIONPREFETCHINGQUEUE_7DEF95B5 =
"标记传输事件速率失败,SubscriptionPrefetchingQueue({}) 不存在";
- public static final String PIPE_LOG_FAILED_TO_UNBIND_FROM_CONSENSUS_SUBSCRIPTION_PREFETCHING_A8F920D9 =
- "解绑 consensus subscription prefetching queue metrics 失败,queue map 非空";
public static final String PIPE_LOG_FAILED_TO_DEREGISTER_CONSENSUS_SUBSCRIPTION_PREFETCHING_8B180091 =
"注销 consensus subscription prefetching queue metrics 失败,ConsensusPrefetchingQueue({}) 不存在";
public static final String PIPE_LOG_FAILED_TO_MARK_TRANSFER_EVENT_RATE_CONSENSUSPREFETCHINGQUEUE_FE9B91C3 =
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/overview/PipeDataNodeSinglePipeMetrics.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/overview/PipeDataNodeSinglePipeMetrics.java
index b6efd64ed2c..0f537fd5800 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/overview/PipeDataNodeSinglePipeMetrics.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/overview/PipeDataNodeSinglePipeMetrics.java
@@ -56,7 +56,7 @@ public class PipeDataNodeSinglePipeMetrics implements IMetricSet {
//////////////////////////// bindTo & unbindFrom (metric framework) ////////////////////////////
@Override
- public void bindTo(final AbstractMetricService metricService) {
+ public synchronized void bindTo(final AbstractMetricService metricService) {
this.metricService = metricService;
ImmutableSet.copyOf(remainingEventAndTimeOperatorMap.keySet()).forEach(this::createMetrics);
}
@@ -135,11 +135,11 @@ public class PipeDataNodeSinglePipeMetrics implements IMetricSet {
}
@Override
- public void unbindFrom(final AbstractMetricService metricService) {
- ImmutableSet.copyOf(remainingEventAndTimeOperatorMap.keySet()).forEach(this::deregister);
- if (!remainingEventAndTimeOperatorMap.isEmpty()) {
- LOGGER.warn(DataNodePipeMessages.FAILED_TO_UNBIND_FROM_PIPE_REMAINING_EVENT);
- }
+ public synchronized void unbindFrom(final AbstractMetricService metricService) {
+ // Keep the operators registered: they hold the states of the pipes and register only once, so
+ // a metric service restart must be able to bind them again.
+ // Synchronized with the (de)registrations, which may remove a registration being unbound.
+ ImmutableSet.copyOf(remainingEventAndTimeOperatorMap.keySet()).forEach(this::removeMetrics);
}
private void removeMetrics(final String pipeID) {
@@ -194,12 +194,11 @@ public class PipeDataNodeSinglePipeMetrics implements IMetricSet {
Metric.PIPE_TSFILE_EVENT_TRANSFER_TIME.toString(),
Tag.NAME.toString(),
operator.getPipeName());
- remainingEventAndTimeOperatorMap.remove(pipeID);
}
//////////////////////////// register & deregister (pipe integration) ////////////////////////////
- public void register(final IoTDBDataRegionSource source) {
+ public synchronized void register(final IoTDBDataRegionSource source) {
// The metric is global thus the regionId is omitted
final String pipeID = source.getPipeName() + "_" + source.getCreationTime();
remainingEventAndTimeOperatorMap
@@ -214,7 +213,7 @@ public class PipeDataNodeSinglePipeMetrics implements IMetricSet {
}
}
- public void register(final IoTDBSchemaRegionSource source) {
+ public synchronized void register(final IoTDBSchemaRegionSource source) {
// The metric is global thus the regionId is omitted
final String pipeID = source.getPipeName() + "_" + source.getCreationTime();
remainingEventAndTimeOperatorMap
@@ -349,7 +348,7 @@ public class PipeDataNodeSinglePipeMetrics implements IMetricSet {
remainingEventAndTimeOperatorMap.get(pipeID).freezeRate(true);
}
- public void deregister(final String pipeID) {
+ public synchronized void deregister(final String pipeID) {
if (!remainingEventAndTimeOperatorMap.containsKey(pipeID)) {
LOGGER.warn(DataNodePipeMessages.FAILED_TO_DEREGISTER_PIPE_REMAINING_EVENT_AND, pipeID);
return;
@@ -357,6 +356,7 @@ public class PipeDataNodeSinglePipeMetrics implements IMetricSet {
if (Objects.nonNull(metricService)) {
removeMetrics(pipeID);
}
+ remainingEventAndTimeOperatorMap.remove(pipeID);
}
public void markRegionCommit(final String pipeID, final boolean isDataRegion) {
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/overview/PipeTsFileToTabletsMetrics.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/overview/PipeTsFileToTabletsMetrics.java
index 1a958c7d3a6..39aef7896eb 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/overview/PipeTsFileToTabletsMetrics.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/overview/PipeTsFileToTabletsMetrics.java
@@ -57,7 +57,7 @@ public class PipeTsFileToTabletsMetrics implements IMetricSet {
//////////////////////////// bindTo & unbindFrom (metric framework) ////////////////////////////
@Override
- public void bindTo(final AbstractMetricService metricService) {
+ public synchronized void bindTo(final AbstractMetricService metricService) {
this.metricService = metricService;
ImmutableSet.copyOf(pipe).forEach(this::createMetrics);
}
@@ -101,11 +101,11 @@ public class PipeTsFileToTabletsMetrics implements IMetricSet {
}
@Override
- public void unbindFrom(final AbstractMetricService metricService) {
- ImmutableSet.copyOf(pipe).forEach(this::deregister);
- if (!pipe.isEmpty()) {
- LOGGER.warn(DataNodePipeMessages.FAILED_TO_UNBIND_FROM_PIPE_TSFILE_TO, pipe);
- }
+ public synchronized void unbindFrom(final AbstractMetricService metricService) {
+ // Keep the pipes registered: they register only once, so a metric service restart
+ // must be able to bind them again.
+ // Synchronized with the (de)registrations, which may remove a registration being unbound.
+ ImmutableSet.copyOf(pipe).forEach(this::removeMetrics);
}
private void removeMetrics(final String pipeID) {
@@ -147,7 +147,7 @@ public class PipeTsFileToTabletsMetrics implements IMetricSet {
//////////////////////////// register & deregister ////////////////////////////
- public void register(final IoTDBDataRegionSource extractor) {
+ public synchronized void register(final IoTDBDataRegionSource extractor) {
final String pipeID = extractor.getPipeName() + "_" + extractor.getCreationTime();
pipe.add(pipeID);
if (Objects.nonNull(metricService)) {
@@ -155,7 +155,7 @@ public class PipeTsFileToTabletsMetrics implements IMetricSet {
}
}
- public void deregister(final String pipeID) {
+ public synchronized void deregister(final String pipeID) {
if (!pipe.contains(pipeID)) {
LOGGER.info(DataNodePipeMessages.SKIP_DEREGISTER_PIPE_TSFILE_TO_TABLETS, pipeID);
return;
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/processor/PipeProcessorMetrics.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/processor/PipeProcessorMetrics.java
index 97bb7f46341..97fb46bdb23 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/processor/PipeProcessorMetrics.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/processor/PipeProcessorMetrics.java
@@ -55,7 +55,7 @@ public class PipeProcessorMetrics implements IMetricSet {
//////////////////////////// bindTo & unbindFrom (metric framework) ////////////////////////////
@Override
- public void bindTo(final AbstractMetricService metricService) {
+ public synchronized void bindTo(final AbstractMetricService metricService) {
this.metricService = metricService;
final ImmutableSet<String> taskIDs = ImmutableSet.copyOf(processorMap.keySet());
for (final String taskID : taskIDs) {
@@ -106,14 +106,11 @@ public class PipeProcessorMetrics implements IMetricSet {
}
@Override
- public void unbindFrom(final AbstractMetricService metricService) {
- final ImmutableSet<String> taskIDs = ImmutableSet.copyOf(processorMap.keySet());
- for (final String taskID : taskIDs) {
- deregister(taskID);
- }
- if (!processorMap.isEmpty()) {
- LOGGER.warn(DataNodePipeMessages.FAILED_TO_UNBIND_FROM_PIPE_PROCESSOR_METRICS);
- }
+ public synchronized void unbindFrom(final AbstractMetricService metricService) {
+ // Keep the subtasks registered: they register only once, so a metric service restart
+ // must be able to bind them again.
+ // Synchronized with the (de)registrations, which may remove a registration being unbound.
+ ImmutableSet.copyOf(processorMap.keySet()).forEach(this::removeMetrics);
}
private void removeMetrics(final String taskID) {
@@ -157,7 +154,7 @@ public class PipeProcessorMetrics implements IMetricSet {
//////////////////////////// register & deregister (pipe integration) ////////////////////////////
- public void register(final PipeProcessorSubtask pipeProcessorSubtask) {
+ public synchronized void register(final PipeProcessorSubtask pipeProcessorSubtask) {
final String taskID = pipeProcessorSubtask.getTaskID();
processorMap.putIfAbsent(taskID, pipeProcessorSubtask);
if (Objects.nonNull(metricService)) {
@@ -165,7 +162,7 @@ public class PipeProcessorMetrics implements IMetricSet {
}
}
- public void deregister(final String taskID) {
+ public synchronized void deregister(final String taskID) {
if (!processorMap.containsKey(taskID)) {
// Allow calls from schema region tasks
return;
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/schema/PipeSchemaRegionListenerMetrics.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/schema/PipeSchemaRegionListenerMetrics.java
index 526ec7df281..8e89d156829 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/schema/PipeSchemaRegionListenerMetrics.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/schema/PipeSchemaRegionListenerMetrics.java
@@ -50,7 +50,7 @@ public class PipeSchemaRegionListenerMetrics implements IMetricSet {
//////////////////////////// bindTo & unbindFrom (metric framework) ////////////////////////////
@Override
- public void bindTo(final AbstractMetricService metricService) {
+ public synchronized void bindTo(final AbstractMetricService metricService) {
this.metricService = metricService;
ImmutableSet.copyOf(listeningQueueMap.keySet()).forEach(this::createMetrics);
}
@@ -70,11 +70,11 @@ public class PipeSchemaRegionListenerMetrics implements IMetricSet {
}
@Override
- public void unbindFrom(final AbstractMetricService metricService) {
- ImmutableSet.copyOf(listeningQueueMap.keySet()).forEach(this::deregister);
- if (!listeningQueueMap.isEmpty()) {
- LOGGER.warn(DataNodePipeMessages.FAILED_TO_UNBIND_FROM_PIPE_SCHEMA_REGION_2);
- }
+ public synchronized void unbindFrom(final AbstractMetricService metricService) {
+ // Keep the queues registered: they register only once, so a metric service restart
+ // must be able to bind them again.
+ // Synchronized with the (de)registrations, which may remove a registration being unbound.
+ ImmutableSet.copyOf(listeningQueueMap.keySet()).forEach(this::removeMetrics);
}
private void removeMetrics(final Integer schemaRegionId) {
@@ -91,7 +91,7 @@ public class PipeSchemaRegionListenerMetrics implements IMetricSet {
//////////////////////////// register & deregister (pipe integration) ////////////////////////////
- public void register(
+ public synchronized void register(
final SchemaRegionListeningQueue schemaRegionListeningQueue, final Integer schemaRegionId) {
listeningQueueMap.putIfAbsent(schemaRegionId, schemaRegionListeningQueue);
if (Objects.nonNull(metricService)) {
@@ -99,7 +99,7 @@ public class PipeSchemaRegionListenerMetrics implements IMetricSet {
}
}
- public void deregister(final Integer schemaRegionId) {
+ public synchronized void deregister(final Integer schemaRegionId) {
if (!listeningQueueMap.containsKey(schemaRegionId)) {
LOGGER.warn(
DataNodePipeMessages.FAILED_TO_DEREGISTER_SCHEMA_REGION_LISTENER_METRICS, schemaRegionId);
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/schema/PipeSchemaRegionSinkMetrics.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/schema/PipeSchemaRegionSinkMetrics.java
index cc8c74e2984..d611592a3ee 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/schema/PipeSchemaRegionSinkMetrics.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/schema/PipeSchemaRegionSinkMetrics.java
@@ -51,7 +51,7 @@ public class PipeSchemaRegionSinkMetrics implements IMetricSet {
//////////////////////////// bindTo & unbindFrom (metric framework) ////////////////////////////
@Override
- public void bindTo(final AbstractMetricService metricService) {
+ public synchronized void bindTo(final AbstractMetricService metricService) {
this.metricService = metricService;
ImmutableSet.copyOf(connectorMap.keySet()).forEach(this::createMetrics);
}
@@ -129,11 +129,11 @@ public class PipeSchemaRegionSinkMetrics implements IMetricSet {
}
@Override
- public void unbindFrom(final AbstractMetricService metricService) {
- ImmutableSet.copyOf(connectorMap.keySet()).forEach(this::deregister);
- if (!connectorMap.isEmpty()) {
- LOGGER.warn(DataNodePipeMessages.FAILED_TO_UNBIND_FROM_PIPE_SCHEMA_REGION);
- }
+ public synchronized void unbindFrom(final AbstractMetricService metricService) {
+ // Keep the subtasks registered: they register only once, so a metric service restart
+ // must be able to bind them again.
+ // Synchronized with the (de)registrations, which may remove a registration being unbound.
+ ImmutableSet.copyOf(connectorMap.keySet()).forEach(this::removeMetrics);
}
private void removeMetrics(final String taskID) {
@@ -167,7 +167,7 @@ public class PipeSchemaRegionSinkMetrics implements IMetricSet {
//////////////////////////// Register & deregister (pipe integration) ////////////////////////////
- public void register(final PipeSinkSubtask pipeSinkSubtask) {
+ public synchronized void register(final PipeSinkSubtask pipeSinkSubtask) {
final String taskID = pipeSinkSubtask.getTaskID();
connectorMap.putIfAbsent(taskID, pipeSinkSubtask);
if (Objects.nonNull(metricService)) {
@@ -175,7 +175,7 @@ public class PipeSchemaRegionSinkMetrics implements IMetricSet {
}
}
- public void deregister(final String taskID) {
+ public synchronized void deregister(final String taskID) {
if (!connectorMap.containsKey(taskID)) {
LOGGER.warn(
DataNodePipeMessages.FAILED_TO_DEREGISTER_PIPE_SCHEMA_REGION_CONNECTOR,
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/schema/PipeSchemaRegionSourceMetrics.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/schema/PipeSchemaRegionSourceMetrics.java
index 459fa9fcaa3..9e9aeaebe0e 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/schema/PipeSchemaRegionSourceMetrics.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/schema/PipeSchemaRegionSourceMetrics.java
@@ -48,7 +48,7 @@ public class PipeSchemaRegionSourceMetrics implements IMetricSet {
//////////////////////////// bindTo & unbindFrom (metric framework) ////////////////////////////
@Override
- public void bindTo(final AbstractMetricService metricService) {
+ public synchronized void bindTo(final AbstractMetricService metricService) {
this.metricService = metricService;
ImmutableSet.copyOf(sourceMap.keySet()).forEach(this::createMetrics);
}
@@ -73,11 +73,11 @@ public class PipeSchemaRegionSourceMetrics implements IMetricSet {
}
@Override
- public void unbindFrom(final AbstractMetricService metricService) {
- ImmutableSet.copyOf(sourceMap.keySet()).forEach(this::deregister);
- if (!sourceMap.isEmpty()) {
- LOGGER.warn(DataNodePipeMessages.FAILED_TO_UNBIND_FROM_PIPE_SCHEMA_REGION_1);
- }
+ public synchronized void unbindFrom(final AbstractMetricService metricService) {
+ // Keep the sources registered: they register only once, so a metric service restart
+ // must be able to bind them again.
+ // Synchronized with the (de)registrations, which may remove a registration being unbound.
+ ImmutableSet.copyOf(sourceMap.keySet()).forEach(this::removeMetrics);
}
private void removeMetrics(final String taskID) {
@@ -100,7 +100,7 @@ public class PipeSchemaRegionSourceMetrics implements IMetricSet {
//////////////////////////// register & deregister (pipe integration) ////////////////////////////
- public void register(final IoTDBSchemaRegionSource source) {
+ public synchronized void register(final IoTDBSchemaRegionSource source) {
final String taskID = source.getTaskID();
sourceMap.putIfAbsent(taskID, source);
if (Objects.nonNull(metricService)) {
@@ -108,7 +108,7 @@ public class PipeSchemaRegionSourceMetrics implements IMetricSet {
}
}
- public void deregister(final String taskID) {
+ public synchronized void deregister(final String taskID) {
if (!sourceMap.containsKey(taskID)) {
LOGGER.warn(DataNodePipeMessages.FAILED_TO_DEREGISTER_PIPE_SCHEMA_REGION_SOURCE, taskID);
return;
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/sink/PipeDataRegionSinkMetrics.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/sink/PipeDataRegionSinkMetrics.java
index 9990625dd79..63189a8e14a 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/sink/PipeDataRegionSinkMetrics.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/sink/PipeDataRegionSinkMetrics.java
@@ -248,13 +248,10 @@ public class PipeDataRegionSinkMetrics implements IMetricSet {
@Override
public synchronized void unbindFrom(final AbstractMetricService metricService) {
- final ImmutableSet<String> taskIDs = ImmutableSet.copyOf(sinkMap.keySet());
- for (final String taskID : taskIDs) {
- deregister(taskID);
- }
- if (!sinkMap.isEmpty()) {
- LOGGER.warn(DataNodePipeMessages.FAILED_TO_UNBIND_FROM_PIPE_DATA_REGION);
- }
+ // Keep the subtasks registered: they register only once, so a metric service restart
+ // must be able to bind them again.
+ // Synchronized with the (de)registrations, which may remove a registration being unbound.
+ ImmutableSet.copyOf(sinkMap.keySet()).forEach(this::removeMetrics);
}
private void removeMetrics(final String taskID) {
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/source/PipeAssignerMetrics.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/source/PipeAssignerMetrics.java
index 3665ec24294..b2a44eef42a 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/source/PipeAssignerMetrics.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/source/PipeAssignerMetrics.java
@@ -28,7 +28,6 @@ import org.apache.iotdb.metrics.metricsets.IMetricSet;
import org.apache.iotdb.metrics.utils.MetricLevel;
import org.apache.iotdb.metrics.utils.MetricType;
-import com.google.common.collect.ImmutableSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -86,12 +85,12 @@ public class PipeAssignerMetrics implements IMetricSet {
@Override
public void unbindFrom(AbstractMetricService metricService) {
- ImmutableSet<Integer> dataRegionIds = ImmutableSet.copyOf(assignerMap.keySet());
- for (int dataRegionId : dataRegionIds) {
- deregister(dataRegionId);
- }
- if (!assignerMap.isEmpty()) {
- LOGGER.warn(DataNodePipeMessages.FAILED_TO_UNBIND_FROM_PIPE_ASSIGNER_METRICS);
+ // Keep the assigners registered: they register only once, so a metric service restart
+ // must be able to bind them again.
+ synchronized (this) {
+ for (int dataRegionId : assignerMap.keySet()) {
+ removeMetrics(dataRegionId);
+ }
}
}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/source/PipeDataRegionSourceMetrics.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/source/PipeDataRegionSourceMetrics.java
index f44799e0550..11622f4bae1 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/source/PipeDataRegionSourceMetrics.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/source/PipeDataRegionSourceMetrics.java
@@ -63,7 +63,7 @@ public class PipeDataRegionSourceMetrics implements IMetricSet {
//////////////////////////// bindTo & unbindFrom (metric framework) ////////////////////////////
@Override
- public void bindTo(final AbstractMetricService metricService) {
+ public synchronized void bindTo(final AbstractMetricService metricService) {
this.metricService = metricService;
final ImmutableSet<String> taskIDs = ImmutableSet.copyOf(extractorMap.keySet());
for (final String taskID : taskIDs) {
@@ -181,14 +181,11 @@ public class PipeDataRegionSourceMetrics implements IMetricSet {
}
@Override
- public void unbindFrom(final AbstractMetricService metricService) {
- final ImmutableSet<String> taskIDs = ImmutableSet.copyOf(extractorMap.keySet());
- for (final String taskID : taskIDs) {
- deregister(taskID);
- }
- if (!extractorMap.isEmpty()) {
- LOGGER.warn(DataNodePipeMessages.FAILED_TO_UNBIND_FROM_PIPE_EXTRACTOR_METRICS);
- }
+ public synchronized void unbindFrom(final AbstractMetricService metricService) {
+ // Keep the extractors registered: they register only once, so a metric service restart
+ // must be able to bind them again.
+ // Synchronized with the (de)registrations, which may remove a registration being unbound.
+ ImmutableSet.copyOf(extractorMap.keySet()).forEach(this::removeMetrics);
}
private void removeMetrics(final String taskID) {
@@ -285,11 +282,12 @@ public class PipeDataRegionSourceMetrics implements IMetricSet {
String.valueOf(extractor.getRegionId()),
Tag.CREATION_TIME.toString(),
String.valueOf(extractor.getCreationTime()));
+ recentProcessedTsFileEpochStateMap.remove(taskID);
}
//////////////////////////// register & deregister (pipe integration) ////////////////////////////
- public void register(final IoTDBDataRegionSource extractor) {
+ public synchronized void register(final IoTDBDataRegionSource extractor) {
final String taskID = extractor.getTaskID();
extractorMap.putIfAbsent(taskID, extractor);
if (Objects.nonNull(metricService)) {
@@ -297,7 +295,7 @@ public class PipeDataRegionSourceMetrics implements IMetricSet {
}
}
- public void deregister(final String taskID) {
+ public synchronized void deregister(final String taskID) {
if (!extractorMap.containsKey(taskID)) {
LOGGER.warn(DataNodePipeMessages.FAILED_TO_DEREGISTER_PIPE_DATA_REGION_EXTRACTOR, taskID);
return;
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/airgap/IoTDBDataRegionAirGapSink.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/airgap/IoTDBDataRegionAirGapSink.java
index 6ef1c47ad2e..7261648ab5f 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/airgap/IoTDBDataRegionAirGapSink.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/airgap/IoTDBDataRegionAirGapSink.java
@@ -665,7 +665,8 @@ public class IoTDBDataRegionAirGapSink extends IoTDBDataNodeAirGapSink {
@Override
protected byte[] compressIfNeeded(final byte[] reqInBytes) throws IOException {
- if (Objects.isNull(compressionTimer) && Objects.nonNull(sinkTaskId)) {
+ if (Objects.nonNull(sinkTaskId)) {
+ // Do not cache the timer, the metric service replaces it when it restarts
compressionTimer = PipeDataRegionSinkMetrics.getInstance().getCompressionTimer(sinkTaskId);
}
return super.compressIfNeeded(reqInBytes);
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/async/IoTDBDataRegionAsyncSink.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/async/IoTDBDataRegionAsyncSink.java
index c0da24658fd..4b7f332dbe3 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/async/IoTDBDataRegionAsyncSink.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/async/IoTDBDataRegionAsyncSink.java
@@ -707,7 +707,8 @@ public class IoTDBDataRegionAsyncSink extends IoTDBSink implements PipeSinkWithS
@Override
public TPipeTransferReq compressIfNeeded(final TPipeTransferReq req) throws IOException {
- if (Objects.isNull(compressionTimer) && Objects.nonNull(sinkTaskId)) {
+ if (Objects.nonNull(sinkTaskId)) {
+ // Do not cache the timer, the metric service replaces it when it restarts
compressionTimer = PipeDataRegionSinkMetrics.getInstance().getCompressionTimer(sinkTaskId);
}
return super.compressIfNeeded(req);
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/sync/IoTDBDataRegionSyncSink.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/sync/IoTDBDataRegionSyncSink.java
index d7538907dd6..3b858b9d712 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/sync/IoTDBDataRegionSyncSink.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/sync/IoTDBDataRegionSyncSink.java
@@ -791,7 +791,8 @@ public class IoTDBDataRegionSyncSink extends IoTDBDataNodeSyncSink {
@Override
public TPipeTransferReq compressIfNeeded(final TPipeTransferReq req) throws IOException {
- if (Objects.isNull(compressionTimer) && Objects.nonNull(sinkTaskId)) {
+ if (Objects.nonNull(sinkTaskId)) {
+ // Do not cache the timer, the metric service replaces it when it restarts
compressionTimer = PipeDataRegionSinkMetrics.getInstance().getCompressionTimer(sinkTaskId);
}
return super.compressIfNeeded(req);
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/handler/RPCServiceThriftHandlerMetrics.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/handler/RPCServiceThriftHandlerMetrics.java
index 4189d5f4b21..12695ee933b 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/handler/RPCServiceThriftHandlerMetrics.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/handler/RPCServiceThriftHandlerMetrics.java
@@ -31,9 +31,12 @@ import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong;
public class RPCServiceThriftHandlerMetrics implements IMetricSet {
+ // The singleton owns the decoding metrics, and each handler registers its own instance owning the
+ // connection metric. A metric must have a single owner, otherwise rebinding one of the instances,
+ // e.g. when the metric service restarts, drops the metrics bound by the other.
private static final RPCServiceThriftHandlerMetrics INSTANCE =
- new RPCServiceThriftHandlerMetrics(new AtomicLong(0));
- private AtomicLong thriftConnectionNumber;
+ new RPCServiceThriftHandlerMetrics(null);
+ private final AtomicLong thriftConnectionNumber;
// region begin
private Gauge unCompressionSizeTimer = DoNothingMetricManager.DO_NOTHING_GAUGE;
@@ -70,13 +73,16 @@ public class RPCServiceThriftHandlerMetrics implements IMetricSet {
@Override
public void bindTo(AbstractMetricService metricService) {
- metricService.createAutoGauge(
- Metric.THRIFT_CONNECTIONS.toString(),
- MetricLevel.CORE,
- thriftConnectionNumber,
- AtomicLong::get,
- Tag.NAME.toString(),
- "ClientRPC");
+ if (Objects.nonNull(thriftConnectionNumber)) {
+ metricService.createAutoGauge(
+ Metric.THRIFT_CONNECTIONS.toString(),
+ MetricLevel.CORE,
+ thriftConnectionNumber,
+ AtomicLong::get,
+ Tag.NAME.toString(),
+ "ClientRPC");
+ return;
+ }
unCompressionSizeTimer =
metricService.getOrCreateGauge(
@@ -116,11 +122,14 @@ public class RPCServiceThriftHandlerMetrics implements IMetricSet {
@Override
public void unbindFrom(AbstractMetricService metricService) {
- metricService.remove(
- MetricType.AUTO_GAUGE,
- Metric.THRIFT_CONNECTIONS.toString(),
- Tag.NAME.toString(),
- "ClientRPC");
+ if (Objects.nonNull(thriftConnectionNumber)) {
+ metricService.remove(
+ MetricType.AUTO_GAUGE,
+ Metric.THRIFT_CONNECTIONS.toString(),
+ Tag.NAME.toString(),
+ "ClientRPC");
+ return;
+ }
metricService.remove(
MetricType.GAUGE,
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/cache/partition/PartitionCache.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/cache/partition/PartitionCache.java
index db58481285e..0b8dfbed002 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/cache/partition/PartitionCache.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/cache/partition/PartitionCache.java
@@ -149,7 +149,7 @@ public class PartitionCache {
this.partitionExecutor =
SeriesPartitionExecutor.getSeriesPartitionExecutor(
this.seriesSlotExecutorName, this.seriesPartitionSlotNum);
- this.cacheMetrics = new CacheMetrics();
+ this.cacheMetrics = CacheMetrics.getInstance();
}
protected void failIfMetadataLeaseFenced() {
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/metric/SchemaEngineMemMetric.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/metric/SchemaEngineMemMetric.java
index e91811c50ec..e87c8cfba17 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/metric/SchemaEngineMemMetric.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/metric/SchemaEngineMemMetric.java
@@ -101,12 +101,14 @@ public class SchemaEngineMemMetric implements ISchemaEngineMetric {
MetricLevel.IMPORTANT,
Tag.NAME.toString(),
SCHEMA_CONSENSUS);
+ // A table binds its metrics only when its first device is added, so bind the existing ones here
+ engineStatistics.getTable2DevicesNumMap().keySet().forEach(this::bindTableMetrics);
}
@Override
public void bindTableMetrics(final String tableName) {
metricService.createAutoGauge(
- Metric.SCHEMA_ENGINE.toString(),
+ Metric.SCHEMA_ENGINE_TABLE.toString(),
MetricLevel.IMPORTANT,
engineStatistics,
statistics -> statistics.getTableDeviceNumber(tableName),
@@ -138,13 +140,14 @@ public class SchemaEngineMemMetric implements ISchemaEngineMetric {
MetricType.GAUGE, Metric.SCHEMA_ENGINE.toString(), Tag.NAME.toString(), SCHEMA_ENGINE_MODE);
metricService.remove(
MetricType.GAUGE, Metric.SCHEMA_ENGINE.toString(), Tag.NAME.toString(), SCHEMA_CONSENSUS);
+ engineStatistics.getTable2DevicesNumMap().keySet().forEach(this::unbindTableMetrics);
}
@Override
public void unbindTableMetrics(final String tableName) {
metricService.remove(
MetricType.AUTO_GAUGE,
- Metric.SCHEMA_ENGINE.toString(),
+ Metric.SCHEMA_ENGINE_TABLE.toString(),
Tag.NAME.toString(),
TABLE_DEVICE_NUMBER,
TABLE,
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/metric/SchemaRegionMemMetric.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/metric/SchemaRegionMemMetric.java
index 31f02e9c353..8969feb3f74 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/metric/SchemaRegionMemMetric.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/metric/SchemaRegionMemMetric.java
@@ -133,12 +133,14 @@ public class SchemaRegionMemMetric implements ISchemaRegionMetric {
regionTagValue,
Tag.DATABASE.toString(),
database);
+ // A table binds its metrics only when its first device is added, so bind the existing ones here
+ regionStatistics.getTable2DevicesNumMap().keySet().forEach(this::bindTableMetrics);
}
@Override
public void bindTableMetrics(final String tableName) {
metricService.createAutoGauge(
- Metric.SCHEMA_REGION.toString(),
+ Metric.SCHEMA_REGION_TABLE.toString(),
MetricLevel.IMPORTANT,
regionStatistics,
statistics -> statistics.getTableDevicesNumber(tableName),
@@ -218,13 +220,14 @@ public class SchemaRegionMemMetric implements ISchemaRegionMetric {
regionTagValue,
Tag.DATABASE.toString(),
database);
+ regionStatistics.getTable2DevicesNumMap().keySet().forEach(this::unbindTableMetrics);
}
@Override
public void unbindTableMetrics(final String tableName) {
metricService.remove(
MetricType.AUTO_GAUGE,
- Metric.SCHEMA_REGION.toString(),
+ Metric.SCHEMA_REGION_TABLE.toString(),
Tag.NAME.toString(),
TABLE_DEVICE_NUMBER,
Tag.REGION.toString(),
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/rescon/MemSchemaEngineStatistics.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/rescon/MemSchemaEngineStatistics.java
index 7c8c2677841..56fb941b577 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/rescon/MemSchemaEngineStatistics.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/rescon/MemSchemaEngineStatistics.java
@@ -144,6 +144,10 @@ public class MemSchemaEngineStatistics implements ISchemaEngineStatistics {
return tableDeviceNumber.getOrDefault(tableName, 0L);
}
+ public Map<String, Long> getTable2DevicesNumMap() {
+ return tableDeviceNumber;
+ }
+
@Override
public int getSchemaRegionNumber() {
return SchemaEngine.getInstance().getSchemaRegionNumber();
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/metrics/CacheMetrics.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/metrics/CacheMetrics.java
index 2225a377f9f..2eb1b3ed9dd 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/metrics/CacheMetrics.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/metrics/CacheMetrics.java
@@ -147,4 +147,21 @@ public class CacheMetrics implements IMetricSet {
break;
}
}
+
+ private static class CacheMetricsHolder {
+
+ private static final CacheMetrics INSTANCE = new CacheMetrics();
+
+ private CacheMetricsHolder() {
+ // empty constructor
+ }
+ }
+
+ public static CacheMetrics getInstance() {
+ return CacheMetricsHolder.INSTANCE;
+ }
+
+ private CacheMetrics() {
+ // empty constructor
+ }
}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/metrics/DataNodeMetricsHelper.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/metrics/DataNodeMetricsHelper.java
index d857f546e2d..55e71ca9ecf 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/metrics/DataNodeMetricsHelper.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/metrics/DataNodeMetricsHelper.java
@@ -87,6 +87,7 @@ public class DataNodeMetricsHelper {
metricService.addMetricSet(DataExchangeCountMetricSet.getInstance());
metricService.addMetricSet(DriverSchedulerMetricSet.getInstance());
metricService.addMetricSet(QueryRelatedResourceMetricSet.getInstance());
+ metricService.addMetricSet(CacheMetrics.getInstance());
// bind performance overview related metrics
metricService.addMetricSet(PerformanceOverviewMetrics.getInstance());
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/metrics/WritingMetrics.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/metrics/WritingMetrics.java
index bd2f124e060..9333a0e6082 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/metrics/WritingMetrics.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/metrics/WritingMetrics.java
@@ -43,10 +43,14 @@ import org.apache.iotdb.metrics.utils.MetricType;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.atomic.DoubleAdder;
import java.util.concurrent.atomic.LongAdder;
+import java.util.stream.Collectors;
public class WritingMetrics implements IMetricSet {
private static final WritingMetrics INSTANCE = new WritingMetrics();
@@ -204,6 +208,8 @@ public class WritingMetrics implements IMetricSet {
private Histogram readWALBufferCostHistogram = DoNothingMetricManager.DO_NOTHING_HISTOGRAM;
private Histogram writeWALBufferCostHistogram = DoNothingMetricManager.DO_NOTHING_HISTOGRAM;
private Gauge walQueueMaxMemSizeGauge = DoNothingMetricManager.DO_NOTHING_GAUGE;
+ // Recorded rarely, so keep the value to restore it when the metric service recreates the gauge
+ private volatile long walQueueMaxMemSize;
private void bindWALMetrics(AbstractMetricService metricService) {
metricService.createAutoGauge(
@@ -277,6 +283,7 @@ public class WritingMetrics implements IMetricSet {
MetricLevel.IMPORTANT,
Tag.NAME.toString(),
WAL_QUEUE_MAX_MEM_COST);
+ walQueueMaxMemSizeGauge.set(walQueueMaxMemSize);
SystemInfo systemInfo = SystemInfo.getInstance();
metricService.createAutoGauge(
Metric.WAL_QUEUE_MEM_COST.toString(),
@@ -455,6 +462,16 @@ public class WritingMetrics implements IMetricSet {
private Gauge flushThreholdGauge = DoNothingMetricManager.DO_NOTHING_GAUGE;
private Gauge rejectThreholdGauge = DoNothingMetricManager.DO_NOTHING_GAUGE;
+ // Recorded rarely, so keep the values to restore them when the metric service recreates the
+ // gauges
+ private volatile long flushThreshold;
+ private volatile long rejectThreshold;
+
+ // The active memtable and time partition counters are levels maintained by deltas, so keep the
+ // levels to restore them when the metric service recreates the counters
+ private final Map<String, Long> activeMemTableCounts = new HashMap<>();
+ private long activeTimePartitionCount;
+ private final Object activeCountLock = new Object();
private Timer memtableLiveTimer = DoNothingMetricManager.DO_NOTHING_TIMER;
@@ -482,6 +499,8 @@ public class WritingMetrics implements IMetricSet {
manualFlushMemtableCounter = createManualFlushMemTableCounterMetrics();
memControlFlushMemtableCounter = createMemControlFlushMemTableCounterMetrics();
+ restoreActiveCounts(allDataRegionIds);
+
flushThreholdGauge =
MetricService.getInstance()
.getOrCreateGauge(
@@ -489,6 +508,7 @@ public class WritingMetrics implements IMetricSet {
MetricLevel.IMPORTANT,
Tag.TYPE.toString(),
FLUSH_THRESHOLD);
+ flushThreholdGauge.set(flushThreshold);
rejectThreholdGauge =
MetricService.getInstance()
.getOrCreateGauge(
@@ -496,6 +516,7 @@ public class WritingMetrics implements IMetricSet {
MetricLevel.IMPORTANT,
Tag.TYPE.toString(),
REJECT_THRESHOLD);
+ rejectThreholdGauge.set(rejectThreshold);
memtableLiveTimer =
MetricService.getInstance()
@@ -981,15 +1002,18 @@ public class WritingMetrics implements IMetricSet {
}
public void recordWALQueueMaxMemorySize(long size) {
+ walQueueMaxMemSize = size;
walQueueMaxMemSizeGauge.set(size);
}
public void recordFlushThreshold(double flushThreshold) {
- flushThreholdGauge.set((long) flushThreshold);
+ this.flushThreshold = (long) flushThreshold;
+ flushThreholdGauge.set(this.flushThreshold);
}
public void recordRejectThreshold(double rejectThreshold) {
- rejectThreholdGauge.set((long) rejectThreshold);
+ this.rejectThreshold = (long) rejectThreshold;
+ rejectThreholdGauge.set(this.rejectThreshold);
}
public void recordMemTableLiveDuration(long durationMillis) {
@@ -1013,18 +1037,52 @@ public class WritingMetrics implements IMetricSet {
}
public void recordActiveMemTableCount(String dataRegionId, int number) {
- MetricService.getInstance()
- .count(
- number,
- Metric.ACTIVE_MEMTABLE_COUNT.toString(),
- MetricLevel.IMPORTANT,
- Tag.REGION.toString(),
- dataRegionId);
+ // Use the region tag of the counter created and removed with the region
+ String region = new DataRegionId(Integer.parseInt(dataRegionId)).toString();
+ synchronized (activeCountLock) {
+ activeMemTableCounts.merge(region, (long) number, Long::sum);
+ MetricService.getInstance()
+ .count(
+ number,
+ Metric.ACTIVE_MEMTABLE_COUNT.toString(),
+ MetricLevel.IMPORTANT,
+ Tag.REGION.toString(),
+ region);
+ }
}
public void recordActiveTimePartitionCount(int number) {
- MetricService.getInstance()
- .count(number, Metric.ACTIVE_TIME_PARTITION_COUNT.toString(), MetricLevel.IMPORTANT);
+ synchronized (activeCountLock) {
+ activeTimePartitionCount += number;
+ MetricService.getInstance()
+ .count(number, Metric.ACTIVE_TIME_PARTITION_COUNT.toString(), MetricLevel.IMPORTANT);
+ }
+ }
+
+ private void restoreActiveCounts(List<DataRegionId> dataRegionIds) {
+ Set<String> existingDataRegionIds =
+ dataRegionIds.stream().map(DataRegionId::toString).collect(Collectors.toSet());
+ synchronized (activeCountLock) {
+ // Do not bring back the counters of the removed regions
+ activeMemTableCounts
+ .entrySet()
+ .removeIf(
+ entry -> entry.getValue() == 0 && !existingDataRegionIds.contains(entry.getKey()));
+ activeMemTableCounts.forEach(
+ (dataRegionId, count) ->
+ restoreCounter(
+ count,
+ Metric.ACTIVE_MEMTABLE_COUNT.toString(),
+ Tag.REGION.toString(),
+ dataRegionId));
+ restoreCounter(activeTimePartitionCount, Metric.ACTIVE_TIME_PARTITION_COUNT.toString());
+ }
+ }
+
+ private static void restoreCounter(long count, String metric, String... tags) {
+ Counter counter =
+ MetricService.getInstance().getOrCreateCounter(metric, MetricLevel.IMPORTANT, tags);
+ counter.inc(count - counter.getCount());
}
// endregion
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/metrics/file/TsFileMetrics.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/metrics/file/TsFileMetrics.java
index de0c0fd23d6..f9c5ff089f0 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/metrics/file/TsFileMetrics.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/metrics/file/TsFileMetrics.java
@@ -83,6 +83,17 @@ public class TsFileMetrics implements IMetricSet {
@Override
public void bindTo(AbstractMetricService metricService) {
this.metricService.set(metricService);
+ // The cached gauges may have been dropped by the metric service, e.g. when it restarts, so
+ // recreate all of them from the recorded values.
+ Arrays.asList(seqFileCountMap, unseqFileCountMap)
+ .forEach(map -> map.values().forEach(TsFileMetrics::clearGauges));
+ Arrays.asList(seqFileSizeMap, unseqFileSizeMap)
+ .forEach(map -> map.values().forEach(TsFileMetrics::clearGauges));
+ Arrays.asList(seqLevelTsFileCountMap, unseqLevelTsFileCountMap)
+ .forEach(TsFileMetrics::clearGauges);
+ Arrays.asList(seqLevelTsFileSizeMap, unseqLevelTsFileSizeMap)
+ .forEach(TsFileMetrics::clearGauges);
+ hasRemainData.set(true);
checkIfThereRemainingData();
}
@@ -91,6 +102,17 @@ public class TsFileMetrics implements IMetricSet {
// do nothing here
}
+ private static <K, V> void clearGauges(Map<K, Pair<V, Gauge>> map) {
+ for (K key : map.keySet()) {
+ map.computeIfPresent(
+ key,
+ (k, v) -> {
+ v.setRight(null);
+ return v;
+ });
+ }
+ }
+
// region external update tsfile related metrics
public void addTsFile(TsFileResource tsFileResource) {
if (!tsFileResource.markAsRecordedByMetric()) {
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/DataRegionMetrics.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/DataRegionMetrics.java
index 7fc0f6b8c8b..dcdc7f533eb 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/DataRegionMetrics.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/DataRegionMetrics.java
@@ -26,9 +26,18 @@ import org.apache.iotdb.metrics.metricsets.IMetricSet;
import org.apache.iotdb.metrics.utils.MetricLevel;
import org.apache.iotdb.metrics.utils.MetricType;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
public class DataRegionMetrics implements IMetricSet {
+ // A database has several data regions but a single memory gauge, which sums up the memory of the
+ // regions bound to it. Each region adds itself when binding, and the last region to unbind
+ // removes the gauge.
+ private static final Map<String, Set<DataRegion>> DATABASE_TO_DATA_REGIONS = new HashMap<>();
+
private final DataRegion dataRegion;
private final String databaseName;
@@ -39,22 +48,38 @@ public class DataRegionMetrics implements IMetricSet {
@Override
public void bindTo(AbstractMetricService metricService) {
- metricService.createAutoGauge(
- Metric.MEM.toString(),
- MetricLevel.IMPORTANT,
- dataRegion,
- DataRegion::getMemCost,
- Tag.NAME.toString(),
- "database_" + databaseName);
+ synchronized (DATABASE_TO_DATA_REGIONS) {
+ Set<DataRegion> dataRegions =
+ DATABASE_TO_DATA_REGIONS.computeIfAbsent(
+ databaseName, database -> ConcurrentHashMap.newKeySet());
+ dataRegions.add(dataRegion);
+ metricService.createAutoGauge(
+ Metric.MEM.toString(),
+ MetricLevel.IMPORTANT,
+ dataRegions,
+ regions -> regions.stream().mapToLong(DataRegion::getMemCost).sum(),
+ Tag.NAME.toString(),
+ "database_" + databaseName);
+ }
}
@Override
public void unbindFrom(AbstractMetricService metricService) {
- metricService.remove(
- MetricType.AUTO_GAUGE,
- Metric.MEM.toString(),
- Tag.NAME.toString(),
- "database_" + databaseName);
+ synchronized (DATABASE_TO_DATA_REGIONS) {
+ Set<DataRegion> dataRegions = DATABASE_TO_DATA_REGIONS.get(databaseName);
+ if (Objects.nonNull(dataRegions)) {
+ dataRegions.remove(dataRegion);
+ if (!dataRegions.isEmpty()) {
+ return;
+ }
+ DATABASE_TO_DATA_REGIONS.remove(databaseName);
+ }
+ metricService.remove(
+ MetricType.AUTO_GAUGE,
+ Metric.MEM.toString(),
+ Tag.NAME.toString(),
+ "database_" + databaseName);
+ }
}
@Override
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessorInfoMetrics.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessorInfoMetrics.java
index fb106a9bac7..dc19f3131ca 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessorInfoMetrics.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessorInfoMetrics.java
@@ -18,7 +18,6 @@
*/
package org.apache.iotdb.db.storageengine.dataregion.memtable;
-import org.apache.iotdb.commons.service.metric.MetricService;
import org.apache.iotdb.commons.service.metric.enums.Metric;
import org.apache.iotdb.commons.service.metric.enums.Tag;
import org.apache.iotdb.metrics.AbstractMetricService;
@@ -26,7 +25,19 @@ import org.apache.iotdb.metrics.metricsets.IMetricSet;
import org.apache.iotdb.metrics.utils.MetricLevel;
import org.apache.iotdb.metrics.utils.MetricType;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
public class TsFileProcessorInfoMetrics implements IMetricSet {
+ // A database has many TsFileProcessors but a single chunk metadata memory gauge, which sums up
+ // the memory of the processors bound to it. Each processor adds itself when binding, and the last
+ // processor to unbind removes the gauge.
+ private static final Map<String, Set<TsFileProcessorInfo>> DATABASE_TO_PROCESSOR_INFOS =
+ new HashMap<>();
+
private final String databaseName;
private final TsFileProcessorInfo tsFileProcessorInfo;
@@ -38,23 +49,37 @@ public class TsFileProcessorInfoMetrics implements IMetricSet {
@Override
public void bindTo(AbstractMetricService metricService) {
- MetricService.getInstance()
- .createAutoGauge(
- Metric.MEM.toString(),
- MetricLevel.IMPORTANT,
- tsFileProcessorInfo,
- TsFileProcessorInfo::getMemCost,
- Tag.NAME.toString(),
- "chunkMetaData_" + databaseName);
+ synchronized (DATABASE_TO_PROCESSOR_INFOS) {
+ Set<TsFileProcessorInfo> processorInfos =
+ DATABASE_TO_PROCESSOR_INFOS.computeIfAbsent(
+ databaseName, database -> ConcurrentHashMap.newKeySet());
+ processorInfos.add(tsFileProcessorInfo);
+ metricService.createAutoGauge(
+ Metric.MEM.toString(),
+ MetricLevel.IMPORTANT,
+ processorInfos,
+ infos -> infos.stream().mapToLong(TsFileProcessorInfo::getMemCost).sum(),
+ Tag.NAME.toString(),
+ "chunkMetaData_" + databaseName);
+ }
}
@Override
public void unbindFrom(AbstractMetricService metricService) {
- MetricService.getInstance()
- .remove(
- MetricType.AUTO_GAUGE,
- Metric.MEM.toString(),
- Tag.NAME.toString(),
- "chunkMetaData_" + databaseName);
+ synchronized (DATABASE_TO_PROCESSOR_INFOS) {
+ Set<TsFileProcessorInfo> processorInfos = DATABASE_TO_PROCESSOR_INFOS.get(databaseName);
+ if (Objects.nonNull(processorInfos)) {
+ processorInfos.remove(tsFileProcessorInfo);
+ if (!processorInfos.isEmpty()) {
+ return;
+ }
+ DATABASE_TO_PROCESSOR_INFOS.remove(databaseName);
+ }
+ metricService.remove(
+ MetricType.AUTO_GAUGE,
+ Metric.MEM.toString(),
+ Tag.NAME.toString(),
+ "chunkMetaData_" + databaseName);
+ }
}
}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/memory/LoadTsFileMemoryBlock.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/memory/LoadTsFileMemoryBlock.java
index 43c239642d0..fd3a76b0122 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/memory/LoadTsFileMemoryBlock.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/memory/LoadTsFileMemoryBlock.java
@@ -19,12 +19,8 @@
package org.apache.iotdb.db.storageengine.load.memory;
-import org.apache.iotdb.commons.service.metric.MetricService;
-import org.apache.iotdb.commons.service.metric.enums.Metric;
-import org.apache.iotdb.commons.service.metric.enums.Tag;
import org.apache.iotdb.db.i18n.StorageEngineMessages;
import org.apache.iotdb.db.storageengine.load.metrics.LoadTsFileMemMetricSet;
-import org.apache.iotdb.metrics.utils.MetricLevel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -56,13 +52,7 @@ public class LoadTsFileMemoryBlock extends LoadTsFileAbstractMemoryBlock {
LOGGER.debug(StorageEngineMessages.EXCEED_TOTAL_MEMORY_SIZE, this);
}
- MetricService.getInstance()
- .getOrCreateGauge(
- Metric.LOAD_MEM.toString(),
- MetricLevel.IMPORTANT,
- Tag.NAME.toString(),
- LoadTsFileMemMetricSet.LOAD_TSFILE_OTHER_MEMORY)
- .incr(memoryInBytes);
+ LoadTsFileMemMetricSet.getInstance().updateOtherMemory(memoryInBytes);
}
@Override
@@ -71,13 +61,7 @@ public class LoadTsFileMemoryBlock extends LoadTsFileAbstractMemoryBlock {
LOGGER.warn(StorageEngineMessages.REDUCE_MEMORY_USAGE_TO_NEGATIVE, this);
}
- MetricService.getInstance()
- .getOrCreateGauge(
- Metric.LOAD_MEM.toString(),
- MetricLevel.IMPORTANT,
- Tag.NAME.toString(),
- LoadTsFileMemMetricSet.LOAD_TSFILE_OTHER_MEMORY)
- .decr(memoryInBytes);
+ LoadTsFileMemMetricSet.getInstance().updateOtherMemory(-memoryInBytes);
}
@Override
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/metrics/ActiveLoadingFilesMetricsSet.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/metrics/ActiveLoadingFilesMetricsSet.java
index cf15065d5b1..c3934e15676 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/metrics/ActiveLoadingFilesMetricsSet.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/metrics/ActiveLoadingFilesMetricsSet.java
@@ -147,6 +147,12 @@ public abstract class ActiveLoadingFilesMetricsSet implements IMetricSet {
// Dir2PendingFileCounters' binding is triggered by updatePendingDirList
// FailedDirCounter's binding is triggered by updateFailedDir
+ // Both are only triggered by changes, so bind them again for the existing dirs, e.g. when the
+ // metric service restarts
+ rebindDir2PendingFileCounters();
+ if (Objects.nonNull(failedDir.get())) {
+ rebindFailedDirCounter();
+ }
bindOtherCounters(metricService);
}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/metrics/ActiveLoadingFilesNumberMetricsSet.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/metrics/ActiveLoadingFilesNumberMetricsSet.java
index 11105ed14c1..69765820a30 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/metrics/ActiveLoadingFilesNumberMetricsSet.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/metrics/ActiveLoadingFilesNumberMetricsSet.java
@@ -35,17 +35,23 @@ public class ActiveLoadingFilesNumberMetricsSet extends ActiveLoadingFilesMetric
private Counter queuingFileCounter = DoNothingMetricManager.DO_NOTHING_COUNTER;
private Counter loadingFileCounter = DoNothingMetricManager.DO_NOTHING_COUNTER;
+ // The queuing and loading counters are levels maintained by deltas, so keep the levels to restore
+ // them when the metric service recreates the counters
+ private long queuingFileNumber = 0;
+ private long loadingFileNumber = 0;
- public void increaseQueuingFileCounter(final long number) {
+ public synchronized void increaseQueuingFileCounter(final long number) {
+ queuingFileNumber += number;
queuingFileCounter.inc(number);
}
- public void increaseLoadingFileCounter(final long number) {
+ public synchronized void increaseLoadingFileCounter(final long number) {
+ loadingFileNumber += number;
loadingFileCounter.inc(number);
}
@Override
- protected void bindOtherCounters(final AbstractMetricService metricService) {
+ protected synchronized void bindOtherCounters(final AbstractMetricService metricService) {
totalPendingFileCounter =
metricService.getOrCreateCounter(
Metric.ACTIVE_LOADING_FILES_NUMBER.toString(),
@@ -58,16 +64,18 @@ public class ActiveLoadingFilesNumberMetricsSet extends ActiveLoadingFilesMetric
MetricLevel.IMPORTANT,
Tag.TYPE.toString(),
QUEUING);
+ queuingFileCounter.inc(queuingFileNumber - queuingFileCounter.getCount());
loadingFileCounter =
metricService.getOrCreateCounter(
Metric.ACTIVE_LOADING_FILES_NUMBER.toString(),
MetricLevel.IMPORTANT,
Tag.TYPE.toString(),
LOADING);
+ loadingFileCounter.inc(loadingFileNumber - loadingFileCounter.getCount());
}
@Override
- protected void unbindOtherCounters(final AbstractMetricService metricService) {
+ protected synchronized void unbindOtherCounters(final AbstractMetricService metricService) {
totalPendingFileCounter = DoNothingMetricManager.DO_NOTHING_COUNTER;
queuingFileCounter = DoNothingMetricManager.DO_NOTHING_COUNTER;
loadingFileCounter = DoNothingMetricManager.DO_NOTHING_COUNTER;
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/metrics/LoadTsFileMemMetricSet.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/metrics/LoadTsFileMemMetricSet.java
index 63608ae12f4..200ba19e509 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/metrics/LoadTsFileMemMetricSet.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/metrics/LoadTsFileMemMetricSet.java
@@ -19,6 +19,7 @@
package org.apache.iotdb.db.storageengine.load.metrics;
+import org.apache.iotdb.commons.service.metric.MetricService;
import org.apache.iotdb.commons.service.metric.enums.Metric;
import org.apache.iotdb.commons.service.metric.enums.Tag;
import org.apache.iotdb.db.storageengine.load.memory.LoadTsFileMemoryManager;
@@ -34,8 +35,11 @@ public class LoadTsFileMemMetricSet implements IMetricSet {
private static final String LOAD_TSFILE_DATA_CACHE_MEMORY = "LoadTsFileDataCacheMemory";
+ // Keep the size to restore it when the metric service recreates the gauge, e.g. on restart
+ private long otherMemorySizeInBytes = 0;
+
@Override
- public void bindTo(AbstractMetricService metricService) {
+ public synchronized void bindTo(AbstractMetricService metricService) {
metricService.createAutoGauge(
Metric.LOAD_MEM.toString(),
MetricLevel.IMPORTANT,
@@ -58,7 +62,18 @@ public class LoadTsFileMemMetricSet implements IMetricSet {
MetricLevel.IMPORTANT,
Tag.NAME.toString(),
LOAD_TSFILE_OTHER_MEMORY)
- .set(0L);
+ .set(otherMemorySizeInBytes);
+ }
+
+ public synchronized void updateOtherMemory(long deltaInBytes) {
+ otherMemorySizeInBytes += deltaInBytes;
+ MetricService.getInstance()
+ .getOrCreateGauge(
+ Metric.LOAD_MEM.toString(),
+ MetricLevel.IMPORTANT,
+ Tag.NAME.toString(),
+ LOAD_TSFILE_OTHER_MEMORY)
+ .set(otherMemorySizeInBytes);
}
@Override
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/metric/ConsensusSubscriptionPrefetchingQueueMetrics.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/metric/ConsensusSubscriptionPrefetchingQueueMetrics.java
index b7331749b8c..19076e9abae 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/metric/ConsensusSubscriptionPrefetchingQueueMetrics.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/metric/ConsensusSubscriptionPrefetchingQueueMetrics.java
@@ -60,15 +60,9 @@ public class ConsensusSubscriptionPrefetchingQueueMetrics implements IMetricSet
@Override
public synchronized void unbindFrom(final AbstractMetricService metricService) {
- final ImmutableSet<QueueMetricsKey> keys = ImmutableSet.copyOf(queueMap.keySet());
- for (final QueueMetricsKey key : keys) {
- deregister(key);
- }
- if (!queueMap.isEmpty()) {
- LOGGER.warn(
- DataNodePipeMessages
- .PIPE_LOG_FAILED_TO_UNBIND_FROM_CONSENSUS_SUBSCRIPTION_PREFETCHING_A8F920D9);
- }
+ // Keep the queues registered: they register only once, so a metric service restart
+ // must be able to bind them again.
+ ImmutableSet.copyOf(queueMap.keySet()).forEach(this::removeMetrics);
}
//////////////////////////// register & deregister ////////////////////////////
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/metric/SubscriptionPrefetchingQueueMetrics.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/metric/SubscriptionPrefetchingQueueMetrics.java
index 250191fcdf1..0bfbf893200 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/metric/SubscriptionPrefetchingQueueMetrics.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/metric/SubscriptionPrefetchingQueueMetrics.java
@@ -50,7 +50,7 @@ public class SubscriptionPrefetchingQueueMetrics implements IMetricSet {
private final Map<String, Rate> rateMap = new ConcurrentHashMap<>();
@Override
- public void bindTo(final AbstractMetricService metricService) {
+ public synchronized void bindTo(final AbstractMetricService metricService) {
this.metricService = metricService;
final ImmutableSet<String> ids = ImmutableSet.copyOf(prefetchingQueueMap.keySet());
for (final String id : ids) {
@@ -59,21 +59,16 @@ public class SubscriptionPrefetchingQueueMetrics implements IMetricSet {
}
@Override
- public void unbindFrom(final AbstractMetricService metricService) {
- final ImmutableSet<String> ids = ImmutableSet.copyOf(prefetchingQueueMap.keySet());
- for (final String id : ids) {
- deregister(id);
- }
- if (!prefetchingQueueMap.isEmpty()) {
- LOGGER.warn(
- DataNodePipeMessages
- .PIPE_LOG_FAILED_TO_UNBIND_FROM_SUBSCRIPTION_PREFETCHING_QUEUE_METRICS_6614388C);
- }
+ public synchronized void unbindFrom(final AbstractMetricService metricService) {
+ // Keep the queues registered: they register only once, so a metric service restart
+ // must be able to bind them again.
+ // Synchronized with the (de)registrations, which may remove a registration being unbound.
+ ImmutableSet.copyOf(prefetchingQueueMap.keySet()).forEach(this::removeMetrics);
}
//////////////////////////// register & deregister ////////////////////////////
- public void register(final SubscriptionPrefetchingQueue prefetchingQueue) {
+ public synchronized void register(final SubscriptionPrefetchingQueue prefetchingQueue) {
final String id = prefetchingQueue.getPrefetchingQueueId();
prefetchingQueueMap.putIfAbsent(id, prefetchingQueue);
if (Objects.nonNull(metricService)) {
@@ -94,16 +89,14 @@ public class SubscriptionPrefetchingQueueMetrics implements IMetricSet {
MetricLevel.IMPORTANT,
queue,
SubscriptionPrefetchingQueue::getSubscriptionUncommittedEventCount,
- Tag.NAME.toString(),
- queue.getPrefetchingQueueId());
+ getTags(queue));
// current commit id
metricService.createAutoGauge(
Metric.SUBSCRIPTION_CURRENT_COMMIT_ID.toString(),
MetricLevel.IMPORTANT,
queue,
SubscriptionPrefetchingQueue::getCurrentCommitId,
- Tag.NAME.toString(),
- queue.getPrefetchingQueueId());
+ getTags(queue));
}
private void createRate(final String id) {
@@ -112,13 +105,10 @@ public class SubscriptionPrefetchingQueueMetrics implements IMetricSet {
rateMap.put(
id,
metricService.getOrCreateRate(
- Metric.SUBSCRIPTION_EVENT_TRANSFER.toString(),
- MetricLevel.IMPORTANT,
- Tag.NAME.toString(),
- queue.getPrefetchingQueueId()));
+ Metric.SUBSCRIPTION_EVENT_TRANSFER.toString(), MetricLevel.IMPORTANT, getTags(queue)));
}
- public void deregister(final String id) {
+ public synchronized void deregister(final String id) {
if (!prefetchingQueueMap.containsKey(id)) {
LOGGER.warn(
DataNodePipeMessages
@@ -143,24 +133,27 @@ public class SubscriptionPrefetchingQueueMetrics implements IMetricSet {
metricService.remove(
MetricType.AUTO_GAUGE,
Metric.SUBSCRIPTION_UNCOMMITTED_EVENT_COUNT.toString(),
- Tag.NAME.toString(),
- queue.getPrefetchingQueueId());
+ getTags(queue));
// current commit id
metricService.remove(
- MetricType.AUTO_GAUGE,
- Metric.SUBSCRIPTION_CURRENT_COMMIT_ID.toString(),
- Tag.NAME.toString(),
- queue.getPrefetchingQueueId());
+ MetricType.AUTO_GAUGE, Metric.SUBSCRIPTION_CURRENT_COMMIT_ID.toString(), getTags(queue));
}
private void removeRate(final String id) {
final SubscriptionPrefetchingQueue queue = prefetchingQueueMap.get(id);
// transfer event rate
metricService.remove(
- MetricType.RATE,
- Metric.SUBSCRIPTION_EVENT_TRANSFER.toString(),
- Tag.NAME.toString(),
- queue.getPrefetchingQueueId());
+ MetricType.RATE, Metric.SUBSCRIPTION_EVENT_TRANSFER.toString(), getTags(queue));
+ rateMap.remove(id);
+ }
+
+ // The consensus-based queues export these metrics with a region tag. A metric name must have the
+ // same tag keys everywhere, otherwise the queues registered later get no metrics at all, so give
+ // them the region tag too. These queues are not bound to a region, hence the empty value.
+ private static String[] getTags(final SubscriptionPrefetchingQueue queue) {
+ return new String[] {
+ Tag.NAME.toString(), queue.getPrefetchingQueueId(), Tag.REGION.toString(), "",
+ };
}
public void mark(final String id, final long size) {
diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/metric/PipeMetricsRestartTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/metric/PipeMetricsRestartTest.java
new file mode 100644
index 00000000000..ab982c78af0
--- /dev/null
+++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/metric/PipeMetricsRestartTest.java
@@ -0,0 +1,426 @@
+/*
+ * 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.iotdb.db.pipe.metric;
+
+import org.apache.iotdb.commons.pipe.sink.protocol.IoTDBSink;
+import org.apache.iotdb.commons.service.metric.MetricService;
+import org.apache.iotdb.commons.service.metric.enums.Metric;
+import org.apache.iotdb.db.pipe.agent.task.subtask.processor.PipeProcessorSubtask;
+import org.apache.iotdb.db.pipe.agent.task.subtask.sink.PipeSinkSubtask;
+import org.apache.iotdb.db.pipe.metric.overview.PipeDataNodeSinglePipeMetrics;
+import org.apache.iotdb.db.pipe.metric.overview.PipeTsFileToTabletsMetrics;
+import org.apache.iotdb.db.pipe.metric.processor.PipeProcessorMetrics;
+import org.apache.iotdb.db.pipe.metric.schema.PipeSchemaRegionSinkMetrics;
+import org.apache.iotdb.db.pipe.metric.schema.PipeSchemaRegionSourceMetrics;
+import org.apache.iotdb.db.pipe.metric.sink.PipeDataRegionSinkMetrics;
+import org.apache.iotdb.db.pipe.metric.source.PipeAssignerMetrics;
+import org.apache.iotdb.db.pipe.metric.source.PipeDataRegionSourceMetrics;
+import org.apache.iotdb.db.pipe.sink.protocol.airgap.IoTDBDataRegionAirGapSink;
+import org.apache.iotdb.db.pipe.sink.protocol.thrift.async.IoTDBDataRegionAsyncSink;
+import org.apache.iotdb.db.pipe.sink.protocol.thrift.sync.IoTDBDataRegionSyncSink;
+import org.apache.iotdb.db.pipe.source.dataregion.IoTDBDataRegionSource;
+import org.apache.iotdb.db.pipe.source.dataregion.realtime.assigner.PipeDataRegionAssigner;
+import org.apache.iotdb.db.pipe.source.schemaregion.IoTDBSchemaRegionSource;
+import org.apache.iotdb.metrics.config.MetricConfig;
+import org.apache.iotdb.metrics.config.MetricConfigDescriptor;
+import org.apache.iotdb.metrics.metricsets.IMetricSet;
+import org.apache.iotdb.metrics.type.IMetric;
+import org.apache.iotdb.metrics.type.Rate;
+import org.apache.iotdb.metrics.type.Timer;
+import org.apache.iotdb.metrics.utils.MetricInfo;
+import org.apache.iotdb.metrics.utils.MetricLevel;
+import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/** Pipe metrics must survive a metric service restart, which drops and rebinds all metrics. */
+public class PipeMetricsRestartTest {
+
+ private static final String PIPE = "pipe";
+ private static final long CREATION_TIME = 1L;
+ private static final String PIPE_ID = PIPE + "_" + CREATION_TIME;
+ private static final String TASK_ID = "task";
+ private static final int REGION_ID = 1;
+
+ private final MetricService service = MetricService.getInstance();
+ private final MetricConfig config = MetricConfigDescriptor.getInstance().getMetricConfig();
+ private final List<IMetricSet> boundMetricSets = new ArrayList<>();
+ private MetricLevel originalLevel;
+ private String originalReporters;
+
+ @Before
+ public void setUp() {
+ originalLevel = config.getMetricLevel();
+ originalReporters =
+ config.getMetricReporterList().stream().map(Enum::name).collect(Collectors.joining(","));
+ config.setMetricLevel(MetricLevel.IMPORTANT);
+ config.setMetricReporterList("");
+ service.startService();
+ }
+
+ @After
+ public void tearDown() {
+ boundMetricSets.forEach(service::removeMetricSet);
+ service.stopService();
+ config.setMetricLevel(originalLevel);
+ config.setMetricReporterList(originalReporters);
+ }
+
+ @Test
+ public void testSinglePipeMetricsKeepOperators() {
+ final PipeDataNodeSinglePipeMetrics metrics = PipeDataNodeSinglePipeMetrics.getInstance();
+ bind(metrics);
+ final IoTDBDataRegionSource source = mockDataRegionSource();
+ metrics.register(source);
+ try {
+ metrics.increaseTsFileEventCount(PIPE, CREATION_TIME);
+ assertEquals(1, count(Metric.PIPE_DATANODE_REMAINING_EVENT_COUNT));
+
+ service.restartService();
+ assertEquals(1, count(Metric.PIPE_DATANODE_REMAINING_EVENT_COUNT));
+ // The operator holding the state of the pipe is kept
+ assertEquals(1L, (long) metrics.getRemainingEventAndTime(PIPE, CREATION_TIME).getLeft());
+ // and records into the timer of the current binding
+ metrics.updateTsFileTransferTimer(PIPE, CREATION_TIME, 10);
+ assertEquals(1, ((Timer) get(Metric.PIPE_TSFILE_EVENT_TRANSFER_TIME)).getCount());
+ } finally {
+ metrics.deregister(PIPE_ID);
+ }
+ assertEquals(0, count(Metric.PIPE_DATANODE_REMAINING_EVENT_COUNT));
+ assertTrue(metrics.remainingEventAndTimeOperatorMap.isEmpty());
+ }
+
+ @Test
+ public void testDataRegionSourceMetrics() {
+ final PipeDataRegionSourceMetrics metrics = PipeDataRegionSourceMetrics.getInstance();
+ bind(metrics);
+ metrics.register(mockDataRegionSource());
+ try {
+ service.restartService();
+ assertEquals(1, count(Metric.UNPROCESSED_TABLET_COUNT));
+ metrics.markTabletEvent(TASK_ID);
+ assertEquals(1, ((Rate) get(Metric.PIPE_EXTRACTOR_TABLET_SUPPLY)).getCount());
+ } finally {
+ metrics.deregister(TASK_ID);
+ }
+ assertEquals(0, count(Metric.UNPROCESSED_TABLET_COUNT));
+ }
+
+ @Test
+ public void testDataRegionSinkMetrics() {
+ final PipeDataRegionSinkMetrics metrics = PipeDataRegionSinkMetrics.getInstance();
+ bind(metrics);
+ final PipeSinkSubtask subtask = mockSinkSubtask();
+ metrics.register(subtask);
+ try {
+ service.restartService();
+ assertEquals(1, count(Metric.UNTRANSFERRED_TABLET_COUNT));
+ assertSame(get(Metric.PIPE_COMPRESSION_TIME), metrics.getCompressionTimer(TASK_ID));
+ // The histograms pushed into the subtask are replaced as well
+ verify(subtask, times(2)).setTabletBatchSizeHistogram(any());
+ } finally {
+ metrics.deregister(TASK_ID);
+ }
+ assertEquals(0, count(Metric.UNTRANSFERRED_TABLET_COUNT));
+ }
+
+ @Test
+ public void testSinksUseCompressionTimerOfCurrentBinding() throws Exception {
+ final PipeDataRegionSinkMetrics metrics = PipeDataRegionSinkMetrics.getInstance();
+ bind(metrics);
+ metrics.register(mockSinkSubtask());
+ try (final IoTDBDataRegionSyncSink syncSink = new IoTDBDataRegionSyncSink();
+ final IoTDBDataRegionAsyncSink asyncSink = new IoTDBDataRegionAsyncSink();
+ final IoTDBDataRegionAirGapSink airGapSink = new IoTDBDataRegionAirGapSink()) {
+ final Method airGapCompress =
+ IoTDBDataRegionAirGapSink.class.getDeclaredMethod("compressIfNeeded", byte[].class);
+ airGapCompress.setAccessible(true);
+ for (final IoTDBSink sink : new IoTDBSink[] {syncSink, asyncSink, airGapSink}) {
+ setField(sink, "sinkTaskId", TASK_ID);
+ }
+
+ for (int i = 0; i < 2; i++) {
+ final Timer timer = metrics.getCompressionTimer(TASK_ID);
+ syncSink.compressIfNeeded(newReq());
+ asyncSink.compressIfNeeded(newReq());
+ airGapCompress.invoke(airGapSink, (Object) new byte[1]);
+ for (final IoTDBSink sink : new IoTDBSink[] {syncSink, asyncSink, airGapSink}) {
+ assertSame(timer, getField(sink, "compressionTimer"));
+ }
+ service.restartService();
+ assertNotSame(timer, metrics.getCompressionTimer(TASK_ID));
+ }
+ } finally {
+ metrics.deregister(TASK_ID);
+ }
+ }
+
+ @Test
+ public void testSchemaRegionSinkMetrics() {
+ final PipeSchemaRegionSinkMetrics metrics = PipeSchemaRegionSinkMetrics.getInstance();
+ bind(metrics);
+ final PipeSinkSubtask subtask = mockSinkSubtask();
+ metrics.register(subtask);
+ try {
+ service.restartService();
+ assertEquals(1, count(Metric.PIPE_CONNECTOR_SCHEMA_TRANSFER));
+ verify(subtask, times(2)).setSchemaBatchSizeHistogram(any());
+ } finally {
+ metrics.deregister(TASK_ID);
+ }
+ assertEquals(0, count(Metric.PIPE_CONNECTOR_SCHEMA_TRANSFER));
+ }
+
+ @Test
+ public void testSchemaRegionSourceMetrics() {
+ final PipeSchemaRegionSourceMetrics metrics = PipeSchemaRegionSourceMetrics.getInstance();
+ bind(metrics);
+ final IoTDBSchemaRegionSource source = Mockito.mock(IoTDBSchemaRegionSource.class);
+ when(source.getTaskID()).thenReturn(TASK_ID);
+ when(source.getPipeName()).thenReturn(PIPE);
+ when(source.getRegionId()).thenReturn(REGION_ID);
+ when(source.getCreationTime()).thenReturn(CREATION_TIME);
+ metrics.register(source);
+ try {
+ service.restartService();
+ assertEquals(1, count(Metric.UNTRANSFERRED_SCHEMA_COUNT));
+ } finally {
+ metrics.deregister(TASK_ID);
+ }
+ assertEquals(0, count(Metric.UNTRANSFERRED_SCHEMA_COUNT));
+ }
+
+ @Test
+ public void testProcessorMetrics() {
+ final PipeProcessorMetrics metrics = PipeProcessorMetrics.getInstance();
+ bind(metrics);
+ final PipeProcessorSubtask subtask = Mockito.mock(PipeProcessorSubtask.class);
+ when(subtask.getTaskID()).thenReturn(TASK_ID);
+ when(subtask.getPipeName()).thenReturn(PIPE);
+ when(subtask.getRegionId()).thenReturn(REGION_ID);
+ when(subtask.getCreationTime()).thenReturn(CREATION_TIME);
+ metrics.register(subtask);
+ try {
+ service.restartService();
+ metrics.markTabletEvent(TASK_ID);
+ assertEquals(1, ((Rate) get(Metric.PIPE_PROCESSOR_TABLET_PROCESS)).getCount());
+ } finally {
+ metrics.deregister(TASK_ID);
+ }
+ assertEquals(0, count(Metric.PIPE_PROCESSOR_TABLET_PROCESS));
+ }
+
+ /**
+ * A subtask may deregister while the metric service restart is unbinding the processor metrics.
+ * The restart must neither fail on it nor drop the metrics of the other subtasks.
+ */
+ @Test
+ public void testProcessorMetricsWithConcurrentDeregistration() throws Exception {
+ final PipeProcessorMetrics metrics = PipeProcessorMetrics.getInstance();
+ bind(metrics);
+ final AtomicBoolean armed = new AtomicBoolean(false);
+ final AtomicReference<PipeProcessorSubtask> paused = new AtomicReference<>();
+ final CountDownLatch pausedLatch = new CountDownLatch(1);
+ final CountDownLatch resumeLatch = new CountDownLatch(1);
+ final PipeProcessorSubtask first =
+ mockBlockingProcessorSubtask("first", armed, paused, pausedLatch, resumeLatch);
+ final PipeProcessorSubtask second =
+ mockBlockingProcessorSubtask("second", armed, paused, pausedLatch, resumeLatch);
+ metrics.register(first);
+ metrics.register(second);
+
+ // Pause the restart while it unbinds the first subtask, and deregister the other one meanwhile
+ armed.set(true);
+ final Thread restart = new Thread(service::restartService);
+ restart.start();
+ Thread deregistration = null;
+ try {
+ assertTrue(pausedLatch.await(30, TimeUnit.SECONDS));
+ final PipeProcessorSubtask other = paused.get() == first ? second : first;
+ deregistration = new Thread(() -> metrics.deregister(other.getTaskID()));
+ deregistration.start();
+ // Either the deregistration completes at once, or it waits for the unbinding to complete
+ final long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(30);
+ while (deregistration.getState() != Thread.State.TERMINATED
+ && deregistration.getState() != Thread.State.BLOCKED
+ && deregistration.getState() != Thread.State.WAITING
+ && System.nanoTime() < deadline) {
+ Thread.sleep(10);
+ }
+ } finally {
+ resumeLatch.countDown();
+ restart.join(TimeUnit.SECONDS.toMillis(30));
+ if (deregistration != null) {
+ deregistration.join(TimeUnit.SECONDS.toMillis(30));
+ }
+ }
+
+ final String remainingPipe = paused.get().getPipeName();
+ try {
+ assertEquals(
+ 1,
+ service.getAllMetrics().keySet().stream()
+ .filter(
+ info ->
+ Metric.PIPE_PROCESSOR_TABLET_PROCESS.toString().equals(info.getName())
+ && remainingPipe.equals(info.getTags().get("name")))
+ .count());
+ assertEquals(1, count(Metric.PIPE_PROCESSOR_TABLET_PROCESS));
+ } finally {
+ metrics.deregister(paused.get().getTaskID());
+ }
+ }
+
+ private static PipeProcessorSubtask mockBlockingProcessorSubtask(
+ final String name,
+ final AtomicBoolean armed,
+ final AtomicReference<PipeProcessorSubtask> paused,
+ final CountDownLatch pausedLatch,
+ final CountDownLatch resumeLatch) {
+ final PipeProcessorSubtask subtask = Mockito.mock(PipeProcessorSubtask.class);
+ when(subtask.getTaskID()).thenReturn(name);
+ when(subtask.getRegionId()).thenReturn(REGION_ID);
+ when(subtask.getCreationTime()).thenReturn(CREATION_TIME);
+ when(subtask.getPipeName())
+ .thenAnswer(
+ invocation -> {
+ // Only the first call after arming pauses, which is during the unbinding
+ if (armed.get() && paused.compareAndSet(null, subtask)) {
+ pausedLatch.countDown();
+ resumeLatch.await(30, TimeUnit.SECONDS);
+ }
+ return name;
+ });
+ return subtask;
+ }
+
+ @Test
+ public void testAssignerMetrics() {
+ final PipeAssignerMetrics metrics = PipeAssignerMetrics.getInstance();
+ bind(metrics);
+ final PipeDataRegionAssigner assigner = Mockito.mock(PipeDataRegionAssigner.class);
+ when(assigner.getDataRegionId()).thenReturn(REGION_ID);
+ metrics.register(assigner);
+ try {
+ service.restartService();
+ assertEquals(1, count(Metric.UNASSIGNED_TABLET_COUNT));
+ } finally {
+ metrics.deregister(REGION_ID);
+ }
+ assertEquals(0, count(Metric.UNASSIGNED_TABLET_COUNT));
+ }
+
+ @Test
+ public void testTsFileToTabletsMetrics() {
+ final PipeTsFileToTabletsMetrics metrics = PipeTsFileToTabletsMetrics.getInstance();
+ bind(metrics);
+ metrics.register(mockDataRegionSource());
+ try {
+ service.restartService();
+ metrics.recordTsFileToTabletTime(PIPE_ID, 10);
+ assertEquals(1, ((Timer) get(Metric.PIPE_TSFILE_TO_TABLETS_TIME)).getCount());
+ } finally {
+ metrics.deregister(PIPE_ID);
+ }
+ assertEquals(0, count(Metric.PIPE_TSFILE_TO_TABLETS_TIME));
+ }
+
+ private void bind(final IMetricSet metricSet) {
+ service.addMetricSet(metricSet);
+ boundMetricSets.add(metricSet);
+ }
+
+ private static IoTDBDataRegionSource mockDataRegionSource() {
+ final IoTDBDataRegionSource source = Mockito.mock(IoTDBDataRegionSource.class);
+ when(source.getTaskID()).thenReturn(TASK_ID);
+ when(source.getPipeName()).thenReturn(PIPE);
+ when(source.getRegionId()).thenReturn(REGION_ID);
+ when(source.getCreationTime()).thenReturn(CREATION_TIME);
+ return source;
+ }
+
+ private static PipeSinkSubtask mockSinkSubtask() {
+ final PipeSinkSubtask subtask = Mockito.mock(PipeSinkSubtask.class);
+ when(subtask.getTaskID()).thenReturn(TASK_ID);
+ when(subtask.getPipeName()).thenReturn(PIPE);
+ when(subtask.getAttributeSortedString()).thenReturn("sink");
+ when(subtask.getCreationTime()).thenReturn(CREATION_TIME);
+ return subtask;
+ }
+
+ private static TPipeTransferReq newReq() {
+ final TPipeTransferReq req = new TPipeTransferReq();
+ req.body = ByteBuffer.wrap(new byte[1]);
+ return req;
+ }
+
+ private long count(final Metric metric) {
+ return service.getAllMetrics().keySet().stream()
+ .filter(info -> metric.toString().equals(info.getName()))
+ .count();
+ }
+
+ private IMetric get(final Metric metric) {
+ for (final Map.Entry<MetricInfo, IMetric> entry : service.getAllMetrics().entrySet()) {
+ if (metric.toString().equals(entry.getKey().getName())) {
+ return entry.getValue();
+ }
+ }
+ throw new AssertionError(metric + " is not registered");
+ }
+
+ private static void setField(final IoTDBSink sink, final String name, final Object value)
+ throws ReflectiveOperationException {
+ final Field field = IoTDBSink.class.getDeclaredField(name);
+ field.setAccessible(true);
+ field.set(sink, value);
+ }
+
+ private static Object getField(final IoTDBSink sink, final String name)
+ throws ReflectiveOperationException {
+ final Field field = IoTDBSink.class.getDeclaredField(name);
+ field.setAccessible(true);
+ return field.get(sink);
+ }
+}
diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/metric/schema/PipeSchemaRegionListenerMetricsTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/metric/schema/PipeSchemaRegionListenerMetricsTest.java
new file mode 100644
index 00000000000..2a8e8b8091d
--- /dev/null
+++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/metric/schema/PipeSchemaRegionListenerMetricsTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.iotdb.db.pipe.metric.schema;
+
+import org.apache.iotdb.commons.service.metric.MetricService;
+import org.apache.iotdb.commons.service.metric.enums.Metric;
+import org.apache.iotdb.commons.service.metric.enums.Tag;
+import org.apache.iotdb.db.pipe.source.schemaregion.SchemaRegionListeningQueue;
+import org.apache.iotdb.metrics.config.MetricConfig;
+import org.apache.iotdb.metrics.config.MetricConfigDescriptor;
+import org.apache.iotdb.metrics.type.AutoGauge;
+import org.apache.iotdb.metrics.type.IMetric;
+import org.apache.iotdb.metrics.utils.MetricInfo;
+import org.apache.iotdb.metrics.utils.MetricLevel;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+public class PipeSchemaRegionListenerMetricsTest {
+
+ private static final int SCHEMA_REGION_ID = 7;
+
+ private final PipeSchemaRegionListenerMetrics metrics =
+ PipeSchemaRegionListenerMetrics.getInstance();
+ private final MetricService service = MetricService.getInstance();
+ private final MetricConfig config = MetricConfigDescriptor.getInstance().getMetricConfig();
+ private MetricLevel originalLevel;
+ private String originalReporters;
+
+ @Before
+ public void setUp() {
+ originalLevel = config.getMetricLevel();
+ originalReporters =
+ config.getMetricReporterList().stream().map(Enum::name).collect(Collectors.joining(","));
+ config.setMetricLevel(MetricLevel.IMPORTANT);
+ config.setMetricReporterList("");
+ service.startService();
+ service.addMetricSet(metrics);
+ }
+
+ @After
+ public void tearDown() {
+ metrics.deregister(SCHEMA_REGION_ID);
+ service.removeMetricSet(metrics);
+ service.stopService();
+ config.setMetricLevel(originalLevel);
+ config.setMetricReporterList(originalReporters);
+ }
+
+ @Test
+ public void testQueueSizeSurvivesMetricServiceRestart() {
+ final SchemaRegionListeningQueue queue = Mockito.mock(SchemaRegionListeningQueue.class);
+ Mockito.when(queue.getSize()).thenReturn(3L);
+ metrics.register(queue, SCHEMA_REGION_ID);
+ assertQueueSize(3);
+
+ for (final MetricLevel level :
+ new MetricLevel[] {MetricLevel.ALL, MetricLevel.CORE, MetricLevel.IMPORTANT}) {
+ config.setMetricLevel(level);
+ service.restartService();
+ if (level == MetricLevel.CORE) {
+ assertNull(getQueueSizeGauge());
+ } else {
+ assertQueueSize(3);
+ }
+ }
+
+ metrics.deregister(SCHEMA_REGION_ID);
+ assertNull(getQueueSizeGauge());
+ service.restartService();
+ assertNull(getQueueSizeGauge());
+ }
+
+ private AutoGauge getQueueSizeGauge() {
+ for (final Map.Entry<MetricInfo, IMetric> entry : service.getAllMetrics().entrySet()) {
+ if (Metric.PIPE_SCHEMA_LINKED_QUEUE_SIZE.toString().equals(entry.getKey().getName())
+ && String.valueOf(SCHEMA_REGION_ID)
+ .equals(entry.getKey().getTags().get(Tag.REGION.toString()))) {
+ return (AutoGauge) entry.getValue();
+ }
+ }
+ return null;
+ }
+
+ private void assertQueueSize(final double expected) {
+ final AutoGauge gauge = getQueueSizeGauge();
+ assertNotNull(gauge);
+ assertEquals(expected, gauge.getValue(), 0);
+ }
+}
diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/metric/SchemaMemMetricTableTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/metric/SchemaMemMetricTableTest.java
new file mode 100644
index 00000000000..294f5df6301
--- /dev/null
+++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/metric/SchemaMemMetricTableTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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.iotdb.db.schemaengine.metric;
+
+import org.apache.iotdb.commons.service.metric.MetricService;
+import org.apache.iotdb.db.schemaengine.rescon.MemSchemaEngineStatistics;
+import org.apache.iotdb.db.schemaengine.rescon.MemSchemaRegionStatistics;
+import org.apache.iotdb.metrics.config.MetricConfig;
+import org.apache.iotdb.metrics.config.MetricConfigDescriptor;
+import org.apache.iotdb.metrics.metricsets.IMetricSet;
+import org.apache.iotdb.metrics.type.AutoGauge;
+import org.apache.iotdb.metrics.type.IMetric;
+import org.apache.iotdb.metrics.utils.MetricInfo;
+import org.apache.iotdb.metrics.utils.MetricLevel;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * The per-table device number gauges must be exported next to the other schema engine and schema
+ * region metrics, and bound again when the metric service restarts.
+ */
+public class SchemaMemMetricTableTest {
+
+ private static final String TABLE = "t1";
+
+ private final MetricService service = MetricService.getInstance();
+ private final MetricConfig config = MetricConfigDescriptor.getInstance().getMetricConfig();
+ private MetricLevel originalLevel;
+ private String originalReporters;
+
+ @Before
+ public void setUp() {
+ originalLevel = config.getMetricLevel();
+ originalReporters =
+ config.getMetricReporterList().stream().map(Enum::name).collect(Collectors.joining(","));
+ config.setMetricLevel(MetricLevel.IMPORTANT);
+ config.setMetricReporterList("");
+ service.startService();
+ }
+
+ @After
+ public void tearDown() {
+ service.stopService();
+ config.setMetricLevel(originalLevel);
+ config.setMetricReporterList(originalReporters);
+ }
+
+ @Test
+ public void testSchemaEngineTableMetrics() {
+ final Map<String, Long> table2DevicesNum = new ConcurrentHashMap<>();
+ final MemSchemaEngineStatistics statistics = mock(MemSchemaEngineStatistics.class);
+ when(statistics.getTable2DevicesNumMap()).thenReturn(table2DevicesNum);
+ when(statistics.getTableDeviceNumber(TABLE)).thenReturn(3L);
+ final SchemaEngineMemMetric metric = new SchemaEngineMemMetric(statistics);
+ checkTableMetrics(metric, metric::bindTableMetrics, table2DevicesNum);
+ }
+
+ @Test
+ public void testSchemaRegionTableMetrics() {
+ final Map<String, Long> table2DevicesNum = new ConcurrentHashMap<>();
+ final MemSchemaRegionStatistics statistics = mock(MemSchemaRegionStatistics.class);
+ when(statistics.getSchemaRegionId()).thenReturn(1);
+ when(statistics.getTable2DevicesNumMap()).thenReturn(table2DevicesNum);
+ when(statistics.getTableDevicesNumber(TABLE)).thenReturn(3L);
+ final SchemaRegionMemMetric metric = new SchemaRegionMemMetric(statistics, "db");
+ checkTableMetrics(metric, metric::bindTableMetrics, table2DevicesNum);
+ }
+
+ private void checkTableMetrics(
+ final IMetricSet metric,
+ final Consumer<String> bindTableMetrics,
+ final Map<String, Long> table2DevicesNum) {
+ service.addMetricSet(metric);
+ try {
+ // A table binds its metrics when its first device is added
+ table2DevicesNum.put(TABLE, 3L);
+ bindTableMetrics.accept(TABLE);
+ assertEquals(3, getTableGauge().getValue(), 0);
+
+ service.restartService();
+ assertEquals(3, getTableGauge().getValue(), 0);
+ } finally {
+ service.removeMetricSet(metric);
+ }
+ assertNull(findTableGauge());
+ }
+
+ private AutoGauge getTableGauge() {
+ final IMetric metric = findTableGauge();
+ if (metric == null) {
+ throw new AssertionError("The gauge of table " + TABLE + " is not registered");
+ }
+ return (AutoGauge) metric;
+ }
+
+ private IMetric findTableGauge() {
+ for (final Map.Entry<MetricInfo, IMetric> entry : service.getAllMetrics().entrySet()) {
+ if (TABLE.equals(entry.getKey().getTags().get(SchemaEngineMemMetric.TABLE))) {
+ return entry.getValue();
+ }
+ }
+ return null;
+ }
+}
diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/service/metrics/DataNodeMetricsRestartTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/service/metrics/DataNodeMetricsRestartTest.java
new file mode 100644
index 00000000000..554bdd9940d
--- /dev/null
+++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/service/metrics/DataNodeMetricsRestartTest.java
@@ -0,0 +1,290 @@
+/*
+ * 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.iotdb.db.service.metrics;
+
+import org.apache.iotdb.commons.consensus.DataRegionId;
+import org.apache.iotdb.commons.service.metric.MetricService;
+import org.apache.iotdb.commons.service.metric.enums.Metric;
+import org.apache.iotdb.commons.service.metric.enums.Tag;
+import org.apache.iotdb.db.protocol.thrift.handler.RPCServiceThriftHandlerMetrics;
+import org.apache.iotdb.db.storageengine.load.metrics.ActiveLoadingFilesNumberMetricsSet;
+import org.apache.iotdb.db.storageengine.load.metrics.LoadTsFileMemMetricSet;
+import org.apache.iotdb.metrics.config.MetricConfig;
+import org.apache.iotdb.metrics.config.MetricConfigDescriptor;
+import org.apache.iotdb.metrics.metricsets.IMetricSet;
+import org.apache.iotdb.metrics.type.AutoGauge;
+import org.apache.iotdb.metrics.type.Counter;
+import org.apache.iotdb.metrics.type.Gauge;
+import org.apache.iotdb.metrics.type.IMetric;
+import org.apache.iotdb.metrics.utils.MetricInfo;
+import org.apache.iotdb.metrics.utils.MetricLevel;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+
+/** DataNode metrics must survive a metric service restart, which drops and rebinds all metrics. */
+public class DataNodeMetricsRestartTest {
+
+ private final MetricService service = MetricService.getInstance();
+ private final MetricConfig config = MetricConfigDescriptor.getInstance().getMetricConfig();
+ private final List<IMetricSet> boundMetricSets = new ArrayList<>();
+ private MetricLevel originalLevel;
+ private String originalReporters;
+
+ @Before
+ public void setUp() {
+ originalLevel = config.getMetricLevel();
+ originalReporters =
+ config.getMetricReporterList().stream().map(Enum::name).collect(Collectors.joining(","));
+ config.setMetricLevel(MetricLevel.IMPORTANT);
+ config.setMetricReporterList("");
+ service.startService();
+ }
+
+ @After
+ public void tearDown() {
+ boundMetricSets.forEach(service::removeMetricSet);
+ service.stopService();
+ config.setMetricLevel(originalLevel);
+ config.setMetricReporterList(originalReporters);
+ }
+
+ @Test
+ public void testWritingMetricsRecordedValues() {
+ final WritingMetrics metrics = WritingMetrics.getInstance();
+ bind(metrics);
+ metrics.recordFlushThreshold(100);
+ metrics.recordRejectThreshold(200);
+ metrics.recordWALQueueMaxMemorySize(300);
+ metrics.recordActiveMemTableCount("1", 3);
+ metrics.recordActiveTimePartitionCount(2);
+ // A region that does not exist any more
+ metrics.recordActiveMemTableCount("2", 1);
+ metrics.recordActiveMemTableCount("2", -1);
+ try {
+ service.restartService();
+ assertEquals(
+ 100,
+ ((Gauge)
+ get(
+ Metric.MEMTABLE_THRESHOLD,
+ Tag.TYPE.toString(),
+ WritingMetrics.FLUSH_THRESHOLD))
+ .getValue());
+ assertEquals(
+ 200,
+ ((Gauge)
+ get(
+ Metric.MEMTABLE_THRESHOLD,
+ Tag.TYPE.toString(),
+ WritingMetrics.REJECT_THRESHOLD))
+ .getValue());
+ assertEquals(
+ 300,
+ ((Gauge)
+ get(
+ Metric.WAL_QUEUE_MEM_COST,
+ Tag.NAME.toString(),
+ WritingMetrics.WAL_QUEUE_MAX_MEM_COST))
+ .getValue());
+ assertEquals(
+ 3,
+ ((Counter)
+ get(
+ Metric.ACTIVE_MEMTABLE_COUNT,
+ Tag.REGION.toString(),
+ new DataRegionId(1).toString()))
+ .getCount());
+ assertEquals(2, ((Counter) get(Metric.ACTIVE_TIME_PARTITION_COUNT)).getCount());
+ assertEquals(
+ 0,
+ count(
+ Metric.ACTIVE_MEMTABLE_COUNT, Tag.REGION.toString(), new DataRegionId(2).toString()));
+ } finally {
+ metrics.recordActiveMemTableCount("1", -3);
+ metrics.recordActiveTimePartitionCount(-2);
+ }
+ }
+
+ @Test
+ public void testActiveMemTableCountIsRecordedToTheCounterOfTheRegion() {
+ final WritingMetrics metrics = WritingMetrics.getInstance();
+ final DataRegionId dataRegionId = new DataRegionId(3);
+ metrics.createActiveMemtableCounterMetrics(dataRegionId);
+ metrics.recordActiveMemTableCount(String.valueOf(dataRegionId.getId()), 2);
+ try {
+ // The counter created with the region is the one recorded to and removed with the region
+ assertEquals(1, countActiveMemTableCounters(dataRegionId));
+ assertEquals(
+ 2,
+ ((Counter)
+ get(Metric.ACTIVE_MEMTABLE_COUNT, Tag.REGION.toString(), dataRegionId.toString()))
+ .getCount());
+ } finally {
+ metrics.recordActiveMemTableCount(String.valueOf(dataRegionId.getId()), -2);
+ metrics.removeActiveMemtableCounterMetrics(dataRegionId);
+ }
+ assertEquals(0, countActiveMemTableCounters(dataRegionId));
+ }
+
+ @Test
+ public void testLoadTsFileOtherMemory() {
+ final LoadTsFileMemMetricSet metrics = LoadTsFileMemMetricSet.getInstance();
+ bind(metrics);
+ metrics.updateOtherMemory(100);
+ try {
+ service.restartService();
+ assertEquals(100, getLoadTsFileOtherMemory());
+ metrics.updateOtherMemory(-40);
+ assertEquals(60, getLoadTsFileOtherMemory());
+ } finally {
+ metrics.updateOtherMemory(-60);
+ }
+ }
+
+ @Test
+ public void testActiveLoadingFilesCounters() {
+ final ActiveLoadingFilesNumberMetricsSet metrics =
+ ActiveLoadingFilesNumberMetricsSet.getInstance();
+ bind(metrics);
+ metrics.updatePendingDirList(Collections.singleton("pendingDir"));
+ metrics.updateFailedDir("failedDir");
+ metrics.increaseQueuingFileCounter(2);
+ try {
+ service.restartService();
+ metrics.updatePendingFileCounterInDir("pendingDir", 3);
+ metrics.updateTotalFailedFileCounter(4);
+ assertEquals(
+ 3,
+ ((Counter)
+ get(
+ Metric.ACTIVE_LOADING_FILES_NUMBER,
+ Tag.TYPE.toString(),
+ "pending - pendingDir"))
+ .getCount());
+ assertEquals(
+ 4,
+ ((Counter)
+ get(
+ Metric.ACTIVE_LOADING_FILES_NUMBER,
+ Tag.TYPE.toString(),
+ "failed - failedDir"))
+ .getCount());
+ assertEquals(
+ 2,
+ ((Counter) get(Metric.ACTIVE_LOADING_FILES_NUMBER, Tag.TYPE.toString(), "queuing"))
+ .getCount());
+ } finally {
+ metrics.increaseQueuingFileCounter(-2);
+ }
+ }
+
+ @Test
+ public void testRpcServiceThriftHandlerMetrics() {
+ final RPCServiceThriftHandlerMetrics metrics = RPCServiceThriftHandlerMetrics.getInstance();
+ bind(metrics);
+ bind(new RPCServiceThriftHandlerMetrics(new AtomicLong(5)));
+
+ service.restartService();
+ metrics.recordMemoryUsage(7);
+ assertEquals(5, ((AutoGauge) get(Metric.THRIFT_CONNECTIONS)).getValue(), 0);
+ assertEquals(7, ((Gauge) get(Metric.THRIFT_RPC_MEMORY_USAGE)).getValue());
+ }
+
+ @Test
+ public void testCacheMetrics() {
+ final CacheMetrics metrics = CacheMetrics.getInstance();
+ bind(metrics);
+
+ service.restartService();
+ metrics.record(true, CacheMetrics.DATABASE_CACHE_NAME);
+ assertEquals(
+ 1,
+ ((Counter)
+ get(
+ Metric.CACHE,
+ Tag.NAME.toString(),
+ CacheMetrics.DATABASE_CACHE_NAME,
+ Tag.TYPE.toString(),
+ "hit"))
+ .getCount());
+ }
+
+ private void bind(final IMetricSet metricSet) {
+ service.addMetricSet(metricSet);
+ boundMetricSets.add(metricSet);
+ }
+
+ private long getLoadTsFileOtherMemory() {
+ return ((Gauge)
+ get(
+ Metric.LOAD_MEM,
+ Tag.NAME.toString(),
+ LoadTsFileMemMetricSet.LOAD_TSFILE_OTHER_MEMORY))
+ .getValue();
+ }
+
+ private long countActiveMemTableCounters(final DataRegionId dataRegionId) {
+ // Match both forms of the region tag
+ return service.getAllMetrics().keySet().stream()
+ .filter(
+ info ->
+ Metric.ACTIVE_MEMTABLE_COUNT.toString().equals(info.getName())
+ && (dataRegionId.toString().equals(info.getTags().get(Tag.REGION.toString()))
+ || String.valueOf(dataRegionId.getId())
+ .equals(info.getTags().get(Tag.REGION.toString()))))
+ .count();
+ }
+
+ private long count(final Metric metric, final String... tags) {
+ return service.getAllMetrics().keySet().stream()
+ .filter(info -> metric.toString().equals(info.getName()) && hasTags(info, tags))
+ .count();
+ }
+
+ private IMetric get(final Metric metric, final String... tags) {
+ for (final Map.Entry<MetricInfo, IMetric> entry : service.getAllMetrics().entrySet()) {
+ if (metric.toString().equals(entry.getKey().getName()) && hasTags(entry.getKey(), tags)) {
+ return entry.getValue();
+ }
+ }
+ throw new AssertionError(metric + Arrays.toString(tags) + " is not registered");
+ }
+
+ private static boolean hasTags(final MetricInfo info, final String... tags) {
+ for (int i = 0; i < tags.length; i += 2) {
+ if (!tags[i + 1].equals(info.getTags().get(tags[i]))) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/service/metrics/file/TsFileMetricsTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/service/metrics/file/TsFileMetricsTest.java
new file mode 100644
index 00000000000..bf846ec1fe6
--- /dev/null
+++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/service/metrics/file/TsFileMetricsTest.java
@@ -0,0 +1,148 @@
+/*
+ * 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.iotdb.db.service.metrics.file;
+
+import org.apache.iotdb.commons.service.metric.MetricService;
+import org.apache.iotdb.commons.service.metric.enums.Tag;
+import org.apache.iotdb.db.storageengine.dataregion.tsfile.TsFileResource;
+import org.apache.iotdb.metrics.config.MetricConfig;
+import org.apache.iotdb.metrics.config.MetricConfigDescriptor;
+import org.apache.iotdb.metrics.type.Gauge;
+import org.apache.iotdb.metrics.type.IMetric;
+import org.apache.iotdb.metrics.utils.MetricInfo;
+import org.apache.iotdb.metrics.utils.MetricLevel;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+public class TsFileMetricsTest {
+
+ private static final String DATABASE = "root.tsfile_metrics_test";
+ private static final String REGION = "1";
+
+ private final TsFileMetrics metrics = new TsFileMetrics();
+ private final MetricService service = MetricService.getInstance();
+ private final MetricConfig config = MetricConfigDescriptor.getInstance().getMetricConfig();
+ private MetricLevel originalLevel;
+ private String originalReporters;
+
+ @Before
+ public void setUp() {
+ originalLevel = config.getMetricLevel();
+ originalReporters =
+ config.getMetricReporterList().stream().map(Enum::name).collect(Collectors.joining(","));
+ config.setMetricLevel(MetricLevel.IMPORTANT);
+ config.setMetricReporterList("");
+ service.startService();
+ service.addMetricSet(metrics);
+ }
+
+ @After
+ public void tearDown() {
+ service.removeMetricSet(metrics);
+ service.stopService();
+ config.setMetricLevel(originalLevel);
+ config.setMetricReporterList(originalReporters);
+ }
+
+ @Test
+ public void testFileGaugesSurviveMetricServiceRestart() {
+ metrics.addTsFile(mockTsFile("1-1-0-0.tsfile", 100));
+ assertFileGauges(1, 100);
+
+ for (final MetricLevel level :
+ new MetricLevel[] {MetricLevel.ALL, MetricLevel.IMPORTANT, MetricLevel.IMPORTANT}) {
+ config.setMetricLevel(level);
+ service.restartService();
+ assertFileGauges(1, 100);
+ }
+
+ // Updates after the restart must reach the gauges that are currently registered.
+ final TsFileResource second = mockTsFile("2-2-0-0.tsfile", 50);
+ metrics.addTsFile(second);
+ assertFileGauges(2, 150);
+ Mockito.when(second.markAsUnrecordedByMetric()).thenReturn(true);
+ metrics.deleteFile(Collections.singletonList(second));
+ assertFileGauges(1, 100);
+ }
+
+ @Test
+ public void testFileGaugesAreCreatedWhenLevelIsEnabledByRestart() {
+ config.setMetricLevel(MetricLevel.OFF);
+ service.restartService();
+ metrics.addTsFile(mockTsFile("1-1-0-0.tsfile", 100));
+
+ config.setMetricLevel(MetricLevel.IMPORTANT);
+ service.restartService();
+ assertFileGauges(1, 100);
+ }
+
+ @Test
+ public void testFileGaugesSurviveMetricServiceStopAndStart() {
+ metrics.addTsFile(mockTsFile("1-1-0-0.tsfile", 100));
+ service.stopService();
+ // Files recorded while the metric service is stopped are exported once it starts again.
+ metrics.addTsFile(mockTsFile("2-2-0-0.tsfile", 50));
+ service.startService();
+ assertFileGauges(2, 150);
+ }
+
+ private static TsFileResource mockTsFile(final String fileName, final long size) {
+ final TsFileResource resource = Mockito.mock(TsFileResource.class);
+ Mockito.when(resource.markAsRecordedByMetric()).thenReturn(true);
+ Mockito.when(resource.getTsFileSize()).thenReturn(size);
+ Mockito.when(resource.isSeq()).thenReturn(true);
+ Mockito.when(resource.getDatabaseName()).thenReturn(DATABASE);
+ Mockito.when(resource.getDataRegionId()).thenReturn(REGION);
+ Mockito.when(resource.getTsFile()).thenReturn(new File(fileName));
+ return resource;
+ }
+
+ private void assertFileGauges(final long count, final long size) {
+ assertEquals(count, getGauge("file_global_count", Tag.REGION.toString(), REGION));
+ assertEquals(size, getGauge("file_global_size", Tag.REGION.toString(), REGION));
+ assertEquals(count, getGauge("file_level_count", "level", "0"));
+ assertEquals(size, getGauge("file_level_size", "level", "0"));
+ }
+
+ private long getGauge(final String name, final String tagKey, final String tagValue) {
+ Gauge gauge = null;
+ for (final Map.Entry<MetricInfo, IMetric> entry : service.getAllMetrics().entrySet()) {
+ final Map<String, String> tags = entry.getKey().getTags();
+ if (name.equals(entry.getKey().getName())
+ && "seq".equals(tags.get(Tag.NAME.toString()))
+ && tagValue.equals(tags.get(tagKey))) {
+ gauge = (Gauge) entry.getValue();
+ }
+ }
+ assertNotNull(name, gauge);
+ return gauge.getValue();
+ }
+}
diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/DatabaseMemMetricsTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/DatabaseMemMetricsTest.java
new file mode 100644
index 00000000000..799cf8a5f70
--- /dev/null
+++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/DatabaseMemMetricsTest.java
@@ -0,0 +1,144 @@
+/*
+ * 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.iotdb.db.storageengine.dataregion;
+
+import org.apache.iotdb.commons.service.metric.MetricService;
+import org.apache.iotdb.commons.service.metric.enums.Metric;
+import org.apache.iotdb.commons.service.metric.enums.Tag;
+import org.apache.iotdb.db.storageengine.dataregion.memtable.TsFileProcessorInfo;
+import org.apache.iotdb.db.storageengine.dataregion.memtable.TsFileProcessorInfoMetrics;
+import org.apache.iotdb.metrics.config.MetricConfig;
+import org.apache.iotdb.metrics.config.MetricConfigDescriptor;
+import org.apache.iotdb.metrics.metricsets.IMetricSet;
+import org.apache.iotdb.metrics.type.AutoGauge;
+import org.apache.iotdb.metrics.type.IMetric;
+import org.apache.iotdb.metrics.utils.MetricInfo;
+import org.apache.iotdb.metrics.utils.MetricLevel;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * A database has several data regions and TsFileProcessors, whose memory is exported as a single
+ * gauge of the database.
+ */
+public class DatabaseMemMetricsTest {
+
+ private static final String DATABASE = "root.db";
+
+ private final MetricService service = MetricService.getInstance();
+ private final MetricConfig config = MetricConfigDescriptor.getInstance().getMetricConfig();
+ private MetricLevel originalLevel;
+ private String originalReporters;
+
+ @Before
+ public void setUp() {
+ originalLevel = config.getMetricLevel();
+ originalReporters =
+ config.getMetricReporterList().stream().map(Enum::name).collect(Collectors.joining(","));
+ config.setMetricLevel(MetricLevel.IMPORTANT);
+ config.setMetricReporterList("");
+ service.startService();
+ }
+
+ @After
+ public void tearDown() {
+ service.stopService();
+ config.setMetricLevel(originalLevel);
+ config.setMetricReporterList(originalReporters);
+ }
+
+ @Test
+ public void testDatabaseMemSumsUpTheDataRegions() {
+ final IMetricSet first = new DataRegionMetrics(mockDataRegion(100));
+ final IMetricSet second = new DataRegionMetrics(mockDataRegion(200));
+ checkSumOfTwoOwners(first, second, "database_" + DATABASE, 300, 100);
+ }
+
+ @Test
+ public void testChunkMetadataMemSumsUpTheTsFileProcessors() {
+ final IMetricSet first = new TsFileProcessorInfoMetrics(DATABASE, mockTsFileProcessorInfo(10));
+ final IMetricSet second = new TsFileProcessorInfoMetrics(DATABASE, mockTsFileProcessorInfo(20));
+ checkSumOfTwoOwners(first, second, "chunkMetaData_" + DATABASE, 30, 10);
+ }
+
+ private void checkSumOfTwoOwners(
+ final IMetricSet first,
+ final IMetricSet second,
+ final String name,
+ final long sum,
+ final long firstValue) {
+ service.addMetricSet(first);
+ service.addMetricSet(second);
+ try {
+ assertEquals(sum, getMem(name).getValue(), 0);
+ service.restartService();
+ assertEquals(sum, getMem(name).getValue(), 0);
+
+ // The gauge stays while the database has any owner left
+ service.removeMetricSet(second);
+ assertEquals(firstValue, getMem(name).getValue(), 0);
+ } finally {
+ service.removeMetricSet(first);
+ service.removeMetricSet(second);
+ }
+ assertNull(findMem(name));
+ }
+
+ private static DataRegion mockDataRegion(final long memCost) {
+ final DataRegion dataRegion = mock(DataRegion.class);
+ when(dataRegion.getDatabaseName()).thenReturn(DATABASE);
+ when(dataRegion.getMemCost()).thenReturn(memCost);
+ return dataRegion;
+ }
+
+ private static TsFileProcessorInfo mockTsFileProcessorInfo(final long memCost) {
+ final TsFileProcessorInfo tsFileProcessorInfo = mock(TsFileProcessorInfo.class);
+ when(tsFileProcessorInfo.getMemCost()).thenReturn(memCost);
+ return tsFileProcessorInfo;
+ }
+
+ private AutoGauge getMem(final String name) {
+ final IMetric metric = findMem(name);
+ if (metric == null) {
+ throw new AssertionError(Metric.MEM + " " + name + " is not registered");
+ }
+ return (AutoGauge) metric;
+ }
+
+ private IMetric findMem(final String name) {
+ for (final Map.Entry<MetricInfo, IMetric> entry : service.getAllMetrics().entrySet()) {
+ if (Metric.MEM.toString().equals(entry.getKey().getName())
+ && name.equals(entry.getKey().getTags().get(Tag.NAME.toString()))) {
+ return entry.getValue();
+ }
+ }
+ return null;
+ }
+}
diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/subscription/metric/SubscriptionMetricsRestartTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/subscription/metric/SubscriptionMetricsRestartTest.java
new file mode 100644
index 00000000000..a2142ad61fd
--- /dev/null
+++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/subscription/metric/SubscriptionMetricsRestartTest.java
@@ -0,0 +1,188 @@
+/*
+ * 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.iotdb.db.subscription.metric;
+
+import org.apache.iotdb.commons.consensus.DataRegionId;
+import org.apache.iotdb.commons.service.metric.MetricService;
+import org.apache.iotdb.commons.service.metric.enums.Metric;
+import org.apache.iotdb.db.subscription.broker.SubscriptionPrefetchingQueue;
+import org.apache.iotdb.db.subscription.broker.consensus.ConsensusPrefetchingQueue;
+import org.apache.iotdb.metrics.config.MetricConfig;
+import org.apache.iotdb.metrics.config.MetricConfigDescriptor;
+import org.apache.iotdb.metrics.metricsets.IMetricSet;
+import org.apache.iotdb.metrics.type.AutoGauge;
+import org.apache.iotdb.metrics.type.IMetric;
+import org.apache.iotdb.metrics.type.Rate;
+import org.apache.iotdb.metrics.utils.MetricInfo;
+import org.apache.iotdb.metrics.utils.MetricLevel;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+
+/** Subscription metrics must survive a metric service restart, which drops all metrics. */
+public class SubscriptionMetricsRestartTest {
+
+ private static final String QUEUE_ID = "consumer_group_topic";
+
+ private final MetricService service = MetricService.getInstance();
+ private final MetricConfig config = MetricConfigDescriptor.getInstance().getMetricConfig();
+ private final List<IMetricSet> boundMetricSets = new ArrayList<>();
+ private MetricLevel originalLevel;
+ private String originalReporters;
+
+ @Before
+ public void setUp() {
+ originalLevel = config.getMetricLevel();
+ originalReporters =
+ config.getMetricReporterList().stream().map(Enum::name).collect(Collectors.joining(","));
+ config.setMetricLevel(MetricLevel.IMPORTANT);
+ config.setMetricReporterList("");
+ service.startService();
+ }
+
+ @After
+ public void tearDown() {
+ boundMetricSets.forEach(service::removeMetricSet);
+ service.stopService();
+ config.setMetricLevel(originalLevel);
+ config.setMetricReporterList(originalReporters);
+ }
+
+ @Test
+ public void testPrefetchingQueueMetrics() {
+ final SubscriptionPrefetchingQueueMetrics metrics =
+ SubscriptionPrefetchingQueueMetrics.getInstance();
+ bind(metrics);
+ final SubscriptionPrefetchingQueue queue = Mockito.mock(SubscriptionPrefetchingQueue.class);
+ when(queue.getPrefetchingQueueId()).thenReturn(QUEUE_ID);
+ when(queue.getSubscriptionUncommittedEventCount()).thenReturn(3L);
+ metrics.register(queue);
+ try {
+ service.restartService();
+ assertEquals(3, ((AutoGauge) get(Metric.SUBSCRIPTION_UNCOMMITTED_EVENT_COUNT)).getValue(), 0);
+ metrics.mark(QUEUE_ID, 5);
+ assertEquals(5, ((Rate) get(Metric.SUBSCRIPTION_EVENT_TRANSFER)).getCount());
+ } finally {
+ metrics.deregister(QUEUE_ID);
+ }
+ assertEquals(0, count(Metric.SUBSCRIPTION_UNCOMMITTED_EVENT_COUNT));
+ assertEquals(0, count(Metric.SUBSCRIPTION_EVENT_TRANSFER));
+ }
+
+ @Test
+ public void testConsensusPrefetchingQueueMetrics() {
+ final ConsensusSubscriptionPrefetchingQueueMetrics metrics =
+ ConsensusSubscriptionPrefetchingQueueMetrics.getInstance();
+ bind(metrics);
+ final ConsensusPrefetchingQueue queue = Mockito.mock(ConsensusPrefetchingQueue.class);
+ when(queue.getPrefetchingQueueId()).thenReturn(QUEUE_ID);
+ when(queue.getConsensusGroupId()).thenReturn(new DataRegionId(1));
+ when(queue.getLag()).thenReturn(7L);
+ metrics.register(queue);
+ try {
+ service.restartService();
+ assertEquals(7, ((AutoGauge) get(Metric.SUBSCRIPTION_CONSENSUS_LAG)).getValue(), 0);
+ metrics.mark(QUEUE_ID, new DataRegionId(1).toString(), 5);
+ assertEquals(5, ((Rate) get(Metric.SUBSCRIPTION_EVENT_TRANSFER)).getCount());
+ } finally {
+ metrics.deregister(queue);
+ }
+ assertEquals(0, count(Metric.SUBSCRIPTION_CONSENSUS_LAG));
+ assertEquals(0, count(Metric.SUBSCRIPTION_EVENT_TRANSFER));
+ }
+
+ @Test
+ public void testPipeAndConsensusQueuesCoexist() {
+ final SubscriptionPrefetchingQueueMetrics pipeMetrics =
+ SubscriptionPrefetchingQueueMetrics.getInstance();
+ final ConsensusSubscriptionPrefetchingQueueMetrics consensusMetrics =
+ ConsensusSubscriptionPrefetchingQueueMetrics.getInstance();
+ bind(pipeMetrics);
+ bind(consensusMetrics);
+ // Whichever kind of queue registers first must not stop the other kind from exporting metrics
+ final ConsensusPrefetchingQueue firstConsensusQueue = mockConsensusQueue("consensus_topic", 1);
+ final SubscriptionPrefetchingQueue pipeQueue = Mockito.mock(SubscriptionPrefetchingQueue.class);
+ when(pipeQueue.getPrefetchingQueueId()).thenReturn(QUEUE_ID);
+ when(pipeQueue.getSubscriptionUncommittedEventCount()).thenReturn(3L);
+ final ConsensusPrefetchingQueue secondConsensusQueue = mockConsensusQueue("consensus_topic", 2);
+ consensusMetrics.register(firstConsensusQueue);
+ pipeMetrics.register(pipeQueue);
+ consensusMetrics.register(secondConsensusQueue);
+ try {
+ assertEquals(3, count(Metric.SUBSCRIPTION_UNCOMMITTED_EVENT_COUNT));
+ pipeMetrics.mark(QUEUE_ID, 5);
+ consensusMetrics.mark("consensus_topic", new DataRegionId(1).toString(), 7);
+ consensusMetrics.mark("consensus_topic", new DataRegionId(2).toString(), 11);
+ assertEquals(
+ 23,
+ service.getAllMetrics().entrySet().stream()
+ .filter(
+ entry ->
+ Metric.SUBSCRIPTION_EVENT_TRANSFER
+ .toString()
+ .equals(entry.getKey().getName()))
+ .mapToLong(entry -> ((Rate) entry.getValue()).getCount())
+ .sum());
+ } finally {
+ pipeMetrics.deregister(QUEUE_ID);
+ consensusMetrics.deregister(firstConsensusQueue);
+ consensusMetrics.deregister(secondConsensusQueue);
+ }
+ assertEquals(0, count(Metric.SUBSCRIPTION_UNCOMMITTED_EVENT_COUNT));
+ }
+
+ private static ConsensusPrefetchingQueue mockConsensusQueue(
+ final String queueId, final int regionId) {
+ final ConsensusPrefetchingQueue queue = Mockito.mock(ConsensusPrefetchingQueue.class);
+ when(queue.getPrefetchingQueueId()).thenReturn(queueId);
+ when(queue.getConsensusGroupId()).thenReturn(new DataRegionId(regionId));
+ return queue;
+ }
+
+ private void bind(final IMetricSet metricSet) {
+ service.addMetricSet(metricSet);
+ boundMetricSets.add(metricSet);
+ }
+
+ private long count(final Metric metric) {
+ return service.getAllMetrics().keySet().stream()
+ .filter(info -> metric.toString().equals(info.getName()))
+ .count();
+ }
+
+ private IMetric get(final Metric metric) {
+ for (final Map.Entry<MetricInfo, IMetric> entry : service.getAllMetrics().entrySet()) {
+ if (metric.toString().equals(entry.getKey().getName())) {
+ return entry.getValue();
+ }
+ }
+ throw new AssertionError(metric + " is not registered");
+ }
+}
diff --git a/iotdb-core/metrics/interface/src/main/java/org/apache/iotdb/metrics/metricsets/jvm/JvmGcMetrics.java b/iotdb-core/metrics/interface/src/main/java/org/apache/iotdb/metrics/metricsets/jvm/JvmGcMetrics.java
index ee65e7eff13..3ddf7af9d3e 100644
--- a/iotdb-core/metrics/interface/src/main/java/org/apache/iotdb/metrics/metricsets/jvm/JvmGcMetrics.java
+++ b/iotdb-core/metrics/interface/src/main/java/org/apache/iotdb/metrics/metricsets/jvm/JvmGcMetrics.java
@@ -43,9 +43,11 @@ import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryType;
import java.lang.management.MemoryUsage;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
@@ -59,6 +61,10 @@ public class JvmGcMetrics implements IMetricSet, AutoCloseable {
private String oldGenPoolName;
private String nonGenerationalMemoryPool;
private final Map<String, AtomicLong> lastGcTotalDurationMap = new ConcurrentHashMap<>();
+ // The action and cause tags of the GC pause timers created by the GC notification listeners
+ private final Set<List<String>> gcPauseTimerTags = ConcurrentHashMap.newKeySet();
+ // The ZGC counters created by the GC notification listeners
+ private final Set<String> zgcCounterNames = ConcurrentHashMap.newKeySet();
public JvmGcMetrics() {
for (MemoryPoolMXBean mbean : ManagementFactory.getMemoryPoolMXBeans()) {
@@ -167,6 +173,21 @@ public class JvmGcMetrics implements IMetricSet, AutoCloseable {
// long live heap pool includes old gen heap pool and non-generation heap pool.
final AtomicLong longLivedHeapPoolSizeAfterGc = new AtomicLong();
+ // The listeners create the GC pause timers and the ZGC counters on the first GC, so create the
+ // known ones again when binding again, e.g. when the metric service restarts
+ for (List<String> tags : gcPauseTimerTags) {
+ metricService.getOrCreateTimer(
+ SystemMetric.JVM_GC_PAUSE.toString(),
+ MetricLevel.CORE,
+ "action",
+ tags.get(0),
+ "cause",
+ tags.get(1));
+ }
+ for (String name : zgcCounterNames) {
+ metricService.getOrCreateCounter(name, MetricLevel.CORE);
+ }
+
// start watching for GC notifications
for (GarbageCollectorMXBean mbean : ManagementFactory.getGarbageCollectorMXBeans()) {
if (!(mbean instanceof NotificationEmitter)) {
@@ -210,6 +231,7 @@ public class JvmGcMetrics implements IMetricSet, AutoCloseable {
"cause",
gcCause);
timer.update(duration, TimeUnit.MILLISECONDS);
+ gcPauseTimerTags.add(Arrays.asList(gcAction, gcCause));
// add support for ZGC
if (mbean.getName().equals("ZGC Cycles")) {
@@ -217,11 +239,13 @@ public class JvmGcMetrics implements IMetricSet, AutoCloseable {
metricService.getOrCreateCounter(
SystemMetric.JVM_ZGC_CYCLES_COUNT.toString(), MetricLevel.CORE);
cyclesCount.inc();
+ zgcCounterNames.add(SystemMetric.JVM_ZGC_CYCLES_COUNT.toString());
} else if (mbean.getName().equals("ZGC Pauses")) {
Counter pausesCount =
metricService.getOrCreateCounter(
SystemMetric.JVM_ZGC_PAUSES_COUNT.toString(), MetricLevel.CORE);
pausesCount.inc();
+ zgcCounterNames.add(SystemMetric.JVM_ZGC_PAUSES_COUNT.toString());
}
// Update promotion and allocation counters
@@ -309,6 +333,10 @@ public class JvmGcMetrics implements IMetricSet, AutoCloseable {
return;
}
+ // Stop the listeners added by bindTo, otherwise they keep running after the metric service
+ // restarts, next to the listeners added by the next bindTo
+ close();
+
metricService.remove(MetricType.AUTO_GAUGE, SystemMetric.JVM_GC_MAX_DATA_SIZE_BYTES.toString());
metricService.remove(
MetricType.AUTO_GAUGE, SystemMetric.JVM_GC_LIVE_DATA_SIZE_BYTES.toString());
@@ -330,51 +358,18 @@ public class JvmGcMetrics implements IMetricSet, AutoCloseable {
}
}
- // start watching for GC notifications
- for (GarbageCollectorMXBean mbean : ManagementFactory.getGarbageCollectorMXBeans()) {
- if (!(mbean instanceof NotificationEmitter)) {
- continue;
- }
- NotificationListener notificationListener =
- (notification, ref) -> {
- CompositeData cd = (CompositeData) notification.getUserData();
- GarbageCollectionNotificationInfo notificationInfo =
- GarbageCollectionNotificationInfo.from(cd);
-
- String gcCause = notificationInfo.getGcCause();
- String gcAction = notificationInfo.getGcAction();
- metricService.remove(
- MetricType.TIMER,
- SystemMetric.JVM_GC_PAUSE.toString(),
- "action",
- gcAction,
- "cause",
- gcCause);
-
- if (mbean.getName().equals("ZGC Cycles")) {
- metricService.remove(
- MetricType.COUNTER, SystemMetric.JVM_ZGC_CYCLES_COUNT.toString());
- } else if (mbean.getName().equals("ZGC Pauses")) {
- metricService.remove(
- MetricType.COUNTER, SystemMetric.JVM_ZGC_PAUSES_COUNT.toString());
- }
- };
- NotificationEmitter notificationEmitter = (NotificationEmitter) mbean;
- notificationEmitter.addNotificationListener(
- notificationListener,
- notification ->
- notification
- .getType()
- .equals(GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION),
- null);
- notificationListenerCleanUpRunnables.add(
- () -> {
- try {
- notificationEmitter.removeNotificationListener(notificationListener);
- } catch (ListenerNotFoundException ignore) {
- // do nothing
- }
- });
+ // Keep the tags and names, the next bindTo creates the metrics again
+ for (List<String> tags : gcPauseTimerTags) {
+ metricService.remove(
+ MetricType.TIMER,
+ SystemMetric.JVM_GC_PAUSE.toString(),
+ "action",
+ tags.get(0),
+ "cause",
+ tags.get(1));
+ }
+ for (String name : zgcCounterNames) {
+ metricService.remove(MetricType.COUNTER, name);
}
}
@@ -420,6 +415,7 @@ public class JvmGcMetrics implements IMetricSet, AutoCloseable {
@Override
public void close() {
notificationListenerCleanUpRunnables.forEach(Runnable::run);
+ notificationListenerCleanUpRunnables.clear();
}
enum GcGenerationAge {
diff --git a/iotdb-core/metrics/interface/src/main/java/org/apache/iotdb/metrics/metricsets/system/SystemMetrics.java b/iotdb-core/metrics/interface/src/main/java/org/apache/iotdb/metrics/metricsets/system/SystemMetrics.java
index e25a1cb4fbd..ce447ac482c 100644
--- a/iotdb-core/metrics/interface/src/main/java/org/apache/iotdb/metrics/metricsets/system/SystemMetrics.java
+++ b/iotdb-core/metrics/interface/src/main/java/org/apache/iotdb/metrics/metricsets/system/SystemMetrics.java
@@ -42,6 +42,7 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.management.ManagementFactory;
import java.nio.file.FileStore;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -61,7 +62,8 @@ public class SystemMetrics implements IMetricSet {
static final String SYSTEM = "system";
private final com.sun.management.OperatingSystemMXBean osMxBean;
- private Set<FileStore> fileStores = new HashSet<>();
+ private volatile Set<FileStore> fileStores = new HashSet<>();
+ private volatile List<String> diskDirs = Collections.emptyList();
private static final String FAILED_TO_STATISTIC = "Failed to statistic the size of {}, because";
public SystemMetrics() {
@@ -69,6 +71,7 @@ public class SystemMetrics implements IMetricSet {
}
public void setDiskDirs(List<String> diskDirs) {
+ this.diskDirs = diskDirs;
if (!MetricConfigDescriptor.getInstance()
.getMetricConfig()
.getMetricLevel()
@@ -292,6 +295,11 @@ public class SystemMetrics implements IMetricSet {
}
private void collectSystemDiskInfo(AbstractMetricService metricService) {
+ // The file stores are skipped when the metric level is OFF, so collect them if the metric
+ // service restarts with another level
+ if (fileStores.isEmpty() && !CONFIG.getMetricLevel().equals(MetricLevel.OFF)) {
+ fileStores = getFileStores(diskDirs);
+ }
metricService.createAutoGauge(
SystemMetric.SYS_DISK_TOTAL_SPACE.toString(),
MetricLevel.CORE,
@@ -331,7 +339,8 @@ public class SystemMetrics implements IMetricSet {
SystemMetric.SYS_DISK_AVAILABLE_SPACE.toString(),
SystemTag.NAME.toString(),
SYSTEM);
- fileStores.clear();
+ // Keep the file stores: they are also read by the disk load sampling, and are only collected
+ // again when the disk dirs change, not when the metric service restarts
}
public long getSystemDiskTotalSpace() {
diff --git a/iotdb-core/node-commons/src/main/i18n/en/org/apache/iotdb/commons/i18n/PipeMessages.java b/iotdb-core/node-commons/src/main/i18n/en/org/apache/iotdb/commons/i18n/PipeMessages.java
index e8646e005a3..0aafc358427 100644
--- a/iotdb-core/node-commons/src/main/i18n/en/org/apache/iotdb/commons/i18n/PipeMessages.java
+++ b/iotdb-core/node-commons/src/main/i18n/en/org/apache/iotdb/commons/i18n/PipeMessages.java
@@ -566,8 +566,6 @@ public final class PipeMessages {
// ===================== PipeEventCommitMetrics =====================
- public static final String FAILED_TO_UNBIND_COMMIT_METRICS =
- "Failed to unbind from pipe event commit metrics, event committer map not empty";
// ===================== PipePhantomReferenceManager =====================
diff --git a/iotdb-core/node-commons/src/main/i18n/en/org/apache/iotdb/commons/i18n/ServiceMessages.java b/iotdb-core/node-commons/src/main/i18n/en/org/apache/iotdb/commons/i18n/ServiceMessages.java
index 8c8bfbc0d29..6b2462b9d2a 100644
--- a/iotdb-core/node-commons/src/main/i18n/en/org/apache/iotdb/commons/i18n/ServiceMessages.java
+++ b/iotdb-core/node-commons/src/main/i18n/en/org/apache/iotdb/commons/i18n/ServiceMessages.java
@@ -117,6 +117,8 @@ public final class ServiceMessages {
"MetricService try to restart.";
public static final String METRIC_SERVICE_REBIND_METRIC_SET =
"MetricService rebind metricSet: {}";
+ public static final String LOG_FAILED_TO_REBIND_METRIC_SET_ARG_2C440B71 =
+ "Failed to rebind metric set {}";
public static final String METRIC_SERVICE_RESTART_SUCCESSFULLY =
"MetricService restart successfully.";
public static final String METRIC_SERVICE_TRY_TO_STOP = "MetricService try to stop.";
diff --git a/iotdb-core/node-commons/src/main/i18n/zh/org/apache/iotdb/commons/i18n/PipeMessages.java b/iotdb-core/node-commons/src/main/i18n/zh/org/apache/iotdb/commons/i18n/PipeMessages.java
index c6da4c6082e..9746b71500d 100644
--- a/iotdb-core/node-commons/src/main/i18n/zh/org/apache/iotdb/commons/i18n/PipeMessages.java
+++ b/iotdb-core/node-commons/src/main/i18n/zh/org/apache/iotdb/commons/i18n/PipeMessages.java
@@ -546,8 +546,6 @@ public final class PipeMessages {
// ===================== PipeEventCommitMetrics =====================
- public static final String FAILED_TO_UNBIND_COMMIT_METRICS =
- "从 pipe 事件提交指标解绑失败,事件提交器映射不为空";
// ===================== PipePhantomReferenceManager =====================
diff --git a/iotdb-core/node-commons/src/main/i18n/zh/org/apache/iotdb/commons/i18n/ServiceMessages.java b/iotdb-core/node-commons/src/main/i18n/zh/org/apache/iotdb/commons/i18n/ServiceMessages.java
index a96ae6f449d..66ce5ebfaf1 100644
--- a/iotdb-core/node-commons/src/main/i18n/zh/org/apache/iotdb/commons/i18n/ServiceMessages.java
+++ b/iotdb-core/node-commons/src/main/i18n/zh/org/apache/iotdb/commons/i18n/ServiceMessages.java
@@ -117,6 +117,8 @@ public final class ServiceMessages {
"指标服务尝试重启。";
public static final String METRIC_SERVICE_REBIND_METRIC_SET =
"指标服务重新绑定指标集:{}";
+ public static final String LOG_FAILED_TO_REBIND_METRIC_SET_ARG_2C440B71 =
+ "重新绑定指标集 {} 失败";
public static final String METRIC_SERVICE_RESTART_SUCCESSFULLY =
"指标服务重启成功。";
public static final String METRIC_SERVICE_TRY_TO_STOP = "指标服务尝试停止。";
diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/concurrent/ThreadPoolMetrics.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/concurrent/ThreadPoolMetrics.java
index 0523daf05a0..37566ccd740 100644
--- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/concurrent/ThreadPoolMetrics.java
+++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/concurrent/ThreadPoolMetrics.java
@@ -29,14 +29,12 @@ import org.apache.iotdb.metrics.utils.SystemTag;
import java.util.HashMap;
import java.util.Map;
-import java.util.Optional;
@SuppressWarnings("java:S6548")
public class ThreadPoolMetrics implements IMetricSet {
private AbstractMetricService metricService;
- private final Map<String, IThreadPoolMBean> notRegisteredPoolMap = new HashMap<>();
- private final Map<String, IThreadPoolMBean> registeredPoolMap = new HashMap<>();
+ private final Map<String, IThreadPoolMBean> poolMap = new HashMap<>();
public static ThreadPoolMetrics getInstance() {
return ThreadPoolMetricsHolder.INSTANCE;
@@ -45,72 +43,53 @@ public class ThreadPoolMetrics implements IMetricSet {
private ThreadPoolMetrics() {}
public synchronized void registerThreadPool(IThreadPoolMBean pool, String name) {
- if (metricService == null) {
- notRegisteredPoolMap.put(name, pool);
- } else {
- registeredPoolMap.put(name, pool);
- registerThreadPoolMetrics(name);
+ poolMap.put(name, pool);
+ if (metricService != null) {
+ registerThreadPoolMetrics(name, pool);
}
}
- private void registerThreadPoolMetrics(String name) {
+ private void registerThreadPoolMetrics(String name, IThreadPoolMBean pool) {
metricService.createAutoGauge(
SystemMetric.THREAD_POOL_ACTIVE_THREAD_COUNT.toString(),
MetricLevel.IMPORTANT,
- registeredPoolMap,
- map ->
- Optional.ofNullable(registeredPoolMap.get(name))
- .map(IThreadPoolMBean::getActiveCount)
- .orElse(0),
+ pool,
+ IThreadPoolMBean::getActiveCount,
SystemTag.POOL_NAME.toString(),
name);
metricService.createAutoGauge(
SystemMetric.THREAD_POOL_CORE_SIZE.toString(),
MetricLevel.IMPORTANT,
- registeredPoolMap,
- map ->
- Optional.ofNullable(registeredPoolMap.get(name))
- .map(IThreadPoolMBean::getCorePoolSize)
- .orElse(0),
+ pool,
+ IThreadPoolMBean::getCorePoolSize,
SystemTag.POOL_NAME.toString(),
name);
metricService.createAutoGauge(
SystemMetric.THREAD_POOL_WAITING_TASK_COUNT.toString(),
MetricLevel.IMPORTANT,
- registeredPoolMap,
- map ->
- Optional.ofNullable(registeredPoolMap.get(name))
- .map(IThreadPoolMBean::getQueueLength)
- .orElse(0),
+ pool,
+ IThreadPoolMBean::getQueueLength,
SystemTag.POOL_NAME.toString(),
name);
metricService.createAutoGauge(
SystemMetric.THREAD_POOL_DONE_TASK_COUNT.toString(),
MetricLevel.IMPORTANT,
- registeredPoolMap,
- map ->
- Optional.ofNullable(registeredPoolMap.get(name))
- .map(IThreadPoolMBean::getCompletedTaskCount)
- .orElse(0L),
+ pool,
+ IThreadPoolMBean::getCompletedTaskCount,
SystemTag.POOL_NAME.toString(),
name);
metricService.createAutoGauge(
SystemMetric.THREAD_POOL_LARGEST_POOL_SIZE.toString(),
MetricLevel.IMPORTANT,
- registeredPoolMap,
- map ->
- Optional.ofNullable(registeredPoolMap.get(name))
- .map(IThreadPoolMBean::getLargestPoolSize)
- .orElse(0),
+ pool,
+ IThreadPoolMBean::getLargestPoolSize,
SystemTag.POOL_NAME.toString(),
name);
}
- public synchronized void unRegisterThreadPool(String name) {
- if (metricService == null) {
- notRegisteredPoolMap.remove(name);
- } else {
- registeredPoolMap.remove(name);
+ public synchronized void unRegisterThreadPool(IThreadPoolMBean pool, String name) {
+ // A pool with the same name may have been registered while the old pool was shutting down.
+ if (poolMap.remove(name, pool) && metricService != null) {
unRegisterThreadPoolMetrics(name);
}
}
@@ -146,20 +125,21 @@ public class ThreadPoolMetrics implements IMetricSet {
@Override
public synchronized void bindTo(AbstractMetricService metricService) {
this.metricService = metricService;
- for (Map.Entry<String, IThreadPoolMBean> entry : notRegisteredPoolMap.entrySet()) {
- registerThreadPoolMetrics(entry.getKey());
+ for (Map.Entry<String, IThreadPoolMBean> entry : poolMap.entrySet()) {
+ registerThreadPoolMetrics(entry.getKey(), entry.getValue());
}
- registeredPoolMap.putAll(notRegisteredPoolMap);
- notRegisteredPoolMap.clear();
}
@Override
public synchronized void unbindFrom(AbstractMetricService metricService) {
- for (Map.Entry<String, IThreadPoolMBean> entry : registeredPoolMap.entrySet()) {
- unRegisterThreadPoolMetrics(entry.getKey());
+ if (this.metricService != metricService) {
+ return;
}
- registeredPoolMap.clear();
- notRegisteredPoolMap.clear();
+ // Keep live pools registered so a metric service restart can bind them again.
+ for (String name : poolMap.keySet()) {
+ unRegisterThreadPoolMetrics(name);
+ }
+ this.metricService = null;
}
private static class ThreadPoolMetricsHolder {
diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/concurrent/threadpool/WrappedScheduledExecutorService.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/concurrent/threadpool/WrappedScheduledExecutorService.java
index 05aa8047739..524ced4858a 100644
--- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/concurrent/threadpool/WrappedScheduledExecutorService.java
+++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/concurrent/threadpool/WrappedScheduledExecutorService.java
@@ -80,13 +80,13 @@ public class WrappedScheduledExecutorService
public void shutdown() {
service.shutdown();
JMXService.deregisterMBean(mbeanName);
- ThreadPoolMetrics.getInstance().unRegisterThreadPool(this.mbeanName);
+ ThreadPoolMetrics.getInstance().unRegisterThreadPool(this, this.mbeanName);
}
@Override
public List<Runnable> shutdownNow() {
JMXService.deregisterMBean(mbeanName);
- ThreadPoolMetrics.getInstance().unRegisterThreadPool(this.mbeanName);
+ ThreadPoolMetrics.getInstance().unRegisterThreadPool(this, this.mbeanName);
return service.shutdownNow();
}
diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/concurrent/threadpool/WrappedSingleThreadExecutorService.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/concurrent/threadpool/WrappedSingleThreadExecutorService.java
index 604a2ae438b..f51792e5f9a 100644
--- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/concurrent/threadpool/WrappedSingleThreadExecutorService.java
+++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/concurrent/threadpool/WrappedSingleThreadExecutorService.java
@@ -56,13 +56,13 @@ public class WrappedSingleThreadExecutorService
public void shutdown() {
service.shutdown();
JMXService.deregisterMBean(mbeanName);
- ThreadPoolMetrics.getInstance().unRegisterThreadPool(this.mbeanName);
+ ThreadPoolMetrics.getInstance().unRegisterThreadPool(this, this.mbeanName);
}
@Override
public List<Runnable> shutdownNow() {
JMXService.deregisterMBean(mbeanName);
- ThreadPoolMetrics.getInstance().unRegisterThreadPool(this.mbeanName);
+ ThreadPoolMetrics.getInstance().unRegisterThreadPool(this, this.mbeanName);
return service.shutdownNow();
}
diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/concurrent/threadpool/WrappedSingleThreadScheduledExecutor.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/concurrent/threadpool/WrappedSingleThreadScheduledExecutor.java
index 406a00371f4..04330f9728c 100644
--- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/concurrent/threadpool/WrappedSingleThreadScheduledExecutor.java
+++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/concurrent/threadpool/WrappedSingleThreadScheduledExecutor.java
@@ -80,13 +80,13 @@ public class WrappedSingleThreadScheduledExecutor
public void shutdown() {
service.shutdown();
JMXService.deregisterMBean(mbeanName);
- ThreadPoolMetrics.getInstance().unRegisterThreadPool(this.mbeanName);
+ ThreadPoolMetrics.getInstance().unRegisterThreadPool(this, this.mbeanName);
}
@Override
public List<Runnable> shutdownNow() {
JMXService.deregisterMBean(mbeanName);
- ThreadPoolMetrics.getInstance().unRegisterThreadPool(this.mbeanName);
+ ThreadPoolMetrics.getInstance().unRegisterThreadPool(this, this.mbeanName);
return service.shutdownNow();
}
diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/concurrent/threadpool/WrappedThreadPoolExecutor.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/concurrent/threadpool/WrappedThreadPoolExecutor.java
index 52780294882..e2df59a6922 100644
--- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/concurrent/threadpool/WrappedThreadPoolExecutor.java
+++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/concurrent/threadpool/WrappedThreadPoolExecutor.java
@@ -97,13 +97,13 @@ public class WrappedThreadPoolExecutor extends ThreadPoolExecutor
public void shutdown() {
super.shutdown();
JMXService.deregisterMBean(mbeanName);
- ThreadPoolMetrics.getInstance().unRegisterThreadPool(this.mbeanName);
+ ThreadPoolMetrics.getInstance().unRegisterThreadPool(this, this.mbeanName);
}
@Override
public List<Runnable> shutdownNow() {
JMXService.deregisterMBean(mbeanName);
- ThreadPoolMetrics.getInstance().unRegisterThreadPool(this.mbeanName);
+ ThreadPoolMetrics.getInstance().unRegisterThreadPool(this, this.mbeanName);
return super.shutdownNow();
}
diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/metric/PipeEventCommitMetrics.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/metric/PipeEventCommitMetrics.java
index 05737e5643c..7c3ba736ce4 100644
--- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/metric/PipeEventCommitMetrics.java
+++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/metric/PipeEventCommitMetrics.java
@@ -47,7 +47,7 @@ public class PipeEventCommitMetrics implements IMetricSet {
//////////////////////////// bindTo & unbindFrom (metric framework) ////////////////////////////
@Override
- public void bindTo(final AbstractMetricService metricService) {
+ public synchronized void bindTo(final AbstractMetricService metricService) {
this.metricService = metricService;
final ImmutableSet<String> committerKeys = ImmutableSet.copyOf(eventCommitterMap.keySet());
for (String committerKey : committerKeys) {
@@ -73,14 +73,11 @@ public class PipeEventCommitMetrics implements IMetricSet {
}
@Override
- public void unbindFrom(final AbstractMetricService metricService) {
- final ImmutableSet<String> committerKeys = ImmutableSet.copyOf(eventCommitterMap.keySet());
- for (String committerKey : committerKeys) {
- deregister(committerKey);
- }
- if (!eventCommitterMap.isEmpty()) {
- LOGGER.warn(PipeMessages.FAILED_TO_UNBIND_COMMIT_METRICS);
- }
+ public synchronized void unbindFrom(final AbstractMetricService metricService) {
+ // Keep the committers registered: they register only once, so a metric service restart
+ // must be able to bind them again.
+ // Synchronized with the (de)registrations, which may remove a registration being unbound.
+ ImmutableSet.copyOf(eventCommitterMap.keySet()).forEach(this::removeMetrics);
}
private void removeMetrics(final String committerKey) {
@@ -100,7 +97,8 @@ public class PipeEventCommitMetrics implements IMetricSet {
//////////////////////////// register & deregister (pipe integration) ////////////////////////////
- public void register(final PipeEventCommitter eventCommitter, final String committerKey) {
+ public synchronized void register(
+ final PipeEventCommitter eventCommitter, final String committerKey) {
if (Objects.isNull(eventCommitter)) {
return;
}
@@ -111,7 +109,7 @@ public class PipeEventCommitMetrics implements IMetricSet {
}
}
- public void deregister(final String committerKey) {
+ public synchronized void deregister(final String committerKey) {
if (!eventCommitterMap.containsKey(committerKey)) {
LOGGER.warn(PipeMessages.FAILED_TO_DEREGISTER_COMMIT_METRICS, committerKey);
return;
diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/service/metric/JvmGcMonitorMetrics.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/service/metric/JvmGcMonitorMetrics.java
index 0bdd863950d..11feb13c43d 100644
--- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/service/metric/JvmGcMonitorMetrics.java
+++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/service/metric/JvmGcMonitorMetrics.java
@@ -88,16 +88,14 @@ public class JvmGcMonitorMetrics implements IMetricSet {
curData,
GcData::getGcTimePercentage);
- monitorStartTime = System.currentTimeMillis();
- // Set start time's accumulated GC Time
- curData.setAccumulatedGcTime(getTotalGCTime());
- // current collect time: startTime + start delay(50ms)
- gcDataBuf[startIdx].setValues(monitorStartTime + TimeUnit.MILLISECONDS.toMillis(50), 0);
+ startMonitoring(System.currentTimeMillis(), getTotalGCTime());
scheduledGcMonitorFuture =
ScheduledExecutorUtil.safelyScheduleWithFixedDelay(
scheduledGCInfoMonitor,
this::scheduledMonitoring,
- TimeUnit.MILLISECONDS.toMillis(50), // to prevent / ZERO exception
+ // The first sample covers a whole interval, as the later ones do, so the percentage is
+ // never calculated over a tiny window
+ SLEEP_INTERVAL_MS,
SLEEP_INTERVAL_MS,
TimeUnit.MILLISECONDS);
}
@@ -113,8 +111,21 @@ public class JvmGcMonitorMetrics implements IMetricSet {
}
}
+ /**
+ * Starts a new observation. The buffer may still hold the samples of a previous binding, e.g.
+ * before the metric service restarts, so start again from an empty buffer.
+ */
+ synchronized void startMonitoring(long startTime, long totalGcTime) {
+ monitorStartTime = startTime;
+ // Set start time's accumulated GC Time
+ curData.setAccumulatedGcTime(totalGcTime);
+ startIdx = 0;
+ endIdx = 0;
+ gcDataBuf[startIdx].setValues(startTime, 0);
+ }
+
private void scheduledMonitoring() {
- calculateGCTimePercentageWithinObservedInterval();
+ calculateGCTimePercentageWithinObservedInterval(System.currentTimeMillis(), getTotalGCTime());
// Alert if necessary
if (alertHandler != null && curData.getGcTimePercentage() > MAX_GC_TIME_PERCENTAGE) {
@@ -130,12 +141,11 @@ public class JvmGcMonitorMetrics implements IMetricSet {
return totalGcTime;
}
- private void calculateGCTimePercentageWithinObservedInterval() {
+ synchronized void calculateGCTimePercentageWithinObservedInterval(
+ long curTime, long totalGcTime) {
long prevTotalGcTime = curData.getAccumulatedGcTime();
- long totalGcTime = getTotalGCTime();
long gcTimeWithinSleepInterval = totalGcTime - prevTotalGcTime;
- long curTime = System.currentTimeMillis();
long gcMonitorRunTime = curTime - monitorStartTime;
endIdx = (endIdx + 1) % bufSize;
@@ -153,10 +163,10 @@ public class JvmGcMonitorMetrics implements IMetricSet {
// in our observation window.
long gcTimeWithinObservationWindow =
Math.min(gcDataBuf[startIdx].gcPause, gcDataBuf[startIdx].ts - startObsWindowTs);
- if (startIdx != endIdx) {
- for (int i = (startIdx + 1) % bufSize; i != endIdx; i = (i + 1) % bufSize) {
- gcTimeWithinObservationWindow += gcDataBuf[i].gcPause;
- }
+ // The entries after the start one are all within the window, including the latest one
+ for (int i = startIdx; i != endIdx; ) {
+ i = (i + 1) % bufSize;
+ gcTimeWithinObservationWindow += gcDataBuf[i].gcPause;
}
curData.update(
diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/service/metric/MetricService.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/service/metric/MetricService.java
index 4af78e3d9cf..69de6df79d2 100644
--- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/service/metric/MetricService.java
+++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/service/metric/MetricService.java
@@ -130,8 +130,17 @@ public class MetricService extends AbstractMetricService implements MetricServic
for (IMetricSet metricSet : metricSets) {
LOGGER.info(
ServiceMessages.METRIC_SERVICE_REBIND_METRIC_SET, metricSet.getClass().getName());
- metricSet.unbindFrom(this);
- metricSet.bindTo(this);
+ // Every metric was dropped by the restart, so a failed metric set must not stop the others
+ // from being bound again
+ try {
+ metricSet.unbindFrom(this);
+ metricSet.bindTo(this);
+ } catch (Exception e) {
+ LOGGER.warn(
+ ServiceMessages.LOG_FAILED_TO_REBIND_METRIC_SET_ARG_2C440B71,
+ metricSet.getClass().getName(),
+ e);
+ }
}
}
LOGGER.info(ServiceMessages.METRIC_SERVICE_RESTART_SUCCESSFULLY);
diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/service/metric/enums/Metric.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/service/metric/enums/Metric.java
index c6e0213630f..5bcafffaa14 100644
--- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/service/metric/enums/Metric.java
+++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/service/metric/enums/Metric.java
@@ -116,6 +116,9 @@ public enum Metric {
LEADER_QUANTITY("leader_quantity"),
SCHEMA_REGION("schema_region"),
SCHEMA_ENGINE("schema_engine"),
+ // The per-table metrics have an extra table tag, so they cannot share the names above
+ SCHEMA_REGION_TABLE("schema_region_table"),
+ SCHEMA_ENGINE_TABLE("schema_engine_table"),
// query engine related
QUERY_PLAN_COST("query_plan_cost"),
OPERATOR_EXECUTION_COST("operator_execution_cost"),
diff --git a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/concurrent/ThreadPoolMetricsTest.java b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/concurrent/ThreadPoolMetricsTest.java
new file mode 100644
index 00000000000..c65554fba34
--- /dev/null
+++ b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/concurrent/ThreadPoolMetricsTest.java
@@ -0,0 +1,157 @@
+/*
+ * 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.iotdb.commons.concurrent;
+
+import org.apache.iotdb.commons.conf.IoTDBConstant;
+import org.apache.iotdb.commons.service.metric.MetricService;
+import org.apache.iotdb.metrics.config.MetricConfig;
+import org.apache.iotdb.metrics.config.MetricConfigDescriptor;
+import org.apache.iotdb.metrics.type.AutoGauge;
+import org.apache.iotdb.metrics.type.IMetric;
+import org.apache.iotdb.metrics.utils.MetricInfo;
+import org.apache.iotdb.metrics.utils.MetricLevel;
+import org.apache.iotdb.metrics.utils.SystemMetric;
+import org.apache.iotdb.metrics.utils.SystemTag;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class ThreadPoolMetricsTest {
+
+ private static final String POOL_PREFIX = "ThreadPoolMetricsTest-";
+
+ private final ThreadPoolMetrics metrics = ThreadPoolMetrics.getInstance();
+ private final MetricService service = MetricService.getInstance();
+ private final MetricConfig config = MetricConfigDescriptor.getInstance().getMetricConfig();
+ private final List<ExecutorService> pools = new ArrayList<>();
+ private MetricLevel originalLevel;
+ private String originalReporters;
+
+ @Before
+ public void setUp() {
+ originalLevel = config.getMetricLevel();
+ originalReporters =
+ config.getMetricReporterList().stream().map(Enum::name).collect(Collectors.joining(","));
+ config.setMetricLevel(MetricLevel.IMPORTANT);
+ config.setMetricReporterList("");
+ service.startService();
+ service.addMetricSet(metrics);
+ }
+
+ @After
+ public void tearDown() {
+ pools.forEach(ExecutorService::shutdownNow);
+ service.removeMetricSet(metrics);
+ service.stopService();
+ config.setMetricLevel(originalLevel);
+ config.setMetricReporterList(originalReporters);
+ }
+
+ @Test
+ public void testMetricsSurviveMetricServiceRestart() {
+ createPool("restart", 2);
+ assertPoolMetrics("restart", 2);
+
+ for (MetricLevel level :
+ new MetricLevel[] {
+ MetricLevel.ALL, MetricLevel.CORE, MetricLevel.IMPORTANT, MetricLevel.IMPORTANT
+ }) {
+ config.setMetricLevel(level);
+ service.restartService();
+ if (level == MetricLevel.CORE) {
+ assertTrue(getPoolMetrics("restart").isEmpty());
+ } else {
+ assertPoolMetrics("restart", 2);
+ }
+ }
+ }
+
+ @Test
+ public void testShutdownPoolIsNotRestoredByRestart() {
+ ExecutorService pool = createPool("shutdown", 1);
+ assertPoolMetrics("shutdown", 1);
+
+ pool.shutdown();
+ assertTrue(getPoolMetrics("shutdown").isEmpty());
+ service.restartService();
+ assertTrue(getPoolMetrics("shutdown").isEmpty());
+ }
+
+ @Test
+ public void testRegistrationWhileUnbound() {
+ service.removeMetricSet(metrics);
+ createPool("unbound", 3);
+ assertTrue(getPoolMetrics("unbound").isEmpty());
+
+ service.addMetricSet(metrics);
+ assertPoolMetrics("unbound", 3);
+ }
+
+ @Test
+ public void testShuttingDownSupersededPoolKeepsReplacementMetrics() {
+ ExecutorService first = createPool("replaced", 1);
+ ExecutorService replacement = createPool("replaced", 4);
+
+ first.shutdown();
+ assertPoolMetrics("replaced", 4);
+ service.restartService();
+ assertPoolMetrics("replaced", 4);
+
+ replacement.shutdown();
+ assertTrue(getPoolMetrics("replaced").isEmpty());
+ }
+
+ private ExecutorService createPool(String name, int size) {
+ ExecutorService pool = IoTDBThreadPoolFactory.newFixedThreadPool(size, POOL_PREFIX + name);
+ pools.add(pool);
+ return pool;
+ }
+
+ private Map<String, Double> getPoolMetrics(String name) {
+ String poolName =
+ String.format(
+ "%s:%s=%s",
+ IoTDBConstant.IOTDB_THREADPOOL_JMX_NAME, IoTDBConstant.JMX_TYPE, POOL_PREFIX + name);
+ Map<String, Double> poolMetrics = new HashMap<>();
+ for (Map.Entry<MetricInfo, IMetric> entry : service.getAllMetrics().entrySet()) {
+ if (poolName.equals(entry.getKey().getTags().get(SystemTag.POOL_NAME.toString()))) {
+ poolMetrics.put(entry.getKey().getName(), ((AutoGauge) entry.getValue()).getValue());
+ }
+ }
+ return poolMetrics;
+ }
+
+ private void assertPoolMetrics(String name, int coreSize) {
+ Map<String, Double> poolMetrics = getPoolMetrics(name);
+ assertEquals(5, poolMetrics.size());
+ assertEquals(coreSize, poolMetrics.get(SystemMetric.THREAD_POOL_CORE_SIZE.toString()), 0);
+ }
+}
diff --git a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/metric/PipeEventCommitMetricsTest.java b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/metric/PipeEventCommitMetricsTest.java
new file mode 100644
index 00000000000..ed6b18d8bd0
--- /dev/null
+++ b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/metric/PipeEventCommitMetricsTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.iotdb.commons.pipe.metric;
+
+import org.apache.iotdb.commons.pipe.agent.task.progress.PipeEventCommitter;
+import org.apache.iotdb.commons.service.metric.MetricService;
+import org.apache.iotdb.commons.service.metric.enums.Metric;
+import org.apache.iotdb.metrics.config.MetricConfig;
+import org.apache.iotdb.metrics.config.MetricConfigDescriptor;
+import org.apache.iotdb.metrics.type.AutoGauge;
+import org.apache.iotdb.metrics.type.IMetric;
+import org.apache.iotdb.metrics.utils.MetricInfo;
+import org.apache.iotdb.metrics.utils.MetricLevel;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+public class PipeEventCommitMetricsTest {
+
+ private static final String COMMITTER_KEY = "committer";
+
+ private final PipeEventCommitMetrics metrics = PipeEventCommitMetrics.getInstance();
+ private final MetricService service = MetricService.getInstance();
+ private final MetricConfig config = MetricConfigDescriptor.getInstance().getMetricConfig();
+ private MetricLevel originalLevel;
+ private String originalReporters;
+
+ @Before
+ public void setUp() {
+ originalLevel = config.getMetricLevel();
+ originalReporters =
+ config.getMetricReporterList().stream().map(Enum::name).collect(Collectors.joining(","));
+ config.setMetricLevel(MetricLevel.IMPORTANT);
+ config.setMetricReporterList("");
+ service.startService();
+ service.addMetricSet(metrics);
+ }
+
+ @After
+ public void tearDown() {
+ service.removeMetricSet(metrics);
+ service.stopService();
+ config.setMetricLevel(originalLevel);
+ config.setMetricReporterList(originalReporters);
+ }
+
+ @Test
+ public void testCommitQueueSizeSurvivesMetricServiceRestart() {
+ final PipeEventCommitter committer = Mockito.mock(PipeEventCommitter.class);
+ Mockito.when(committer.getPipeName()).thenReturn("pipe");
+ Mockito.when(committer.getRegionId()).thenReturn(1);
+ Mockito.when(committer.commitQueueSize()).thenReturn(3L);
+ metrics.register(committer, COMMITTER_KEY);
+ try {
+ service.restartService();
+ final AutoGauge gauge = getCommitQueueSizeGauge();
+ assertNotNull(gauge);
+ assertEquals(3, gauge.getValue(), 0);
+ } finally {
+ metrics.deregister(COMMITTER_KEY);
+ }
+ assertNull(getCommitQueueSizeGauge());
+ }
+
+ private AutoGauge getCommitQueueSizeGauge() {
+ for (final Map.Entry<MetricInfo, IMetric> entry : service.getAllMetrics().entrySet()) {
+ if (Metric.PIPE_EVENT_COMMIT_QUEUE_SIZE.toString().equals(entry.getKey().getName())) {
+ return (AutoGauge) entry.getValue();
+ }
+ }
+ return null;
+ }
+}
diff --git a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/service/metric/JvmGcMonitorMetricsTest.java b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/service/metric/JvmGcMonitorMetricsTest.java
new file mode 100644
index 00000000000..799df7960c6
--- /dev/null
+++ b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/service/metric/JvmGcMonitorMetricsTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.iotdb.commons.service.metric;
+
+import org.junit.Test;
+
+import static org.apache.iotdb.commons.service.metric.JvmGcMonitorMetrics.OBSERVATION_WINDOW_MS;
+import static org.apache.iotdb.commons.service.metric.JvmGcMonitorMetrics.SLEEP_INTERVAL_MS;
+import static org.junit.Assert.assertEquals;
+
+public class JvmGcMonitorMetricsTest {
+
+ private static final long START_TIME = 1_000_000L;
+
+ private final JvmGcMonitorMetrics monitor = JvmGcMonitorMetrics.getInstance();
+
+ /** Each sample spends the given GC time within its interval, returns the latest percentage. */
+ private long sample(final long startTime, final int samples, final long gcTimePerSample) {
+ long totalGcTime = monitor.getGcData().getAccumulatedGcTime();
+ for (int i = 1; i <= samples; i++) {
+ totalGcTime += gcTimePerSample;
+ monitor.calculateGCTimePercentageWithinObservedInterval(
+ startTime + i * SLEEP_INTERVAL_MS, totalGcTime);
+ }
+ return monitor.getGcData().getGcTimePercentage();
+ }
+
+ @Test
+ public void testLatestSampleIsCounted() {
+ monitor.startMonitoring(START_TIME, 0);
+ // 300 ms GC in the first 3 s
+ assertEquals(10, sample(START_TIME, 1, SLEEP_INTERVAL_MS / 10));
+ }
+
+ @Test
+ public void testWindowIsFullyCountedWhileSliding() {
+ monitor.startMonitoring(START_TIME, 0);
+ final int samplesToFillTheWindow = (int) (OBSERVATION_WINDOW_MS / SLEEP_INTERVAL_MS);
+ // Keep spending 10% of the time in GC, after the window is full and while it wraps the buffer
+ assertEquals(10, sample(START_TIME, samplesToFillTheWindow, SLEEP_INTERVAL_MS / 10));
+ final long windowFullTime = START_TIME + samplesToFillTheWindow * SLEEP_INTERVAL_MS;
+ assertEquals(10, sample(windowFullTime, 3 * samplesToFillTheWindow, SLEEP_INTERVAL_MS / 10));
+ }
+
+ @Test
+ public void testRestartDropsTheSamplesOfThePreviousRun() {
+ monitor.startMonitoring(START_TIME, 0);
+ // Half of the time in GC before the restart
+ sample(START_TIME, 3, SLEEP_INTERVAL_MS / 2);
+
+ final long restartTime = START_TIME + 4 * SLEEP_INTERVAL_MS;
+ monitor.startMonitoring(restartTime, monitor.getGcData().getAccumulatedGcTime());
+ assertEquals(0, sample(restartTime, 1, 0));
+ }
+}
diff --git a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/service/metric/MetricServiceRestartTest.java b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/service/metric/MetricServiceRestartTest.java
new file mode 100644
index 00000000000..8b93cb13aa3
--- /dev/null
+++ b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/service/metric/MetricServiceRestartTest.java
@@ -0,0 +1,218 @@
+/*
+ * 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.iotdb.commons.service.metric;
+
+import org.apache.iotdb.metrics.AbstractMetricService;
+import org.apache.iotdb.metrics.config.MetricConfig;
+import org.apache.iotdb.metrics.config.MetricConfigDescriptor;
+import org.apache.iotdb.metrics.metricsets.IMetricSet;
+import org.apache.iotdb.metrics.metricsets.jvm.JvmGcMetrics;
+import org.apache.iotdb.metrics.metricsets.system.SystemMetrics;
+import org.apache.iotdb.metrics.type.IMetric;
+import org.apache.iotdb.metrics.type.Timer;
+import org.apache.iotdb.metrics.utils.MetricInfo;
+import org.apache.iotdb.metrics.utils.MetricLevel;
+import org.apache.iotdb.metrics.utils.MetricType;
+import org.apache.iotdb.metrics.utils.SystemMetric;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
+
+import static org.awaitility.Awaitility.await;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class MetricServiceRestartTest {
+
+ private static final String METRIC = "metric_service_restart_test";
+
+ private final MetricService service = MetricService.getInstance();
+ private final MetricConfig config = MetricConfigDescriptor.getInstance().getMetricConfig();
+ private final List<IMetricSet> boundMetricSets = new ArrayList<>();
+ private MetricLevel originalLevel;
+ private String originalReporters;
+
+ @Before
+ public void setUp() {
+ originalLevel = config.getMetricLevel();
+ originalReporters =
+ config.getMetricReporterList().stream().map(Enum::name).collect(Collectors.joining(","));
+ config.setMetricLevel(MetricLevel.IMPORTANT);
+ config.setMetricReporterList("");
+ service.startService();
+ }
+
+ @After
+ public void tearDown() {
+ boundMetricSets.forEach(service::removeMetricSet);
+ service.stopService();
+ config.setMetricLevel(originalLevel);
+ config.setMetricReporterList(originalReporters);
+ }
+
+ @Test
+ public void testFailedMetricSetDoesNotStopOthersFromRebinding() {
+ // The hash codes make the failed set iterated before the other one
+ final FailingMetricSet failing = new FailingMetricSet();
+ final GaugeMetricSet gauge = new GaugeMetricSet();
+ bind(failing);
+ bind(gauge);
+ assertTrue(service.getAllMetrics().keySet().stream().anyMatch(this::isTestMetric));
+
+ failing.fail = true;
+ service.restartService();
+ failing.fail = false;
+ assertTrue(service.getAllMetrics().keySet().stream().anyMatch(this::isTestMetric));
+ }
+
+ @Test
+ public void testSystemDiskMetricsSurviveRestart() {
+ final SystemMetrics metrics = new SystemMetrics();
+ metrics.setDiskDirs(Collections.singletonList(System.getProperty("java.io.tmpdir")));
+ bind(metrics);
+ assertTrue(metrics.getSystemDiskTotalSpace() > 0);
+
+ service.restartService();
+ assertTrue(metrics.getSystemDiskTotalSpace() > 0);
+ }
+
+ @Test
+ public void testSystemDiskMetricsAreCollectedWhenLevelIsEnabledByRestart() {
+ config.setMetricLevel(MetricLevel.OFF);
+ final SystemMetrics metrics = new SystemMetrics();
+ metrics.setDiskDirs(Collections.singletonList(System.getProperty("java.io.tmpdir")));
+ bind(metrics);
+ assertEquals(0, metrics.getSystemDiskTotalSpace());
+
+ config.setMetricLevel(MetricLevel.IMPORTANT);
+ service.restartService();
+ assertTrue(metrics.getSystemDiskTotalSpace() > 0);
+ }
+
+ @Test
+ public void testGcPauseTimersKeepCountingAfterRestart() {
+ bind(new JvmGcMetrics());
+ service.restartService();
+ service.restartService();
+
+ // The listeners of previous bindings used to reset the timers on every GC
+ System.gc();
+ await().atMost(30, TimeUnit.SECONDS).until(() -> getExplicitGcCount() >= 1);
+ System.gc();
+ await().atMost(30, TimeUnit.SECONDS).until(() -> getExplicitGcCount() >= 2);
+ }
+
+ @Test
+ public void testGcPauseTimersAreBoundAgainBeforeTheNextGc() {
+ bind(new JvmGcMetrics());
+ System.gc();
+ await().atMost(30, TimeUnit.SECONDS).until(() -> getExplicitGcCount() >= 1);
+
+ service.restartService();
+ // The timers used to be created again only by the next GC with the same cause
+ assertTrue(
+ service.getAllMetrics().keySet().stream()
+ .anyMatch(
+ info ->
+ SystemMetric.JVM_GC_PAUSE.toString().equals(info.getName())
+ && "System.gc()".equals(info.getTags().get("cause"))));
+ }
+
+ private void bind(final IMetricSet metricSet) {
+ service.addMetricSet(metricSet);
+ boundMetricSets.add(metricSet);
+ }
+
+ private boolean isTestMetric(final MetricInfo info) {
+ return METRIC.equals(info.getName());
+ }
+
+ private long getExplicitGcCount() {
+ long count = 0;
+ for (final Map.Entry<MetricInfo, IMetric> entry : service.getAllMetrics().entrySet()) {
+ if (SystemMetric.JVM_GC_PAUSE.toString().equals(entry.getKey().getName())
+ && "System.gc()".equals(entry.getKey().getTags().get("cause"))) {
+ count += ((Timer) entry.getValue()).getCount();
+ }
+ }
+ return count;
+ }
+
+ private static class FailingMetricSet implements IMetricSet {
+
+ private volatile boolean fail = false;
+
+ @Override
+ public void bindTo(final AbstractMetricService metricService) {
+ if (fail) {
+ throw new IllegalStateException("bind failure for test");
+ }
+ }
+
+ @Override
+ public void unbindFrom(final AbstractMetricService metricService) {
+ // do nothing
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ return this == o;
+ }
+
+ @Override
+ public int hashCode() {
+ return 0;
+ }
+ }
+
+ private static class GaugeMetricSet implements IMetricSet {
+
+ private final AtomicLong value = new AtomicLong(1);
+
+ @Override
+ public void bindTo(final AbstractMetricService metricService) {
+ metricService.createAutoGauge(METRIC, MetricLevel.IMPORTANT, value, AtomicLong::get);
+ }
+
+ @Override
+ public void unbindFrom(final AbstractMetricService metricService) {
+ metricService.remove(MetricType.AUTO_GAUGE, METRIC);
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ return this == o;
+ }
+
+ @Override
+ public int hashCode() {
+ return 1;
+ }
+ }
+}