Skip to content

Commit a272370

Browse files
committed
Fixes #12706 - Export ArrayByteBufferPool statistics via JMX.
* Exposed bucket statistics as a list of maps. * Exposed non-bucket statistic map. * Fixed get[Direct|Heap]Memory() and exposed getAvailable[Direct|Heap]Memory(). Signed-off-by: Simone Bordet <[email protected]>
1 parent 73e6058 commit a272370

File tree

2 files changed

+124
-32
lines changed

2 files changed

+124
-32
lines changed

jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ArrayByteBufferPool.java

+108-32
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,13 @@
1616
import java.io.IOException;
1717
import java.io.PrintWriter;
1818
import java.io.StringWriter;
19+
import java.lang.reflect.RecordComponent;
1920
import java.nio.ByteBuffer;
2021
import java.time.Instant;
2122
import java.util.Arrays;
23+
import java.util.HashMap;
2224
import java.util.List;
25+
import java.util.Map;
2326
import java.util.Objects;
2427
import java.util.Set;
2528
import java.util.concurrent.ConcurrentHashMap;
@@ -29,6 +32,7 @@
2932
import java.util.concurrent.atomic.AtomicBoolean;
3033
import java.util.concurrent.atomic.LongAdder;
3134
import java.util.function.IntUnaryOperator;
35+
import java.util.function.ToLongFunction;
3236
import java.util.stream.Collectors;
3337

3438
import org.eclipse.jetty.io.internal.CompoundPool;
@@ -333,7 +337,7 @@ private void checkMaxMemory(RetainedBucket bucket, boolean direct)
333337
return;
334338
try
335339
{
336-
long memory = getMemory(direct);
340+
long memory = getTotalMemory(direct);
337341
long excess = memory - max;
338342
if (excess > 0)
339343
{
@@ -418,34 +422,81 @@ private long getAvailableByteBufferCount(boolean direct)
418422
return Arrays.stream(buckets).mapToLong(bucket -> bucket.getPool().getIdleCount()).sum();
419423
}
420424

421-
@ManagedAttribute("The bytes retained by direct ByteBuffers")
425+
@ManagedAttribute("The total bytes retained by direct ByteBuffers")
422426
public long getDirectMemory()
423427
{
424-
return getMemory(true);
428+
return getTotalMemory(true);
425429
}
426430

427-
@ManagedAttribute("The bytes retained by heap ByteBuffers")
431+
@ManagedAttribute("The total bytes retained by heap ByteBuffers")
428432
public long getHeapMemory()
429433
{
430-
return getMemory(false);
434+
return getTotalMemory(false);
431435
}
432436

433-
private long getMemory(boolean direct)
437+
private long getTotalMemory(boolean direct)
438+
{
439+
return getMemory(direct, bucket -> bucket.getPool().size());
440+
}
441+
442+
private long getMemory(boolean direct, ToLongFunction<RetainedBucket> count)
434443
{
435444
long size = 0;
436445
for (RetainedBucket bucket : direct ? _direct : _indirect)
437-
size += (long)bucket.getPool().getIdleCount() * bucket.getCapacity();
446+
size += count.applyAsLong(bucket) * bucket.getCapacity();
438447
return size;
439448
}
440449

450+
@ManagedAttribute("The available bytes retained by direct ByteBuffers")
441451
public long getAvailableDirectMemory()
442452
{
443-
return getDirectMemory();
453+
return getAvailableMemory(true);
444454
}
445455

456+
@ManagedAttribute("The available bytes retained by heap ByteBuffers")
446457
public long getAvailableHeapMemory()
447458
{
448-
return getHeapMemory();
459+
return getAvailableMemory(false);
460+
}
461+
462+
private long getAvailableMemory(boolean direct)
463+
{
464+
return getMemory(direct, bucket -> bucket.getPool().getIdleCount());
465+
}
466+
467+
@ManagedAttribute("The heap buckets statistics")
468+
public List<Map<String, Object>> getHeapBucketsStatistics()
469+
{
470+
return getBucketsStatistics(false);
471+
}
472+
473+
@ManagedAttribute("The direct buckets statistics")
474+
public List<Map<String, Object>> getDirectBucketsStatistics()
475+
{
476+
return getBucketsStatistics(true);
477+
}
478+
479+
private List<Map<String, Object>> getBucketsStatistics(boolean direct)
480+
{
481+
RetainedBucket[] buckets = direct ? _direct : _indirect;
482+
return Arrays.stream(buckets).map(b -> b.getStatistics().toMap()).toList();
483+
}
484+
485+
@ManagedAttribute("The acquires for direct non-pooled bucket capacities")
486+
public Map<Integer, Long> getNoBucketDirectAcquires()
487+
{
488+
return getNoBucketAcquires(true);
489+
}
490+
491+
@ManagedAttribute("The acquires for heap non-pooled bucket capacities")
492+
public Map<Integer, Long> getNoBucketHeapAcquires()
493+
{
494+
return getNoBucketAcquires(false);
495+
}
496+
497+
private Map<Integer, Long> getNoBucketAcquires(boolean direct)
498+
{
499+
return new HashMap<>(direct ? _noBucketDirectAcquires : _noBucketIndirectAcquires);
449500
}
450501

451502
@ManagedOperation(value = "Clears this ByteBufferPool", impact = "ACTION")
@@ -475,7 +526,7 @@ public void dump(Appendable out, String indent) throws IOException
475526
DumpableCollection.fromArray("direct", _direct),
476527
new DumpableMap("direct non-pooled acquisitions", _noBucketDirectAcquires),
477528
DumpableCollection.fromArray("indirect", _indirect),
478-
new DumpableMap("indirect non-pooled acquisitions", _noBucketIndirectAcquires)
529+
new DumpableMap("heap non-pooled acquisitions", _noBucketIndirectAcquires)
479530
);
480531
}
481532

@@ -576,6 +627,17 @@ private int evict()
576627
return getCapacity();
577628
}
578629

630+
private Statistics getStatistics()
631+
{
632+
List<Pool.Entry<RetainableByteBuffer>> entries = getPool().stream().toList();
633+
int inUse = (int)entries.stream().filter(Pool.Entry::isInUse).count();
634+
long pooled = _pooled.longValue();
635+
long acquires = _acquires.longValue();
636+
float hitRatio = acquires == 0 ? Float.NaN : pooled * 100F / acquires;
637+
return new Statistics(getCapacity(), inUse, entries.size(), pooled, acquires, _releases.longValue(),
638+
hitRatio, _nonPooled.longValue(), _evicts.longValue(), _removes.longValue());
639+
}
640+
579641
public void clear()
580642
{
581643
_acquires.reset();
@@ -590,31 +652,45 @@ public void clear()
590652
@Override
591653
public String toString()
592654
{
593-
int entries = 0;
594-
int inUse = 0;
595-
for (Pool.Entry<RetainableByteBuffer> entry : getPool().stream().toList())
655+
return String.format("%s[%s]", super.toString(), getStatistics());
656+
}
657+
658+
private record Statistics(int capacity, int inUseEntries, int totalEntries, long pooled, long acquires,
659+
long releases, float hitRatio, long nonPooled, long evicts, long removes)
660+
{
661+
private Map<String, Object> toMap()
596662
{
597-
entries++;
598-
if (entry.isInUse())
599-
inUse++;
663+
try
664+
{
665+
Map<String, Object> statistics = new HashMap<>();
666+
for (RecordComponent c : getClass().getRecordComponents())
667+
{
668+
statistics.put(c.getName(), c.getAccessor().invoke(this));
669+
}
670+
return statistics;
671+
}
672+
catch (Throwable x)
673+
{
674+
return Map.of();
675+
}
600676
}
601677

602-
long pooled = _pooled.longValue();
603-
long acquires = _acquires.longValue();
604-
float hitRatio = acquires == 0 ? Float.NaN : pooled * 100F / acquires;
605-
return String.format("%s{capacity=%d,in-use=%d/%d,pooled/acquires=%d/%d(%.3f%%),non-pooled/evicts/removes/releases=%d/%d/%d/%d}",
606-
super.toString(),
607-
getCapacity(),
608-
inUse,
609-
entries,
610-
pooled,
611-
acquires,
612-
hitRatio,
613-
_nonPooled.longValue(),
614-
_evicts.longValue(),
615-
_removes.longValue(),
616-
_releases.longValue()
617-
);
678+
@Override
679+
public String toString()
680+
{
681+
return "capacity=%d,in-use=%d/%d,pooled/acquires/releases=%d/%d/%d(%.3f%%),non-pooled/evicts/removes=%d/%d/%d".formatted(
682+
capacity,
683+
inUseEntries,
684+
totalEntries,
685+
pooled,
686+
acquires,
687+
releases,
688+
hitRatio,
689+
nonPooled,
690+
evicts,
691+
removes
692+
);
693+
}
618694
}
619695

620696
private static class BucketCompoundPool extends CompoundPool<RetainableByteBuffer>

jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/ArrayByteBufferPoolTest.java

+16
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import java.util.ArrayList;
1818
import java.util.Collections;
1919
import java.util.List;
20+
import java.util.Map;
2021

2122
import org.eclipse.jetty.io.internal.CompoundPool;
2223
import org.eclipse.jetty.util.ConcurrentPool;
@@ -33,6 +34,7 @@
3334
import static org.hamcrest.Matchers.notNullValue;
3435
import static org.hamcrest.Matchers.sameInstance;
3536
import static org.hamcrest.core.Is.is;
37+
import static org.junit.jupiter.api.Assertions.assertEquals;
3638
import static org.junit.jupiter.api.Assertions.assertFalse;
3739
import static org.junit.jupiter.api.Assertions.assertThrows;
3840
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -121,6 +123,7 @@ public void testBelowMinCapacityDoesNotPool()
121123
public void testOverMaxCapacityDoesNotPool()
122124
{
123125
ArrayByteBufferPool pool = new ArrayByteBufferPool(10, 10, 20, Integer.MAX_VALUE);
126+
pool.setStatisticsEnabled(true);
124127

125128
RetainableByteBuffer buf1 = pool.acquire(21, true);
126129
assertThat(buf1.capacity(), is(21));
@@ -130,6 +133,10 @@ public void testOverMaxCapacityDoesNotPool()
130133
buf1.release();
131134
assertThat(pool.getDirectByteBufferCount(), is(0L));
132135
assertThat(pool.getDirectMemory(), is(0L));
136+
137+
Map<Integer, Long> noBucketDirectAcquires = pool.getNoBucketDirectAcquires();
138+
assertFalse(noBucketDirectAcquires.isEmpty());
139+
assertEquals(1L, noBucketDirectAcquires.get(30));
133140
}
134141

135142
@Test
@@ -165,6 +172,15 @@ public void testRetain()
165172
assertThat(pool.getAvailableDirectMemory(), is(10L));
166173
assertThat(pool.getAvailableDirectByteBufferCount(), is(1L));
167174
assertThat(pool.getDirectByteBufferCount(), is(1L));
175+
176+
buf1 = pool.acquire(10, true);
177+
178+
assertThat(pool.getDirectMemory(), is(10L));
179+
assertThat(pool.getAvailableDirectMemory(), is(0L));
180+
assertThat(pool.getDirectByteBufferCount(), is(1L));
181+
assertThat(pool.getAvailableDirectByteBufferCount(), is(0L));
182+
183+
buf1.release();
168184
}
169185

170186
@Test

0 commit comments

Comments
 (0)