001/*
002 * JGrapes Event Driven Framework
003 * Copyright (C) 2016-2018 Michael N. Lipp
004 * 
005 * This program is free software; you can redistribute it and/or modify it 
006 * under the terms of the GNU Affero General Public License as published by 
007 * the Free Software Foundation; either version 3 of the License, or 
008 * (at your option) any later version.
009 * 
010 * This program is distributed in the hope that it will be useful, but 
011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License 
013 * for more details.
014 * 
015 * You should have received a copy of the GNU Affero General Public License along 
016 * with this program; if not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jgrapes.io.util;
020
021import java.beans.ConstructorProperties;
022import java.lang.management.ManagementFactory;
023import java.lang.ref.ReferenceQueue;
024import java.lang.ref.WeakReference;
025import java.nio.Buffer;
026import java.time.Duration;
027import java.util.Collections;
028import java.util.HashMap;
029import java.util.IntSummaryStatistics;
030import java.util.Map;
031import java.util.Optional;
032import java.util.Set;
033import java.util.SortedMap;
034import java.util.TreeMap;
035import java.util.WeakHashMap;
036import java.util.concurrent.ArrayBlockingQueue;
037import java.util.concurrent.BlockingQueue;
038import java.util.concurrent.TimeUnit;
039import java.util.concurrent.atomic.AtomicInteger;
040import java.util.concurrent.atomic.AtomicReference;
041import java.util.function.BiFunction;
042import java.util.function.Supplier;
043import java.util.logging.Level;
044import java.util.logging.Logger;
045import java.util.stream.Collectors;
046
047import javax.management.InstanceAlreadyExistsException;
048import javax.management.MBeanRegistrationException;
049import javax.management.MBeanServer;
050import javax.management.MalformedObjectNameException;
051import javax.management.NotCompliantMBeanException;
052import javax.management.ObjectName;
053
054import org.jgrapes.core.Components;
055import org.jgrapes.core.Components.Timer;
056import org.jgrapes.io.IOSubchannel;
057import org.jgrapes.io.events.Output;
058
059/**
060 * A queue based buffer pool. Using buffers from a pool is an important
061 * feature for limiting the computational resources for an {@link IOSubchannel}.
062 * A producer of {@link Output} events that simply creates its own buffers
063 * may produce and enqueue a large number of events that are not consumed
064 * as fast as they are produced. 
065 * 
066 * Using a buffer pool with a typical size of two synchronizes the 
067 * producer and the consumers of events nicely. The producer
068 * (thread) holds one buffer and fills it, the consumer (thread) holds 
069 * the other buffer and works with its content. If the producer finishes
070 * before the consumer, it has to stop until the consumer has processed 
071 * previous event and releases the buffer. The consumer can continue
072 * without delay, because the data has already been prepared and enqueued
073 * as the next event.
074 * 
075 * One of the biggest problems when using a pool can be to identify 
076 * leaking buffers, i.e. buffers that are not properly returned to the pool.
077 * This implementation therefore tracks all created buffers 
078 * (with a small overhead) and logs a warning if a buffer is no longer
079 * used (referenced) but has not been returned to the pool. If the
080 * log level for {@link ManagedBufferPool} is set to {@link Level#FINE},
081 * the warning also includes a stack trace of the call to {@link #acquire()}
082 * that handed out the buffer. Providing this information in addition 
083 * obviously requires a larger overhead and is therefore limited to the
084 * finer log levels.
085 *
086 * @param <W> the type of the wrapped (managed) buffer
087 * @param <T> the type of the content buffer that is wrapped
088 */
089@SuppressWarnings({ "PMD.ExcessiveImports", "PMD.NcssCount",
090    "PMD.EmptyCatchBlock" })
091public class ManagedBufferPool<W extends ManagedBuffer<T>, T extends Buffer>
092        implements BufferCollector<W> {
093
094    @SuppressWarnings("PMD.FieldNamingConventions")
095    protected static final Logger logger
096        = Logger.getLogger(ManagedBufferPool.class.getName());
097
098    private static long defaultDrainDelay = 1500;
099    private static long acquireWarningLimit = 1000;
100
101    private String name = Components.objectName(this);
102    private BiFunction<T, BufferCollector<W>, W> wrapper;
103    private Supplier<T> bufferFactory;
104    private BufferMonitor bufferMonitor;
105    private BlockingQueue<W> queue;
106    private int bufferSize = -1;
107    private int preservedBufs;
108    private int maximumBufs;
109    private AtomicInteger createdBufs;
110    private long drainDelay = -1;
111    private final AtomicReference<Timer> idleTimer
112        = new AtomicReference<>(null);
113
114    /**
115     * Sets the default delay after which buffers are removed from
116     * the pool. The default value is 1500ms.
117     * 
118     * @param delay the delay in ms
119     */
120    public static void setDefaultDrainDelay(long delay) {
121        defaultDrainDelay = delay;
122    }
123
124    /**
125     * Returns the default drain delay.
126     * 
127     * @return the delay
128     */
129    public static long defaultDrainDelay() {
130        return defaultDrainDelay;
131    }
132
133    /**
134     * Create a pool that contains a varying number of (wrapped) buffers.
135     * The pool is initially empty. When buffers are requested and none 
136     * are left in the pool, new buffers are created up to the given 
137     * upper limit. Recollected buffers are put in the pool until it holds
138     * the number specified by the lower threshold. Any additional 
139     * recollected buffers are discarded. 
140     * 
141     * @param wrapper the function that converts buffers to managed buffers
142     * @param bufferFactory a function that creates a new buffer
143     * @param lowerThreshold the number of buffers kept in the pool
144     * @param upperLimit the maximum number of buffers
145     */
146    public ManagedBufferPool(BiFunction<T, BufferCollector<W>, W> wrapper,
147            Supplier<T> bufferFactory, int lowerThreshold, int upperLimit) {
148        this.wrapper = wrapper;
149        this.bufferFactory = bufferFactory;
150        preservedBufs = lowerThreshold;
151        maximumBufs = upperLimit;
152        createdBufs = new AtomicInteger();
153        queue = new ArrayBlockingQueue<>(lowerThreshold);
154        bufferMonitor = new BufferMonitor(upperLimit);
155        MBeanView.addPool(this);
156    }
157
158    /**
159     * Create a pool that keeps up to the given number of (wrapped) buffers
160     * in the pool and also uses that number as upper limit.
161     * 
162     * @param wrapper the function that converts buffers to managed buffers
163     * @param bufferFactory a function that creates a new buffer
164     * @param buffers the number of buffers
165     */
166    public ManagedBufferPool(BiFunction<T, BufferCollector<W>, W> wrapper,
167            Supplier<T> bufferFactory, int buffers) {
168        this(wrapper, bufferFactory, buffers, buffers);
169    }
170
171    /**
172     * Sets a name for this pool (to be used in status reports).
173     * 
174     * @param name the name
175     * @return the object for easy chaining
176     */
177    public ManagedBufferPool<W, T> setName(String name) {
178        this.name = name;
179        return this;
180    }
181
182    /**
183     * Returns the name of this pool.
184     * 
185     * @return the name
186     */
187    public String name() {
188        return name;
189    }
190
191    /**
192     * Sets the delay after which buffers are removed from
193     * the pool.
194     * 
195     * @param delay the delay
196     * @return the object for easy chaining
197     */
198    public ManagedBufferPool<W, T> setDrainDelay(long delay) {
199        this.drainDelay = delay;
200        return this;
201    }
202
203    private W createBuffer() {
204        createdBufs.incrementAndGet();
205        W buffer = wrapper.apply(this.bufferFactory.get(), this);
206        bufferMonitor.put(buffer, new BufferProperties());
207        bufferSize = buffer.capacity();
208        return buffer;
209    }
210
211    /**
212     * Removes the buffer from the pool.
213     * 
214     * @param buffer the buffer to remove
215     */
216    private void removeBuffer(W buffer) {
217        createdBufs.decrementAndGet();
218        if (bufferMonitor.remove(buffer) == null) {
219            if (logger.isLoggable(Level.FINE)) {
220                logger.log(Level.WARNING,
221                    "Attempt to remove unknown buffer from pool.",
222                    new Throwable());
223            } else {
224                logger.warning("Attempt to remove unknown buffer from pool.");
225            }
226        }
227    }
228
229    /**
230     * Returns the size of the buffers managed by this pool.
231     * 
232     * @return the buffer size
233     */
234    public int bufferSize() {
235        if (bufferSize < 0) {
236            createBuffer().unlockBuffer();
237        }
238        return bufferSize;
239    }
240
241    /**
242     * Acquires a managed buffer from the pool. If the pool is empty,
243     * waits for a buffer to become available. The acquired buffer has 
244     * a lock count of one.
245     * 
246     * @return the acquired buffer
247     * @throws InterruptedException if the current thread is interrupted
248     */
249    @SuppressWarnings("PMD.GuardLogStatement")
250    public W acquire() throws InterruptedException {
251        // Stop draining, because we obviously need this kind of buffers
252        Optional.ofNullable(idleTimer.getAndSet(null)).ifPresent(
253            timer -> timer.cancel());
254        if (createdBufs.get() < maximumBufs) {
255            // Haven't reached maximum, so if no buffer is queued, create one.
256            W buffer = queue.poll();
257            if (buffer != null) {
258                buffer.lockBuffer();
259                return buffer;
260            }
261            return createBuffer();
262        }
263        // Wait for buffer to become available.
264        if (logger.isLoggable(Level.FINE)) {
265            // If configured, log message after waiting some time.
266            W buffer = queue.poll(acquireWarningLimit, TimeUnit.MILLISECONDS);
267            if (buffer != null) {
268                buffer.lockBuffer();
269                return buffer;
270            }
271            logger.log(Level.FINE,
272                Thread.currentThread().getName() + " waiting > "
273                    + acquireWarningLimit + "ms for buffer, while executing:",
274                new Throwable());
275        }
276        W buffer = queue.take();
277        buffer.lockBuffer();
278        return buffer;
279    }
280
281    /**
282     * Re-adds the buffer to the pool. The buffer is cleared.
283     *
284     * @param buffer the buffer
285     * @see org.jgrapes.io.util.BufferCollector#recollect(org.jgrapes.io.util.ManagedBuffer)
286     */
287    @Override
288    public void recollect(W buffer) {
289        if (queue.size() < preservedBufs) {
290            long effectiveDrainDelay
291                = drainDelay > 0 ? drainDelay : defaultDrainDelay;
292            if (effectiveDrainDelay > 0) {
293                // Enqueue
294                buffer.clear();
295                queue.add(buffer);
296                Timer old = idleTimer.getAndSet(Components.schedule(this::drain,
297                    Duration.ofMillis(effectiveDrainDelay)));
298                if (old != null) {
299                    old.cancel();
300                }
301                return;
302            }
303        }
304        // Discard
305        removeBuffer(buffer);
306    }
307
308    @SuppressWarnings("PMD.UnusedFormalParameter")
309    private void drain(Timer timer) {
310        idleTimer.set(null);
311        while (true) {
312            W buffer = queue.poll();
313            if (buffer == null) {
314                break;
315            }
316            removeBuffer(buffer);
317        }
318    }
319
320    /*
321     * (non-Javadoc)
322     * 
323     * @see java.lang.Object#toString()
324     */
325    @Override
326    public String toString() {
327        StringBuilder builder = new StringBuilder(50);
328        builder.append("ManagedBufferPool [");
329        if (queue != null) {
330            builder.append("queue=");
331            builder.append(queue);
332        }
333        builder.append(']');
334        return builder.toString();
335    }
336
337    /**
338     * Buffer properties.
339     */
340    private static class BufferProperties {
341
342        private StackTraceElement[] createdBy;
343
344        /**
345         * Instantiates new buffer properties.
346         */
347        public BufferProperties() {
348            if (logger.isLoggable(Level.FINE)) {
349                createdBy = Thread.currentThread().getStackTrace();
350            }
351        }
352
353        /**
354         * Returns where the buffer was created.
355         *
356         * @return the stack trace element[]
357         */
358        @SuppressWarnings("PMD.MethodReturnsInternalArray")
359        public StackTraceElement[] createdBy() {
360            return createdBy;
361        }
362    }
363
364    /**
365     * This is basically a WeakHashMap. We cannot use WeakHashMap
366     * because there is no "hook" into the collection of orphaned
367     * references, which is what we want here.
368     */
369    @SuppressWarnings("PMD.DataflowAnomalyAnalysis")
370    private class BufferMonitor {
371
372        private Entry<W>[] data;
373        private int indexMask;
374        private final ReferenceQueue<W> orphanedEntries
375            = new ReferenceQueue<>();
376
377        /**
378         * An Entry.
379         *
380         * @param <B> the generic type
381         */
382        private class Entry<B extends ManagedBuffer<?>> extends WeakReference<B>
383                implements Map.Entry<B, BufferProperties> {
384            /* default */ final int index;
385            /* default */ BufferProperties props;
386            /* default */ Entry<B> next;
387
388            /**
389             * Instantiates a new entry.
390             *
391             * @param buffer the buffer
392             * @param props the props
393             * @param queue the queue
394             * @param index the index
395             * @param next the next
396             */
397            /* default */ Entry(B buffer, BufferProperties props,
398                    ReferenceQueue<B> queue, int index, Entry<B> next) {
399                super(buffer, queue);
400                this.index = index;
401                this.props = props;
402                this.next = next;
403            }
404
405            @Override
406            public B getKey() {
407                return get();
408            }
409
410            @Override
411            public BufferProperties getValue() {
412                return props;
413            }
414
415            @Override
416            public BufferProperties setValue(BufferProperties props) {
417                return this.props = props;
418            }
419        }
420
421        /**
422         * @param data
423         */
424        @SuppressWarnings("unchecked")
425        public BufferMonitor(int maxBuffers) {
426            int lists = 1;
427            while (lists < maxBuffers) {
428                lists <<= 1;
429                indexMask = (indexMask << 1) + 1;
430            }
431            data = new Entry[lists];
432        }
433
434        /**
435         * Put an entry in the map.
436         *
437         * @param buffer the buffer
438         * @param properties the properties
439         * @return the buffer properties
440         */
441        @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
442        public BufferProperties put(W buffer, BufferProperties properties) {
443            check();
444            int index = buffer.hashCode() & indexMask;
445            synchronized (data) {
446                Entry<W> entry = data[index];
447                Entry<W> prev = null;
448                while (true) {
449                    if (entry == null) {
450                        // Not found, create new.
451                        entry = new Entry<>(buffer, properties,
452                            orphanedEntries, index, null);
453                        if (prev == null) {
454                            data[index] = entry; // Is first.
455                        } else {
456                            prev.next = entry; // Is next (last).
457                        }
458                        return properties;
459                    }
460                    if (entry.getKey() == buffer) {
461                        // Found, update.
462                        BufferProperties old = entry.getValue();
463                        entry.setValue(properties);
464                        return old;
465                    }
466                    prev = entry;
467                    entry = entry.next;
468                }
469            }
470        }
471
472        /**
473         * Returns the properties for the given buffer.
474         *
475         * @param buffer the buffer
476         * @return the buffer properties
477         */
478        @SuppressWarnings("unused")
479        public BufferProperties get(ManagedBuffer<?> buffer) {
480            check();
481            int index = buffer.hashCode() & indexMask;
482            synchronized (data) {
483                Entry<W> entry = data[index];
484                while (entry != null) {
485                    if (entry.getKey() == buffer) {
486                        return entry.getValue();
487                    }
488                    entry = entry.next;
489                }
490                return null;
491            }
492        }
493
494        /**
495         * Removes the given buffer.
496         *
497         * @param buffer the buffer
498         * @return the buffer properties
499         */
500        public BufferProperties remove(ManagedBuffer<?> buffer) {
501            check();
502            int index = buffer.hashCode() & indexMask;
503            synchronized (data) {
504                Entry<W> entry = data[index];
505                Entry<W> prev = null;
506                while (entry != null) {
507                    if (entry.getKey() == buffer) {
508                        if (prev == null) {
509                            data[index] = entry.next; // Was first.
510                        } else {
511                            prev.next = entry.next;
512                        }
513                        return entry.getValue();
514                    }
515                    prev = entry;
516                    entry = entry.next;
517                }
518                return null;
519            }
520        }
521
522        @SuppressWarnings("PMD.CompareObjectsWithEquals")
523        private BufferProperties remove(Entry<W> toBeRemoved) {
524            synchronized (data) {
525                Entry<W> entry = data[toBeRemoved.index];
526                Entry<W> prev = null;
527                while (entry != null) {
528                    if (entry == toBeRemoved) {
529                        if (prev == null) {
530                            data[toBeRemoved.index] = entry.next; // Was first.
531                        } else {
532                            prev.next = entry.next;
533                        }
534                        return entry.getValue();
535                    }
536                    prev = entry;
537                    entry = entry.next;
538                }
539                return null;
540            }
541        }
542
543        private void check() {
544            while (true) {
545                @SuppressWarnings("unchecked")
546                Entry<W> entry = (Entry<W>) orphanedEntries.poll();
547                if (entry == null) {
548                    return;
549                }
550                // Managed buffer has not been properly recollected, fix.
551                BufferProperties props = remove(entry);
552                if (props == null) {
553                    return;
554                }
555                createdBufs.decrementAndGet();
556                // Create warning
557                if (logger.isLoggable(Level.WARNING)) {
558                    @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
559                    final StringBuilder msg = new StringBuilder(
560                        "Orphaned buffer from pool ");
561                    msg.append(name());
562                    StackTraceElement[] trace = props.createdBy();
563                    if (trace != null) {
564                        msg.append(", created");
565                        for (StackTraceElement e : trace) {
566                            msg.append(System.lineSeparator());
567                            msg.append("\tat ");
568                            msg.append(e.toString());
569                        }
570                    }
571                    logger.warning(msg.toString());
572                }
573            }
574        }
575    }
576
577    /**
578     * An MBean interface for getting information about the managed
579     * buffer pools. Note that created buffer pools are tracked using
580     * weak references. Therefore, the MBean may report more pools than
581     * are really in use. 
582     */
583    public interface ManagedBufferPoolMXBean {
584
585        /**
586         * Information about a single managed pool.
587         */
588        @SuppressWarnings("PMD.DataClass")
589        class PoolInfo {
590            private final int created;
591            private final int pooled;
592            private final int preserved;
593            private final int maximum;
594            private final int bufferSize;
595
596            /**
597             * Instantiates a new pool info.
598             *
599             * @param created the created
600             * @param pooled the pooled
601             * @param preserved the preserved
602             * @param maximum the maximum
603             * @param bufferSize the buffer size
604             */
605            @ConstructorProperties({ "created", "pooled",
606                "preserved", "maximum", "bufferSize" })
607            public PoolInfo(int created, int pooled,
608                    int preserved, int maximum, int bufferSize) {
609                this.created = created;
610                this.pooled = pooled;
611                this.preserved = preserved;
612                this.maximum = maximum;
613                this.bufferSize = bufferSize;
614            }
615
616            /**
617             * The number of buffers created by this pool.
618             * 
619             * @return the value
620             */
621            public int getCreated() {
622                return created;
623            }
624
625            /**
626             * The number of buffers pooled (ready to be acquired).
627             * 
628             * @return the value
629             */
630            public int getPooled() {
631                return pooled;
632            }
633
634            /**
635             * The number of buffers preserved.
636             * 
637             * @return the value
638             */
639            public int getPreserved() {
640                return preserved;
641            }
642
643            /**
644             * The maximum number of buffers created by this pool.
645             * 
646             * @return the value
647             */
648            public int getMaximum() {
649                return maximum;
650            }
651
652            /**
653             * The size of the buffers in items.
654             * 
655             * @return the buffer size
656             */
657            public int getBufferSize() {
658                return bufferSize;
659            }
660        }
661
662        /**
663         * Three views on the existing pool.
664         */
665        class PoolInfos {
666            private final SortedMap<String, PoolInfo> allPools;
667            private final SortedMap<String, PoolInfo> nonEmptyPools;
668            private final SortedMap<String, PoolInfo> usedPools;
669
670            /**
671             * Instantiates a new pool infos.
672             *
673             * @param pools the pools
674             */
675            public PoolInfos(Set<ManagedBufferPool<?, ?>> pools) {
676                allPools = new TreeMap<>();
677                nonEmptyPools = new TreeMap<>();
678                usedPools = new TreeMap<>();
679
680                @SuppressWarnings("PMD.UseConcurrentHashMap")
681                Map<String, Integer> dupsNext = new HashMap<>();
682                for (ManagedBufferPool<?, ?> mbp : pools) {
683                    String key = mbp.name();
684                    PoolInfo infos = new PoolInfo(
685                        mbp.createdBufs.get(), mbp.queue.size(),
686                        mbp.preservedBufs, mbp.maximumBufs,
687                        mbp.bufferSize());
688                    if (allPools.containsKey(key)
689                        || dupsNext.containsKey(key)) {
690                        if (allPools.containsKey(key)) {
691                            // Found first duplicate, rename
692                            allPools.put(key + "#1", allPools.get(key));
693                            allPools.remove(key);
694                            dupsNext.put(key, 2);
695                        }
696                        allPools.put(key + "#"
697                            + (dupsNext.put(key, dupsNext.get(key) + 1)),
698                            infos);
699                    } else {
700                        allPools.put(key, infos);
701                    }
702                }
703                for (Map.Entry<String, PoolInfo> e : allPools.entrySet()) {
704                    PoolInfo infos = e.getValue();
705                    if (infos.getPooled() > 0) {
706                        nonEmptyPools.put(e.getKey(), infos);
707                    }
708                    if (infos.getCreated() > 0) {
709                        usedPools.put(e.getKey(), infos);
710                    }
711                }
712            }
713
714            /**
715             * All pools.
716             *
717             * @return the all pools
718             */
719            public SortedMap<String, PoolInfo> getAllPools() {
720                return allPools;
721            }
722
723            /**
724             * Pools that have at least managed buffer enqueued
725             * (ready to be acquired).
726             *
727             * @return the non empty pools
728             */
729            public SortedMap<String, PoolInfo> getNonEmptyPools() {
730                return nonEmptyPools;
731            }
732
733            /**
734             * Pools that have at least one associated buffer
735             * (in pool or in use).
736             *
737             * @return the used pools
738             */
739            public SortedMap<String, PoolInfo> getUsedPools() {
740                return usedPools;
741            }
742        }
743
744        /**
745         * Set the default drain delay.
746         * 
747         * @param millis the drain delay in milli seconds
748         */
749        void setDefaultDrainDelay(long millis);
750
751        /**
752         * Returns the drain delay in milli seconds.
753         * 
754         * @return the value
755         */
756        long getDefaultDrainDelay();
757
758        /**
759         * Set the acquire warning limit.
760         * 
761         * @param millis the limit
762         */
763        void setAcquireWarningLimit(long millis);
764
765        /**
766         * Returns the acquire warning limit.
767         * 
768         * @return the value
769         */
770        long getAcquireWarningLimit();
771
772        /**
773         * Informations about the pools.
774         * 
775         * @return the map
776         */
777        PoolInfos getPoolInfos();
778
779        /**
780         * Summary information about the pooled buffers.
781         * 
782         * @return the values
783         */
784        IntSummaryStatistics getPooledPerPoolStatistics();
785
786        /**
787         * Summary information about the created buffers.
788         * 
789         * @return the values
790         */
791        IntSummaryStatistics getCreatedPerPoolStatistics();
792    }
793
794    /**
795     * The MBean view
796     */
797    private static class MBeanView implements ManagedBufferPoolMXBean {
798
799        private static Set<ManagedBufferPool<?, ?>> allPools
800            = Collections.synchronizedSet(
801                Collections.newSetFromMap(
802                    new WeakHashMap<ManagedBufferPool<?, ?>, Boolean>()));
803
804        /**
805         * Adds the pool.
806         *
807         * @param pool the pool
808         */
809        public static void addPool(ManagedBufferPool<?, ?> pool) {
810            allPools.add(pool);
811        }
812
813        @Override
814        public void setDefaultDrainDelay(long millis) {
815            ManagedBufferPool.setDefaultDrainDelay(millis);
816        }
817
818        @Override
819        public long getDefaultDrainDelay() {
820            return ManagedBufferPool.defaultDrainDelay();
821        }
822
823        @Override
824        public void setAcquireWarningLimit(long millis) {
825            ManagedBufferPool.acquireWarningLimit = millis;
826        }
827
828        @Override
829        public long getAcquireWarningLimit() {
830            return ManagedBufferPool.acquireWarningLimit;
831        }
832
833        @Override
834        public PoolInfos getPoolInfos() {
835            return new PoolInfos(allPools);
836        }
837
838        @Override
839        public IntSummaryStatistics getPooledPerPoolStatistics() {
840            return allPools.stream().collect(
841                Collectors.summarizingInt(mbp -> mbp.queue.size()));
842        }
843
844        @Override
845        public IntSummaryStatistics getCreatedPerPoolStatistics() {
846            return allPools.stream().collect(
847                Collectors.summarizingInt(mbp -> mbp.createdBufs.get()));
848        }
849    }
850
851    static {
852        try {
853            MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
854            ObjectName mxbeanName = new ObjectName("org.jgrapes.io:type="
855                + ManagedBufferPool.class.getSimpleName() + "s");
856            mbs.registerMBean(new MBeanView(), mxbeanName);
857        } catch (MalformedObjectNameException | InstanceAlreadyExistsException
858                | MBeanRegistrationException | NotCompliantMBeanException e) {
859            // Does not happen
860        }
861    }
862}