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..9861f5ffe35 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().OP_COUNT_PING); 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().OP_COUNT_CREATE_SESSION); 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().OP_COUNT_MULTI); 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().OP_COUNT_MULTI_READ); 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().OP_COUNT_CREATE); 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().OP_COUNT_CREATE); 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().OP_COUNT_DELETE); err = Code.get(rc.err); requestPathMetricsCollector.registerRequest(request.type, rc.path); break; } case OpCode.setData: { lastOp = "SETD"; + incrementOpCount(ServerMetrics.getMetrics().OP_COUNT_SET_DATA); rsp = new SetDataResponse(rc.stat); err = Code.get(rc.err); requestPathMetricsCollector.registerRequest(request.type, rc.path); @@ -327,14 +336,16 @@ public void processRequest(Request request) { } case OpCode.reconfig: { lastOp = "RECO"; + incrementOpCount(ServerMetrics.getMetrics().OP_COUNT_RECONFIG); rsp = new GetDataResponse( - ((QuorumZooKeeperServer) zks).self.getQuorumVerifier().toString().getBytes(UTF_8), - rc.stat); + ((QuorumZooKeeperServer) zks).self.getQuorumVerifier().toString().getBytes(UTF_8), + rc.stat); err = Code.get(rc.err); break; } case OpCode.setACL: { lastOp = "SETA"; + incrementOpCount(ServerMetrics.getMetrics().OP_COUNT_SET_ACL); 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().OP_COUNT_CLOSE_SESSION); err = Code.get(rc.err); break; } case OpCode.sync: { lastOp = "SYNC"; + incrementOpCount(ServerMetrics.getMetrics().OP_COUNT_SYNC); 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().OP_COUNT_CHECK); rsp = new SetDataResponse(rc.stat); err = Code.get(rc.err); break; } case OpCode.exists: { lastOp = "EXIS"; + incrementOpCount(ServerMetrics.getMetrics().OP_COUNT_EXISTS); 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().OP_COUNT_GET_DATA); 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().OP_COUNT_SET_WATCHES); 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().OP_COUNT_SET_WATCHES); 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().OP_COUNT_ADD_WATCH); 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().OP_COUNT_GET_ACL); 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().OP_COUNT_GET_CHILDREN); 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().OP_COUNT_GET_ALL_CHILDREN_NUMBER); 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().OP_COUNT_GET_CHILDREN); 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().OP_COUNT_CHECK_WATCHES); 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().OP_COUNT_REMOVE_WATCHES); 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().OP_COUNT_WHO_AM_I); rsp = new WhoAmIResponse(AuthUtil.getClientInfos(request.authInfo)); break; } case OpCode.getEphemerals: { lastOp = "GETE"; + incrementOpCount(ServerMetrics.getMetrics().OP_COUNT_GET_EPHEMERALS); 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.OP_COUNT_TOTAL.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 07d2c1b904a..dab8446472e 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 @@ -272,6 +272,32 @@ private ServerMetrics(MetricsProvider metricsProvider) { TTL_NODE_DELETED_COUNT = metricsContext.getCounter("ttl_node_deleted_count"); TTL_NODE_CREATED_COUNT = metricsContext.getCounter("ttl_node_created_count"); + + // Operation count metrics + OP_COUNT_TOTAL = metricsContext.getCounter("op_count_total"); + OP_COUNT_ADD_WATCH = metricsContext.getCounter("op_count_add_watch"); + OP_COUNT_CHECK = metricsContext.getCounter("op_count_check"); + OP_COUNT_CHECK_WATCHES = metricsContext.getCounter("op_count_check_watches"); + OP_COUNT_CLOSE_SESSION = metricsContext.getCounter("op_count_close_session"); + OP_COUNT_CREATE = metricsContext.getCounter("op_count_create"); + OP_COUNT_CREATE_SESSION = metricsContext.getCounter("op_count_create_session"); + OP_COUNT_DELETE = metricsContext.getCounter("op_count_delete"); + OP_COUNT_EXISTS = metricsContext.getCounter("op_count_exists"); + OP_COUNT_GET_ACL = metricsContext.getCounter("op_count_get_acl"); + OP_COUNT_GET_ALL_CHILDREN_NUMBER = metricsContext.getCounter("op_count_get_all_children_number"); + OP_COUNT_GET_CHILDREN = metricsContext.getCounter("op_count_get_children"); + OP_COUNT_GET_DATA = metricsContext.getCounter("op_count_get_data"); + OP_COUNT_GET_EPHEMERALS = metricsContext.getCounter("op_count_get_ephemerals"); + OP_COUNT_MULTI = metricsContext.getCounter("op_count_multi"); + OP_COUNT_MULTI_READ = metricsContext.getCounter("op_count_multi_read"); + OP_COUNT_PING = metricsContext.getCounter("op_count_ping"); + OP_COUNT_RECONFIG = metricsContext.getCounter("op_count_reconfig"); + OP_COUNT_REMOVE_WATCHES = metricsContext.getCounter("op_count_remove_watches"); + OP_COUNT_SET_ACL = metricsContext.getCounter("op_count_set_acl"); + OP_COUNT_SET_DATA = metricsContext.getCounter("op_count_set_data"); + OP_COUNT_SET_WATCHES = metricsContext.getCounter("op_count_set_watches"); + OP_COUNT_SYNC = metricsContext.getCounter("op_count_sync"); + OP_COUNT_WHO_AM_I = metricsContext.getCounter("op_count_who_am_i"); } /** @@ -556,6 +582,33 @@ private ServerMetrics(MetricsProvider metricsProvider) { public final Counter TTL_NODE_DELETED_COUNT; public final Counter TTL_NODE_CREATED_COUNT; + // Operation count metrics + public final Counter OP_COUNT_TOTAL; + public final Counter OP_COUNT_ADD_WATCH; + public final Counter OP_COUNT_CHECK; + public final Counter OP_COUNT_CHECK_WATCHES; + public final Counter OP_COUNT_CLOSE_SESSION; + public final Counter OP_COUNT_CREATE; + public final Counter OP_COUNT_CREATE_SESSION; + public final Counter OP_COUNT_DELETE; + public final Counter OP_COUNT_EXISTS; + public final Counter OP_COUNT_GET_ACL; + public final Counter OP_COUNT_GET_ALL_CHILDREN_NUMBER; + public final Counter OP_COUNT_GET_CHILDREN; + public final Counter OP_COUNT_GET_DATA; + public final Counter OP_COUNT_GET_EPHEMERALS; + public final Counter OP_COUNT_MULTI; + public final Counter OP_COUNT_MULTI_READ; + public final Counter OP_COUNT_PING; + public final Counter OP_COUNT_RECONFIG; + public final Counter OP_COUNT_REMOVE_WATCHES; + public final Counter OP_COUNT_SET_ACL; + public final Counter OP_COUNT_SET_DATA; + public final Counter OP_COUNT_SET_WATCHES; + public final Counter OP_COUNT_SYNC; + public final Counter OP_COUNT_WHO_AM_I; + + private final MetricsProvider metricsProvider; public void resetAll() { 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..e12976ab9cb --- /dev/null +++ b/zookeeper-server/src/test/java/org/apache/zookeeper/server/ServerMetricsOpCountTest.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.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 { + + // Metric name constants + private static final String OP_COUNT_TOTAL_METRIC_NAME = "op_count_total"; + private static final String OP_COUNT_CREATE_METRIC_NAME = "op_count_create"; + private static final String OP_COUNT_EXISTS_METRIC_NAME = "op_count_exists"; + private static final String OP_COUNT_GET_DATA_METRIC_NAME = "op_count_get_data"; + private static final String OP_COUNT_SET_DATA_METRIC_NAME = "op_count_set_data"; + private static final String OP_COUNT_GET_ACL_METRIC_NAME = "op_count_get_acl"; + private static final String OP_COUNT_SET_ACL_METRIC_NAME = "op_count_set_acl"; + private static final String OP_COUNT_GET_CHILDREN_METRIC_NAME = "op_count_get_children"; + private static final String OP_COUNT_GET_ALL_CHILDREN_NUMBER_METRIC_NAME = "op_count_get_all_children_number"; + private static final String OP_COUNT_ADD_WATCH_METRIC_NAME = "op_count_add_watch"; + private static final String OP_COUNT_DELETE_METRIC_NAME = "op_count_delete"; + private static final String OP_COUNT_MULTI_METRIC_NAME = "op_count_multi"; + private static final String OP_COUNT_MULTI_READ_METRIC_NAME = "op_count_multi_read"; + private static final String OP_COUNT_GET_EPHEMERALS_METRIC_NAME = "op_count_get_ephemerals"; + private static final String OP_COUNT_WHO_AM_I_METRIC_NAME = "op_count_who_am_i"; + private static final String OP_COUNT_CREATE_SESSION_METRIC_NAME = "op_count_create_session"; + private static final String OP_COUNT_CLOSE_SESSION_METRIC_NAME = "op_count_close_session"; + + @Test + public void testBasicOpCounts() throws Exception { + final Map initialMetrics = MetricsUtils.currentServerMetrics(); + final long initialTotalCount = (Long) initialMetrics.getOrDefault(OP_COUNT_TOTAL_METRIC_NAME, 0L); + + final Map initialCounts = new HashMap<>(); + initialCounts.put(OP_COUNT_CREATE_METRIC_NAME, (Long) initialMetrics.getOrDefault(OP_COUNT_CREATE_METRIC_NAME, 0L)); + initialCounts.put(OP_COUNT_EXISTS_METRIC_NAME, (Long) initialMetrics.getOrDefault(OP_COUNT_EXISTS_METRIC_NAME, 0L)); + initialCounts.put(OP_COUNT_GET_DATA_METRIC_NAME, (Long) initialMetrics.getOrDefault(OP_COUNT_GET_DATA_METRIC_NAME, 0L)); + initialCounts.put(OP_COUNT_SET_DATA_METRIC_NAME, (Long) initialMetrics.getOrDefault(OP_COUNT_SET_DATA_METRIC_NAME, 0L)); + initialCounts.put(OP_COUNT_GET_ACL_METRIC_NAME, (Long) initialMetrics.getOrDefault(OP_COUNT_GET_ACL_METRIC_NAME, 0L)); + initialCounts.put(OP_COUNT_SET_ACL_METRIC_NAME, (Long) initialMetrics.getOrDefault(OP_COUNT_SET_ACL_METRIC_NAME, 0L)); + initialCounts.put(OP_COUNT_GET_CHILDREN_METRIC_NAME, (Long) initialMetrics.getOrDefault(OP_COUNT_GET_CHILDREN_METRIC_NAME, 0L)); + initialCounts.put(OP_COUNT_GET_ALL_CHILDREN_NUMBER_METRIC_NAME, (Long) initialMetrics.getOrDefault(OP_COUNT_GET_ALL_CHILDREN_NUMBER_METRIC_NAME, 0L)); + initialCounts.put(OP_COUNT_ADD_WATCH_METRIC_NAME, (Long) initialMetrics.getOrDefault(OP_COUNT_ADD_WATCH_METRIC_NAME, 0L)); + initialCounts.put(OP_COUNT_DELETE_METRIC_NAME, (Long) initialMetrics.getOrDefault(OP_COUNT_DELETE_METRIC_NAME, 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(OP_COUNT_TOTAL_METRIC_NAME, 0L); + + // Total count should have increased by at least 10 operations + assertTrue(finalTotalCount >= initialTotalCount + 10, + "Total count should increase by at least 10 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 increase, initial: " + initialCount + + ", final: " + finalCount); + } + } + } + + @Test + public void testMultiOpCount() throws Exception { + testSingleOpCount(OP_COUNT_MULTI_METRIC_NAME, (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(OP_COUNT_MULTI_READ_METRIC_NAME, (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 testGetEphemeralsOpCount() throws Exception { + testSingleOpCount(OP_COUNT_GET_EPHEMERALS_METRIC_NAME, (zk, path) -> { + zk.create(path, "test".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); + zk.getEphemerals("/"); + }); + } + + @Test + public void testWhoAmIOpCount() throws Exception { + testSingleOpCount(OP_COUNT_WHO_AM_I_METRIC_NAME, (zk, path) -> zk.whoAmI()); + } + + @Test + public void testSessionOperationCounts() throws Exception { + final Map initialMetrics = MetricsUtils.currentServerMetrics(); + final long initialCreateCount = (Long) initialMetrics.getOrDefault(OP_COUNT_CREATE_SESSION_METRIC_NAME, 0L); + final long initialCloseCount = (Long) initialMetrics.getOrDefault(OP_COUNT_CLOSE_SESSION_METRIC_NAME, 0L); + + // Create and close a new client to trigger session operations + final ZooKeeper zk = createClient(); + zk.close(); + + final Map finalMetrics = MetricsUtils.currentServerMetrics(); + final long finalCreateCount = (Long) finalMetrics.getOrDefault(OP_COUNT_CREATE_SESSION_METRIC_NAME, 0L); + final long finalCloseCount = (Long) finalMetrics.getOrDefault(OP_COUNT_CLOSE_SESSION_METRIC_NAME, 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(OP_COUNT_TOTAL_METRIC_NAME, 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 + final Map finalMetrics = MetricsUtils.currentServerMetrics(); + final long finalOpCount = (Long) finalMetrics.getOrDefault(metricName, 0L); + final long finalTotalCount = (Long) finalMetrics.getOrDefault(OP_COUNT_TOTAL_METRIC_NAME, 0L); + + assertTrue(finalTotalCount > initialTotalCount, + "Total count should increase after " + metricName + " operations" + + " initial: " + initialTotalCount + ", final: " + finalTotalCount); + + 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(); + } +} +