Skip to content

Commit a57b7db

Browse files
authored
Fixes #12706 - Export ArrayByteBufferPool statistics via JMX. (#12709)
* Exposed bucket statistics as a list of maps. * Exposed non-bucket statistic map. * Fixed get[Direct|Heap]Memory() and exposed getAvailable[Direct|Heap]Memory(). * Updated JMX context attribute for Server in ServerMBean. This allow to distinguish ByteBufferPools, Schedulers, etc. among multiple servers and from HttpClient or other components. Signed-off-by: Simone Bordet <[email protected]>
1 parent 5c6c8e4 commit a57b7db

File tree

4 files changed

+130
-33
lines changed

4 files changed

+130
-33
lines changed

jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ public class HttpClient extends ContainerLifeCycle implements AutoCloseable
130130
private long addressResolutionTimeout = 15000;
131131
private boolean strictEventOrdering = false;
132132
private long destinationIdleTimeout;
133-
private String name = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
133+
private String name = "%s@%x".formatted(getClass().getSimpleName(), hashCode());
134134
private HttpCompliance httpCompliance = HttpCompliance.RFC7230;
135135
private String defaultRequestContentType = "application/octet-stream";
136136
private boolean useInputDirectByteBuffers = true;

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

+106-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,15 @@ private int evict()
576627
return getCapacity();
577628
}
578629

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

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-
);
676+
@Override
677+
public String toString()
678+
{
679+
return "capacity=%d,in-use=%d/%d,pooled/acquires/releases=%d/%d/%d(%.3f%%),non-pooled/evicts/removes=%d/%d/%d".formatted(
680+
capacity,
681+
inUseEntries,
682+
totalEntries,
683+
pooled,
684+
acquires,
685+
releases,
686+
hitRatio,
687+
nonPooled,
688+
evicts,
689+
removes
690+
);
691+
}
618692
}
619693

620694
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

jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/jmx/ServerMBean.java

+7
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ public Server getManagedObject()
3939
return (Server)super.getManagedObject();
4040
}
4141

42+
@Override
43+
public String getObjectContextBasis()
44+
{
45+
Server server = getManagedObject();
46+
return "%s@%x".formatted(server.getClass().getSimpleName(), server.hashCode());
47+
}
48+
4249
@ManagedAttribute("The contexts on this server")
4350
public List<ContextHandler> getContexts()
4451
{

0 commit comments

Comments
 (0)