Hi,

I've been looking into the performance problems associated with many
concurrent requests of large directories. After doing some informal
benchmarking I've come up with the attached patch that improves
performance in this scenario. The patch adds a size limited Map with a 5
second timeout for caching rendered directory listings. My tests show a
significant performance improvement and the server no longer keels over
from OutOfMemory exceptions at higher concurrency levels.
Below are the results of the benchmarks I performed against the
unmodified server.

    c | total             | mean
  -------------------------------------------------------------
    1 | 33.72985 seconds  | 330.730 ms
   10 | 12.807910 seconds | 1280.791 ms
   20 | 12.641188 seconds | 2528.238 ms
   30 | 12.930321 seconds | 3879.096 ms
   40 | --                | --
   50 | --                | --
   60 | --                | --
   70 | --                | --
   80 | --                | --
   90 | --                | --
  100 | --                | --


Here are the same benchmarks with the attached patch applied:

    c | total             | mean
  -------------------------------------------------------------
    1 | 0.949313 seconds  | 9.493 ms
   10 | 0.148746 seconds  | 14.875 ms
   20 | 0.167047 seconds  | 33.409 ms
   30 | 0.198910 seconds  | 59.673 ms
   40 | 0.202527 seconds  | 81.011 ms
   50 | 0.176821 seconds  | 88.410 ms
   60 | 0.203042 seconds  | 121.825 ms
   70 | 0.257176 seconds  | 180.023 ms
   80 | 0.283165 seconds  | 226.532 ms
   90 | 0.304787 seconds  | 274.308 ms
  100 | 0.196776 seconds  | 196.776 ms

  c = number of concurrent requests (see the -c flag for ab)
  total = total time for benchmark to complete
  mean = the average time until each request is complete
  -- = benchmark timed out

  In all cases the total number of requests is 100 and the test
  directory being listed contains 2000 empty files.

-- 
Rafael H. Schloming <[EMAIL PROTECTED]>
Index: container/catalina/src/share/org/apache/catalina/servlets/DefaultServlet.java
===================================================================
--- container/catalina/src/share/org/apache/catalina/servlets/DefaultServlet.java	(revision 347964)
+++ container/catalina/src/share/org/apache/catalina/servlets/DefaultServlet.java	(working copy)
@@ -32,7 +32,10 @@
 import java.io.Reader;
 import java.io.StringReader;
 import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
 import java.util.Enumeration;
+import java.util.LinkedHashMap;
+import java.util.Map;
 import java.util.StringTokenizer;
 import java.util.Vector;
 
@@ -1111,22 +1114,64 @@
         return result;
     }
 
+    private long cacheTTL = 5000; // 5 seconds
+    private int cacheMax = 1000; // keep at most 1000 listings
 
+    // cache for directory listings
+    private Map cache = new LinkedHashMap(100, (float) 0.75, true) {
+        protected boolean removeEldestEntry(Map.Entry entry) {
+            return size() > cacheMax;
+        }
+    };
 
     /**
+     * Struct for storing directory listing and timestamp.
+     */
+    private static class Listing {
+        public byte[] bytes;
+        public long timestamp;
+    }
+
+    /**
+     * Return true if the directory listing is expired.
+     */
+    private boolean isExpired(Listing lst) {
+        return System.currentTimeMillis() - lst.timestamp > cacheTTL;
+    }
+
+    protected InputStream render
+        (String contextPath, CacheEntry cacheEntry) throws IOException {
+        synchronized (cache) {
+            Listing lst = (Listing) cache.get(contextPath);
+            if (lst == null || isExpired(lst)) {
+                if (lst == null) { lst = new Listing(); }
+                lst.bytes = doRender(contextPath, cacheEntry);
+                lst.timestamp = System.currentTimeMillis();
+                cache.put(contextPath, lst);
+            }
+            return new ByteArrayInputStream(lst.bytes);
+        }
+    }
+
+    /**
      *  Decide which way to render. HTML or XML.
      */
-    protected InputStream render
-        (String contextPath, CacheEntry cacheEntry) {
+    protected byte[] doRender
+        (String contextPath, CacheEntry cacheEntry) throws IOException {
         InputStream xsltInputStream =
             findXsltInputStream(cacheEntry.context);
 
+        String str;
         if (xsltInputStream==null) {
-            return renderHtml(contextPath, cacheEntry);
+            str = renderHtml(contextPath, cacheEntry);
         } else {
-            return renderXml(contextPath, cacheEntry, xsltInputStream);
+            str = renderXml(contextPath, cacheEntry, xsltInputStream);
         }
-
+        try {
+            return str.getBytes("UTF8");
+        } catch (UnsupportedEncodingException e) {
+            return str.getBytes();
+        }
     }
 
     /**
@@ -1136,9 +1181,9 @@
      * @param contextPath Context path to which our internal paths are
      *  relative
      */
-    protected InputStream renderXml(String contextPath,
-                                    CacheEntry cacheEntry,
-                                    InputStream xsltInputStream) {
+    protected String renderXml(String contextPath,
+                               CacheEntry cacheEntry,
+                               InputStream xsltInputStream) {
 
         StringBuffer sb = new StringBuffer();
 
@@ -1230,12 +1275,11 @@
             Source xslSource = new StreamSource(xsltInputStream);
             Transformer transformer = tFactory.newTransformer(xslSource);
 
-            ByteArrayOutputStream stream = new ByteArrayOutputStream();
-            OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
-            StreamResult out = new StreamResult(osWriter);
+            StringWriter writer = new StringWriter();
+            StreamResult out = new StreamResult(writer);
             transformer.transform(xmlSource, out);
-            osWriter.flush();
-            return (new ByteArrayInputStream(stream.toByteArray()));
+            writer.flush();
+            return writer.toString();
         } catch (Exception e) {
             log("directory transform failure: " + e.getMessage());
             return renderHtml(contextPath, cacheEntry);
@@ -1249,7 +1293,7 @@
      * @param contextPath Context path to which our internal paths are
      *  relative
      */
-    protected InputStream renderHtml
+    protected String renderHtml
         (String contextPath, CacheEntry cacheEntry) {
 
         String name = cacheEntry.name;
@@ -1261,17 +1305,6 @@
         if (name.equals("/"))
             trim = 1;
 
-        // Prepare a writer to a buffered area
-        ByteArrayOutputStream stream = new ByteArrayOutputStream();
-        OutputStreamWriter osWriter = null;
-        try {
-            osWriter = new OutputStreamWriter(stream, "UTF8");
-        } catch (Exception e) {
-            // Should never happen
-            osWriter = new OutputStreamWriter(stream);
-        }
-        PrintWriter writer = new PrintWriter(osWriter);
-
         StringBuffer sb = new StringBuffer();
         
         // rewriteUrl(contextPath) is expensive. cache result for later reuse
@@ -1406,11 +1439,7 @@
         sb.append("</body>\r\n");
         sb.append("</html>\r\n");
 
-        // Return an input stream to the underlying bytes
-        writer.write(sb.toString());
-        writer.flush();
-        return (new ByteArrayInputStream(stream.toByteArray()));
-
+        return sb.toString();
     }
 
 

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

Reply via email to