Author: psteitz
Date: Mon Jul 16 23:11:36 2007
New Revision: 556823

URL: http://svn.apache.org/viewvc?view=rev&rev=556823
Log:
Initial Commit.

Added:
    jakarta/commons/sandbox/performance/
    jakarta/commons/sandbox/performance/build.properties
    jakarta/commons/sandbox/performance/build.xml
    jakarta/commons/sandbox/performance/config.xml
    jakarta/commons/sandbox/performance/logging.properties
    jakarta/commons/sandbox/performance/src/
    jakarta/commons/sandbox/performance/src/java/
    jakarta/commons/sandbox/performance/src/java/org/
    jakarta/commons/sandbox/performance/src/java/org/apache/
    jakarta/commons/sandbox/performance/src/java/org/apache/commons/
    jakarta/commons/sandbox/performance/src/java/org/apache/commons/performance/
    
jakarta/commons/sandbox/performance/src/java/org/apache/commons/performance/ClientThread.java
    
jakarta/commons/sandbox/performance/src/java/org/apache/commons/performance/dbcp/
    
jakarta/commons/sandbox/performance/src/java/org/apache/commons/performance/dbcp/ConfigurationException.java
    
jakarta/commons/sandbox/performance/src/java/org/apache/commons/performance/dbcp/DBCPClientThread.java
    
jakarta/commons/sandbox/performance/src/java/org/apache/commons/performance/dbcp/DBCPSoak.java
    
jakarta/commons/sandbox/performance/src/java/org/apache/commons/performance/dbcp/DBCPTest.java

Added: jakarta/commons/sandbox/performance/build.properties
URL: 
http://svn.apache.org/viewvc/jakarta/commons/sandbox/performance/build.properties?view=auto&rev=556823
==============================================================================
--- jakarta/commons/sandbox/performance/build.properties (added)
+++ jakarta/commons/sandbox/performance/build.properties Mon Jul 16 23:11:36 
2007
@@ -0,0 +1,24 @@
+###############################################################################
+#
+# 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.
+###############################################################################
+
+# Remote maven repository
+mavenRepo=http://repo1.maven.org/maven2
+
+# JDBC driver
+jdbc-jar=/home/phil/mysql/mysql-connector-java-5.0.2.jar
+

Added: jakarta/commons/sandbox/performance/build.xml
URL: 
http://svn.apache.org/viewvc/jakarta/commons/sandbox/performance/build.xml?view=auto&rev=556823
==============================================================================
--- jakarta/commons/sandbox/performance/build.xml (added)
+++ jakarta/commons/sandbox/performance/build.xml Mon Jul 16 23:11:36 2007
@@ -0,0 +1,124 @@
+<!--
+/*
+ * 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.
+ */
+ -->
+
+<project name="DBCPTest" default="run" basedir=".">
+     
+  <property name="src" location="src"/>
+  <property name="build" location="build"/>
+  <property name="lib" location="lib"/>
+       
+  <property name="component-propfile" value="${basedir}/build.properties"/>
+  <property file="${component-propfile}"/>
+
+  <path id="compile.classpath">
+    <pathelement location="${jdbc-jar}"/>
+    <fileset dir="${lib}">
+      <include name="*.jar"/>
+    </fileset>
+  </path>
+       
+  <path id="run.classpath">
+    <pathelement path="${build}"/>
+    <pathelement path="${java.class.path}"/>
+    <path refid="compile.classpath" /> 
+  </path>
+
+  <target name="init">
+    <mkdir dir="${build}"/>
+    <mkdir dir="${lib}"/>
+  </target>
+
+  <target name="get-collections">
+    <get
+    
src="${mavenRepo}/commons-collections/commons-collections/3.1/commons-collections-3.1.jar"
+    dest="${lib}/commons-collections-3.1.jar"
+    usetimestamp="true"/>
+  </target>
+  
+  <target name="get-beanutils">
+    <get
+    
src="${mavenRepo}/commons-beanutils/commons-beanutils/1.6.1/commons-beanutils-1.6.1.jar"
+    dest="${lib}/commons-beanutils-1.6.1.jar"
+    usetimestamp="true"/>
+  </target>
+
+  <target name="get-digester">
+    <get
+    
src="${mavenRepo}/commons-digester/commons-digester/1.4.1/commons-digester-1.4.1.jar"
+    dest="${lib}/commons-digester-1.4.1.jar"
+    usetimestamp="true"/>
+  </target>
+
+  <target name="get-math">
+    <get
+    src="${mavenRepo}/commons-math/commons-math/1.1/commons-math-1.1.jar"
+    dest="${lib}/commons-math-1.1.jar"
+    usetimestamp="true"/>
+  </target>
+
+  <target name="get-dbcp">
+    <get
+    src="${mavenRepo}/commons-dbcp/commons-dbcp/1.2.2/commons-dbcp-1.2.2.jar"
+    dest="${lib}/commons-dbcp-1.2.2.jar"
+    usetimestamp="true"/>
+  </target>
+
+  <target name="get-pool">
+    <get
+    src="${mavenRepo}/commons-pool/commons-pool/1.3/commons-pool-1.3.jar"
+    dest="${lib}/commons-pool-1.3.jar"
+    usetimestamp="true"/>
+  </target>
+
+  <target name="get-logging">
+    <get
+    
src="${mavenRepo}/commons-logging/commons-logging/1.0.4/commons-logging-1.0.4.jar"
+    dest="${lib}/commons-logging-1.0.4.jar"
+    usetimestamp="true"/>
+  </target>
+ 
+  <target name="get-deps"
+      
depends="get-collections,get-beanutils,get-digester,get-math,get-logging,get-dbcp,get-pool">
+  </target>
+
+  <target name="compile" depends="clean,init,get-deps">
+    <javac srcdir="${src}/java" 
+          destdir="${build}">
+      <classpath refid="compile.classpath"/>
+      <compilerarg value="-Xlint:unchecked" />
+    </javac>
+    <copy file="${basedir}/config.xml" tofile="${build}/config.xml"/>
+    <copy file="${basedir}/logging.properties" 
tofile="${build}/logging.properties"/>
+  </target>
+
+  <target name="run" depends="compile">
+    <java classname="org.apache.commons.performance.dbcp.DBCPTest" fork="true">
+      <classpath refid="run.classpath"/>
+      <jvmarg 
+      
value="-Dorg.apache.commons.logging.Log=org.apache.commons.logging.impl.Jdk14Logger"/>
+      <jvmarg value="-Djava.util.logging.config.file=logging.properties"/>
+    </java>
+  </target>
+       
+  <target name="clean">
+    <delete dir="${build}"/>
+  </target>
+</project>

Added: jakarta/commons/sandbox/performance/config.xml
URL: 
http://svn.apache.org/viewvc/jakarta/commons/sandbox/performance/config.xml?view=auto&rev=556823
==============================================================================
--- jakarta/commons/sandbox/performance/config.xml (added)
+++ jakarta/commons/sandbox/performance/config.xml Mon Jul 16 23:11:36 2007
@@ -0,0 +1,86 @@
+<!--
+/*
+ * 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.
+ */
+ -->
+
+<configuration>
+  <database>
+    <driver>com.mysql.jdbc.Driver</driver>
+    <url>jdbc:mysql:///test</url>
+    <username></username>
+    <password></password>
+  </database>
+  
+  <connection-factory>
+    <!-- DriverManager or Driver -->
+    <type>DriverManager</type> 
+    <auto-commit>true</auto-commit>
+    <read-only>false</read-only>
+    <!-- Validation query to use when testOnBorrow or testOnReturn is true -->
+    <validation-query></validation-query>
+  </connection-factory>
+  
+  <poolable-connection-factory>
+    <!-- PoolableConnectionFactory or CPDSConnectionFactory (not yet) -->
+    <type>PoolableConnectionFactory</type> 
+    <pool-prepared-statements>true</pool-prepared-statements>
+    <max-open-statements>-1</max-open-statements>
+  </poolable-connection-factory>
+  
+  <pool>
+    <!-- GenericObjectPool or AbandonedObjectPool -->
+    <type>GenericObjectPool</type>
+    <max-active>15</max-active>
+    <max-idle>15</max-idle>
+    <min-idle>0</min-idle>
+    <max-wait>-1</max-wait>
+    <!-- block, fail, or grow -->
+    <exhausted-action>block</exhausted-action>
+    <test-on-borrow>false</test-on-borrow>
+    <test-on-return>false</test-on-return>
+    <time-between-evictions>-1</time-between-evictions>
+    <tests-per-eviction>3</tests-per-eviction>
+    <idle-timeout>-1</idle-timeout>
+    <test-while-idle>false</test-while-idle>
+  </pool>
+  
+  <!-- Ignored unless pool type is AbandonedObjectPool -->
+  <abandoned-config>
+    <log-abandoned>true</log-abandoned>
+    <remove-abandoned>true</remove-abandoned>
+    <abandoned-timeout>50000</abandoned-timeout>
+  </abandoned-config>
+  
+  <run>
+    <!-- integerIndexed, integerScan, or textScan -->
+    <query-type>integerIndexed</query-type>
+    <iterations>1000</iterations>
+    <clients>50</clients>
+    <delay-mean>250</delay-mean>
+    <delay-sigma>50</delay-sigma>
+    <!-- constant, gaussian, or poisson -->
+    <delay-type>gaussian</delay-type>
+    <!-- none, linear, random --> 
+    <ramp-type>random</ramp-type>
+    <period>20000</period>
+    <!-- none, oscillating (others?)-->
+    <cycle-type>oscillating</cycle-type>
+  </run>
+  
+</configuration>

Added: jakarta/commons/sandbox/performance/logging.properties
URL: 
http://svn.apache.org/viewvc/jakarta/commons/sandbox/performance/logging.properties?view=auto&rev=556823
==============================================================================
--- jakarta/commons/sandbox/performance/logging.properties (added)
+++ jakarta/commons/sandbox/performance/logging.properties Mon Jul 16 23:11:36 
2007
@@ -0,0 +1,38 @@
+###############################################################################
+#
+# 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.
+###############################################################################
+
+handlers=java.util.logging.FileHandler, java.util.logging.ConsoleHandler
+
+.level=WARNING
+
+org.apache.commons.pool.level=FINE
+org.apache.commons.dbcp.level=FINE
+org.apache.commons.performance.level=FINE
+
+java.util.logging.ConsoleHandler.level=SEVERE
+java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
+java.util.logging.FileHandler.level=FINE
+java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
+
+java.util.logging.FileHandler.pattern=%h/dbcpTest/dbcpSoak%u.log
+
+java.util.logging.FileHandler.limit=1000000
+
+java.util.logging.FileHandler.count=10
+
+

Added: 
jakarta/commons/sandbox/performance/src/java/org/apache/commons/performance/ClientThread.java
URL: 
http://svn.apache.org/viewvc/jakarta/commons/sandbox/performance/src/java/org/apache/commons/performance/ClientThread.java?view=auto&rev=556823
==============================================================================
--- 
jakarta/commons/sandbox/performance/src/java/org/apache/commons/performance/ClientThread.java
 (added)
+++ 
jakarta/commons/sandbox/performance/src/java/org/apache/commons/performance/ClientThread.java
 Mon Jul 16 23:11:36 2007
@@ -0,0 +1,286 @@
+/*
+ * 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.commons.performance;
+
+import java.util.List;
+import java.util.logging.Logger;
+
+import org.apache.commons.math.random.RandomData;
+import org.apache.commons.math.random.RandomDataImpl;
+import org.apache.commons.math.stat.descriptive.SummaryStatistics;
+import org.apache.commons.math.stat.descriptive.SummaryStatisticsImpl;
+
+/**
+ * Base for performance / load test clients. 
+ * Run method executes init, then setup-execute-cleanup in a loop, gathering
+ * performance statistics, with time between executions determined by nextDelay
+ * method. See [EMAIL PROTECTED] for details on interarrival time computation.
+ * 
+ */
+public abstract class ClientThread implements Runnable {
+
+    /** Number of iterations */
+    protected long iterations;
+    /** Mean time between requests */
+    protected long delay;
+    /** Standard deviation of delay distribution */
+    protected double sigma;
+    /** Delay type - determines how next start times are computed */
+    protected String delayType;
+    /** Period for cyclic mean delay */
+    protected long period;
+    /** Cycle type */
+    protected String cycleType;
+    /** Ramp type */
+    protected String rampType;
+    /** Logger shared by client threads */
+    protected Logger logger = null;
+    /** List of statistics to which to append stats for this client */
+    protected List <SummaryStatistics> statsList = null;
+    /** Start time of run */
+    protected long startTime;
+    /** Start time of current period */
+    protected long periodStart;
+    /** Last mean delay */
+    protected double lastMean;
+    /** Random data generator */
+    protected RandomData randomData;
+    /** Whether or now this thread is ramping up */
+    protected boolean rampingUp = true;
+    
+    /**
+     * Create a client thread.
+     * 
+     * @param iterations number of iterations
+     * @param delay mean time between client requests
+     * @param delayType distribution of time between client requests
+     * @param period period of cycle for cyclic load
+     * @param cycleType type of cycle for mean delay
+     * @param logger common logger shared by all clients
+     * @param statsList List of SummaryStatistics to add results to
+     */
+    public ClientThread(long iterations, long delay, double sigma, 
+            String delayType, long period, String cycleType,
+            String rampType, Logger logger, 
+            List <SummaryStatistics> statsList) {
+        this.iterations = iterations;
+        this.delay = delay;
+        this.sigma = sigma;
+        this.delayType = delayType;
+        this.period = period;
+        this.cycleType = cycleType;
+        this.rampType = rampType;
+        this.logger = logger;
+        this.statsList = statsList;
+    }
+    
+    public void run() {
+        try {
+            init();
+        } catch (Exception ex) {
+            logger.severe("init failed.");
+            ex.printStackTrace();
+            return;
+        }
+        long start = 0;
+        startTime = System.currentTimeMillis();
+        long lastStart = startTime;
+        long numMisses = 0;
+        long numErrors = 0;
+        SummaryStatistics stats = new SummaryStatisticsImpl();
+        randomData = new RandomDataImpl();
+        periodStart = System.currentTimeMillis();
+        lastMean = (double) (2 * delay); // Ramp up, if any, starts here
+        for (int i = 0; i < iterations; i++) {
+            try {
+                setup();
+                // Generate next interarrival time. If that is in the
+                // past, go right away and log a miss; otherwise wait.
+                long elapsed = System.currentTimeMillis() - lastStart;
+                long nextDelay = nextDelay();
+                if (elapsed > nextDelay) {
+                    numMisses++;
+                } else {
+                    try {
+                        Thread.sleep(nextDelay - elapsed);
+                    } catch (InterruptedException ex) {
+                        logger.info("Sleep interrupted");
+                    }
+                }
+                
+                // Fire the request and measure response time
+                start = System.currentTimeMillis();
+                execute();
+            } catch (Exception ex) {
+                ex.printStackTrace();
+                numErrors++;
+            } finally {
+                try {
+                    stats.addValue(System.currentTimeMillis() - start);
+                    lastStart = start;
+                    cleanUp();
+                } catch (Exception e) {
+                    e.printStackTrace();                    
+                }
+            }
+        }
+        
+        // Report statistics
+        logger.info(stats.toString() + 
+          "Number of misses: " + numMisses + "\n" +
+          "Number or errors: " + numErrors + "\n");
+        statsList.add(stats);
+    }
+    
+    /** Executed once at the beginning of the run */
+    protected void init() throws Exception {}
+    
+    /** Executed at the beginning of each iteration */
+    protected void setup() throws Exception {}
+    
+    /** Executed in finally block of iteration try-catch */
+    protected void cleanUp() throws Exception {}
+    
+    /** Core iteration code.  Timings are based on this,
+     *  so keep it tight.
+     *  */
+    public abstract void execute() throws Exception;
+    
+    /**
+     * <p>Computes the next interarrival time (time to wait between requests)
+     * based on configured values for mean delay, delay type, cycle type, 
+     * ramp type and period.
+     * </p>
+     * <p>Currently supports constant (just returning mean delay time), Poisson
+     * and Gaussian distributed random time delays, linear and random ramps,
+     * and oscillating / non-oscillating cycle types.
+     * </p>
+     * <p>If delayType = "constant" the configured delay mean is always 
returned.
+     * If delayType is "gaussian" or "exponential" and cycleType is "none",
+     * random deviates with the configured parameters are returned.
+     * </p>
+     * <p>If delayType is not "constant" and cycleType is "oscillating", means
+     * of random deviates ramp up and down between delay and twice delay. Ramp
+     * type is controlled by rampType.  Linear rampType means the means
+     * increase or decrease linearly over the time of the period.  Random
+     * makes random jumps up or down toward the next peak or trough. "None" for
+     * rampType under oscillating cycleType makes the means alternate bttween
+     * peak (delay) and trough (twice delay) with no ramp between.
+     * </p>
+     * <p>For non-oscillating, non-constant runs, linear and random rampTypes
+     * work similarly, but over just one ramp up period at the beginning of
+     * the run.
+     * </p>
+     * 
+     * @param currentTime current time
+     * @return next value for delay
+     */
+    protected long nextDelay() {
+        long currentTime = System.currentTimeMillis();
+        double mean = 0;
+        if (delayType.equals("constant")) { 
+            //TODO: should support single ramp up to constant
+            return delay;
+        } else { // delay not constant, use random variate
+            
+            // Determine mean to use 
+            double doubleDelay = (double) delay;
+            double top = 2d * doubleDelay; // delay is peak, ramp starts at top
+            if (cycleType.equals("none")) {
+                if (rampType.equals("none") || 
+                        (currentTime - startTime) > period) { 
+                    // use configured mean
+                    mean = doubleDelay;
+                } else if (rampType.equals("linear")) { // single period linear
+                    double prop = (double) (currentTime - startTime) / period ;
+                    mean =  top - doubleDelay * prop;
+                } else { // Random jumps down to delay - single period
+                    // Where we last were as proportion of way down to delay
+                    double lastProp = 
+                        (top - lastMean) / doubleDelay;
+                    // Make a random jump toward 1 (1 = all the way down)
+                    double prop = randomData.nextUniform(lastProp, 1);
+                    mean = top - doubleDelay * prop;
+                }
+            } else if (cycleType.equals("oscillating")) {
+                // First check if we need to change directions
+                if ((currentTime - periodStart) >= period) {
+                    if (rampingUp) {
+                        rampingUp = false;
+                        lastMean = doubleDelay;
+                    } else {
+                        rampingUp = true;
+                        lastMean = top;
+                    }
+                    periodStart = currentTime;
+                }
+                if (rampType.equals("none")) { // mean or twice mean, no ramp
+                    if (rampingUp) {
+                        mean = top;
+                    } else {
+                        mean = doubleDelay;
+                    }
+                } else if (rampType.equals("linear")) { // ramp down, then up
+                    double prop = 
+                        (double)(currentTime - periodStart) / (double) period;
+                    if (rampingUp) {
+                        mean =  top - doubleDelay * prop; 
+                    } else {
+                        mean = doubleDelay + doubleDelay * prop;
+                    }
+                } else { // random jumps down, then back up
+                    // Where we last were as proportion of way down to delay
+                    double lastProp = 
+                        (top - lastMean) / doubleDelay;
+                    // Where we would be if this were a linear ramp
+                    double linearProp = 
+                        (double)(currentTime - periodStart) / (double) period;
+                    // Need to govern size of jumps, otherwise "convergence"
+                    // can be too fast - use linear ramp as governor
+                    if ((rampingUp && (lastProp > linearProp)) || 
+                       (!rampingUp && (lastProp < linearProp))) { // Slow down
+                        lastProp = linearProp;
+                    }
+                    double prop = 0;
+                    if (rampingUp) { // Random jump toward 1
+                        prop = randomData.nextUniform(lastProp, 1);
+                    } else { // Random jump toward 0
+                        prop = randomData.nextUniform(0, lastProp);
+                    }
+                    // Make sure sequence is monotone
+                    if (rampingUp) {
+                        mean = Math.min(lastMean, top - doubleDelay * prop);
+                    } else {
+                        mean = Math.max(lastMean, top - doubleDelay * prop);
+                    }
+                }
+            }
+            
+            // Remember last mean for ramp up / down
+            lastMean = mean;
+            
+            // Generate and return random deviate
+            if (delayType.equals("gaussian")) {
+                return Math.round(randomData.nextGaussian(mean, sigma));
+            } else { // must be Poisson
+                return Math.round(randomData.nextPoisson(mean));
+            }
+        }  
+    }
+
+}

Added: 
jakarta/commons/sandbox/performance/src/java/org/apache/commons/performance/dbcp/ConfigurationException.java
URL: 
http://svn.apache.org/viewvc/jakarta/commons/sandbox/performance/src/java/org/apache/commons/performance/dbcp/ConfigurationException.java?view=auto&rev=556823
==============================================================================
--- 
jakarta/commons/sandbox/performance/src/java/org/apache/commons/performance/dbcp/ConfigurationException.java
 (added)
+++ 
jakarta/commons/sandbox/performance/src/java/org/apache/commons/performance/dbcp/ConfigurationException.java
 Mon Jul 16 23:11:36 2007
@@ -0,0 +1,37 @@
+/*
+ * 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.commons.performance.dbcp;
+
+public class ConfigurationException extends Exception {
+
+    public ConfigurationException() {
+        super();
+    }
+
+    public ConfigurationException(String arg0, Throwable arg1) {
+        super(arg0, arg1);
+    }
+
+    public ConfigurationException(String arg0) {
+        super(arg0);
+    }
+
+    public ConfigurationException(Throwable arg0) {
+        super(arg0);
+    }
+
+}

Added: 
jakarta/commons/sandbox/performance/src/java/org/apache/commons/performance/dbcp/DBCPClientThread.java
URL: 
http://svn.apache.org/viewvc/jakarta/commons/sandbox/performance/src/java/org/apache/commons/performance/dbcp/DBCPClientThread.java?view=auto&rev=556823
==============================================================================
--- 
jakarta/commons/sandbox/performance/src/java/org/apache/commons/performance/dbcp/DBCPClientThread.java
 (added)
+++ 
jakarta/commons/sandbox/performance/src/java/org/apache/commons/performance/dbcp/DBCPClientThread.java
 Mon Jul 16 23:11:36 2007
@@ -0,0 +1,111 @@
+/*
+ * 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.commons.performance.dbcp;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.Statement;
+import java.util.List;
+import java.util.logging.Logger;
+import javax.sql.DataSource;
+
+import org.apache.commons.math.stat.descriptive.SummaryStatistics;
+import org.apache.commons.performance.ClientThread;
+
+/**
+ * Client thread that executes requests in a loop using a configured 
DataSource,
+ * with number of requests, time between requests and query strings governed by
+ * constructor parameters. See ClientThread javadoc for a description
+ * of how times between requests are computed.
+ *
+ */
+public class DBCPClientThread extends ClientThread {
+    /** Initial segment of query string */
+    private String queryString;
+    /** Whether or not the query is on the text column */
+    private boolean textQuery = false;
+    /** DataSource used to connect */
+    private DataSource dataSource = null;
+    /** Database connection */
+    Connection conn = null;
+    /** Current query */
+    String currentQuery = null;
+     
+    /**
+     * Create a dbcp client thread.
+     * 
+     * @param iterations number of iterations
+     * @param delay mean time between client requests
+     * @param delayType distribution of time between client requests
+     * @param queryType type of query 
+     * @param period period of cycle for cyclic load
+     * @param cycleType type of cycle for mean delay
+     * @param logger common logger shared by all clients
+     * @param dataSource DataSource for connections
+     * @param statsList List of SummaryStatistics to add results to
+     */
+    public DBCPClientThread(long iterations, long delay, double sigma, 
+            String delayType, String queryType, long period, String cycleType,
+            String rampType, Logger logger, DataSource dataSource,
+            List <SummaryStatistics> statsList) {
+        
+        super(iterations, delay, sigma, delayType, period, cycleType,
+                rampType, logger, statsList);
+        
+        this.dataSource = dataSource;
+        
+        if (queryType.equals("integerIndexed")) {
+            queryString = "select * from test_table WHERE indexed=";
+        } else if (queryType.equals("integerScan")) {
+            queryString = "select * from test_table WHERE not_indexed=";  
+        } else {
+            queryString = "select * from test_table WHERE text='"; 
+            textQuery = true;
+        }
+    }
+    
+    /** Generate a random query */
+    public void setup() throws Exception {
+        if (textQuery) {
+            currentQuery = queryString +
+                randomData.nextHexString(20) + "';";
+        } else {
+            currentQuery = queryString +
+                randomData.nextInt(0, 100) + ";";
+        }
+    }
+    
+    /** Execute query */
+    public void execute() throws Exception {
+        conn = dataSource.getConnection();
+        Statement stmt = conn.createStatement();
+        stmt.execute(currentQuery);
+        ResultSet rs = stmt.getResultSet();
+        if (!rs.isAfterLast()) {
+            rs.next();
+        }
+        rs.close();
+        stmt.close();
+    }
+    
+    /** Close connection */
+    public void cleanUp() throws Exception {
+        conn.close();
+    }
+
+}

Added: 
jakarta/commons/sandbox/performance/src/java/org/apache/commons/performance/dbcp/DBCPSoak.java
URL: 
http://svn.apache.org/viewvc/jakarta/commons/sandbox/performance/src/java/org/apache/commons/performance/dbcp/DBCPSoak.java?view=auto&rev=556823
==============================================================================
--- 
jakarta/commons/sandbox/performance/src/java/org/apache/commons/performance/dbcp/DBCPSoak.java
 (added)
+++ 
jakarta/commons/sandbox/performance/src/java/org/apache/commons/performance/dbcp/DBCPSoak.java
 Mon Jul 16 23:11:36 2007
@@ -0,0 +1,415 @@
+/*
+ * 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.commons.performance.dbcp;
+import java.sql.Connection;
+import java.sql.Driver;
+import java.sql.DriverManager;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Logger;
+
+import javax.sql.DataSource;
+
+import org.apache.commons.dbcp.AbandonedConfig;
+import org.apache.commons.dbcp.AbandonedObjectPool;
+import org.apache.commons.dbcp.ConnectionFactory;
+import org.apache.commons.dbcp.DataSourceConnectionFactory;
+import org.apache.commons.dbcp.DriverConnectionFactory;
+import org.apache.commons.dbcp.DriverManagerConnectionFactory;
+import org.apache.commons.dbcp.PoolableConnectionFactory;
+import org.apache.commons.dbcp.PoolingDataSource;
+import org.apache.commons.digester.Digester;
+import org.apache.commons.pool.KeyedObjectPoolFactory;
+import org.apache.commons.pool.PoolableObjectFactory;
+import org.apache.commons.pool.impl.GenericKeyedObjectPool;
+import org.apache.commons.pool.impl.GenericKeyedObjectPoolFactory;
+import org.apache.commons.pool.impl.GenericObjectPool;
+import org.apache.commons.math.random.RandomData;
+import org.apache.commons.math.random.RandomDataImpl;
+import org.apache.commons.math.stat.descriptive.SummaryStatistics;
+import org.apache.commons.math.stat.descriptive.SummaryStatisticsImpl;
+ 
+/**
+ * Configurable load / performance tester for commons dbcp.
+ * Uses Commons Digester to parse and load configuration and spawns
+ * DBCPClientThread instances to generate load and gather statistics.
+ *
+ */
+public class DBCPSoak {
+    private static Logger logger = Logger.getLogger(DBCPSoak.class.getName());
+    private static List <SummaryStatistics> statsList =
+        new ArrayList <SummaryStatistics>();
+    private String driverClass;
+    private String connectUrl;
+    private String connectUser;
+    private String connectPassword;
+    private String poolType;
+    private String driverType;
+    private String factoryType;
+    private GenericObjectPool connectionPool;
+    private PoolingDataSource dataSource;
+    private long numClients;
+    private long iterations;
+    private int maxActive;
+    private int maxIdle;
+    private int minIdle;
+    private long maxWait;
+    private long delay;
+    private double sigma;
+    private String delayType;
+    private String queryType;
+    private String rampType;
+    private long period;
+    private String cycleType;
+    private boolean autocommit;
+    private boolean readOnly;
+    private byte exhaustedAction;
+    private boolean testOnBorrow;
+    private boolean testOnReturn;
+    private long timeBetweenEvictions;
+    private int testsPerEviction;
+    private long idleTimeout;
+    private boolean testWhileIdle;
+    private String validationQuery;
+    private AbandonedConfig abandonedConfig = new AbandonedConfig();
+    private boolean poolPreparedStatements;
+    private int maxOpenStatements;
+    
+    public void execute() throws Exception {
+        Class.forName(driverClass);
+        
+        // Create object pool
+        if (poolType.equals("GenericObjectPool")) {
+            connectionPool = new GenericObjectPool(
+                    null, maxActive, exhaustedAction,
+                    maxWait, maxIdle, minIdle, testOnBorrow, testOnReturn,
+                    timeBetweenEvictions, testsPerEviction, idleTimeout,
+                    testWhileIdle);
+        } else if (poolType.equals("AbandonedObjectPool")) {
+            connectionPool = new AbandonedObjectPool(null,abandonedConfig);
+        } else {
+            throw new ConfigurationException(
+                    "invalid pool type configuration: " + poolType);
+        }
+        
+        // Create raw connection factory
+        ConnectionFactory connectionFactory = null;
+        if (driverType.equals("DriverManager")) {
+            connectionFactory = new DriverManagerConnectionFactory(
+                    connectUrl,connectUser,
+                    connectPassword);
+        } else if (driverType.equals("Driver")) {
+            Properties props = new Properties();
+            props.put("user", connectUser);
+            props.put("password", connectPassword);
+            connectionFactory = new DriverConnectionFactory(
+                    (Driver) Class.forName(driverClass).newInstance(),
+                    connectUrl, props);
+        } else {
+            throw new ConfigurationException(
+            "Bad config setting for driver type"); 
+        } 
+   
+        // Create object factory
+        PoolableObjectFactory poolableConnectionFactory = null;
+        KeyedObjectPoolFactory statementPoolFactory = null;
+        if (poolPreparedStatements) { // Use same defaults as BasicDataSource
+            statementPoolFactory = new GenericKeyedObjectPoolFactory(null,
+                        -1, // unlimited maxActive (per key)
+                        GenericKeyedObjectPool.WHEN_EXHAUSTED_FAIL,
+                        0, // maxWait
+                        1, // maxIdle (per key)
+                        maxOpenStatements); //TODO: make all configurable
+        }
+        if (factoryType.equals("PoolableConnectionFactory")) {
+            poolableConnectionFactory = 
+                new PoolableConnectionFactory(
+                        connectionFactory,connectionPool, statementPoolFactory,
+                        validationQuery, readOnly, autocommit);
+        } else if (factoryType.equals("CPDSConnectionFactory")) {
+            throw new ConfigurationException(
+            "CPDSConnectionFactory not implemented yet");
+        } else {
+            throw new ConfigurationException(
+                    "Invalid factory type: " + factoryType);
+        }
+        
+        // Create DataSource
+        dataSource = new PoolingDataSource(connectionPool); 
+        
+        // Try to connect and query test_table. If "test_table" appears in 
+        // exception message, assume table needs to be created. 
+        Connection conn = dataSource.getConnection();
+        try {
+            Statement stmnt = conn.createStatement();
+            stmnt.execute("select * from test_table where indexed=1;");
+            stmnt.close();
+        } catch (Exception ex) {
+            if (ex.getMessage().indexOf("test_table") > 0) {
+                logger.info("Creating test_table");
+                makeTable();
+                logger.info("test_table created successfully");
+            } else {
+                throw ex;
+            }
+        } finally {
+            conn.close();
+        }
+        
+        logger.info("Starting");
+        
+        // Spawn and execute client threads
+               ExecutorService ex = 
Executors.newFixedThreadPool((int)numClients);
+               for (int i = 0; i < numClients; i++) {
+                       ex.execute(new DBCPClientThread(iterations, delay, 
sigma, delayType,
+                    queryType, period, cycleType, rampType, logger,
+                    dataSource, statsList));
+               } 
+        ex.shutdown();
+        // hard time limit of one day for now 
+        // TODO: make this configurable
+        ex.awaitTermination(60 * 60 * 24, TimeUnit.SECONDS);
+        
+        // Compute summary statistics
+        SummaryStatistics meanSummary = new SummaryStatisticsImpl();
+        SummaryStatistics stdSummary = new SummaryStatisticsImpl();
+        SummaryStatistics minSummary = new SummaryStatisticsImpl();
+        SummaryStatistics maxSummary = new SummaryStatisticsImpl();
+        for (int i = 0; i < statsList.size(); i++) {
+            SummaryStatistics stats = (SummaryStatistics) statsList.get(i);
+            meanSummary.addValue(stats.getMean());
+            stdSummary.addValue(stats.getStandardDeviation());
+            minSummary.addValue(stats.getMin());
+            maxSummary.addValue(stats.getMax());
+        }
+        logger.info("Overall statistics for the mean");
+        logger.info(meanSummary.toString());
+        logger.info("Overall statistics for the standard deviation");
+        logger.info(stdSummary.toString());
+        logger.info("Overall statistics for the min");
+        logger.info(minSummary.toString());
+        logger.info("Overall statistics for the max");
+        logger.info(maxSummary.toString());
+       }
+    
+    public void configureDataBase(String driver, String url,
+            String username, String password) {
+        this.driverClass = driver;
+        this.connectUrl = url;
+        this.connectUser = username;
+        this.connectPassword = password;
+    }
+    
+    public void configureConnectionFactory(String type,
+            String autoCommit, String readOnly, String validationQuery) {
+        this.driverType = type;
+        this.autocommit = Boolean.parseBoolean(autoCommit);
+        this.readOnly = Boolean.parseBoolean(readOnly);
+        this.validationQuery = validationQuery;
+    }
+    
+    public void configurePoolableConnectionFactory(String type, 
+            String poolPreparedStatements, String maxOpenStatements) {
+        this.factoryType = type;
+        this.poolPreparedStatements = 
+            Boolean.parseBoolean(poolPreparedStatements);
+        this.maxOpenStatements = Integer.parseInt(maxOpenStatements);
+    }
+    
+    public void configurePool(String maxActive, String maxIdle, String minIdle,
+            String maxWait, String exhaustedAction, String testOnBorrow,
+            String testOnReturn, String timeBetweenEvictions,
+            String testsPerEviction, String idleTimeout, 
+            String testWhileIdle, String type) throws ConfigurationException { 
+        this.maxActive = Integer.parseInt(maxActive);
+        this.maxIdle = Integer.parseInt(maxIdle);
+        this.maxWait = Long.parseLong(maxWait);
+        this.testOnBorrow = Boolean.parseBoolean(testOnBorrow);
+        this.testOnReturn = Boolean.parseBoolean(testOnReturn);
+        this.timeBetweenEvictions = Long.parseLong(timeBetweenEvictions);
+        this.testsPerEviction = Integer.parseInt(testsPerEviction);
+        this.idleTimeout = Long.parseLong(idleTimeout);
+        this.testWhileIdle = Boolean.parseBoolean(testWhileIdle);
+        this.poolType = type;
+        if (exhaustedAction.equals("block")) {
+            this.exhaustedAction = GenericObjectPool.WHEN_EXHAUSTED_BLOCK;
+        } else if (exhaustedAction.equals("fail")) {
+            this.exhaustedAction = GenericObjectPool.WHEN_EXHAUSTED_FAIL;
+        } else if (exhaustedAction.equals("grow")) {
+            this.exhaustedAction = GenericObjectPool.WHEN_EXHAUSTED_GROW;
+        } else { 
+            throw new ConfigurationException(
+            "Bad configuration setting for exhausted action: "
+                    + exhaustedAction); 
+        }           
+    }
+    
+    public void configureAbandonedConfig(String logAbandoned,
+            String removeAbandoned, String abandonedTimeout) {
+        abandonedConfig.setLogAbandoned(Boolean.parseBoolean(logAbandoned));
+        abandonedConfig.setRemoveAbandoned(
+                Boolean.parseBoolean(removeAbandoned));
+        abandonedConfig.setRemoveAbandonedTimeout(
+                Integer.parseInt(abandonedTimeout));
+    }
+    
+    public void configureRun(String queryType, String iterations,
+            String clients, String delay, String sigma, String delayType,
+            String rampType, String period, String cycleType) 
+            throws ConfigurationException {
+        this.queryType = queryType;
+        this.iterations = Long.parseLong(iterations);
+        this.numClients = Long.parseLong(clients);
+        this.delay = Long.parseLong(delay);
+        this.sigma = Double.parseDouble(sigma);
+        this.delayType = delayType;
+        this.rampType = rampType;
+        this.period = Long.parseLong(period);
+        this.cycleType = cycleType;
+        if (cycleType.equals("oscillating") && this.period <= 0) {
+            throw new ConfigurationException(
+              "Period must be positive for oscillating cycle type");
+        }
+    }
+    
+    private void makeTable() throws Exception {
+        Class.forName(driverClass); 
+        Connection db = DriverManager.getConnection(connectUrl,connectUser,
+                connectPassword);
+        try {
+            Statement sql = db.createStatement();
+            String sqlText = 
+                "create table test_table (indexed int, text varchar(20)," + 
+                " not_indexed int)";
+            sql.executeUpdate(sqlText);
+            sqlText = "CREATE INDEX test1_id_index ON test_table (indexed);";
+            sql.executeUpdate(sqlText);
+            RandomData randomData = new RandomDataImpl();
+            for (int i = 0; i < 10000; i++) {
+                int indexed = randomData.nextInt(0, 100);
+                int not_indexed = randomData.nextInt(0, 1000);
+                String text = randomData.nextHexString(20);
+                sqlText = 
+                    "INSERT INTO test_table (indexed, text, not_indexed)" + 
+                    "VALUES (" + indexed + "," + "'"+ text + "'," + 
+                    not_indexed + ");";
+                sql.executeUpdate(sqlText);
+            }
+            sql.close();
+        } finally {
+            db.close();
+        }
+    }
+    
+    public void configure() throws Exception {
+        Digester digester = new Digester();
+        digester.push(this);
+        
+        digester.addCallMethod("configuration/database",
+                "configureDataBase", 4);
+        digester.addCallParam("configuration/database/driver", 0);
+        digester.addCallParam("configuration/database/url", 1);
+        digester.addCallParam("configuration/database/username", 2);
+        digester.addCallParam("configuration/database/password", 3);
+        
+        digester.addCallMethod("configuration/connection-factory", 
+                "configureConnectionFactory", 4);
+        digester.addCallParam(
+                "configuration/connection-factory/type", 0);
+        digester.addCallParam(
+                "configuration/connection-factory/auto-commit", 1);
+        digester.addCallParam(
+                "configuration/connection-factory/read-only", 2);
+        digester.addCallParam(
+                "configuration/connection-factory/validation-query", 3);
+        
+        digester.addCallMethod("configuration/poolable-connection-factory", 
+                "configurePoolableConnectionFactory", 3);
+        digester.addCallParam(
+                "configuration/poolable-connection-factory/type", 0);
+        digester.addCallParam(
+                
"configuration/poolable-connection-factory/pool-prepared-statements", 1);
+        digester.addCallParam(
+                
"configuration/poolable-connection-factory/max-open-statements", 2);
+        
+        digester.addCallMethod("configuration/pool", 
+                "configurePool", 12);
+        digester.addCallParam(
+                "configuration/pool/max-active", 0);
+        digester.addCallParam(
+                "configuration/pool/max-idle", 1);
+        digester.addCallParam(
+                "configuration/pool/min-idle", 2);
+        digester.addCallParam(
+                "configuration/pool/max-wait", 3);
+        digester.addCallParam(
+                "configuration/pool/exhausted-action", 4);
+        digester.addCallParam(
+                "configuration/pool/test-on-borrow", 5);
+        digester.addCallParam(
+                "configuration/pool/test-on-return", 6);
+        digester.addCallParam(
+                "configuration/pool/time-between-evictions", 7);
+        digester.addCallParam(
+                "configuration/pool/tests-per-eviction", 8);
+        digester.addCallParam(
+                "configuration/pool/idle-timeout", 9);
+        digester.addCallParam(
+                "configuration/pool/test-while-idle", 10);
+        digester.addCallParam(
+                "configuration/pool/type", 11);
+        
+        digester.addCallMethod("configuration/run", 
+                "configureRun", 9);
+        digester.addCallParam(
+                "configuration/run/query-type", 0);
+        digester.addCallParam(
+                "configuration/run/iterations", 1);
+        digester.addCallParam(
+                "configuration/run/clients", 2);
+        digester.addCallParam(
+                "configuration/run/delay-mean", 3);
+        digester.addCallParam(
+                "configuration/run/delay-sigma", 4);
+        digester.addCallParam(
+                "configuration/run/delay-type", 5);
+        digester.addCallParam(
+                "configuration/run/ramp-type", 6);
+        digester.addCallParam(
+                "configuration/run/period", 7);
+        digester.addCallParam(
+                "configuration/run/cycle-type", 8);
+        
+        digester.addCallMethod("configuration/abandoned-config",
+                "configureAbandonedConfig", 3);
+        digester.addCallParam(
+                "configuration/abandoned-config/log-abandoned", 0);
+        digester.addCallParam(
+                "configuration/abandoned-config/remove-abandoned", 1);
+        digester.addCallParam(
+                "configuration/abandoned-config/abandoned-timeout", 2);
+        
+        digester.parse("/home/phil/dbcpTest/config.xml");
+        
+    }
+}

Added: 
jakarta/commons/sandbox/performance/src/java/org/apache/commons/performance/dbcp/DBCPTest.java
URL: 
http://svn.apache.org/viewvc/jakarta/commons/sandbox/performance/src/java/org/apache/commons/performance/dbcp/DBCPTest.java?view=auto&rev=556823
==============================================================================
--- 
jakarta/commons/sandbox/performance/src/java/org/apache/commons/performance/dbcp/DBCPTest.java
 (added)
+++ 
jakarta/commons/sandbox/performance/src/java/org/apache/commons/performance/dbcp/DBCPTest.java
 Mon Jul 16 23:11:36 2007
@@ -0,0 +1,33 @@
+/*
+ * 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.commons.performance.dbcp;
+
+/**
+ * Load / performance test runner.
+ *
+ */
+public class DBCPTest {
+    private static DBCPSoak soaker = new DBCPSoak();
+    public static void main(String[] args) {
+        try {
+            soaker.configure();
+            soaker.execute();
+        } catch (Exception ex) {
+            ex.printStackTrace();
+        }
+    }
+}



---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to