This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-pool.git


The following commit(s) were added to refs/heads/master by this push:
     new abfb1255 Made statistics collection optional in BaseGenericObjectPool 
(#429)
abfb1255 is described below

commit abfb12555a024de6257eb3642e982016f969a2b0
Author: Pratyay <[email protected]>
AuthorDate: Tue Dec 2 02:32:04 2025 +0530

    Made statistics collection optional in BaseGenericObjectPool (#429)
    
    * made statistic collection optional
    
    * Add Javadoc @since 2.13.0
    
    * Reduce whitespace
    
    * Add Javadoc @since 2.13.0
    
    * Fix Checkstyle trailing whitespace
    
    * Clean up blank lines in testCollectDetailedStatisticsEnabled
    
    Removed unnecessary blank lines in test method.
    
    * Fix Checkstyle trailing whitespace
    
    * Javadoc
    
    * Javadoc
    
    ---------
    
    Co-authored-by: Gary Gregory <[email protected]>
---
 .../commons/pool3/impl/BaseGenericObjectPool.java  |  63 ++++++--
 .../commons/pool3/impl/BaseObjectPoolConfig.java   |  54 +++++++
 .../pool3/impl/TestBaseGenericObjectPool.java      | 170 +++++++++++++++++++++
 3 files changed, 272 insertions(+), 15 deletions(-)

diff --git 
a/src/main/java/org/apache/commons/pool3/impl/BaseGenericObjectPool.java 
b/src/main/java/org/apache/commons/pool3/impl/BaseGenericObjectPool.java
index fc3bfbbf..e128d52a 100644
--- a/src/main/java/org/apache/commons/pool3/impl/BaseGenericObjectPool.java
+++ b/src/main/java/org/apache/commons/pool3/impl/BaseGenericObjectPool.java
@@ -408,6 +408,7 @@ public abstract class BaseGenericObjectPool<T, E extends 
Exception> extends Base
 
     private volatile SwallowedExceptionListener swallowedExceptionListener;
     private volatile boolean messageStatistics;
+    private volatile boolean collectDetailedStatistics = 
BaseObjectPoolConfig.DEFAULT_COLLECT_DETAILED_STATISTICS;
 
     /** Additional configuration properties for abandoned object tracking. */
     protected volatile AbandonedConfig abandonedConfig;
@@ -835,6 +836,20 @@ public abstract class BaseGenericObjectPool<T, E extends 
Exception> extends Base
         return messageStatistics;
     }
 
+    /**
+     * Gets whether detailed timing statistics collection is enabled.
+     * When {@code false}, the pool will not collect detailed timing 
statistics for
+     * mean active time, mean idle time, and mean borrow wait time,
+     * improving performance under high load.
+     *
+     * @return {@code true} if detailed statistics collection is enabled,
+     *         {@code false} if disabled for improved performance.
+     * @since 2.13.0
+     */
+    public boolean getCollectDetailedStatistics() {
+        return collectDetailedStatistics;
+    }
+
     /**
      * Gets the minimum amount of time an object may sit idle in the pool
      * before it is eligible for eviction by the idle object evictor (if any -
@@ -1270,6 +1285,7 @@ public abstract class BaseGenericObjectPool<T, E extends 
Exception> extends Base
             setEvictionPolicy(policy);
         }
         setEvictorShutdownTimeout(config.getEvictorShutdownTimeoutDuration());
+        setCollectDetailedStatistics(config.getCollectDetailedStatistics());
     }
 
     /**
@@ -1455,6 +1471,21 @@ public abstract class BaseGenericObjectPool<T, E extends 
Exception> extends Base
         this.messageStatistics = messagesDetails;
     }
 
+    /**
+     * Sets whether detailed timing statistics collection is enabled.
+     * When {@code false}, the pool will not collect detailed timing 
statistics,
+     * improving performance under high load at the cost of reduced monitoring 
capabilities.
+     * <p>
+     * This setting affects data collection for mean active time, mean idle 
time, and mean borrow wait time.
+     * </p>
+     *
+     * @param collectDetailedStatistics whether to collect detailed statistics.
+     * @since 2.13.0
+     */
+    public void setCollectDetailedStatistics(final boolean 
collectDetailedStatistics) {
+        this.collectDetailedStatistics = collectDetailedStatistics;
+    }
+
     /**
      * Sets the minimum amount of time an object may sit idle in the pool
      * before it is eligible for eviction by the idle object evictor (if any -
@@ -1738,20 +1769,19 @@ public abstract class BaseGenericObjectPool<T, E 
extends Exception> extends Base
      */
     final void updateStatsBorrow(final PooledObject<T> p, final Duration 
waitDuration) {
         borrowedCount.incrementAndGet();
-        idleTimes.add(p.getIdleDuration());
-        waitTimes.add(waitDuration);
-
-        // lock-free optimistic-locking maximum
-        Duration currentMaxDuration;
-        do {
-            currentMaxDuration = maxBorrowWaitDuration.get();
-//            if (currentMaxDuration >= waitDuration) {
-//                break;
-//            }
-            if (currentMaxDuration.compareTo(waitDuration) >= 0) {
-                break;
-            }
-        } while (!maxBorrowWaitDuration.compareAndSet(currentMaxDuration, 
waitDuration));
+        // Only collect detailed statistics if enabled
+        if (collectDetailedStatistics) {
+            idleTimes.add(p.getIdleDuration());
+            waitTimes.add(waitDuration);
+            // lock-free optimistic-locking maximum
+            Duration currentMaxDuration;
+            do {
+                currentMaxDuration = maxBorrowWaitDuration.get();
+                if (currentMaxDuration.compareTo(waitDuration) >= 0) {
+                    break;
+                }
+            } while (!maxBorrowWaitDuration.compareAndSet(currentMaxDuration, 
waitDuration));
+        }
     }
 
     /**
@@ -1762,7 +1792,10 @@ public abstract class BaseGenericObjectPool<T, E extends 
Exception> extends Base
      */
     final void updateStatsReturn(final Duration activeTime) {
         returnedCount.incrementAndGet();
-        activeTimes.add(activeTime);
+        // Only collect detailed statistics if enabled
+        if (collectDetailedStatistics) {
+            activeTimes.add(activeTime);
+        }
     }
 
 }
diff --git 
a/src/main/java/org/apache/commons/pool3/impl/BaseObjectPoolConfig.java 
b/src/main/java/org/apache/commons/pool3/impl/BaseObjectPoolConfig.java
index 5abbbece..e47a0451 100644
--- a/src/main/java/org/apache/commons/pool3/impl/BaseObjectPoolConfig.java
+++ b/src/main/java/org/apache/commons/pool3/impl/BaseObjectPoolConfig.java
@@ -166,6 +166,19 @@ public abstract class BaseObjectPoolConfig<T> extends 
BaseObject implements Clon
      */
     public static final String DEFAULT_EVICTION_POLICY_CLASS_NAME = 
DefaultEvictionPolicy.class.getName();
 
+    /**
+     * The default value for the {@code collectDetailedStatistics} 
configuration
+     * attribute. When {@code true}, the pool will collect detailed timing 
statistics
+     * for monitoring purposes. When {@code false}, detailed statistics 
collection
+     * is disabled, improving performance under high load.
+     * <p>
+     * This setting affects data collection for mean active time, mean idle 
time, and mean borrow wait time.
+     * </p>
+     *
+     * @since 2.13.0
+     */
+    public static final boolean DEFAULT_COLLECT_DETAILED_STATISTICS = true;
+
     private boolean lifo = DEFAULT_LIFO;
 
     private boolean fairness = DEFAULT_FAIRNESS;
@@ -203,6 +216,8 @@ public abstract class BaseObjectPoolConfig<T> extends 
BaseObject implements Clon
 
     private String jmxNameBase = DEFAULT_JMX_NAME_BASE;
 
+    private boolean collectDetailedStatistics = 
DEFAULT_COLLECT_DETAILED_STATISTICS;
+
     /**
      * Constructs a new instance.
      */
@@ -224,6 +239,23 @@ public abstract class BaseObjectPoolConfig<T> extends 
BaseObject implements Clon
         return blockWhenExhausted;
     }
 
+    /**
+     * Gets the value for the {@code collectDetailedStatistics} configuration 
attribute
+     * for pools created with this configuration instance.
+     * <p>
+     * This setting affects data collection for mean active time, mean idle 
time, and mean borrow wait time.
+     * </p>
+     *
+     * @return  {@code true} if detailed statistics collection is enabled,
+     *          {@code false} if disabled for improved performance.
+     * @see GenericObjectPool#getCollectDetailedStatistics()
+     * @see GenericKeyedObjectPool#getCollectDetailedStatistics()
+     * @since 2.13.0
+     */
+    public boolean getCollectDetailedStatistics() {
+        return collectDetailedStatistics;
+    }
+
     /**
      * Gets the value for the {@code timeBetweenEvictionRuns} configuration
      * attribute for pools created with this configuration instance.
@@ -478,6 +510,26 @@ public abstract class BaseObjectPoolConfig<T> extends 
BaseObject implements Clon
         this.blockWhenExhausted = blockWhenExhausted;
     }
 
+    /**
+     * Sets the value for the {@code collectDetailedStatistics} configuration 
attribute
+     * for pools created with this configuration instance. When {@code false}, 
the pool
+     * will not collect detailed timing statistics, improving performance 
under high load
+     * at the cost of reduced monitoring capabilities.
+     * <p>
+     * This setting affects data collection for mean active time, mean idle 
time, and mean borrow wait time.
+     * </p>
+     *
+     * @param collectDetailedStatistics The new setting of {@code 
collectDetailedStatistics}
+     *        for this configuration instance.
+     *
+     * @see GenericObjectPool#getCollectDetailedStatistics()
+     * @see GenericKeyedObjectPool#getCollectDetailedStatistics()
+     * @since 2.13.0
+     */
+    public void setCollectDetailedStatistics(final boolean 
collectDetailedStatistics) {
+        this.collectDetailedStatistics = collectDetailedStatistics;
+    }
+
     /**
      * Sets the value for the {@code timeBetweenEvictionRuns} configuration
      * attribute for pools created with this configuration instance.
@@ -755,5 +807,7 @@ public abstract class BaseObjectPoolConfig<T> extends 
BaseObject implements Clon
         builder.append(jmxNamePrefix);
         builder.append(", jmxNameBase=");
         builder.append(jmxNameBase);
+        builder.append(", collectDetailedStatistics=");
+        builder.append(collectDetailedStatistics);
     }
 }
diff --git 
a/src/test/java/org/apache/commons/pool3/impl/TestBaseGenericObjectPool.java 
b/src/test/java/org/apache/commons/pool3/impl/TestBaseGenericObjectPool.java
index d9fb5ec2..caeb2d85 100644
--- a/src/test/java/org/apache/commons/pool3/impl/TestBaseGenericObjectPool.java
+++ b/src/test/java/org/apache/commons/pool3/impl/TestBaseGenericObjectPool.java
@@ -18,10 +18,17 @@
 package org.apache.commons.pool3.impl;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.lang.management.ManagementFactory;
 import java.time.Duration;
 import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -140,4 +147,167 @@ class TestBaseGenericObjectPool {
             pools.forEach(GenericObjectPool::close);
         }
     }
+
+    @Test
+    void testCollectDetailedStatisticsDefault() {
+        // Test that collectDetailedStatistics defaults to true for backward 
compatibility
+        assertTrue(pool.getCollectDetailedStatistics());
+    }
+
+    @Test
+    void testCollectDetailedStatisticsConfiguration() {
+        // Test configuration through config object
+        final GenericObjectPoolConfig<String> config = new 
GenericObjectPoolConfig<>();
+        config.setCollectDetailedStatistics(false);
+        try (GenericObjectPool<String, TestException> testPool = new 
GenericObjectPool<>(factory, config)) {
+            assertFalse(testPool.getCollectDetailedStatistics());
+        }
+        // Test runtime configuration
+        pool.setCollectDetailedStatistics(false);
+        assertFalse(pool.getCollectDetailedStatistics());
+        pool.setCollectDetailedStatistics(true);
+        assertTrue(pool.getCollectDetailedStatistics());
+    }
+
+    @Test
+    void testCollectDetailedStatisticsDisabled() throws Exception {
+        // Configure pool to disable detailed statistics
+        pool.setCollectDetailedStatistics(false);
+        final DefaultPooledObject<String> pooledObject = 
(DefaultPooledObject<String>) factory.makeObject();
+        // Record initial values
+        final long initialActiveTime = pool.getMeanActiveTimeMillis();
+        final long initialIdleTime = pool.getMeanIdleDuration().toMillis();
+        final long initialWaitTime = pool.getMeanBorrowWaitTimeMillis();
+        final long initialMaxWaitTime = pool.getMaxBorrowWaitTimeMillis();
+        // Update statistics - should be ignored for detailed stats
+        pool.updateStatsBorrow(pooledObject, Duration.ofMillis(100));
+        pool.updateStatsReturn(Duration.ofMillis(200));
+        // Basic counters should still work
+        assertEquals(1, pool.getBorrowedCount());
+        assertEquals(1, pool.getReturnedCount());
+        // Detailed statistics should remain unchanged
+        assertEquals(initialActiveTime, pool.getMeanActiveTimeMillis());
+        assertEquals(initialIdleTime, pool.getMeanIdleDuration().toMillis());
+        assertEquals(initialWaitTime, pool.getMeanBorrowWaitTimeMillis());
+        assertEquals(initialMaxWaitTime, pool.getMaxBorrowWaitTimeMillis());
+    }
+
+    @Test
+    void testCollectDetailedStatisticsEnabled() throws Exception {
+        // Ensure detailed statistics are enabled (default)
+        pool.setCollectDetailedStatistics(true);
+        final DefaultPooledObject<String> pooledObject = 
(DefaultPooledObject<String>) factory.makeObject();
+        // Update statistics
+        pool.updateStatsBorrow(pooledObject, Duration.ofMillis(100));
+        pool.updateStatsReturn(Duration.ofMillis(200));
+        // All counters should work
+        assertEquals(1, pool.getBorrowedCount());
+        assertEquals(1, pool.getReturnedCount());
+        // Detailed statistics should be updated
+        assertEquals(200, pool.getMeanActiveTimeMillis());
+        assertEquals(100, pool.getMeanBorrowWaitTimeMillis());
+        assertEquals(100, pool.getMaxBorrowWaitTimeMillis());
+    }
+
+    @Test
+    void testCollectDetailedStatisticsToggling() throws Exception {
+        final DefaultPooledObject<String> pooledObject = 
(DefaultPooledObject<String>) factory.makeObject();
+        // Start with detailed stats enabled
+        pool.setCollectDetailedStatistics(true);
+        pool.updateStatsBorrow(pooledObject, Duration.ofMillis(50));
+        pool.updateStatsReturn(Duration.ofMillis(100));
+        assertEquals(50, pool.getMeanBorrowWaitTimeMillis());
+        assertEquals(100, pool.getMeanActiveTimeMillis());
+        // Disable detailed stats
+        pool.setCollectDetailedStatistics(false);
+        pool.updateStatsBorrow(pooledObject, Duration.ofMillis(200));
+        pool.updateStatsReturn(Duration.ofMillis(300));
+        // Detailed stats should remain at previous values
+        assertEquals(50, pool.getMeanBorrowWaitTimeMillis());
+        assertEquals(100, pool.getMeanActiveTimeMillis());
+        // Basic counters should continue to increment
+        assertEquals(2, pool.getBorrowedCount());
+        assertEquals(2, pool.getReturnedCount());
+    }
+
+    @Test
+    void testStatsStoreConcurrentAccess() throws Exception {
+        // Test the lock-free StatsStore implementation under concurrent load
+        final int numThreads = 10;
+        final int operationsPerThread = 1000;
+        final ExecutorService executor = 
Executors.newFixedThreadPool(numThreads);
+        final CountDownLatch startLatch = new CountDownLatch(1);
+        final CountDownLatch completeLatch = new CountDownLatch(numThreads);
+        final List<Future<Void>> futures = new ArrayList<>();
+        // Create threads that will concurrently update statistics
+        for (int i = 0; i < numThreads; i++) {
+            final int threadId = i;
+            futures.add(executor.submit(() -> {
+                try {
+                    final DefaultPooledObject<String> pooledObject = 
(DefaultPooledObject<String>) factory.makeObject();
+                    // Wait for all threads to be ready
+                    startLatch.await();
+                    // Perform concurrent operations
+                    for (int j = 0; j < operationsPerThread; j++) {
+                        pool.updateStatsBorrow(pooledObject, 
Duration.ofMillis(threadId * 10 + j));
+                        pool.updateStatsReturn(Duration.ofMillis(threadId * 20 
+ j));
+                    }
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                } finally {
+                    completeLatch.countDown();
+                }
+                return null;
+            }));
+        }
+        // Start all threads simultaneously
+        startLatch.countDown();
+        // Wait for completion
+        assertTrue(completeLatch.await(30, TimeUnit.SECONDS), "Concurrent test 
should complete within 30 seconds");
+        // Verify no exceptions occurred
+        for (Future<Void> future : futures) {
+            future.get(); // Will throw if there was an exception
+        }
+        // Verify that statistics were collected (exact values may vary due to 
race conditions)
+        assertEquals(numThreads * operationsPerThread, 
pool.getBorrowedCount());
+        assertEquals(numThreads * operationsPerThread, 
pool.getReturnedCount());
+        // Mean values should be reasonable (not zero or wildly incorrect)
+        assertTrue(pool.getMeanActiveTimeMillis() >= 0);
+        assertTrue(pool.getMeanBorrowWaitTimeMillis() >= 0);
+        assertTrue(pool.getMaxBorrowWaitTimeMillis() >= 0);
+        executor.shutdown();
+        assertTrue(executor.awaitTermination(5, TimeUnit.SECONDS));
+    }
+
+    @Test
+    void testStatsStoreCircularBuffer() throws Exception {
+        // Test that StatsStore properly handles circular buffer behavior
+        final DefaultPooledObject<String> pooledObject = 
(DefaultPooledObject<String>) factory.makeObject();
+        // Fill beyond the cache size (100) to test circular behavior
+        final int cacheSize = 100; // 
BaseGenericObjectPool.MEAN_TIMING_STATS_CACHE_SIZE
+        for (int i = 0; i < cacheSize + 50; i++) {
+            pool.updateStatsBorrow(pooledObject, Duration.ofMillis(i));
+            pool.updateStatsReturn(Duration.ofMillis(i * 2));
+        }
+        // Statistics should still be meaningful after circular buffer wrapping
+        assertTrue(pool.getMeanActiveTimeMillis() > 0);
+        assertTrue(pool.getMeanBorrowWaitTimeMillis() > 0);
+        assertTrue(pool.getMaxBorrowWaitTimeMillis() > 0);
+        // The mean should reflect recent values, not all historical values
+        // (exact assertion depends on circular buffer implementation)
+        assertTrue(pool.getMeanBorrowWaitTimeMillis() >= 50); // Should be 
influenced by recent higher values
+    }
+
+    @Test
+    void testDetailedStatisticsConfigIntegration() {
+        // Test that config property is properly applied during pool 
construction
+        final GenericObjectPoolConfig<String> config = new 
GenericObjectPoolConfig<>();
+        config.setCollectDetailedStatistics(false);
+        try (GenericObjectPool<String, TestException> testPool = new 
GenericObjectPool<>(factory, config)) {
+            assertFalse(testPool.getCollectDetailedStatistics(), "Pool should 
respect collectDetailedStatistics setting from config");
+            // Test that toString includes the new property
+            final String configString = config.toString();
+            assertTrue(configString.contains("collectDetailedStatistics"), 
"Config toString should include collectDetailedStatistics property");
+        }
+    }
 }

Reply via email to