diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/FinalRequestProcessor.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/FinalRequestProcessor.java index 911583f9fc2..15a7a2e056d 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/FinalRequestProcessor.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/FinalRequestProcessor.java @@ -49,6 +49,7 @@ import org.apache.zookeeper.data.ACL; import org.apache.zookeeper.data.Id; import org.apache.zookeeper.data.Stat; +import org.apache.zookeeper.metrics.Counter; import org.apache.zookeeper.proto.AddWatchRequest; import org.apache.zookeeper.proto.CheckWatchesRequest; import org.apache.zookeeper.proto.Create2Response; @@ -213,6 +214,7 @@ public void processRequest(Request request) { switch (request.type) { case OpCode.ping: { lastOp = "PING"; + incrementOpCount(ServerMetrics.getMetrics().PING_OP_COUNT); updateStats(request, lastOp, lastZxid); responseSize = cnxn.sendResponse(new ReplyHeader(ClientCnxn.PING_XID, lastZxid, 0), null, "response"); @@ -220,6 +222,7 @@ public void processRequest(Request request) { } case OpCode.createSession: { lastOp = "SESS"; + incrementOpCount(ServerMetrics.getMetrics().CREATE_SESSION_OP_COUNT); updateStats(request, lastOp, lastZxid); zks.finishSessionInit(request.cnxn, true); @@ -227,6 +230,7 @@ public void processRequest(Request request) { } case OpCode.multi: { lastOp = "MULT"; + incrementOpCount(ServerMetrics.getMetrics().MULTI_OP_COUNT); rsp = new MultiResponse(); for (ProcessTxnResult subTxnResult : rc.multiResult) { @@ -269,6 +273,7 @@ public void processRequest(Request request) { } case OpCode.multiRead: { lastOp = "MLTR"; + incrementOpCount(ServerMetrics.getMetrics().MULTI_READ_OP_COUNT); MultiOperationRecord multiReadRecord = request.readRequestRecord(MultiOperationRecord::new); rsp = new MultiResponse(); OpResult subResult; @@ -297,6 +302,7 @@ public void processRequest(Request request) { } case OpCode.create: { lastOp = "CREA"; + incrementOpCount(ServerMetrics.getMetrics().CREATE_OP_COUNT); rsp = new CreateResponse(rc.path); err = Code.get(rc.err); requestPathMetricsCollector.registerRequest(request.type, rc.path); @@ -306,6 +312,7 @@ public void processRequest(Request request) { case OpCode.createTTL: case OpCode.createContainer: { lastOp = "CREA"; + incrementOpCount(ServerMetrics.getMetrics().CREATE_OP_COUNT); rsp = new Create2Response(rc.path, rc.stat); err = Code.get(rc.err); requestPathMetricsCollector.registerRequest(request.type, rc.path); @@ -314,12 +321,14 @@ public void processRequest(Request request) { case OpCode.delete: case OpCode.deleteContainer: { lastOp = "DELE"; + incrementOpCount(ServerMetrics.getMetrics().DELETE_OP_COUNT); err = Code.get(rc.err); requestPathMetricsCollector.registerRequest(request.type, rc.path); break; } case OpCode.setData: { lastOp = "SETD"; + incrementOpCount(ServerMetrics.getMetrics().SET_DATA_OP_COUNT); rsp = new SetDataResponse(rc.stat); err = Code.get(rc.err); requestPathMetricsCollector.registerRequest(request.type, rc.path); @@ -327,6 +336,7 @@ public void processRequest(Request request) { } case OpCode.reconfig: { lastOp = "RECO"; + incrementOpCount(ServerMetrics.getMetrics().RECONFIG_OP_COUNT); rsp = new GetDataResponse( ((QuorumZooKeeperServer) zks).self.getQuorumVerifier().toString().getBytes(UTF_8), rc.stat); @@ -335,6 +345,7 @@ public void processRequest(Request request) { } case OpCode.setACL: { lastOp = "SETA"; + incrementOpCount(ServerMetrics.getMetrics().SET_ACL_OP_COUNT); rsp = new SetACLResponse(rc.stat); err = Code.get(rc.err); requestPathMetricsCollector.registerRequest(request.type, rc.path); @@ -342,11 +353,13 @@ public void processRequest(Request request) { } case OpCode.closeSession: { lastOp = "CLOS"; + incrementOpCount(ServerMetrics.getMetrics().CLOSE_SESSION_OP_COUNT); err = Code.get(rc.err); break; } case OpCode.sync: { lastOp = "SYNC"; + incrementOpCount(ServerMetrics.getMetrics().SYNC_OP_COUNT); SyncRequest syncRequest = request.readRequestRecord(SyncRequest::new); rsp = new SyncResponse(syncRequest.getPath()); requestPathMetricsCollector.registerRequest(request.type, syncRequest.getPath()); @@ -354,12 +367,14 @@ public void processRequest(Request request) { } case OpCode.check: { lastOp = "CHEC"; + incrementOpCount(ServerMetrics.getMetrics().CHECK_OP_COUNT); rsp = new SetDataResponse(rc.stat); err = Code.get(rc.err); break; } case OpCode.exists: { lastOp = "EXIS"; + incrementOpCount(ServerMetrics.getMetrics().EXISTS_OP_COUNT); ExistsRequest existsRequest = request.readRequestRecord(ExistsRequest::new); path = existsRequest.getPath(); if (path.indexOf('\0') != -1) { @@ -382,6 +397,7 @@ public void processRequest(Request request) { } case OpCode.getData: { lastOp = "GETD"; + incrementOpCount(ServerMetrics.getMetrics().GET_DATA_OP_COUNT); GetDataRequest getDataRequest = request.readRequestRecord(GetDataRequest::new); path = getDataRequest.getPath(); rsp = handleGetDataRequest(getDataRequest, cnxn, request.authInfo); @@ -390,6 +406,7 @@ public void processRequest(Request request) { } case OpCode.setWatches: { lastOp = "SETW"; + incrementOpCount(ServerMetrics.getMetrics().SET_WATCHES_OP_COUNT); SetWatches setWatches = request.readRequestRecord(SetWatches::new); long relativeZxid = setWatches.getRelativeZxid(); zks.getZKDatabase() @@ -405,6 +422,7 @@ public void processRequest(Request request) { } case OpCode.setWatches2: { lastOp = "STW2"; + incrementOpCount(ServerMetrics.getMetrics().SET_WATCHES_OP_COUNT); SetWatches2 setWatches = request.readRequestRecord(SetWatches2::new); long relativeZxid = setWatches.getRelativeZxid(); zks.getZKDatabase().setWatches(relativeZxid, @@ -418,6 +436,7 @@ public void processRequest(Request request) { } case OpCode.addWatch: { lastOp = "ADDW"; + incrementOpCount(ServerMetrics.getMetrics().ADD_WATCH_OP_COUNT); AddWatchRequest addWatcherRequest = request.readRequestRecord(AddWatchRequest::new); zks.getZKDatabase().addWatch(addWatcherRequest.getPath(), cnxn, addWatcherRequest.getMode()); rsp = new ErrorResponse(0); @@ -425,6 +444,7 @@ public void processRequest(Request request) { } case OpCode.getACL: { lastOp = "GETA"; + incrementOpCount(ServerMetrics.getMetrics().GET_ACL_OP_COUNT); GetACLRequest getACLRequest = request.readRequestRecord(GetACLRequest::new); path = getACLRequest.getPath(); DataNode n = zks.getZKDatabase().getNode(path); @@ -467,6 +487,7 @@ public void processRequest(Request request) { } case OpCode.getChildren: { lastOp = "GETC"; + incrementOpCount(ServerMetrics.getMetrics().GET_CHILDREN_OP_COUNT); GetChildrenRequest getChildrenRequest = request.readRequestRecord(GetChildrenRequest::new); path = getChildrenRequest.getPath(); rsp = handleGetChildrenRequest(getChildrenRequest, cnxn, request.authInfo); @@ -475,6 +496,7 @@ public void processRequest(Request request) { } case OpCode.getAllChildrenNumber: { lastOp = "GETACN"; + incrementOpCount(ServerMetrics.getMetrics().GET_ALL_CHILDREN_NUMBER_OP_COUNT); GetAllChildrenNumberRequest getAllChildrenNumberRequest = request.readRequestRecord(GetAllChildrenNumberRequest::new); path = getAllChildrenNumberRequest.getPath(); DataNode n = zks.getZKDatabase().getNode(path); @@ -494,6 +516,7 @@ public void processRequest(Request request) { } case OpCode.getChildren2: { lastOp = "GETC"; + incrementOpCount(ServerMetrics.getMetrics().GET_CHILDREN_OP_COUNT); GetChildren2Request getChildren2Request = request.readRequestRecord(GetChildren2Request::new); Stat stat = new Stat(); path = getChildren2Request.getPath(); @@ -515,6 +538,7 @@ public void processRequest(Request request) { } case OpCode.checkWatches: { lastOp = "CHKW"; + incrementOpCount(ServerMetrics.getMetrics().CHECK_WATCHES_OP_COUNT); CheckWatchesRequest checkWatches = request.readRequestRecord(CheckWatchesRequest::new); WatcherType type = WatcherType.fromInt(checkWatches.getType()); path = checkWatches.getPath(); @@ -528,6 +552,7 @@ public void processRequest(Request request) { } case OpCode.removeWatches: { lastOp = "REMW"; + incrementOpCount(ServerMetrics.getMetrics().REMOVE_WATCHES_OP_COUNT); RemoveWatchesRequest removeWatches = request.readRequestRecord(RemoveWatchesRequest::new); WatcherType type = WatcherType.fromInt(removeWatches.getType()); path = removeWatches.getPath(); @@ -541,11 +566,13 @@ public void processRequest(Request request) { } case OpCode.whoAmI: { lastOp = "HOMI"; + incrementOpCount(ServerMetrics.getMetrics().WHO_AM_I_OP_COUNT); rsp = new WhoAmIResponse(AuthUtil.getClientInfos(request.authInfo)); break; } case OpCode.getEphemerals: { lastOp = "GETE"; + incrementOpCount(ServerMetrics.getMetrics().GET_EPHEMERALS_OP_COUNT); GetEphemeralsRequest getEphemerals = request.readRequestRecord(GetEphemeralsRequest::new); String prefixPath = getEphemerals.getPrefixPath(); Set allEphems = zks.getZKDatabase().getDataTree().getEphemerals(request.sessionId); @@ -677,4 +704,9 @@ private void updateStats(Request request, String lastOp, long lastZxid) { request.cnxn.updateStatsForResponse(request.cxid, lastZxid, lastOp, request.createTime, currentTime); } + private void incrementOpCount(final Counter specificCounter) { + final ServerMetrics metrics = ServerMetrics.getMetrics(); + specificCounter.add(1); + metrics.TOTAL_OP_COUNT.add(1); + } } diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/ServerMetrics.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/ServerMetrics.java index f9b56f5f084..1ea8d5cd06f 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/ServerMetrics.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/ServerMetrics.java @@ -271,6 +271,36 @@ private ServerMetrics(MetricsProvider metricsProvider) { QUOTA_EXCEEDED_ERROR_PER_NAMESPACE = metricsContext.getCounterSet(QuotaMetricsUtils.QUOTA_EXCEEDED_ERROR_PER_NAMESPACE); TTL_NODE_DELETED_COUNT = metricsContext.getCounter("ttl_node_deleted_count"); + + /** + * Operation count metrics + */ + TOTAL_OP_COUNT = metricsContext.getCounter("total_op_count"); + PING_OP_COUNT = metricsContext.getCounter("ping_op_count"); + CREATE_SESSION_OP_COUNT = metricsContext.getCounter("create_session_op_count"); + CLOSE_SESSION_OP_COUNT = metricsContext.getCounter("close_session_op_count"); + MULTI_OP_COUNT = metricsContext.getCounter("multi_op_count"); + CREATE_OP_COUNT = metricsContext.getCounter("create_op_count"); + DELETE_OP_COUNT = metricsContext.getCounter("delete_op_count"); + SET_DATA_OP_COUNT = metricsContext.getCounter("set_data_op_count"); + EXISTS_OP_COUNT = metricsContext.getCounter("exists_op_count"); + GET_DATA_OP_COUNT = metricsContext.getCounter("get_data_op_count"); + GET_CHILDREN_OP_COUNT = metricsContext.getCounter("get_children_op_count"); + SYNC_OP_COUNT = metricsContext.getCounter("sync_op_count"); + GET_ACL_OP_COUNT = metricsContext.getCounter("get_acl_op_count"); + SET_ACL_OP_COUNT = metricsContext.getCounter("set_acl_op_count"); + CHECK_OP_COUNT = metricsContext.getCounter("check_op_count"); + SET_WATCHES_OP_COUNT = metricsContext.getCounter("set_watches_op_count"); + MULTI_READ_OP_COUNT = metricsContext.getCounter("multi_read_op_count"); + RECONFIG_OP_COUNT = metricsContext.getCounter("reconfig_op_count"); + ADD_WATCH_OP_COUNT = metricsContext.getCounter("add_watch_op_count"); + CHECK_WATCHES_OP_COUNT = metricsContext.getCounter("check_watches_op_count"); + REMOVE_WATCHES_OP_COUNT = metricsContext.getCounter("remove_watches_op_count"); + WHO_AM_I_OP_COUNT = metricsContext.getCounter("who_am_i_op_count"); + GET_EPHEMERALS_OP_COUNT = metricsContext.getCounter("get_ephemerals_op_count"); + GET_ALL_CHILDREN_NUMBER_OP_COUNT = metricsContext.getCounter("get_all_children_number_op_count"); + SASL_OP_COUNT = metricsContext.getCounter("sasl_op_count"); + AUTH_OP_COUNT = metricsContext.getCounter("auth_op_count"); } /** @@ -554,6 +584,36 @@ private ServerMetrics(MetricsProvider metricsProvider) { */ public final Counter TTL_NODE_DELETED_COUNT; + /** + * Operation count metrics + */ + public final Counter TOTAL_OP_COUNT; + public final Counter PING_OP_COUNT; + public final Counter CREATE_SESSION_OP_COUNT; + public final Counter CLOSE_SESSION_OP_COUNT; + public final Counter MULTI_OP_COUNT; + public final Counter CREATE_OP_COUNT; + public final Counter DELETE_OP_COUNT; + public final Counter SET_DATA_OP_COUNT; + public final Counter EXISTS_OP_COUNT; + public final Counter GET_DATA_OP_COUNT; + public final Counter GET_CHILDREN_OP_COUNT; + public final Counter SYNC_OP_COUNT; + public final Counter GET_ACL_OP_COUNT; + public final Counter SET_ACL_OP_COUNT; + public final Counter CHECK_OP_COUNT; + public final Counter SET_WATCHES_OP_COUNT; + public final Counter MULTI_READ_OP_COUNT; + public final Counter RECONFIG_OP_COUNT; + public final Counter ADD_WATCH_OP_COUNT; + public final Counter CHECK_WATCHES_OP_COUNT; + public final Counter REMOVE_WATCHES_OP_COUNT; + public final Counter WHO_AM_I_OP_COUNT; + public final Counter GET_EPHEMERALS_OP_COUNT; + public final Counter GET_ALL_CHILDREN_NUMBER_OP_COUNT; + public final Counter SASL_OP_COUNT; + public final Counter AUTH_OP_COUNT; + private final MetricsProvider metricsProvider; public void resetAll() { diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/ZooKeeperServer.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/ZooKeeperServer.java index 080f4f8638d..b6b49c62034 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/ZooKeeperServer.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/ZooKeeperServer.java @@ -1697,6 +1697,7 @@ public void processPacket(ServerCnxn cnxn, RequestHeader h, RequestRecord reques if (h.getType() == OpCode.auth) { LOG.info("got auth packet {}", cnxn.getRemoteSocketAddress()); + ServerMetrics.getMetrics().AUTH_OP_COUNT.add(1); AuthPacket authPacket = request.readRecord(AuthPacket::new); String scheme = authPacket.getScheme(); ServerAuthenticationProvider ap = ProviderRegistry.getServerProvider(scheme); @@ -1737,6 +1738,7 @@ public void processPacket(ServerCnxn cnxn, RequestHeader h, RequestRecord reques } return; } else if (h.getType() == OpCode.sasl) { + ServerMetrics.getMetrics().SASL_OP_COUNT.add(1); processSasl(request, cnxn, h); } else { if (!authHelper.enforceAuthentication(cnxn, h.getXid())) { diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/server/ServerMetricsOpCountTest.java b/zookeeper-server/src/test/java/org/apache/zookeeper/server/ServerMetricsOpCountTest.java new file mode 100644 index 00000000000..9d5bde8a8a5 --- /dev/null +++ b/zookeeper-server/src/test/java/org/apache/zookeeper/server/ServerMetricsOpCountTest.java @@ -0,0 +1,206 @@ +/* + * 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.zookeeper.server; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.metrics.MetricsUtils; +import org.apache.zookeeper.test.ClientBase; +import org.junit.jupiter.api.Test; + +public class ServerMetricsOpCountTest extends ClientBase { + + @Test + public void testBasicOpCounts() throws Exception { + final Map initialMetrics = MetricsUtils.currentServerMetrics(); + final long initialTotalCount = (Long) initialMetrics.getOrDefault("total_op_count", 0L); + + final Map initialCounts = new HashMap<>(); + initialCounts.put("create_op_count", (Long) initialMetrics.getOrDefault("create_op_count", 0L)); + initialCounts.put("exists_op_count", (Long) initialMetrics.getOrDefault("exists_op_count", 0L)); + initialCounts.put("get_data_op_count", (Long) initialMetrics.getOrDefault("get_data_op_count", 0L)); + initialCounts.put("set_data_op_count", (Long) initialMetrics.getOrDefault("set_data_op_count", 0L)); + initialCounts.put("get_acl_op_count", (Long) initialMetrics.getOrDefault("get_acl_op_count", 0L)); + initialCounts.put("set_acl_op_count", (Long) initialMetrics.getOrDefault("set_acl_op_count", 0L)); + initialCounts.put("get_children_op_count", (Long) initialMetrics.getOrDefault("get_children_op_count", 0L)); + initialCounts.put("get_all_children_number_op_count", (Long) initialMetrics.getOrDefault("get_all_children_number_op_count", 0L)); + initialCounts.put("add_watch_op_count", (Long) initialMetrics.getOrDefault("add_watch_op_count", 0L)); + initialCounts.put("delete_op_count", (Long) initialMetrics.getOrDefault("delete_op_count", 0L)); + + try (final ZooKeeper zk = createClient()) { + final String path = generateUniquePath("testBasicOps"); + + // Create node + zk.create(path, "test".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + + // Perform various operations + zk.exists(path, false); + zk.getData(path, false, null); + zk.setData(path, "updated".getBytes(), -1); + zk.getACL(path, null); + + zk.setACL(path, Ids.READ_ACL_UNSAFE, -1); + zk.getChildren(path, false); + + zk.getAllChildrenNumber(path); + + zk.addWatch(path, event -> {}, org.apache.zookeeper.AddWatchMode.PERSISTENT); + + // Delete node + zk.delete(path, -1); + + // Verify all metrics increased + final Map finalMetrics = MetricsUtils.currentServerMetrics(); + final long finalTotalCount = (Long) finalMetrics.getOrDefault("total_op_count", 0L); + + // Total count should have increased by at least 10 operations + assertTrue(finalTotalCount >= initialTotalCount + 10, + "Total count should increase by at least 12 operations, initial: " + initialTotalCount + + ", final: " + finalTotalCount); + + // Verify each specific metric increased + for (final Map.Entry entry : initialCounts.entrySet()) { + final String metricName = entry.getKey(); + final long initialCount = entry.getValue(); + final long finalCount = (Long) finalMetrics.getOrDefault(metricName, 0L); + + assertTrue(finalCount > initialCount, + metricName + " should should increased, initial: " + initialCount + + ", final: " + finalCount); + } + } + } + + + @Test + public void testMultiOpCount() throws Exception { + testSingleOpCount("multi_op_count", (zk, basePath) -> { + final String path1 = basePath + "_1"; + final String path2 = basePath + "_2"; + final List ops = Arrays.asList( + org.apache.zookeeper.Op.create(path1, "test".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT), + org.apache.zookeeper.Op.create(path2, "test".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT) + ); + zk.multi(ops); + zk.delete(path1, -1); + zk.delete(path2, -1); + }); + } + + @Test + public void testMultiReadOpCount() throws Exception { + testSingleOpCount("multi_read_op_count", (zk, basePath) -> { + final String path1 = basePath + "_1"; + final String path2 = basePath + "_2"; + + // Create nodes first + zk.create(path1, "test1".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + zk.create(path2, "test2".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + + // Perform multi-read operation + final List readOps = Arrays.asList( + org.apache.zookeeper.Op.getData(path1), + org.apache.zookeeper.Op.getData(path2) + ); + zk.multi(readOps); + + zk.delete(path1, -1); + zk.delete(path2, -1); + }); + } + + @Test + public void testReconfigOpCount() { + final Map initialMetrics = MetricsUtils.currentServerMetrics(); + final long initialCount = (Long) initialMetrics.getOrDefault("reconfig_op_count", 0L); + assertTrue(initialCount >= 0, "reconfig_op_count metric should exist and be non-negative"); + } + + @Test + public void testGetEphemeralsOpCount() throws Exception { + testSingleOpCount("get_ephemerals_op_count", (zk, path) -> { + zk.create(path, "test".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); + zk.getEphemerals("/"); + }); + } + + @Test + public void testWhoAmIOpCount() throws Exception { + testSingleOpCount("who_am_i_op_count", (zk, path) -> zk.whoAmI()); + } + + @Test + public void testSessionOperationCounts() throws Exception { + final Map initialMetrics = MetricsUtils.currentServerMetrics(); + final long initialCreateCount = (Long) initialMetrics.getOrDefault("create_session_op_count", 0L); + final long initialCloseCount = (Long) initialMetrics.getOrDefault("close_session_op_count", 0L); + + // Create and close a new client to trigger session operations + final ZooKeeper zk = createClient(); + zk.exists("/", false); // Ensure session is active + zk.close(); + + final Map finalMetrics = MetricsUtils.currentServerMetrics(); + final long finalCreateCount = (Long) finalMetrics.getOrDefault("create_session_op_count", 0L); + final long finalCloseCount = (Long) finalMetrics.getOrDefault("close_session_op_count", 0L); + + assertTrue(finalCreateCount > initialCreateCount,"Create session count should not decrease"); + assertTrue(finalCloseCount > initialCloseCount,"Close session count should not decrease"); + } + + private void testSingleOpCount(final String metricName, final OperationExecutor executor) throws Exception { + final Map initialMetrics = MetricsUtils.currentServerMetrics(); + final long initialOpCount = (Long) initialMetrics.getOrDefault(metricName, 0L); + final long initialTotalCount = (Long) initialMetrics.getOrDefault("total_op_count", 0L); + + try (final ZooKeeper zk = createClient()) { + final String path = generateUniquePath("test" + metricName.replace("_op_count", "")); + + // Execute the operation + executor.execute(zk, path); + + // Verify metrics increased (immediate verification since metrics increment synchronously) + final Map finalMetrics = MetricsUtils.currentServerMetrics(); + final long finalOpCount = (Long) finalMetrics.getOrDefault(metricName, 0L); + final long finalTotalCount = (Long) finalMetrics.getOrDefault("total_op_count", 0L); + + assertTrue(finalTotalCount > initialTotalCount, + "Total count should increase after " + metricName + " operations"); + + assertTrue(finalOpCount > initialOpCount, + metricName + " should increase, initial: " + initialOpCount + + ", final: " + finalOpCount); + } + } + + @FunctionalInterface + private interface OperationExecutor { + void execute(ZooKeeper zk, String path) throws Exception; + } + + private String generateUniquePath(final String baseName) { + return "/" + baseName + "_" + System.nanoTime(); + } +}