You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by db...@apache.org on 2010/05/06 17:13:47 UTC

svn commit: r941772 - in /openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb: monitoring/ util/

Author: dblevins
Date: Thu May  6 15:13:47 2010
New Revision: 941772

URL: http://svn.apache.org/viewvc?rev=941772&view=rev
Log:
OPENEJB-1272: JMX Monitoring
OPENEJB-1273: JMX: Stateless Pool Stats

Added:
    openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/monitoring/Event.java   (with props)
    openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/monitoring/ManagedMBean.java   (with props)
    openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/monitoring/ScratchPad.java   (with props)
Removed:
    openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/monitoring/MBeanExporter.java
    openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/monitoring/MBeanInfoBuilder.java
    openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/monitoring/ObjectNames.java
Modified:
    openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/monitoring/Managed.java
    openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/util/Pool.java

Added: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/monitoring/Event.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/monitoring/Event.java?rev=941772&view=auto
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/monitoring/Event.java (added)
+++ openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/monitoring/Event.java Thu May  6 15:13:47 2010
@@ -0,0 +1,65 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.openejb.monitoring;
+
+//import org.apache.commons.math.stat.descriptive.SynchronizedDescriptiveStatistics;
+//import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;
+
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.TimeUnit;
+import java.util.Date;
+import java.text.SimpleDateFormat;
+import java.text.DateFormat;
+
+/**
+ * @version $Rev$ $Date$
+*/
+@Managed(append = true)
+public class Event {
+    private final AtomicLong count = new AtomicLong();
+    private final AtomicLong last = new AtomicLong();
+//    private final SynchronizedDescriptiveStatistics frequency = new SynchronizedDescriptiveStatistics(2000);
+
+    public void record() {
+        long start = last.getAndSet(System.nanoTime());
+//        frequency.addValue(millis(start - last.get()));
+        count.incrementAndGet();
+    }
+
+    @Managed
+    public long get() {
+        return count.get();
+    }
+
+    @Managed
+    public String getLatest() {
+        long last = millis(this.last.get());
+
+        if (last <= 0) return "-";
+        
+        DateFormat format = SimpleDateFormat.getDateTimeInstance();
+        return format.format(new Date(last));
+    }
+
+//    public DescriptiveStatistics getFrequency() {
+//        return frequency;
+//    }
+
+    private long millis(long nanos) {
+        return TimeUnit.MILLISECONDS.convert(nanos, TimeUnit.NANOSECONDS);
+    }
+}

Propchange: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/monitoring/Event.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/monitoring/Managed.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/monitoring/Managed.java?rev=941772&r1=941771&r2=941772&view=diff
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/monitoring/Managed.java (original)
+++ openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/monitoring/Managed.java Thu May  6 15:13:47 2010
@@ -22,7 +22,8 @@ import java.lang.annotation.RetentionPol
 import java.lang.annotation.Target;
 
 @Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.METHOD})
+@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
 public @interface Managed {
     String description() default "";
+    boolean append() default false;
 }
\ No newline at end of file

Added: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/monitoring/ManagedMBean.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/monitoring/ManagedMBean.java?rev=941772&view=auto
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/monitoring/ManagedMBean.java (added)
+++ openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/monitoring/ManagedMBean.java Thu May  6 15:13:47 2010
@@ -0,0 +1,250 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.openejb.monitoring;
+
+import org.apache.xbean.finder.ClassFinder;
+
+import javax.management.DynamicMBean;
+import javax.management.MBeanAttributeInfo;
+import javax.management.AttributeNotFoundException;
+import javax.management.MBeanException;
+import javax.management.ReflectionException;
+import javax.management.Attribute;
+import javax.management.InvalidAttributeValueException;
+import javax.management.AttributeList;
+import javax.management.MBeanInfo;
+import javax.management.MBeanConstructorInfo;
+import javax.management.MBeanOperationInfo;
+import javax.management.MBeanNotificationInfo;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Collections;
+import java.util.Comparator;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * @version $Rev$ $Date$
+*/
+class ManagedMBean implements DynamicMBean {
+
+    private final List<MBeanAttributeInfo> attributes = new ArrayList<MBeanAttributeInfo>();
+    private final Map<String, Member> map = new HashMap<String, Member>();
+
+    ManagedMBean(Object managed) {
+        scan(managed, "");
+
+        for (Member member : map.values()) {
+            attributes.add(new MBeanAttributeInfo(member.getName(), member.getType().getName(), "", true, false, false));
+        }
+
+        Collections.sort(attributes, new Comparator<MBeanAttributeInfo>(){
+            public int compare(MBeanAttributeInfo o1, MBeanAttributeInfo o2) {
+                return o1.getName().compareTo(o2.getName());
+            }
+        });
+    }
+
+    private void scan(Object managed, String prefix) {
+        ClassFinder finder = new ClassFinder(managed.getClass());
+
+        List<Field> fields = finder.findAnnotatedFields(Managed.class);
+        for (Field field : fields) {
+            scan(new FieldMember(field, managed, prefix));
+        }
+
+        List<Method> methods = finder.findAnnotatedMethods(Managed.class);
+        for (Method method : methods) {
+            scan(new MethodMember(method, managed, prefix));
+        }
+    }
+
+    private void scan(Member member) {
+        Class<?> type = member.getType();
+
+        Managed managed = type.getAnnotation(Managed.class);
+        if (managed != null) {
+            try {
+                String s = "";
+                if (managed.append()) s = member.getName();
+                scan(member.get(), s);
+            } catch (IllegalAccessException e) {
+                e.printStackTrace();
+            } catch (InvocationTargetException e) {
+                e.printStackTrace();
+            }
+        } else {
+            map.put(member.getName(), member);
+        }
+    }
+
+    public Object getAttribute(String s) throws AttributeNotFoundException, MBeanException, ReflectionException {
+        try {
+            Member member = map.get(s);
+
+            if (member == null) throw new AttributeNotFoundException(s);
+
+            return member.get();
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new ReflectionException(e);
+        }
+    }
+
+    public void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException {
+    }
+
+    public AttributeList getAttributes(String[] strings) {
+        AttributeList list = new AttributeList(strings.length);
+        for (String attribute : strings) {
+            try {
+                list.add(new Attribute(attribute, getAttribute(attribute)));
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+        return list;
+    }
+
+    public AttributeList setAttributes(AttributeList attributeList) {
+        return new AttributeList();
+    }
+
+    public Object invoke(String s, Object[] objects, String[] strings) throws MBeanException, ReflectionException {
+        return null;
+    }
+
+    public MBeanInfo getMBeanInfo() {
+        return new MBeanInfo(this.getClass().getName(), "The description", attributes.toArray(new MBeanAttributeInfo[0]), new MBeanConstructorInfo[0], new MBeanOperationInfo[0], new MBeanNotificationInfo[0]);
+    }
+
+    /**
+     * Small utility interface used to allow polymorphing
+     * of java.lang.reflect.Method and java.lang.reflect.Field
+     * so that each can be treated as injection targets using
+     * the same code.
+     */
+    public static interface Member {
+        Object get() throws IllegalAccessException, InvocationTargetException;
+
+        String getName();
+
+        Class getType();
+    }
+
+    /**
+     * Implementation of Member for java.lang.reflect.Method
+     * Used for injection targets that are annotated methods
+     */
+    public static class MethodMember implements Member {
+        private final Method getter;
+        private final Object target;
+        private final String prefix;
+
+        public MethodMember(Method getter, Object target, String prefix) {
+            getter.setAccessible(true);
+            this.getter = getter;
+            this.target = target;
+            this.prefix = prefix;
+        }
+
+        public Class getType() {
+            return getter.getReturnType();
+        }
+
+        public Class getDeclaringClass() {
+            return getter.getDeclaringClass();
+        }
+
+        /**
+         * The method name needs to be changed from "getFoo" to "foo"
+         *
+         * @return
+         */
+        public String getName() {
+            StringBuilder name = new StringBuilder(getter.getName());
+
+            // remove 'get'
+            name.delete(0, 3);
+
+            if (!"".equals(prefix)){
+                if (!"".equals(name.toString())) name.insert(0, ".");
+                name.insert(0, prefix);
+            }
+
+            return name.toString();
+        }
+
+        public String toString() {
+            return getter.toString();
+        }
+
+        public Object get() throws IllegalAccessException, InvocationTargetException {
+            return getter.invoke(target);
+        }
+    }
+
+    /**
+     * Implementation of Member for java.lang.reflect.Field
+     * Used for injection targets that are annotated fields
+     */
+    public static class FieldMember implements Member {
+        private final Field field;
+        private final Object target;
+        private final String prefix;
+
+        public FieldMember(Field field, Object target, String prefix) {
+            field.setAccessible(true);
+            this.field = field;
+            this.target = target;
+            this.prefix = prefix;
+        }
+
+        public Class getType() {
+            return field.getType();
+        }
+
+        public String toString() {
+            return field.toString();
+        }
+
+        public Class getDeclaringClass() {
+            return field.getDeclaringClass();
+        }
+
+        public String getName() {
+            StringBuilder name = new StringBuilder(field.getName());
+
+            name.setCharAt(0, Character.toUpperCase(name.charAt(0)));
+
+            if (!"".equals(prefix)){
+                if (!"".equals(name.toString())) name.insert(0, ".");
+                name.insert(0, prefix);
+            }
+
+            return name.toString();
+        }
+
+        public Object get() throws IllegalAccessException {
+            return field.get(target);
+        }
+    }
+
+}

Propchange: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/monitoring/ManagedMBean.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/monitoring/ScratchPad.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/monitoring/ScratchPad.java?rev=941772&view=auto
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/monitoring/ScratchPad.java (added)
+++ openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/monitoring/ScratchPad.java Thu May  6 15:13:47 2010
@@ -0,0 +1,102 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.openejb.monitoring;
+
+import java.lang.management.ManagementFactory;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.apache.openejb.util.Pool;
+import org.apache.openejb.util.Duration;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class ScratchPad {
+
+    public static void main(String[] args) throws Exception {
+        new ScratchPad().main();
+    }
+
+    public void main() throws Exception {
+
+        MBeanServer server = ManagementFactory.getPlatformMBeanServer();
+
+        Pool.Builder builder = new Pool.Builder();
+        builder.setPoolMin(4);
+        builder.setIdleTimeout(new Duration("30 seconds"));
+        builder.setPollInterval(new Duration("15 seconds"));
+        builder.setMaxAge(new Duration("2 minutes"));
+        builder.setSupplier(new Pool.Supplier(){
+            public void discard(Object o, Pool.Event reason) {
+            }
+
+            public Object create() {
+                return "";
+            }
+        });
+        Pool pool = builder.build();
+        pool.start();
+        pool.add("");
+        pool.add("");
+        pool.add("");
+        pool.add("");
+        pool.add("");
+        pool.add("");
+
+        server.registerMBean(new ManagedMBean(pool), new ObjectName("something:name=Pool"));
+
+//        while ("".equals("")) {
+//            object.tick(System.currentTimeMillis() % 1000);
+//            Thread.sleep(287);
+//        }
+
+//        server.createMBean()
+        while (true) {
+            List<Pool.Entry> entries = new ArrayList<Pool.Entry>();
+
+            try {
+                while (true) {
+                    entries.add(pool.pop(1, TimeUnit.SECONDS));
+                    snooze();
+                }
+            } catch (TimeoutException e) {
+            }
+
+            for (Pool.Entry entry : entries) {
+                pool.push(entry);
+                snooze();
+            }
+        }
+//        new CountDownLatch(1).await();
+
+    }
+
+    private void snooze() {
+        try {
+            Thread.sleep(10000);
+        } catch (InterruptedException e) {
+            Thread.interrupted();
+        }
+    }
+
+}

Propchange: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/monitoring/ScratchPad.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/util/Pool.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/util/Pool.java?rev=941772&r1=941771&r2=941772&view=diff
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/util/Pool.java (original)
+++ openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/util/Pool.java Thu May  6 15:13:47 2010
@@ -16,6 +16,8 @@
  */
 package org.apache.openejb.util;
 
+import org.apache.openejb.monitoring.Managed;
+
 import java.lang.ref.SoftReference;
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -50,120 +52,41 @@ import java.util.concurrent.atomic.Atomi
  */
 public class Pool<T> {
 
+    // TODO: replace System.currentTimeMillis() with System.nanoTime()
+    
     private final LinkedList<Entry> pool = new LinkedList<Entry>();
     private final Semaphore instances;
     private final Semaphore available;
     private final Semaphore minimum;
     private final Executor executor;
+
+    @Managed
     private final long maxAge;
+
+    @Managed
     private final AtomicInteger poolVersion = new AtomicInteger();
+    
     private final Supplier<T> supplier;
     private final AtomicReference<Timer> timer = new AtomicReference<Timer>();
     private final Sweeper sweeper;
-    private final long interval;
-    private final boolean replaceAged;
-    private double maxAgeOffset;
-
-    public static class Builder<T> {
-
-        private int max = 10;
-        private int min = 0;
-        private boolean strict = true;
-        private Duration maxAge = new Duration(0, MILLISECONDS);
-        private double maxAgeOffset = -1;
-        private Duration idleTimeout =  new Duration(0, MILLISECONDS);
-        private Duration interval =  new Duration(5 * 60, TimeUnit.SECONDS);
-        private Supplier<T> supplier;
-        private Executor executor;
-        private boolean replaceAged;
-
-        public Builder(Builder<T> that) {
-            this.max = that.max;
-            this.min = that.min;
-            this.strict = that.strict;
-            this.maxAge = that.maxAge;
-            this.idleTimeout = that.idleTimeout;
-            this.interval = that.interval;
-            this.executor = that.executor;
-            this.supplier = that.supplier;
-            this.maxAgeOffset = that.maxAgeOffset;
-            this.replaceAged = that.replaceAged;
-        }
-
-        public Builder() {
-        }
-
-        public int getMin() {
-            return min;
-        }
-
-        public void setReplaceAged(boolean replaceAged) {
-            this.replaceAged = replaceAged;
-        }
-
-        public void setPoolMax(int max) {
-            this.max = max;
-        }
-
-        /**
-         * Alias for pool size
-         * @param max
-         * @return
-         */
-        public void setPoolSize(int max) {
-            setPoolMax(max);
-        }
-
-        public void setPoolMin(int min) {
-            this.min = min;
-        }
-
-        public void setStrictPooling(boolean strict) {
-            this.strict = strict;
-        }
-
-        public void setMaxAge(Duration maxAge) {
-            this.maxAge = maxAge;
-        }
-
-        public Duration getMaxAge() {
-            return maxAge;
-        }
-
-        public void setMaxAgeOffset(double maxAgeOffset) {
-            this.maxAgeOffset = maxAgeOffset;
-        }
-
-        public double getMaxAgeOffset() {
-            return maxAgeOffset;
-        }
-
-        public void setIdleTimeout(Duration idleTimeout) {
-            this.idleTimeout = idleTimeout;
-        }
 
-        public void setPollInterval(Duration interval) {
-            this.interval = interval;
-        }
+    @Managed
+    private final long interval;
 
-        public void setSupplier(Supplier<T> supplier) {
-            this.supplier = supplier;
-        }
+    @Managed
+    private final boolean replaceAged;
 
-        public void setExecutor(Executor executor) {
-            this.executor = executor;
-        }
+    @Managed
+    private double maxAgeOffset;
 
-        public Pool<T> build() {
-            return new Pool(max, min, strict, maxAge.getTime(MILLISECONDS), idleTimeout.getTime(MILLISECONDS), interval.getTime(MILLISECONDS), executor, supplier, false);
-        }
-    }
+    @Managed
+    private final Stats stats;
 
     public Pool(int max, int min, boolean strict) {
-        this(max, min, strict, 0, 0, 0, null, null, false);
+        this(max, min, strict, 0, 0, 0, null, null, false, -1);
     }
     
-    public Pool(int max, int min, boolean strict, long maxAge, long idleTimeout, long interval, Executor executor, Supplier<T> supplier, boolean replaceAged) {
+    public Pool(int max, int min, boolean strict, long maxAge, long idleTimeout, long interval, Executor executor, Supplier<T> supplier, boolean replaceAged, double maxAgeOffset) {
         if (min > max) greater("max", max, "min", min);
         if (maxAge != 0 && idleTimeout > maxAge) greater("MaxAge", maxAge, "IdleTimeout", idleTimeout);
         this.executor = executor != null ? executor : createExecutor();
@@ -172,11 +95,12 @@ public class Pool<T> {
         this.minimum = new Semaphore(min);
         this.instances = new Semaphore(max);
         this.maxAge = maxAge;
+        this.maxAgeOffset = maxAgeOffset;
         this.replaceAged = replaceAged;
         if (interval == 0) interval = 5 * 60 * 1000; // five minutes
         this.interval = interval;
-        
-        sweeper = new Sweeper(idleTimeout, max);
+        this.sweeper = new Sweeper(idleTimeout, max);
+        this.stats = new Stats(min, max, idleTimeout);
     }
 
     public Pool start() {
@@ -204,6 +128,7 @@ public class Pool<T> {
     }
 
     public void flush() {
+        stats.flushes.record();
         poolVersion.incrementAndGet();
     }
 
@@ -220,7 +145,10 @@ public class Pool<T> {
      * @throws TimeoutException      if no instance could be obtained within the timeout
      */
     public Entry<T> pop(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException {
-        if (!available.tryAcquire(timeout, unit)) throw new TimeoutException("Waited " + timeout + " " + unit);
+        if (!available.tryAcquire(timeout, unit)) {
+            stats.accessTimeouts.record();
+            throw new TimeoutException("Waited " + timeout + " " + unit);
+        }
 
         Entry<T> entry = null;
         while (entry == null) {
@@ -242,6 +170,7 @@ public class Pool<T> {
                 if (notBusy) return entry;
             } else {
                 // the SoftReference was garbage collected
+                stats.garbageCollected.record();
                 instances.release();
             }
 
@@ -317,7 +246,7 @@ public class Pool<T> {
             return push(new Entry<T>(obj, offset, poolVersion.get()));
         }
 
-        if (obj != null) supplier.discard(obj, Event.FULL);
+        if (obj != null) new Discard(obj, Event.FULL).run();
         return false;
     }
 
@@ -377,7 +306,7 @@ public class Pool<T> {
                 // queue it up instead.
                 executor.execute(new Discard(obj, event));
             } else {
-                supplier.discard(obj, event);
+                new Discard(obj, event).run();
             }
         }
         
@@ -521,6 +450,8 @@ public class Pool<T> {
 
         public void run() {
 
+            stats.sweeps.record();
+            
             final int currentVersion = poolVersion.get();
 
             final boolean isCurrent = previousVersion.getAndSet(currentVersion) == currentVersion;
@@ -723,6 +654,8 @@ public class Pool<T> {
                 // Retry and logging should be done in
                 // the Supplier implementation
                 discard(expired);
+            } finally {
+                stats.replaced.record();
             }
         }
     }
@@ -738,6 +671,12 @@ public class Pool<T> {
         }
 
         public void run() {
+            switch (event) {
+                case AGED: stats.aged.record(); break;
+                case FLUSHED: stats.flushed.record(); break;
+                case FULL: stats.overdrafts.record(); break;
+                case IDLE: stats.idleTimeouts.record(); break;
+            }
             supplier.discard(expired, event);
         }
     }
@@ -810,7 +749,7 @@ public class Pool<T> {
 
         @Override
         public int availablePermits() {
-            return 0;
+            return Integer.MAX_VALUE;
         }
 
         @Override
@@ -822,4 +761,160 @@ public class Pool<T> {
         protected void reducePermits(int reduction) {
         }
     }
+
+    @Managed
+    private class Stats {
+
+        @Managed
+        private final org.apache.openejb.monitoring.Event sweeps = new org.apache.openejb.monitoring.Event();
+
+        @Managed
+        private final org.apache.openejb.monitoring.Event flushes = new org.apache.openejb.monitoring.Event();
+
+        @Managed
+        private final org.apache.openejb.monitoring.Event accessTimeouts = new org.apache.openejb.monitoring.Event();
+
+        @Managed
+        private final org.apache.openejb.monitoring.Event garbageCollected = new org.apache.openejb.monitoring.Event();
+
+        @Managed
+        private final org.apache.openejb.monitoring.Event idleTimeouts = new org.apache.openejb.monitoring.Event();
+
+        @Managed
+        private final org.apache.openejb.monitoring.Event aged = new org.apache.openejb.monitoring.Event();
+
+        @Managed
+        private final org.apache.openejb.monitoring.Event flushed = new org.apache.openejb.monitoring.Event();
+
+        @Managed
+        private final org.apache.openejb.monitoring.Event overdrafts = new org.apache.openejb.monitoring.Event();
+
+        @Managed
+        private final org.apache.openejb.monitoring.Event replaced = new org.apache.openejb.monitoring.Event();
+
+        @Managed
+        private final int minSize;
+
+        @Managed
+        private final int maxSize;
+
+        @Managed
+        private long idleTimeout;
+
+        private Stats(int minSize, int maxSize, long idleTimeout) {
+            this.minSize = minSize;
+            this.maxSize = maxSize;
+            this.idleTimeout = idleTimeout;
+        }
+
+        @Managed
+        private long getAvailable() {
+            return available.availablePermits();
+        }
+
+        @Managed
+        private int getInstances() {
+            return maxSize - Pool.this.instances.availablePermits();
+        }
+
+        @Managed
+        private int getMinimumInstances() {
+            return minSize - minimum.availablePermits();
+        }
+    }
+
+    public static class Builder<T> {
+
+        private int max = 10;
+        private int min = 0;
+        private boolean strict = true;
+        private Duration maxAge = new Duration(0, MILLISECONDS);
+        private double maxAgeOffset = -1;
+        private Duration idleTimeout =  new Duration(0, MILLISECONDS);
+        private Duration interval =  new Duration(5 * 60, TimeUnit.SECONDS);
+        private Supplier<T> supplier;
+        private Executor executor;
+        private boolean replaceAged;
+
+        public Builder(Builder<T> that) {
+            this.max = that.max;
+            this.min = that.min;
+            this.strict = that.strict;
+            this.maxAge = that.maxAge;
+            this.idleTimeout = that.idleTimeout;
+            this.interval = that.interval;
+            this.executor = that.executor;
+            this.supplier = that.supplier;
+            this.maxAgeOffset = that.maxAgeOffset;
+            this.replaceAged = that.replaceAged;
+        }
+
+        public Builder() {
+        }
+
+        public int getMin() {
+            return min;
+        }
+
+        public void setReplaceAged(boolean replaceAged) {
+            this.replaceAged = replaceAged;
+        }
+
+        public void setPoolMax(int max) {
+            this.max = max;
+        }
+
+        /**
+         * Alias for pool size
+         * @param max
+         * @return
+         */
+        public void setPoolSize(int max) {
+            setPoolMax(max);
+        }
+
+        public void setPoolMin(int min) {
+            this.min = min;
+        }
+
+        public void setStrictPooling(boolean strict) {
+            this.strict = strict;
+        }
+
+        public void setMaxAge(Duration maxAge) {
+            this.maxAge = maxAge;
+        }
+
+        public Duration getMaxAge() {
+            return maxAge;
+        }
+
+        public void setMaxAgeOffset(double maxAgeOffset) {
+            this.maxAgeOffset = maxAgeOffset;
+        }
+
+        public double getMaxAgeOffset() {
+            return maxAgeOffset;
+        }
+
+        public void setIdleTimeout(Duration idleTimeout) {
+            this.idleTimeout = idleTimeout;
+        }
+
+        public void setPollInterval(Duration interval) {
+            this.interval = interval;
+        }
+
+        public void setSupplier(Supplier<T> supplier) {
+            this.supplier = supplier;
+        }
+
+        public void setExecutor(Executor executor) {
+            this.executor = executor;
+        }
+
+        public Pool<T> build() {
+            return new Pool(max, min, strict, maxAge.getTime(MILLISECONDS), idleTimeout.getTime(MILLISECONDS), interval.getTime(MILLISECONDS), executor, supplier, false, maxAgeOffset);
+        }
+    }
 }