This is an automated email from the ASF dual-hosted git repository.
jt2594838 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 89d4fbbcd9c Add consensus subscription progress observability (#18521)
89d4fbbcd9c is described below
commit 89d4fbbcd9c0af99b1a12cd8721276e8c24985b5
Author: Caideyipi <[email protected]>
AuthorDate: Wed Aug 26 10:44:46 2026 +0800
Add consensus subscription progress observability (#18521)
---
.../org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4 | 2 +-
.../iotdb/confignode/manager/ConfigManager.java | 111 +++++++-
.../impl/DataNodeInternalRPCServiceImpl.java | 8 +-
.../common/header/DatasetHeaderFactory.java | 4 +
.../config/executor/ClusterConfigTaskExecutor.java | 19 +-
.../sys/subscription/ShowSubscriptionsTask.java | 50 ++++
.../db/queryengine/plan/parser/ASTVisitor.java | 1 +
.../plan/relational/sql/ast/ShowSubscriptions.java | 16 +-
.../plan/relational/sql/parser/AstBuilder.java | 2 +-
.../relational/sql/util/DataNodeSqlFormatter.java | 8 +-
.../subscription/ShowSubscriptionsStatement.java | 9 +
.../agent/SubscriptionBrokerAgent.java | 16 ++
.../broker/ConsensusSubscriptionBroker.java | 16 ++
.../subscription/broker/ISubscriptionBroker.java | 5 +
.../consensus/ConsensusPrefetchingQueue.java | 112 +++++++++
...nsensusSubscriptionPrefetchingQueueMetrics.java | 98 ++++++++
.../subscription/ShowSubscriptionsTaskTest.java | 80 ++++++
.../parser/ShowSubscriptionsStatementTest.java | 48 ++++
.../plan/relational/sql/ShowSubscriptionsTest.java | 64 +++++
...susSubscriptionPrefetchingQueueMetricsTest.java | 20 ++
.../schema/column/ColumnHeaderConstant.java | 36 +++
.../iotdb/commons/service/metric/enums/Metric.java | 11 +
.../consumer/SubscriptionProgressSnapshot.java | 278 +++++++++++++++++++++
.../consumer/SubscriptionProgressSnapshotTest.java | 78 ++++++
.../db/relational/grammar/sql/RelationalSql.g4 | 2 +-
.../src/main/thrift/confignode.thrift | 24 ++
.../src/main/thrift/datanode.thrift | 1 +
27 files changed, 1101 insertions(+), 18 deletions(-)
diff --git
a/iotdb-core/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4
b/iotdb-core/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4
index 7bf79721e8f..5a3198d1aab 100644
---
a/iotdb-core/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4
+++
b/iotdb-core/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4
@@ -742,7 +742,7 @@ showTopics
;
showSubscriptions
- : SHOW SUBSCRIPTIONS (ON topicName=identifier)?
+ : SHOW SUBSCRIPTIONS (DETAILS)? (ON topicName=identifier)?
;
dropSubscription
diff --git
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ConfigManager.java
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ConfigManager.java
index 7ff21974540..f86681f2bbe 100644
---
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ConfigManager.java
+++
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ConfigManager.java
@@ -68,6 +68,7 @@ import
org.apache.iotdb.commons.schema.tree.AlterTimeSeriesOperationType;
import org.apache.iotdb.commons.schema.ttl.TTLCache;
import org.apache.iotdb.commons.service.metric.MetricService;
import
org.apache.iotdb.commons.subscription.meta.consumer.CommitProgressKeeper;
+import
org.apache.iotdb.commons.subscription.meta.consumer.SubscriptionProgressSnapshot;
import org.apache.iotdb.commons.utils.AuthUtils;
import org.apache.iotdb.commons.utils.PathUtils;
import org.apache.iotdb.commons.utils.StatusUtils;
@@ -256,6 +257,7 @@ import
org.apache.iotdb.confignode.rpc.thrift.TSpaceQuotaResp;
import org.apache.iotdb.confignode.rpc.thrift.TStartPipeReq;
import org.apache.iotdb.confignode.rpc.thrift.TStopPipeReq;
import org.apache.iotdb.confignode.rpc.thrift.TSubscribeReq;
+import org.apache.iotdb.confignode.rpc.thrift.TSubscriptionProgressInfo;
import org.apache.iotdb.confignode.rpc.thrift.TThrottleQuotaResp;
import org.apache.iotdb.confignode.rpc.thrift.TTimeSlotList;
import org.apache.iotdb.confignode.rpc.thrift.TUnsetSchemaTemplateReq;
@@ -264,6 +266,7 @@ import org.apache.iotdb.consensus.common.DataSet;
import org.apache.iotdb.consensus.exception.ConsensusException;
import org.apache.iotdb.db.schemaengine.template.TemplateAlterOperationType;
import
org.apache.iotdb.db.schemaengine.template.alter.TemplateAlterOperationUtil;
+import org.apache.iotdb.mpp.rpc.thrift.TPullCommitProgressResp;
import org.apache.iotdb.rpc.RpcUtils;
import org.apache.iotdb.rpc.TSStatusCode;
import org.apache.iotdb.rpc.subscription.payload.poll.RegionProgress;
@@ -2676,9 +2679,111 @@ public class ConfigManager implements IManager {
@Override
public TShowSubscriptionResp showSubscription(TShowSubscriptionReq req) {
TSStatus status = confirmLeader();
- return status.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()
- ?
subscriptionManager.getSubscriptionCoordinator().showSubscription(req)
- : new TShowSubscriptionResp().setStatus(status);
+ if (status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+ return new TShowSubscriptionResp().setStatus(status);
+ }
+ final TShowSubscriptionResp response =
+ subscriptionManager.getSubscriptionCoordinator().showSubscription(req);
+ if (!req.isSetDetails()
+ || !req.isDetails()
+ || response.getStatus().getCode() !=
TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+ return response;
+ }
+
+ final Set<String> visibleSubscriptions = new HashSet<>();
+ final Set<String> subscriptionsWithProgress = new HashSet<>();
+ if (response.isSetSubscriptionInfoList()) {
+ response
+ .getSubscriptionInfoList()
+ .forEach(
+ info ->
+ visibleSubscriptions.add(
+ info.getTopicName() + "\u0000" +
info.getConsumerGroupId()));
+ }
+ final List<TSubscriptionProgressInfo> progressInfoList = new ArrayList<>();
+ final Map<Integer, TPullCommitProgressResp> dataNodeResponses =
+
getProcedureManager().getEnv().pullCommitProgressFromDataNodesBestEffort();
+ for (final Map.Entry<Integer, TPullCommitProgressResp> dataNodeEntry :
+ dataNodeResponses.entrySet()) {
+ final TPullCommitProgressResp dataNodeResponse =
dataNodeEntry.getValue();
+ if (!dataNodeResponse.isSetSubscriptionProgress()) {
+ continue;
+ }
+ for (final ByteBuffer serializedSnapshot :
+ dataNodeResponse.getSubscriptionProgress().values()) {
+ final SubscriptionProgressSnapshot snapshot;
+ try {
+ snapshot =
SubscriptionProgressSnapshot.deserialize(serializedSnapshot);
+ } catch (final RuntimeException ignored) {
+ // A rolling upgrade may return a snapshot encoded by a different
software version.
+ continue;
+ }
+ if (!visibleSubscriptions.contains(
+ snapshot.getTopicName() + "\u0000" +
snapshot.getConsumerGroupId())) {
+ continue;
+ }
+ subscriptionsWithProgress.add(
+ snapshot.getTopicName() + "\u0000" +
snapshot.getConsumerGroupId());
+ progressInfoList.add(
+ new TSubscriptionProgressInfo(
+ snapshot.getTopicName(),
+ snapshot.getConsumerGroupId(),
+ snapshot.getRegionId(),
+ dataNodeEntry.getKey(),
+ snapshot.isActive(),
+ snapshot.isInitialized(),
+ snapshot.getStatus(),
+ snapshot.getRemainingEventCount(),
+ snapshot.getRawWalGap(),
+ snapshot.getApproximateLag(),
+ snapshot.getInFlightEventCount(),
+ snapshot.getPrefetchedEventCount(),
+ snapshot.getPendingEventCount(),
+ snapshot.getCurrentWalSearchIndex(),
+ snapshot.getNextReadSearchIndex(),
+ snapshot.getLastProgressTimeMs(),
+ snapshot.getLastPollTimeMs(),
+ snapshot.getLastConsumerId(),
+ snapshot.getSeekGeneration()));
+ }
+ }
+ if (response.isSetSubscriptionInfoList()) {
+ response
+ .getSubscriptionInfoList()
+ .forEach(
+ info -> {
+ final String key = info.getTopicName() + "\u0000" +
info.getConsumerGroupId();
+ if (!subscriptionsWithProgress.contains(key)) {
+ progressInfoList.add(
+ new TSubscriptionProgressInfo(
+ info.getTopicName(),
+ info.getConsumerGroupId(),
+ "",
+ -1,
+ false,
+ false,
+ SubscriptionProgressSnapshot.STATUS_NO_QUEUE,
+ 0L,
+ 0L,
+ 0L,
+ 0L,
+ 0L,
+ 0L,
+ 0L,
+ 0L,
+ 0L,
+ 0L,
+ "",
+ 0L));
+ }
+ });
+ }
+ progressInfoList.sort(
+ Comparator.comparing(TSubscriptionProgressInfo::getTopicName)
+ .thenComparing(TSubscriptionProgressInfo::getConsumerGroupId)
+ .thenComparingInt(TSubscriptionProgressInfo::getDataNodeId)
+ .thenComparing(TSubscriptionProgressInfo::getRegionId));
+ return response.setSubscriptionProgressList(progressInfoList);
}
@Override
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java
index 8685df8f4be..34ef951296c 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java
@@ -1637,15 +1637,19 @@ public class DataNodeInternalRPCServiceImpl implements
IDataNodeRPCService.Iface
public TPullCommitProgressResp pullCommitProgress(TPullCommitProgressReq
req) {
if (!SubscriptionConfig.getInstance().getSubscriptionEnabled()) {
return new
TPullCommitProgressResp(RpcUtils.getStatus(TSStatusCode.UNSUPPORTED_OPERATION))
- .setCommitRegionProgress(Collections.emptyMap());
+ .setCommitRegionProgress(Collections.emptyMap())
+ .setSubscriptionProgress(Collections.emptyMap());
}
try {
final int dataNodeId =
IoTDBDescriptor.getInstance().getConfig().getDataNodeId();
final Map<String, ByteBuffer> regionProgress =
SubscriptionAgent.broker().collectAllRegionCommitProgress(dataNodeId);
+ final Map<String, ByteBuffer> subscriptionProgress =
+ SubscriptionAgent.broker().collectAllProgressSnapshots();
return new TPullCommitProgressResp(new
TSStatus(TSStatusCode.SUCCESS_STATUS.getStatusCode()))
- .setCommitRegionProgress(regionProgress);
+ .setCommitRegionProgress(regionProgress)
+ .setSubscriptionProgress(subscriptionProgress);
} catch (Exception e) {
LOGGER.warn(
DataNodeMiscMessages.MISC_LOG_ERROR_OCCURRED_WHEN_PULLING_COMMIT_PROGRESS_48C12E4B,
e);
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/common/header/DatasetHeaderFactory.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/common/header/DatasetHeaderFactory.java
index e674199df52..1c83be23650 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/common/header/DatasetHeaderFactory.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/common/header/DatasetHeaderFactory.java
@@ -182,6 +182,10 @@ public class DatasetHeaderFactory {
return new
DatasetHeader(ColumnHeaderConstant.showSubscriptionColumnHeaders, true);
}
+ public static DatasetHeader getShowSubscriptionDetailsHeader() {
+ return new
DatasetHeader(ColumnHeaderConstant.showSubscriptionDetailsColumnHeaders, true);
+ }
+
public static DatasetHeader getGetRegionIdHeader() {
return new DatasetHeader(ColumnHeaderConstant.getRegionIdColumnHeaders,
true);
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/executor/ClusterConfigTaskExecutor.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/executor/ClusterConfigTaskExecutor.java
index 65d4b3f102d..3ddd2531200 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/executor/ClusterConfigTaskExecutor.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/executor/ClusterConfigTaskExecutor.java
@@ -3085,6 +3085,7 @@ public class ClusterConfigTaskExecutor implements
IConfigTaskExecutor {
showSubscriptionReq.setTopicName(showSubscriptionsStatement.getTopicName());
}
showSubscriptionReq.setIsTableModel(showSubscriptionsStatement.isTableModel());
+ showSubscriptionReq.setDetails(showSubscriptionsStatement.isDetails());
final TShowSubscriptionResp showSubscriptionResp =
configNodeClient.showSubscription(showSubscriptionReq);
@@ -3097,11 +3098,19 @@ public class ClusterConfigTaskExecutor implements
IConfigTaskExecutor {
return future;
}
- ShowSubscriptionsTask.buildTSBlock(
- showSubscriptionResp.isSetSubscriptionInfoList()
- ? showSubscriptionResp.getSubscriptionInfoList()
- : Collections.emptyList(),
- future);
+ if (showSubscriptionsStatement.isDetails()) {
+ ShowSubscriptionsTask.buildDetailsTSBlock(
+ showSubscriptionResp.isSetSubscriptionProgressList()
+ ? showSubscriptionResp.getSubscriptionProgressList()
+ : Collections.emptyList(),
+ future);
+ } else {
+ ShowSubscriptionsTask.buildTSBlock(
+ showSubscriptionResp.isSetSubscriptionInfoList()
+ ? showSubscriptionResp.getSubscriptionInfoList()
+ : Collections.emptyList(),
+ future);
+ }
} catch (final Exception e) {
future.setException(e);
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/sys/subscription/ShowSubscriptionsTask.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/sys/subscription/ShowSubscriptionsTask.java
index 87e07063629..711e85bcf19 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/sys/subscription/ShowSubscriptionsTask.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/sys/subscription/ShowSubscriptionsTask.java
@@ -22,6 +22,7 @@ package
org.apache.iotdb.db.queryengine.plan.execution.config.sys.subscription;
import org.apache.iotdb.commons.schema.column.ColumnHeader;
import org.apache.iotdb.commons.schema.column.ColumnHeaderConstant;
import org.apache.iotdb.confignode.rpc.thrift.TShowSubscriptionInfo;
+import org.apache.iotdb.confignode.rpc.thrift.TSubscriptionProgressInfo;
import org.apache.iotdb.db.queryengine.common.header.DatasetHeaderFactory;
import org.apache.iotdb.db.queryengine.plan.execution.config.ConfigTaskResult;
import org.apache.iotdb.db.queryengine.plan.execution.config.IConfigTask;
@@ -50,6 +51,7 @@ public class ShowSubscriptionsTask implements IConfigTask {
public ShowSubscriptionsTask(final ShowSubscriptions showSubscriptions) {
this.showSubscriptionsStatement = new ShowSubscriptionsStatement();
this.showSubscriptionsStatement.setTopicName(showSubscriptions.getTopicName());
+ this.showSubscriptionsStatement.setDetails(showSubscriptions.isDetails());
this.showSubscriptionsStatement.setTableModel(true);
}
@@ -100,4 +102,52 @@ public class ShowSubscriptionsTask implements IConfigTask {
builder.build(),
DatasetHeaderFactory.getShowSubscriptionHeader()));
}
+
+ public static void buildDetailsTSBlock(
+ final List<TSubscriptionProgressInfo> progressInfoList,
+ final SettableFuture<ConfigTaskResult> future) {
+ final TsBlockBuilder builder =
+ new TsBlockBuilder(
+ ColumnHeaderConstant.showSubscriptionDetailsColumnHeaders.stream()
+ .map(ColumnHeader::getColumnType)
+ .collect(Collectors.toList()));
+
+ for (final TSubscriptionProgressInfo progressInfo : progressInfoList) {
+ builder.getTimeColumnBuilder().writeLong(0L);
+ writeText(builder, 0, progressInfo.getTopicName() + "_" +
progressInfo.getConsumerGroupId());
+ writeText(builder, 1, progressInfo.getTopicName());
+ writeText(builder, 2, progressInfo.getConsumerGroupId());
+ builder.getColumnBuilder(3).writeInt(progressInfo.getDataNodeId());
+ writeText(builder, 4, progressInfo.getRegionId());
+ writeText(builder, 5, progressInfo.getStatus());
+ builder.getColumnBuilder(6).writeBoolean(progressInfo.isActive());
+ builder.getColumnBuilder(7).writeBoolean(progressInfo.isInitialized());
+
builder.getColumnBuilder(8).writeLong(progressInfo.getRemainingEventCount());
+ builder.getColumnBuilder(9).writeLong(progressInfo.getRawWalGap());
+ builder.getColumnBuilder(10).writeLong(progressInfo.getApproximateLag());
+
builder.getColumnBuilder(11).writeLong(progressInfo.getInFlightEventCount());
+
builder.getColumnBuilder(12).writeLong(progressInfo.getPrefetchedEventCount());
+
builder.getColumnBuilder(13).writeLong(progressInfo.getPendingEventCount());
+
builder.getColumnBuilder(14).writeLong(progressInfo.getCurrentWalSearchIndex());
+
builder.getColumnBuilder(15).writeLong(progressInfo.getNextReadSearchIndex());
+
builder.getColumnBuilder(16).writeLong(progressInfo.getLastProgressTimeMs());
+ builder.getColumnBuilder(17).writeLong(progressInfo.getLastPollTimeMs());
+ writeText(builder, 18, progressInfo.getLastConsumerId());
+ builder.getColumnBuilder(19).writeLong(progressInfo.getSeekGeneration());
+ builder.declarePosition();
+ }
+
+ future.set(
+ new ConfigTaskResult(
+ TSStatusCode.SUCCESS_STATUS,
+ builder.build(),
+ DatasetHeaderFactory.getShowSubscriptionDetailsHeader()));
+ }
+
+ private static void writeText(
+ final TsBlockBuilder builder, final int columnIndex, final String value)
{
+ builder
+ .getColumnBuilder(columnIndex)
+ .writeBinary(new Binary(value == null ? "" : value,
TSFileConfig.STRING_CHARSET));
+ }
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/parser/ASTVisitor.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/parser/ASTVisitor.java
index dc78f695902..ce3c9f6e485 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/parser/ASTVisitor.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/parser/ASTVisitor.java
@@ -4543,6 +4543,7 @@ public class ASTVisitor extends
IoTDBSqlParserBaseVisitor<Statement> {
if (ctx.topicName != null) {
showSubscriptionsStatement.setTopicName(parseIdentifier(ctx.topicName.getText()));
}
+ showSubscriptionsStatement.setDetails(ctx.DETAILS() != null);
return showSubscriptionsStatement;
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/ShowSubscriptions.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/ShowSubscriptions.java
index 81af08652d9..7a2501f502a 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/ShowSubscriptions.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/ShowSubscriptions.java
@@ -34,15 +34,25 @@ public class ShowSubscriptions extends
SubscriptionStatement {
RamUsageEstimator.shallowSizeOfInstance(ShowSubscriptions.class);
private final String topicName;
+ private final boolean details;
public ShowSubscriptions(final String topicName) {
+ this(topicName, false);
+ }
+
+ public ShowSubscriptions(final String topicName, final boolean details) {
this.topicName = topicName;
+ this.details = details;
}
public String getTopicName() {
return topicName;
}
+ public boolean isDetails() {
+ return details;
+ }
+
@Override
public <R, C> R accept(final IAstVisitor<R, C> visitor, final C context) {
return ((AstVisitor<R, C>) visitor).visitShowSubscriptions(this, context);
@@ -50,7 +60,7 @@ public class ShowSubscriptions extends SubscriptionStatement {
@Override
public int hashCode() {
- return Objects.hash(topicName);
+ return Objects.hash(topicName, details);
}
@Override
@@ -62,12 +72,12 @@ public class ShowSubscriptions extends
SubscriptionStatement {
return false;
}
final ShowSubscriptions that = (ShowSubscriptions) obj;
- return Objects.equals(this.topicName, that.topicName);
+ return Objects.equals(this.topicName, that.topicName) && this.details ==
that.details;
}
@Override
public String toString() {
- return toStringHelper(this).add("topicName", topicName).toString();
+ return toStringHelper(this).add("topicName", topicName).add("details",
details).toString();
}
@Override
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/AstBuilder.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/AstBuilder.java
index 87e1b417e44..f7a99ab3485 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/AstBuilder.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/AstBuilder.java
@@ -1455,7 +1455,7 @@ public class AstBuilder extends
RelationalSqlBaseVisitor<Node> {
RelationalSqlParser.ShowSubscriptionsStatementContext ctx) {
final String topicName =
getIdentifierIfPresent(ctx.identifier()).map(Identifier::getValue).orElse(null);
- return new ShowSubscriptions(topicName);
+ return new ShowSubscriptions(topicName, ctx.DETAILS() != null);
}
@Override
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/util/DataNodeSqlFormatter.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/util/DataNodeSqlFormatter.java
index 28a8dc47138..dec3cc58b96 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/util/DataNodeSqlFormatter.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/util/DataNodeSqlFormatter.java
@@ -850,10 +850,14 @@ public final class DataNodeSqlFormatter extends
CommonQuerySqlFormatter
@Override
public Void visitShowSubscriptions(ShowSubscriptions node, Integer context) {
+ builder.append("SHOW SUBSCRIPTIONS");
+ if (node.isDetails()) {
+ builder.append(" DETAILS");
+ }
if (Objects.isNull(node.getTopicName())) {
- builder.append("SHOW SUBSCRIPTIONS");
+ return null;
} else {
- builder.append("SHOW SUBSCRIPTIONS ON ").append(node.getTopicName());
+ builder.append(" ON
").append(ShowCreateTableTask.getIdentifier(node.getTopicName()));
}
return null;
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/metadata/subscription/ShowSubscriptionsStatement.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/metadata/subscription/ShowSubscriptionsStatement.java
index aa2abbfc188..c86d7254d46 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/metadata/subscription/ShowSubscriptionsStatement.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/metadata/subscription/ShowSubscriptionsStatement.java
@@ -33,6 +33,7 @@ public class ShowSubscriptionsStatement extends Statement
implements IConfigStat
private String topicName;
private boolean isTableModel;
+ private boolean details;
public ShowSubscriptionsStatement() {
super();
@@ -47,6 +48,10 @@ public class ShowSubscriptionsStatement extends Statement
implements IConfigStat
return isTableModel;
}
+ public boolean isDetails() {
+ return details;
+ }
+
public void setTopicName(final String topicName) {
this.topicName = topicName;
}
@@ -55,6 +60,10 @@ public class ShowSubscriptionsStatement extends Statement
implements IConfigStat
this.isTableModel = tableModel;
}
+ public void setDetails(final boolean details) {
+ this.details = details;
+ }
+
@Override
public <R, C> R accept(final StatementVisitor<R, C> visitor, final C
context) {
return visitor.visitShowSubscriptions(this, context);
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/agent/SubscriptionBrokerAgent.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/agent/SubscriptionBrokerAgent.java
index 83e816e9cf3..e65f1d88041 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/agent/SubscriptionBrokerAgent.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/agent/SubscriptionBrokerAgent.java
@@ -21,6 +21,7 @@ package org.apache.iotdb.db.subscription.agent;
import org.apache.iotdb.commons.consensus.ConsensusGroupId;
import org.apache.iotdb.commons.schema.table.TsTable;
+import
org.apache.iotdb.commons.subscription.meta.consumer.SubscriptionProgressSnapshot;
import org.apache.iotdb.consensus.ConsensusFactory;
import org.apache.iotdb.consensus.IConsensus;
import org.apache.iotdb.consensus.iot.IoTConsensus;
@@ -988,6 +989,21 @@ public class SubscriptionBrokerAgent {
return
ConsensusSubscriptionCommitManager.getInstance().collectAllRegionProgress(dataNodeId);
}
+ public Map<String, ByteBuffer> collectAllProgressSnapshots() {
+ final Map<String, ByteBuffer> result = new ConcurrentHashMap<>();
+ for (final Map.Entry<String, List<ISubscriptionBroker>> entry :
+ consumerGroupIdToBrokers.entrySet()) {
+ for (final ISubscriptionBroker broker : entry.getValue()) {
+ for (final Map.Entry<String, SubscriptionProgressSnapshot>
snapshotEntry :
+ broker.getProgressSnapshotMap().entrySet()) {
+ result.put(
+ entry.getKey() + "/" + snapshotEntry.getKey(),
snapshotEntry.getValue().serialize());
+ }
+ }
+ }
+ return result;
+ }
+
/**
* Receives a committed progress broadcast from another DataNode (Leader →
Follower). Delegates to
* CommitManager to update local progress state.
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/ConsensusSubscriptionBroker.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/ConsensusSubscriptionBroker.java
index 441f6ac0daf..b911478c2b2 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/ConsensusSubscriptionBroker.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/ConsensusSubscriptionBroker.java
@@ -21,6 +21,7 @@ package org.apache.iotdb.db.subscription.broker;
import org.apache.iotdb.commons.consensus.ConsensusGroupId;
import org.apache.iotdb.commons.subscription.config.SubscriptionConfig;
+import
org.apache.iotdb.commons.subscription.meta.consumer.SubscriptionProgressSnapshot;
import org.apache.iotdb.consensus.iot.IoTConsensusServerImpl;
import org.apache.iotdb.consensus.iot.SubscriptionWalRetentionPolicy;
import org.apache.iotdb.db.i18n.DataNodePipeMessages;
@@ -496,6 +497,21 @@ public class ConsensusSubscriptionBroker implements
ISubscriptionBroker {
return lagMap;
}
+ @Override
+ public Map<String, SubscriptionProgressSnapshot> getProgressSnapshotMap() {
+ final Map<String, SubscriptionProgressSnapshot> progressMap = new
ConcurrentHashMap<>();
+ for (final Map.Entry<String, List<ConsensusPrefetchingQueue>> entry :
+ topicNameToConsensusPrefetchingQueues.entrySet()) {
+ for (final ConsensusPrefetchingQueue queue : entry.getValue()) {
+ if (!queue.isClosed()) {
+ progressMap.put(
+ entry.getKey() + "/" + queue.getConsensusGroupId(),
queue.getProgressSnapshot());
+ }
+ }
+ }
+ return progressMap;
+ }
+
private TopicOwnershipSnapshot refreshAndGetTopicOwnership(
final String topicName,
final List<ConsensusPrefetchingQueue> queues,
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/ISubscriptionBroker.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/ISubscriptionBroker.java
index 547ffc6b763..90eb947292e 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/ISubscriptionBroker.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/ISubscriptionBroker.java
@@ -19,6 +19,7 @@
package org.apache.iotdb.db.subscription.broker;
+import
org.apache.iotdb.commons.subscription.meta.consumer.SubscriptionProgressSnapshot;
import org.apache.iotdb.db.subscription.event.SubscriptionEvent;
import
org.apache.iotdb.rpc.subscription.payload.poll.SubscriptionCommitContext;
import org.apache.iotdb.rpc.subscription.payload.poll.TopicProgress;
@@ -86,6 +87,10 @@ public interface ISubscriptionBroker {
return Collections.emptyMap();
}
+ default Map<String, SubscriptionProgressSnapshot> getProgressSnapshotMap() {
+ return Collections.emptyMap();
+ }
+
void unbind(String topicName);
void removeQueue(String topicName);
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/consensus/ConsensusPrefetchingQueue.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/consensus/ConsensusPrefetchingQueue.java
index 3a47be814e5..c6fd52301a4 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/consensus/ConsensusPrefetchingQueue.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/consensus/ConsensusPrefetchingQueue.java
@@ -21,6 +21,7 @@ package org.apache.iotdb.db.subscription.broker.consensus;
import org.apache.iotdb.commons.consensus.ConsensusGroupId;
import org.apache.iotdb.commons.subscription.config.SubscriptionConfig;
+import
org.apache.iotdb.commons.subscription.meta.consumer.SubscriptionProgressSnapshot;
import org.apache.iotdb.commons.utils.TestOnly;
import org.apache.iotdb.consensus.common.request.IndexedConsensusRequest;
import org.apache.iotdb.consensus.iot.IoTConsensusServerImpl;
@@ -263,6 +264,13 @@ public class ConsensusPrefetchingQueue {
private volatile long lastStatsLogTimeMs = System.currentTimeMillis();
+ /** Wall-clock time of the most recent event poll and committed progress
update. */
+ private volatile long lastPollTimeMs = 0L;
+
+ private volatile long lastProgressTimeMs = 0L;
+
+ private volatile String lastConsumerId = "";
+
private volatile long lastPendingAcceptedEntries = 0L;
private volatile long lastWalAcceptedEntries = 0L;
@@ -1285,6 +1293,8 @@ public class ConsensusPrefetchingQueue {
// Mark as polled before updating inFlightEvents
event.recordLastPolledTimestamp();
+ lastPollTimeMs = System.currentTimeMillis();
+ lastConsumerId = consumerId;
inFlightEvents.put(new InFlightEventKey(consumerId,
event.getCommitContext()), event);
event.recordLastPolledConsumerId(consumerId);
return event;
@@ -3282,6 +3292,7 @@ public class ConsensusPrefetchingQueue {
commitManager.commit(
consumerGroupId, topicName, consensusGroupId, writerId,
writerProgress);
if (committed) {
+ lastProgressTimeMs = System.currentTimeMillis();
refreshCommittedWalRetentionBoundAndNotify();
}
return committed;
@@ -3293,6 +3304,7 @@ public class ConsensusPrefetchingQueue {
commitManager.commitWithoutOutstanding(
consumerGroupId, topicName, consensusGroupId, writerId,
writerProgress);
if (committed) {
+ lastProgressTimeMs = System.currentTimeMillis();
refreshCommittedWalRetentionBoundAndNotify();
}
return committed;
@@ -3918,6 +3930,98 @@ public class ConsensusPrefetchingQueue {
return prefetchInitialized ? 1L : 0L;
}
+ public long getRawWalGap() {
+ final long currentSearchIndex = consensusReqReader.getCurrentSearchIndex();
+ final long nextSearchIndex = nextExpectedSearchIndex.get();
+ return currentSearchIndex >= nextSearchIndex && currentSearchIndex > 0
+ ? currentSearchIndex - nextSearchIndex + 1
+ : 0L;
+ }
+
+ public long getRemainingEventCount() {
+ return 0L
+ + prefetchingQueue.size()
+ + inFlightEvents.size()
+ + pendingEntries.size()
+ + getRealtimeBufferedEntryCount()
+ + lingerBatch.getEntryCount();
+ }
+
+ public long getLastPollTimeMs() {
+ return lastPollTimeMs;
+ }
+
+ public long getLastProgressTimeMs() {
+ return lastProgressTimeMs;
+ }
+
+ /** Returns 0=uninitialized, 1=inactive, 2=caught up, 3=catching up,
4=stalled. */
+ public long getProgressStatus() {
+ switch (getProgressStatusName()) {
+ case SubscriptionProgressSnapshot.STATUS_INACTIVE:
+ return 1L;
+ case SubscriptionProgressSnapshot.STATUS_CAUGHT_UP:
+ return 2L;
+ case SubscriptionProgressSnapshot.STATUS_CATCHING_UP:
+ return 3L;
+ case SubscriptionProgressSnapshot.STATUS_STALLED:
+ return 4L;
+ default:
+ return 0L;
+ }
+ }
+
+ public String getProgressStatusName() {
+ if (!prefetchInitialized) {
+ return SubscriptionProgressSnapshot.STATUS_UNINITIALIZED;
+ }
+ if (!isActive) {
+ return SubscriptionProgressSnapshot.STATUS_INACTIVE;
+ }
+ if (getLag() <= 0L) {
+ return SubscriptionProgressSnapshot.STATUS_CAUGHT_UP;
+ }
+ final long stalledTimeoutMs =
+
SubscriptionConfig.getInstance().getSubscriptionConsensusConsumerEvictionTimeoutMs();
+ final long progressReferenceTimeMs =
+ lastProgressTimeMs > 0L ? lastProgressTimeMs : lastPollTimeMs;
+ if (progressReferenceTimeMs > 0L
+ && stalledTimeoutMs > 0L
+ && System.currentTimeMillis() - progressReferenceTimeMs >=
stalledTimeoutMs) {
+ return SubscriptionProgressSnapshot.STATUS_STALLED;
+ }
+ return SubscriptionProgressSnapshot.STATUS_CATCHING_UP;
+ }
+
+ public SubscriptionProgressSnapshot getProgressSnapshot() {
+ final long currentWalSearchIndex =
consensusReqReader.getCurrentSearchIndex();
+ final long nextReadSearchIndex = nextExpectedSearchIndex.get();
+ return new SubscriptionProgressSnapshot(
+ IoTDBDescriptor.getInstance().getConfig().getDataNodeId(),
+ consumerGroupId,
+ topicName,
+ consensusGroupId.toString(),
+ isActive,
+ prefetchInitialized,
+ currentWalSearchIndex,
+ nextReadSearchIndex,
+ getRawWalGap(),
+ getLag(),
+ prefetchingQueue.size(),
+ inFlightEvents.size(),
+ pendingEntries.size(),
+ getRealtimeBufferedEntryCount(),
+ lingerBatch.getEntryCount(),
+ lastPollTimeMs,
+ lastProgressTimeMs,
+ lastConsumerId,
+ seekGeneration.get(),
+ walGapSkippedEntries.get(),
+ runtimeVersionChangeCount.get(),
+ maxObservedTimestamp,
+ getProgressStatusName());
+ }
+
public void setActiveWriterNodeIds(final Set<Integer> activeWriterNodeIds) {
this.runtimeActiveWriterNodeIds =
Collections.unmodifiableSet(
@@ -4119,10 +4223,18 @@ public class ConsensusPrefetchingQueue {
return prefetchingQueue.size();
}
+ public int getPendingEventCount() {
+ return pendingEntries.size();
+ }
+
public long getCurrentReadSearchIndex() {
return nextExpectedSearchIndex.get();
}
+ public long getCurrentWalSearchIndex() {
+ return consensusReqReader.getCurrentSearchIndex();
+ }
+
public long getPendingPathAcceptedEntries() {
return pendingPathAcceptedEntries.get();
}
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 50ea8402118..b7331749b8c 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
@@ -145,6 +145,66 @@ public class ConsensusSubscriptionPrefetchingQueueMetrics
implements IMetricSet
queue,
ConsensusPrefetchingQueue::getInitializedStatus,
key.getTags());
+ metricService.createAutoGauge(
+ Metric.SUBSCRIPTION_CONSENSUS_REMAINING_EVENT_COUNT.toString(),
+ MetricLevel.IMPORTANT,
+ queue,
+ ConsensusPrefetchingQueue::getRemainingEventCount,
+ key.getTags());
+ metricService.createAutoGauge(
+ Metric.SUBSCRIPTION_CONSENSUS_RAW_WAL_GAP.toString(),
+ MetricLevel.IMPORTANT,
+ queue,
+ ConsensusPrefetchingQueue::getRawWalGap,
+ key.getTags());
+ metricService.createAutoGauge(
+ Metric.SUBSCRIPTION_CONSENSUS_CURRENT_WAL_SEARCH_INDEX.toString(),
+ MetricLevel.IMPORTANT,
+ queue,
+ ConsensusPrefetchingQueue::getCurrentWalSearchIndex,
+ key.getTags());
+ metricService.createAutoGauge(
+ Metric.SUBSCRIPTION_CONSENSUS_NEXT_READ_SEARCH_INDEX.toString(),
+ MetricLevel.IMPORTANT,
+ queue,
+ ConsensusPrefetchingQueue::getCurrentReadSearchIndex,
+ key.getTags());
+ metricService.createAutoGauge(
+ Metric.SUBSCRIPTION_CONSENSUS_IN_FLIGHT_EVENT_COUNT.toString(),
+ MetricLevel.IMPORTANT,
+ queue,
+ ConsensusPrefetchingQueue::getSubscriptionUncommittedEventCount,
+ key.getTags());
+ metricService.createAutoGauge(
+ Metric.SUBSCRIPTION_CONSENSUS_PREFETCHED_EVENT_COUNT.toString(),
+ MetricLevel.IMPORTANT,
+ queue,
+ ConsensusPrefetchingQueue::getPrefetchedEventCount,
+ key.getTags());
+ metricService.createAutoGauge(
+ Metric.SUBSCRIPTION_CONSENSUS_PENDING_EVENT_COUNT.toString(),
+ MetricLevel.IMPORTANT,
+ queue,
+ ConsensusPrefetchingQueue::getPendingEventCount,
+ key.getTags());
+ metricService.createAutoGauge(
+ Metric.SUBSCRIPTION_CONSENSUS_LAST_POLL_TIME.toString(),
+ MetricLevel.IMPORTANT,
+ queue,
+ ConsensusPrefetchingQueue::getLastPollTimeMs,
+ key.getTags());
+ metricService.createAutoGauge(
+ Metric.SUBSCRIPTION_CONSENSUS_LAST_PROGRESS_TIME.toString(),
+ MetricLevel.IMPORTANT,
+ queue,
+ ConsensusPrefetchingQueue::getLastProgressTimeMs,
+ key.getTags());
+ metricService.createAutoGauge(
+ Metric.SUBSCRIPTION_CONSENSUS_PROGRESS_STATUS.toString(),
+ MetricLevel.IMPORTANT,
+ queue,
+ ConsensusPrefetchingQueue::getProgressStatus,
+ key.getTags());
}
private void createRate(final QueueMetricsKey key) {
@@ -217,6 +277,44 @@ public class ConsensusSubscriptionPrefetchingQueueMetrics
implements IMetricSet
MetricType.AUTO_GAUGE,
Metric.SUBSCRIPTION_CONSENSUS_ACTIVE.toString(), key.getTags());
metricService.remove(
MetricType.AUTO_GAUGE,
Metric.SUBSCRIPTION_CONSENSUS_INITIALIZED.toString(), key.getTags());
+ metricService.remove(
+ MetricType.AUTO_GAUGE,
+ Metric.SUBSCRIPTION_CONSENSUS_REMAINING_EVENT_COUNT.toString(),
+ key.getTags());
+ metricService.remove(
+ MetricType.AUTO_GAUGE,
Metric.SUBSCRIPTION_CONSENSUS_RAW_WAL_GAP.toString(), key.getTags());
+ metricService.remove(
+ MetricType.AUTO_GAUGE,
+ Metric.SUBSCRIPTION_CONSENSUS_CURRENT_WAL_SEARCH_INDEX.toString(),
+ key.getTags());
+ metricService.remove(
+ MetricType.AUTO_GAUGE,
+ Metric.SUBSCRIPTION_CONSENSUS_NEXT_READ_SEARCH_INDEX.toString(),
+ key.getTags());
+ metricService.remove(
+ MetricType.AUTO_GAUGE,
+ Metric.SUBSCRIPTION_CONSENSUS_IN_FLIGHT_EVENT_COUNT.toString(),
+ key.getTags());
+ metricService.remove(
+ MetricType.AUTO_GAUGE,
+ Metric.SUBSCRIPTION_CONSENSUS_PREFETCHED_EVENT_COUNT.toString(),
+ key.getTags());
+ metricService.remove(
+ MetricType.AUTO_GAUGE,
+ Metric.SUBSCRIPTION_CONSENSUS_PENDING_EVENT_COUNT.toString(),
+ key.getTags());
+ metricService.remove(
+ MetricType.AUTO_GAUGE,
+ Metric.SUBSCRIPTION_CONSENSUS_LAST_POLL_TIME.toString(),
+ key.getTags());
+ metricService.remove(
+ MetricType.AUTO_GAUGE,
+ Metric.SUBSCRIPTION_CONSENSUS_LAST_PROGRESS_TIME.toString(),
+ key.getTags());
+ metricService.remove(
+ MetricType.AUTO_GAUGE,
+ Metric.SUBSCRIPTION_CONSENSUS_PROGRESS_STATUS.toString(),
+ key.getTags());
}
private void removeRate(final QueueMetricsKey key) {
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/execution/config/sys/subscription/ShowSubscriptionsTaskTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/execution/config/sys/subscription/ShowSubscriptionsTaskTest.java
new file mode 100644
index 00000000000..b8191dca629
--- /dev/null
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/execution/config/sys/subscription/ShowSubscriptionsTaskTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.queryengine.plan.execution.config.sys.subscription;
+
+import org.apache.iotdb.commons.schema.column.ColumnHeaderConstant;
+import org.apache.iotdb.confignode.rpc.thrift.TSubscriptionProgressInfo;
+import org.apache.iotdb.db.queryengine.plan.execution.config.ConfigTaskResult;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import com.google.common.util.concurrent.SettableFuture;
+import org.apache.tsfile.read.common.block.TsBlock;
+import org.junit.Test;
+
+import java.util.Collections;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class ShowSubscriptionsTaskTest {
+
+ @Test
+ public void testBuildDetailsTSBlock() throws Exception {
+ final TSubscriptionProgressInfo progressInfo =
+ new TSubscriptionProgressInfo(
+ "topic",
+ "group",
+ "DataRegion[1]",
+ 3,
+ true,
+ true,
+ "CATCHING_UP",
+ 5L,
+ 20L,
+ 6L,
+ 2L,
+ 1L,
+ 2L,
+ 100L,
+ 81L,
+ 123L,
+ 120L,
+ "consumer",
+ 4L);
+ final SettableFuture<ConfigTaskResult> future = SettableFuture.create();
+
+
ShowSubscriptionsTask.buildDetailsTSBlock(Collections.singletonList(progressInfo),
future);
+
+ final ConfigTaskResult result = future.get();
+ final TsBlock resultSet = result.getResultSet();
+ assertEquals(TSStatusCode.SUCCESS_STATUS, result.getStatusCode());
+ assertEquals(
+ ColumnHeaderConstant.SUBSCRIPTION_ID,
result.getResultSetHeader().getRespColumns().get(0));
+ assertEquals(
+ ColumnHeaderConstant.SEEK_GENERATION,
result.getResultSetHeader().getRespColumns().get(19));
+ assertEquals(1, resultSet.getPositionCount());
+ assertEquals("topic_group",
resultSet.getColumn(0).getBinary(0).toString());
+ assertEquals("CATCHING_UP",
resultSet.getColumn(5).getBinary(0).toString());
+ assertTrue(resultSet.getColumn(6).getBoolean(0));
+ assertEquals(5L, resultSet.getColumn(8).getLong(0));
+ assertEquals(20L, resultSet.getColumn(9).getLong(0));
+ assertEquals(4L, resultSet.getColumn(19).getLong(0));
+ }
+}
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/parser/ShowSubscriptionsStatementTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/parser/ShowSubscriptionsStatementTest.java
new file mode 100644
index 00000000000..b4d05f71b33
--- /dev/null
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/parser/ShowSubscriptionsStatementTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.queryengine.plan.parser;
+
+import org.apache.iotdb.db.queryengine.plan.statement.Statement;
+import
org.apache.iotdb.db.queryengine.plan.statement.metadata.subscription.ShowSubscriptionsStatement;
+
+import org.junit.Test;
+
+import java.time.ZoneId;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class ShowSubscriptionsStatementTest {
+
+ @Test
+ public void testParseDetails() {
+ assertDetails("SHOW SUBSCRIPTIONS DETAILS", null);
+ assertDetails("SHOW SUBSCRIPTIONS DETAILS ON `topic-1`", "topic-1");
+ }
+
+ private void assertDetails(final String sql, final String topicName) {
+ final Statement statement = StatementGenerator.createStatement(sql,
ZoneId.systemDefault());
+
+ assertTrue(statement instanceof ShowSubscriptionsStatement);
+ final ShowSubscriptionsStatement showSubscriptions =
(ShowSubscriptionsStatement) statement;
+ assertTrue(showSubscriptions.isDetails());
+ assertEquals(topicName, showSubscriptions.getTopicName());
+ }
+}
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ShowSubscriptionsTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ShowSubscriptionsTest.java
new file mode 100644
index 00000000000..389fcaf0c25
--- /dev/null
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ShowSubscriptionsTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.queryengine.plan.relational.sql;
+
+import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Statement;
+import org.apache.iotdb.db.protocol.session.IClientSession;
+import org.apache.iotdb.db.protocol.session.InternalClientSession;
+import
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ShowSubscriptions;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.parser.SqlParser;
+import
org.apache.iotdb.db.queryengine.plan.relational.sql.util.DataNodeSqlFormatter;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.time.ZoneId;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class ShowSubscriptionsTest {
+
+ private SqlParser sqlParser;
+ private IClientSession clientSession;
+
+ @Before
+ public void setUp() {
+ sqlParser = new SqlParser();
+ clientSession = new InternalClientSession("testClient");
+ }
+
+ @Test
+ public void testDetailsRoundTrip() {
+ assertDetailsRoundTrip("SHOW SUBSCRIPTIONS DETAILS", null);
+ assertDetailsRoundTrip("SHOW SUBSCRIPTIONS DETAILS ON \"topic-1\"",
"topic-1");
+ }
+
+ private void assertDetailsRoundTrip(final String sql, final String
topicName) {
+ final Statement statement =
+ sqlParser.createStatement(sql, ZoneId.systemDefault(), clientSession);
+
+ assertTrue(statement instanceof ShowSubscriptions);
+ final ShowSubscriptions showSubscriptions = (ShowSubscriptions) statement;
+ assertTrue(showSubscriptions.isDetails());
+ assertEquals(topicName, showSubscriptions.getTopicName());
+ assertEquals(sql, DataNodeSqlFormatter.formatDataNodeSql(statement));
+ }
+}
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/subscription/metric/ConsensusSubscriptionPrefetchingQueueMetricsTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/subscription/metric/ConsensusSubscriptionPrefetchingQueueMetricsTest.java
index f6bae43f238..08880f4fcb3 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/subscription/metric/ConsensusSubscriptionPrefetchingQueueMetricsTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/subscription/metric/ConsensusSubscriptionPrefetchingQueueMetricsTest.java
@@ -112,6 +112,26 @@ public class
ConsensusSubscriptionPrefetchingQueueMetricsTest {
eq(queueId),
eq(Tag.REGION.toString()),
eq(firstRegionId.toString()));
+ verify(metricService)
+ .createAutoGauge(
+
eq(Metric.SUBSCRIPTION_CONSENSUS_REMAINING_EVENT_COUNT.toString()),
+ eq(MetricLevel.IMPORTANT),
+ eq(firstQueue),
+ any(),
+ eq(Tag.NAME.toString()),
+ eq(queueId),
+ eq(Tag.REGION.toString()),
+ eq(firstRegionId.toString()));
+ verify(metricService)
+ .createAutoGauge(
+ eq(Metric.SUBSCRIPTION_CONSENSUS_PROGRESS_STATUS.toString()),
+ eq(MetricLevel.IMPORTANT),
+ eq(firstQueue),
+ any(),
+ eq(Tag.NAME.toString()),
+ eq(queueId),
+ eq(Tag.REGION.toString()),
+ eq(firstRegionId.toString()));
verify(metricService)
.createAutoGauge(
eq(Metric.SUBSCRIPTION_CONSENSUS_LAG.toString()),
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/column/ColumnHeaderConstant.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/column/ColumnHeaderConstant.java
index 06c361eba0a..d66b5a8a301 100644
---
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/column/ColumnHeaderConstant.java
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/column/ColumnHeaderConstant.java
@@ -140,6 +140,19 @@ public class ColumnHeaderConstant {
public static final String CONSUMER_GROUP_NAME = "ConsumerGroupName";
public static final String SUBSCRIBED_CONSUMERS = "SubscribedConsumers";
public static final String SUBSCRIPTION_ID = "SubscriptionID";
+ public static final String SUBSCRIPTION_ACTIVE = "Active";
+ public static final String SUBSCRIPTION_INITIALIZED = "Initialized";
+ public static final String RAW_WAL_GAP = "RawWalGap";
+ public static final String APPROXIMATE_LAG = "ApproximateLag";
+ public static final String IN_FLIGHT_EVENT_COUNT = "InFlightEventCount";
+ public static final String PREFETCHED_EVENT_COUNT = "PrefetchedEventCount";
+ public static final String PENDING_EVENT_COUNT = "PendingEventCount";
+ public static final String CURRENT_WAL_SEARCH_INDEX =
"CurrentWalSearchIndex";
+ public static final String NEXT_READ_SEARCH_INDEX = "NextReadSearchIndex";
+ public static final String LAST_PROGRESS_TIME = "LastProgressTime";
+ public static final String LAST_POLL_TIME = "LastPollTime";
+ public static final String LAST_CONSUMER_ID = "LastConsumerId";
+ public static final String SEEK_GENERATION = "SeekGeneration";
// show cluster status
public static final String NODE_TYPE_CONFIG_NODE = "ConfigNode";
@@ -647,6 +660,29 @@ public class ColumnHeaderConstant {
new ColumnHeader(CONSUMER_GROUP_NAME, TSDataType.TEXT),
new ColumnHeader(SUBSCRIBED_CONSUMERS, TSDataType.TEXT));
+ public static final List<ColumnHeader> showSubscriptionDetailsColumnHeaders =
+ ImmutableList.of(
+ new ColumnHeader(SUBSCRIPTION_ID, TSDataType.TEXT),
+ new ColumnHeader(TOPIC_NAME, TSDataType.TEXT),
+ new ColumnHeader(CONSUMER_GROUP_NAME, TSDataType.TEXT),
+ new ColumnHeader(DATA_NODE_ID, TSDataType.INT32),
+ new ColumnHeader(REGION_ID, TSDataType.TEXT),
+ new ColumnHeader(STATUS, TSDataType.TEXT),
+ new ColumnHeader(SUBSCRIPTION_ACTIVE, TSDataType.BOOLEAN),
+ new ColumnHeader(SUBSCRIPTION_INITIALIZED, TSDataType.BOOLEAN),
+ new ColumnHeader(REMAINING_EVENT_COUNT, TSDataType.INT64),
+ new ColumnHeader(RAW_WAL_GAP, TSDataType.INT64),
+ new ColumnHeader(APPROXIMATE_LAG, TSDataType.INT64),
+ new ColumnHeader(IN_FLIGHT_EVENT_COUNT, TSDataType.INT64),
+ new ColumnHeader(PREFETCHED_EVENT_COUNT, TSDataType.INT64),
+ new ColumnHeader(PENDING_EVENT_COUNT, TSDataType.INT64),
+ new ColumnHeader(CURRENT_WAL_SEARCH_INDEX, TSDataType.INT64),
+ new ColumnHeader(NEXT_READ_SEARCH_INDEX, TSDataType.INT64),
+ new ColumnHeader(LAST_PROGRESS_TIME, TSDataType.INT64),
+ new ColumnHeader(LAST_POLL_TIME, TSDataType.INT64),
+ new ColumnHeader(LAST_CONSUMER_ID, TSDataType.TEXT),
+ new ColumnHeader(SEEK_GENERATION, TSDataType.INT64));
+
public static final List<ColumnHeader> selectIntoColumnHeaders =
ImmutableList.of(
new ColumnHeader(SOURCE_COLUMN, TSDataType.TEXT),
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 70c4976df28..b41d8fb53ac 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
@@ -217,6 +217,17 @@ public enum Metric {
SUBSCRIPTION_CONSENSUS_SEEK_GENERATION("subscription_consensus_seek_generation"),
SUBSCRIPTION_CONSENSUS_ACTIVE("subscription_consensus_active"),
SUBSCRIPTION_CONSENSUS_INITIALIZED("subscription_consensus_initialized"),
+
SUBSCRIPTION_CONSENSUS_REMAINING_EVENT_COUNT("subscription_consensus_remaining_event_count"),
+ SUBSCRIPTION_CONSENSUS_RAW_WAL_GAP("subscription_consensus_raw_wal_gap"),
+ SUBSCRIPTION_CONSENSUS_CURRENT_WAL_SEARCH_INDEX(
+ "subscription_consensus_current_wal_search_index"),
+
SUBSCRIPTION_CONSENSUS_NEXT_READ_SEARCH_INDEX("subscription_consensus_next_read_search_index"),
+
SUBSCRIPTION_CONSENSUS_IN_FLIGHT_EVENT_COUNT("subscription_consensus_in_flight_event_count"),
+
SUBSCRIPTION_CONSENSUS_PREFETCHED_EVENT_COUNT("subscription_consensus_prefetched_event_count"),
+
SUBSCRIPTION_CONSENSUS_PENDING_EVENT_COUNT("subscription_consensus_pending_event_count"),
+
SUBSCRIPTION_CONSENSUS_LAST_POLL_TIME("subscription_consensus_last_poll_time"),
+
SUBSCRIPTION_CONSENSUS_LAST_PROGRESS_TIME("subscription_consensus_last_progress_time"),
+
SUBSCRIPTION_CONSENSUS_PROGRESS_STATUS("subscription_consensus_progress_status"),
// load related
ACTIVE_LOADING_FILES_NUMBER("active_loading_files_number"),
ACTIVE_LOADING_FILES_SIZE("active_loading_files_size"),
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/subscription/meta/consumer/SubscriptionProgressSnapshot.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/subscription/meta/consumer/SubscriptionProgressSnapshot.java
new file mode 100644
index 00000000000..75ff3776923
--- /dev/null
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/subscription/meta/consumer/SubscriptionProgressSnapshot.java
@@ -0,0 +1,278 @@
+/*
+ * 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.subscription.meta.consumer;
+
+import org.apache.tsfile.utils.ReadWriteIOUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * A side-effect-free point-in-time view of a consensus subscription queue.
+ *
+ * <p>The in-memory counts are exact for the queue lifecycle stages. {@link
#rawWalGap} is a
+ * search-index distance and therefore is deliberately named as such: it is
not the number of
+ * topic-filtered events remaining in the WAL.
+ */
+public final class SubscriptionProgressSnapshot {
+
+ public static final String STATUS_UNINITIALIZED = "UNINITIALIZED";
+ public static final String STATUS_INACTIVE = "INACTIVE";
+ public static final String STATUS_CAUGHT_UP = "CAUGHT_UP";
+ public static final String STATUS_CATCHING_UP = "CATCHING_UP";
+ public static final String STATUS_STALLED = "STALLED";
+ public static final String STATUS_NO_QUEUE = "NO_QUEUE";
+
+ private final int dataNodeId;
+ private final String consumerGroupId;
+ private final String topicName;
+ private final String regionId;
+ private final boolean active;
+ private final boolean initialized;
+ private final long currentWalSearchIndex;
+ private final long nextReadSearchIndex;
+ private final long rawWalGap;
+ private final long approximateLag;
+ private final long prefetchedEventCount;
+ private final long inFlightEventCount;
+ private final long pendingEventCount;
+ private final long realtimeBufferedEventCount;
+ private final long lingerEventCount;
+ private final long lastPollTimeMs;
+ private final long lastProgressTimeMs;
+ private final String lastConsumerId;
+ private final long seekGeneration;
+ private final long walGapSkippedEntries;
+ private final long routingEpochChangeCount;
+ private final long maxObservedTimestamp;
+ private final String status;
+
+ public SubscriptionProgressSnapshot(
+ final int dataNodeId,
+ final String consumerGroupId,
+ final String topicName,
+ final String regionId,
+ final boolean active,
+ final boolean initialized,
+ final long currentWalSearchIndex,
+ final long nextReadSearchIndex,
+ final long rawWalGap,
+ final long approximateLag,
+ final long prefetchedEventCount,
+ final long inFlightEventCount,
+ final long pendingEventCount,
+ final long realtimeBufferedEventCount,
+ final long lingerEventCount,
+ final long lastPollTimeMs,
+ final long lastProgressTimeMs,
+ final String lastConsumerId,
+ final long seekGeneration,
+ final long walGapSkippedEntries,
+ final long routingEpochChangeCount,
+ final long maxObservedTimestamp,
+ final String status) {
+ this.dataNodeId = dataNodeId;
+ this.consumerGroupId = consumerGroupId;
+ this.topicName = topicName;
+ this.regionId = regionId;
+ this.active = active;
+ this.initialized = initialized;
+ this.currentWalSearchIndex = currentWalSearchIndex;
+ this.nextReadSearchIndex = nextReadSearchIndex;
+ this.rawWalGap = rawWalGap;
+ this.approximateLag = approximateLag;
+ this.prefetchedEventCount = prefetchedEventCount;
+ this.inFlightEventCount = inFlightEventCount;
+ this.pendingEventCount = pendingEventCount;
+ this.realtimeBufferedEventCount = realtimeBufferedEventCount;
+ this.lingerEventCount = lingerEventCount;
+ this.lastPollTimeMs = lastPollTimeMs;
+ this.lastProgressTimeMs = lastProgressTimeMs;
+ this.lastConsumerId = lastConsumerId;
+ this.seekGeneration = seekGeneration;
+ this.walGapSkippedEntries = walGapSkippedEntries;
+ this.routingEpochChangeCount = routingEpochChangeCount;
+ this.maxObservedTimestamp = maxObservedTimestamp;
+ this.status = status;
+ }
+
+ public int getDataNodeId() {
+ return dataNodeId;
+ }
+
+ public String getConsumerGroupId() {
+ return consumerGroupId;
+ }
+
+ public String getTopicName() {
+ return topicName;
+ }
+
+ public String getRegionId() {
+ return regionId;
+ }
+
+ public boolean isActive() {
+ return active;
+ }
+
+ public boolean isInitialized() {
+ return initialized;
+ }
+
+ public long getCurrentWalSearchIndex() {
+ return currentWalSearchIndex;
+ }
+
+ public long getNextReadSearchIndex() {
+ return nextReadSearchIndex;
+ }
+
+ public long getRawWalGap() {
+ return rawWalGap;
+ }
+
+ public long getApproximateLag() {
+ return approximateLag;
+ }
+
+ public long getPrefetchedEventCount() {
+ return prefetchedEventCount;
+ }
+
+ public long getInFlightEventCount() {
+ return inFlightEventCount;
+ }
+
+ public long getPendingEventCount() {
+ return pendingEventCount;
+ }
+
+ public long getRealtimeBufferedEventCount() {
+ return realtimeBufferedEventCount;
+ }
+
+ public long getLingerEventCount() {
+ return lingerEventCount;
+ }
+
+ public long getRemainingEventCount() {
+ return prefetchedEventCount
+ + inFlightEventCount
+ + pendingEventCount
+ + realtimeBufferedEventCount
+ + lingerEventCount;
+ }
+
+ public long getLastPollTimeMs() {
+ return lastPollTimeMs;
+ }
+
+ public long getLastProgressTimeMs() {
+ return lastProgressTimeMs;
+ }
+
+ public String getLastConsumerId() {
+ return lastConsumerId;
+ }
+
+ public long getSeekGeneration() {
+ return seekGeneration;
+ }
+
+ public long getWalGapSkippedEntries() {
+ return walGapSkippedEntries;
+ }
+
+ public long getRoutingEpochChangeCount() {
+ return routingEpochChangeCount;
+ }
+
+ public long getMaxObservedTimestamp() {
+ return maxObservedTimestamp;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public ByteBuffer serialize() {
+ try (final ByteArrayOutputStream output = new ByteArrayOutputStream();
+ final DataOutputStream stream = new DataOutputStream(output)) {
+ ReadWriteIOUtils.write(dataNodeId, stream);
+ ReadWriteIOUtils.write(consumerGroupId, stream);
+ ReadWriteIOUtils.write(topicName, stream);
+ ReadWriteIOUtils.write(regionId, stream);
+ ReadWriteIOUtils.write(active, stream);
+ ReadWriteIOUtils.write(initialized, stream);
+ ReadWriteIOUtils.write(currentWalSearchIndex, stream);
+ ReadWriteIOUtils.write(nextReadSearchIndex, stream);
+ ReadWriteIOUtils.write(rawWalGap, stream);
+ ReadWriteIOUtils.write(approximateLag, stream);
+ ReadWriteIOUtils.write(prefetchedEventCount, stream);
+ ReadWriteIOUtils.write(inFlightEventCount, stream);
+ ReadWriteIOUtils.write(pendingEventCount, stream);
+ ReadWriteIOUtils.write(realtimeBufferedEventCount, stream);
+ ReadWriteIOUtils.write(lingerEventCount, stream);
+ ReadWriteIOUtils.write(lastPollTimeMs, stream);
+ ReadWriteIOUtils.write(lastProgressTimeMs, stream);
+ ReadWriteIOUtils.write(lastConsumerId, stream);
+ ReadWriteIOUtils.write(seekGeneration, stream);
+ ReadWriteIOUtils.write(walGapSkippedEntries, stream);
+ ReadWriteIOUtils.write(routingEpochChangeCount, stream);
+ ReadWriteIOUtils.write(maxObservedTimestamp, stream);
+ ReadWriteIOUtils.write(status, stream);
+ stream.flush();
+ return ByteBuffer.wrap(output.toByteArray());
+ } catch (final IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ public static SubscriptionProgressSnapshot deserialize(final ByteBuffer
input) {
+ final ByteBuffer buffer = input.asReadOnlyBuffer();
+ return new SubscriptionProgressSnapshot(
+ ReadWriteIOUtils.readInt(buffer),
+ ReadWriteIOUtils.readString(buffer),
+ ReadWriteIOUtils.readString(buffer),
+ ReadWriteIOUtils.readString(buffer),
+ ReadWriteIOUtils.readBool(buffer),
+ ReadWriteIOUtils.readBool(buffer),
+ ReadWriteIOUtils.readLong(buffer),
+ ReadWriteIOUtils.readLong(buffer),
+ ReadWriteIOUtils.readLong(buffer),
+ ReadWriteIOUtils.readLong(buffer),
+ ReadWriteIOUtils.readLong(buffer),
+ ReadWriteIOUtils.readLong(buffer),
+ ReadWriteIOUtils.readLong(buffer),
+ ReadWriteIOUtils.readLong(buffer),
+ ReadWriteIOUtils.readLong(buffer),
+ ReadWriteIOUtils.readLong(buffer),
+ ReadWriteIOUtils.readLong(buffer),
+ ReadWriteIOUtils.readString(buffer),
+ ReadWriteIOUtils.readLong(buffer),
+ ReadWriteIOUtils.readLong(buffer),
+ ReadWriteIOUtils.readLong(buffer),
+ ReadWriteIOUtils.readLong(buffer),
+ ReadWriteIOUtils.readString(buffer));
+ }
+}
diff --git
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/subscription/meta/consumer/SubscriptionProgressSnapshotTest.java
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/subscription/meta/consumer/SubscriptionProgressSnapshotTest.java
new file mode 100644
index 00000000000..e3bbb2c4728
--- /dev/null
+++
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/subscription/meta/consumer/SubscriptionProgressSnapshotTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.subscription.meta.consumer;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class SubscriptionProgressSnapshotTest {
+
+ @Test
+ public void testSerializationRoundTrip() {
+ final SubscriptionProgressSnapshot snapshot =
+ new SubscriptionProgressSnapshot(
+ 3,
+ "group",
+ "topic",
+ "3_1",
+ true,
+ true,
+ 100L,
+ 90L,
+ 11L,
+ 12L,
+ 1L,
+ 2L,
+ 3L,
+ 4L,
+ 5L,
+ 6L,
+ 7L,
+ "consumer",
+ 8L,
+ 9L,
+ 10L,
+ 11L,
+ SubscriptionProgressSnapshot.STATUS_STALLED);
+
+ final SubscriptionProgressSnapshot restored =
+ SubscriptionProgressSnapshot.deserialize(snapshot.serialize());
+
+ assertEquals(snapshot.getDataNodeId(), restored.getDataNodeId());
+ assertEquals(snapshot.getConsumerGroupId(), restored.getConsumerGroupId());
+ assertEquals(snapshot.getTopicName(), restored.getTopicName());
+ assertEquals(snapshot.getRegionId(), restored.getRegionId());
+ assertTrue(restored.isActive());
+ assertTrue(restored.isInitialized());
+ assertEquals(snapshot.getCurrentWalSearchIndex(),
restored.getCurrentWalSearchIndex());
+ assertEquals(snapshot.getNextReadSearchIndex(),
restored.getNextReadSearchIndex());
+ assertEquals(snapshot.getRawWalGap(), restored.getRawWalGap());
+ assertEquals(snapshot.getApproximateLag(), restored.getApproximateLag());
+ assertEquals(snapshot.getRemainingEventCount(),
restored.getRemainingEventCount());
+ assertEquals(snapshot.getLastPollTimeMs(), restored.getLastPollTimeMs());
+ assertEquals(snapshot.getLastProgressTimeMs(),
restored.getLastProgressTimeMs());
+ assertEquals(snapshot.getLastConsumerId(), restored.getLastConsumerId());
+ assertEquals(snapshot.getSeekGeneration(), restored.getSeekGeneration());
+ assertEquals(snapshot.getStatus(), restored.getStatus());
+ assertTrue(restored.serialize().remaining() > 0);
+ }
+}
diff --git
a/iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/RelationalSql.g4
b/iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/RelationalSql.g4
index 6ab05044dfb..037778597d4 100644
---
a/iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/RelationalSql.g4
+++
b/iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/RelationalSql.g4
@@ -574,7 +574,7 @@ showCreateTopicStatement
;
showSubscriptionsStatement
- : SHOW SUBSCRIPTIONS (ON topicName=identifier)?
+ : SHOW SUBSCRIPTIONS (DETAILS)? (ON topicName=identifier)?
;
dropSubscriptionStatement
diff --git a/iotdb-protocol/thrift-confignode/src/main/thrift/confignode.thrift
b/iotdb-protocol/thrift-confignode/src/main/thrift/confignode.thrift
index f0f2292027a..512e2e05f05 100644
--- a/iotdb-protocol/thrift-confignode/src/main/thrift/confignode.thrift
+++ b/iotdb-protocol/thrift-confignode/src/main/thrift/confignode.thrift
@@ -1068,11 +1068,13 @@ struct TUnsubscribeReq {
struct TShowSubscriptionReq {
1: optional string topicName
2: optional bool isTableModel
+ 3: optional bool details
}
struct TShowSubscriptionResp {
1: required common.TSStatus status
2: optional list<TShowSubscriptionInfo> subscriptionInfoList
+ 3: optional list<TSubscriptionProgressInfo> subscriptionProgressList
}
struct TShowSubscriptionInfo {
@@ -1082,6 +1084,28 @@ struct TShowSubscriptionInfo {
4: optional i64 creationTime
}
+struct TSubscriptionProgressInfo {
+ 1: required string topicName
+ 2: required string consumerGroupId
+ 3: required string regionId
+ 4: required i32 dataNodeId
+ 5: required bool active
+ 6: required bool initialized
+ 7: required string status
+ 8: required i64 remainingEventCount
+ 9: required i64 rawWalGap
+ 10: required i64 approximateLag
+ 11: required i64 inFlightEventCount
+ 12: required i64 prefetchedEventCount
+ 13: required i64 pendingEventCount
+ 14: required i64 currentWalSearchIndex
+ 15: required i64 nextReadSearchIndex
+ 16: required i64 lastProgressTimeMs
+ 17: required i64 lastPollTimeMs
+ 18: required string lastConsumerId
+ 19: required i64 seekGeneration
+}
+
struct TDropSubscriptionReq {
1: required string subsciptionId
2: optional bool ifExistsCondition
diff --git a/iotdb-protocol/thrift-datanode/src/main/thrift/datanode.thrift
b/iotdb-protocol/thrift-datanode/src/main/thrift/datanode.thrift
index 1c3fd78470d..bcef41032aa 100644
--- a/iotdb-protocol/thrift-datanode/src/main/thrift/datanode.thrift
+++ b/iotdb-protocol/thrift-datanode/src/main/thrift/datanode.thrift
@@ -630,6 +630,7 @@ struct TPullCommitProgressReq {
struct TPullCommitProgressResp {
1: required common.TSStatus status
2: optional map<string, binary> commitRegionProgress
+ 3: optional map<string, binary> subscriptionProgress
}
struct TSyncSubscriptionProgressReq {