Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #12706 - Export ArrayByteBufferPool statistics via JMX. #12709

Open
wants to merge 3 commits into
base: jetty-12.0.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ public class HttpClient extends ContainerLifeCycle implements AutoCloseable
private long addressResolutionTimeout = 15000;
private boolean strictEventOrdering = false;
private long destinationIdleTimeout;
private String name = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
private String name = "%s@%x".formatted(getClass().getSimpleName(), hashCode());
private HttpCompliance httpCompliance = HttpCompliance.RFC7230;
private String defaultRequestContentType = "application/octet-stream";
private boolean useInputDirectByteBuffers = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.RecordComponent;
import java.nio.ByteBuffer;
import java.time.Instant;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
Expand All @@ -29,6 +32,7 @@
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.LongAdder;
import java.util.function.IntUnaryOperator;
import java.util.function.ToLongFunction;
import java.util.stream.Collectors;

import org.eclipse.jetty.io.internal.CompoundPool;
Expand Down Expand Up @@ -333,7 +337,7 @@ private void checkMaxMemory(RetainedBucket bucket, boolean direct)
return;
try
{
long memory = getMemory(direct);
long memory = getTotalMemory(direct);
long excess = memory - max;
if (excess > 0)
{
Expand Down Expand Up @@ -418,34 +422,81 @@ private long getAvailableByteBufferCount(boolean direct)
return Arrays.stream(buckets).mapToLong(bucket -> bucket.getPool().getIdleCount()).sum();
}

@ManagedAttribute("The bytes retained by direct ByteBuffers")
@ManagedAttribute("The total bytes retained by direct ByteBuffers")
public long getDirectMemory()
{
return getMemory(true);
return getTotalMemory(true);
}

@ManagedAttribute("The bytes retained by heap ByteBuffers")
@ManagedAttribute("The total bytes retained by heap ByteBuffers")
public long getHeapMemory()
{
return getMemory(false);
return getTotalMemory(false);
}

private long getMemory(boolean direct)
private long getTotalMemory(boolean direct)
{
return getMemory(direct, bucket -> bucket.getPool().size());
}

private long getMemory(boolean direct, ToLongFunction<RetainedBucket> count)
{
long size = 0;
for (RetainedBucket bucket : direct ? _direct : _indirect)
size += (long)bucket.getPool().getIdleCount() * bucket.getCapacity();
size += count.applyAsLong(bucket) * bucket.getCapacity();
return size;
}

@ManagedAttribute("The available bytes retained by direct ByteBuffers")
public long getAvailableDirectMemory()
{
return getDirectMemory();
return getAvailableMemory(true);
}

@ManagedAttribute("The available bytes retained by heap ByteBuffers")
public long getAvailableHeapMemory()
{
return getHeapMemory();
return getAvailableMemory(false);
}

private long getAvailableMemory(boolean direct)
{
return getMemory(direct, bucket -> bucket.getPool().getIdleCount());
}

@ManagedAttribute("The heap buckets statistics")
public List<Map<String, Object>> getHeapBucketsStatistics()
{
return getBucketsStatistics(false);
}

@ManagedAttribute("The direct buckets statistics")
public List<Map<String, Object>> getDirectBucketsStatistics()
{
return getBucketsStatistics(true);
}

private List<Map<String, Object>> getBucketsStatistics(boolean direct)
{
RetainedBucket[] buckets = direct ? _direct : _indirect;
return Arrays.stream(buckets).map(b -> b.getStatistics().toMap()).toList();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we filter out buckets that don't have allocations at all?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That looks like something better left to the caller of this method as we do want to report the buckets that did no allocation.

}

@ManagedAttribute("The acquires for direct non-pooled bucket capacities")
public Map<Integer, Long> getNoBucketDirectAcquires()
{
return getNoBucketAcquires(true);
}

@ManagedAttribute("The acquires for heap non-pooled bucket capacities")
public Map<Integer, Long> getNoBucketHeapAcquires()
{
return getNoBucketAcquires(false);
}

private Map<Integer, Long> getNoBucketAcquires(boolean direct)
{
return new HashMap<>(direct ? _noBucketDirectAcquires : _noBucketIndirectAcquires);
}

@ManagedOperation(value = "Clears this ByteBufferPool", impact = "ACTION")
Expand Down Expand Up @@ -475,7 +526,7 @@ public void dump(Appendable out, String indent) throws IOException
DumpableCollection.fromArray("direct", _direct),
new DumpableMap("direct non-pooled acquisitions", _noBucketDirectAcquires),
DumpableCollection.fromArray("indirect", _indirect),
new DumpableMap("indirect non-pooled acquisitions", _noBucketIndirectAcquires)
new DumpableMap("heap non-pooled acquisitions", _noBucketIndirectAcquires)
);
}

Expand Down Expand Up @@ -576,6 +627,15 @@ private int evict()
return getCapacity();
}

private Statistics getStatistics()
{
long pooled = _pooled.longValue();
long acquires = _acquires.longValue();
float hitRatio = acquires == 0 ? Float.NaN : pooled * 100F / acquires;
return new Statistics(getCapacity(), getPool().getInUseCount(), getPool().size(), pooled, acquires,
_releases.longValue(), hitRatio, _nonPooled.longValue(), _evicts.longValue(), _removes.longValue());
}

public void clear()
{
_acquires.reset();
Expand All @@ -590,31 +650,45 @@ public void clear()
@Override
public String toString()
{
int entries = 0;
int inUse = 0;
for (Pool.Entry<RetainableByteBuffer> entry : getPool().stream().toList())
return String.format("%s[%s]", super.toString(), getStatistics());
}

private record Statistics(int capacity, int inUseEntries, int totalEntries, long pooled, long acquires,
long releases, float hitRatio, long nonPooled, long evicts, long removes)
{
private Map<String, Object> toMap()
{
entries++;
if (entry.isInUse())
inUse++;
try
{
Map<String, Object> statistics = new HashMap<>();
for (RecordComponent c : getClass().getRecordComponents())
{
statistics.put(c.getName(), c.getAccessor().invoke(this));
}
return statistics;
}
catch (Throwable x)
{
return Map.of();
}
}

long pooled = _pooled.longValue();
long acquires = _acquires.longValue();
float hitRatio = acquires == 0 ? Float.NaN : pooled * 100F / acquires;
return String.format("%s{capacity=%d,in-use=%d/%d,pooled/acquires=%d/%d(%.3f%%),non-pooled/evicts/removes/releases=%d/%d/%d/%d}",
super.toString(),
getCapacity(),
inUse,
entries,
pooled,
acquires,
hitRatio,
_nonPooled.longValue(),
_evicts.longValue(),
_removes.longValue(),
_releases.longValue()
);
@Override
public String toString()
{
return "capacity=%d,in-use=%d/%d,pooled/acquires/releases=%d/%d/%d(%.3f%%),non-pooled/evicts/removes=%d/%d/%d".formatted(
capacity,
inUseEntries,
totalEntries,
pooled,
acquires,
releases,
hitRatio,
nonPooled,
evicts,
removes
);
}
}

private static class BucketCompoundPool extends CompoundPool<RetainableByteBuffer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.eclipse.jetty.io.internal.CompoundPool;
import org.eclipse.jetty.util.ConcurrentPool;
Expand All @@ -33,6 +34,7 @@
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.sameInstance;
import static org.hamcrest.core.Is.is;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
Expand Down Expand Up @@ -121,6 +123,7 @@ public void testBelowMinCapacityDoesNotPool()
public void testOverMaxCapacityDoesNotPool()
{
ArrayByteBufferPool pool = new ArrayByteBufferPool(10, 10, 20, Integer.MAX_VALUE);
pool.setStatisticsEnabled(true);

RetainableByteBuffer buf1 = pool.acquire(21, true);
assertThat(buf1.capacity(), is(21));
Expand All @@ -130,6 +133,10 @@ public void testOverMaxCapacityDoesNotPool()
buf1.release();
assertThat(pool.getDirectByteBufferCount(), is(0L));
assertThat(pool.getDirectMemory(), is(0L));

Map<Integer, Long> noBucketDirectAcquires = pool.getNoBucketDirectAcquires();
assertFalse(noBucketDirectAcquires.isEmpty());
assertEquals(1L, noBucketDirectAcquires.get(30));
}

@Test
Expand Down Expand Up @@ -165,6 +172,15 @@ public void testRetain()
assertThat(pool.getAvailableDirectMemory(), is(10L));
assertThat(pool.getAvailableDirectByteBufferCount(), is(1L));
assertThat(pool.getDirectByteBufferCount(), is(1L));

buf1 = pool.acquire(10, true);

assertThat(pool.getDirectMemory(), is(10L));
assertThat(pool.getAvailableDirectMemory(), is(0L));
assertThat(pool.getDirectByteBufferCount(), is(1L));
assertThat(pool.getAvailableDirectByteBufferCount(), is(0L));

buf1.release();
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ public Server getManagedObject()
return (Server)super.getManagedObject();
}

@Override
public String getObjectContextBasis()
{
Server server = getManagedObject();
return "%s@%x".formatted(server.getClass().getSimpleName(), server.hashCode());
}

@ManagedAttribute("The contexts on this server")
public List<ContextHandler> getContexts()
{
Expand Down
Loading