diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ArrayByteBufferPool.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ArrayByteBufferPool.java index eff48e666838..eab35fcd45c8 100644 --- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ArrayByteBufferPool.java +++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ArrayByteBufferPool.java @@ -23,6 +23,7 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicBoolean; @@ -40,6 +41,7 @@ import org.eclipse.jetty.util.annotation.ManagedOperation; import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.component.DumpableCollection; +import org.eclipse.jetty.util.component.DumpableMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -65,7 +67,10 @@ public class ArrayByteBufferPool implements ByteBufferPool, Dumpable private final long _maxHeapMemory; private final long _maxDirectMemory; private final IntUnaryOperator _bucketIndexFor; + private final IntUnaryOperator _bucketCapacity; private final AtomicBoolean _evictor = new AtomicBoolean(false); + private final ConcurrentMap _noBucketDirectAcquires = new ConcurrentHashMap<>(); + private final ConcurrentMap _noBucketIndirectAcquires = new ConcurrentHashMap<>(); private boolean _statisticsEnabled; /** @@ -164,6 +169,7 @@ protected ArrayByteBufferPool(int minCapacity, int factor, int maxCapacity, int _maxHeapMemory = maxMemory(maxHeapMemory); _maxDirectMemory = maxMemory(maxDirectMemory); _bucketIndexFor = bucketIndexFor; + _bucketCapacity = bucketCapacity; } private long maxMemory(long maxMemory) @@ -205,7 +211,10 @@ public RetainableByteBuffer acquire(int size, boolean direct) // No bucket, return non-pooled. if (bucket == null) + { + recordNoBucketAcquire(size, direct); return RetainableByteBuffer.wrap(BufferUtil.allocate(size, direct)); + } bucket.recordAcquire(); @@ -223,6 +232,22 @@ public RetainableByteBuffer acquire(int size, boolean direct) return buffer; } + private void recordNoBucketAcquire(int size, boolean direct) + { + if (isStatisticsEnabled()) + { + ConcurrentMap map = direct ? _noBucketDirectAcquires : _noBucketIndirectAcquires; + int idx = _bucketIndexFor.applyAsInt(size); + int key = _bucketCapacity.applyAsInt(idx); + map.compute(key, (k, v) -> + { + if (v == null) + return 1L; + return v + 1L; + }); + } + } + @Override public boolean removeAndRelease(RetainableByteBuffer buffer) { @@ -427,7 +452,9 @@ public long getAvailableHeapMemory() public void clear() { clearBuckets(_direct); + _noBucketDirectAcquires.clear(); clearBuckets(_indirect); + _noBucketIndirectAcquires.clear(); } private void clearBuckets(RetainedBucket[] buckets) @@ -446,7 +473,10 @@ public void dump(Appendable out, String indent) throws IOException indent, this, DumpableCollection.fromArray("direct", _direct), - DumpableCollection.fromArray("indirect", _indirect)); + new DumpableMap("direct non-pooled acquisitions", _noBucketDirectAcquires), + DumpableCollection.fromArray("indirect", _indirect), + new DumpableMap("indirect non-pooled acquisitions", _noBucketIndirectAcquires) + ); } @Override diff --git a/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/ArrayByteBufferPoolTest.java b/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/ArrayByteBufferPoolTest.java index bb92c805080d..8a1d370368ec 100644 --- a/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/ArrayByteBufferPoolTest.java +++ b/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/ArrayByteBufferPoolTest.java @@ -25,6 +25,7 @@ import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.lessThan; @@ -38,6 +39,30 @@ public class ArrayByteBufferPoolTest { + @Test + public void testDump() + { + ArrayByteBufferPool pool = new ArrayByteBufferPool(0, 10, 100, Integer.MAX_VALUE, 200, 200); + pool.setStatisticsEnabled(true); + + List buffers = new ArrayList<>(); + + for (int i = 1; i < 151; i++) + buffers.add(pool.acquire(i, true)); + + buffers.forEach(RetainableByteBuffer::release); + + String dump = pool.dump(); + assertThat(dump, containsString("direct non-pooled acquisitions size=5\n")); + assertThat(dump, containsString("110: 10\n")); + assertThat(dump, containsString("120: 10\n")); + assertThat(dump, containsString("130: 10\n")); + assertThat(dump, containsString("140: 10\n")); + assertThat(dump, containsString("150: 10\n")); + pool.clear(); + assertThat(pool.dump(), containsString("direct non-pooled acquisitions size=0\n")); + } + @Test public void testMaxMemoryEviction() {