1 // Copyright (c) 2012 Jeff Ichnowski 2 // All rights reserved. 3 // 4 // Redistribution and use in source and binary forms, with or without 5 // modification, are permitted provided that the following conditions 6 // are met: 7 // 8 // * Redistributions of source code must retain the above 9 // copyright notice, this list of conditions and the following 10 // disclaimer. 11 // 12 // * Redistributions in binary form must reproduce the above 13 // copyright notice, this list of conditions and the following 14 // disclaimer in the documentation and/or other materials 15 // provided with the distribution. 16 // 17 // * Neither the name of the OWASP nor the names of its 18 // contributors may be used to endorse or promote products 19 // derived from this software without specific prior written 20 // permission. 21 // 22 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 // COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 27 // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 28 // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 29 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 31 // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 32 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 33 // OF THE POSSIBILITY OF SUCH DAMAGE. 34 35 package org.owasp.encoder; 36 37 import java.io.BufferedReader; 38 import java.io.InputStream; 39 import java.io.InputStreamReader; 40 import java.util.ArrayList; 41 import java.util.List; 42 import java.util.concurrent.CountDownLatch; 43 import java.util.concurrent.TimeUnit; 44 45 import junit.framework.Test; 46 import junit.framework.TestCase; 47 import junit.framework.TestSuite; 48 49 /** 50 * BenchmarkTest -- tests that the encoders operate reasonably fast. 51 * 52 * @author Jeff Ichnowski 53 */ 54 public class BenchmarkTest extends TestCase { 55 56 static final int LOOPS = 5000; 57 suite()58 public static Test suite() throws Exception { 59 // For benchmarking, we want to make sure that we warm-up 60 // (e.g. get everything in caches, etc...) 61 // then... Establish the baseline 62 // then... run each benchmark 63 // in order. The benchmark value needs to be stored somewhere 64 // so we attach it to the TestSuite instance itself. 65 66 // We allow a system property to disable benchmark tests. This is 67 // particularly useful when running code coverage as the coverage 68 // instrumentation can slow everything down. 69 if ("false".equalsIgnoreCase( 70 System.getProperty("org.owasp.encoder.benchmark", "true"))) 71 { 72 return new TestSuite(BenchmarkTest.class); 73 } 74 75 return new TestSuite() { 76 final String[] _samples = loadSamples(); 77 final String[] _output = new String[_samples.length]; 78 long _baseline; 79 double _denom = (double) (LOOPS * _samples.length); 80 81 { 82 addTest(new TestCase("warmup") { 83 @Override 84 protected void runTest() throws Throwable { 85 int loops = 100; 86 87 runBench(BASELINE, loops, _samples, _output); 88 89 for (Bench bench : BENCHMARKS) { 90 runBench(bench, loops, _samples, _output); 91 } 92 } 93 }); 94 95 addTest(new TestCase("baseline") { 96 @Override 97 protected void runTest() throws Throwable { 98 _baseline = runBench(BASELINE, LOOPS, _samples, _output); 99 System.out.printf("Baseline is %f ns/op\n", 100 _baseline / _denom); 101 } 102 }); 103 104 for (final Bench bench : BENCHMARKS) { 105 addTest(new TestCase(bench.toString()) { 106 @Override 107 protected void runTest() throws Throwable { 108 long time = runBench(bench, LOOPS, _samples, _output); 109 110 double percentOnBaseline = (time - _baseline) * 100.0 / _baseline; 111 112 System.out.printf("Benchmarked %s: %f ns/op (%+.2f%% on baseline)\n", 113 bench, time / _denom, percentOnBaseline); 114 115 assertTrue(percentOnBaseline < 200); 116 } 117 }); 118 } 119 } 120 }; 121 } 122 loadSamples()123 static String[] loadSamples() throws Exception { 124 List<String> lines = new ArrayList<String>(); 125 InputStream in = BenchmarkTest.class 126 .getResourceAsStream("benchmark-data-2.txt"); 127 128 try { 129 BufferedReader reader = new BufferedReader( 130 new InputStreamReader(in, "utf-8")); 131 String line; 132 while ((line = reader.readLine()) != null) { 133 lines.add(line.replaceAll("\\\\r", "\r").replaceAll("\\\\n", "\n")); 134 } 135 } finally { 136 in.close(); 137 } 138 139 return lines.toArray(new String[lines.size()]); 140 } 141 rungc()142 private static void rungc() { 143 // Code snippet from Caliper micro-benchmarking suite. 144 System.gc(); 145 System.runFinalization(); 146 final CountDownLatch latch = new CountDownLatch(1); 147 new Object() { 148 @Override 149 protected void finalize() throws Throwable { 150 latch.countDown(); 151 } 152 }; 153 System.gc(); 154 System.runFinalization(); 155 156 try { 157 if (!latch.await(2, TimeUnit.SECONDS)) { 158 System.out.println("WARNING! GC did collect/finalize in allotted time."); 159 } 160 } catch (InterruptedException e) { 161 throw new AssertionError(e); 162 } 163 } 164 runBench(Bench bench, int loops, String[] input, String[] output)165 private static long runBench(Bench bench, int loops, String[] input, String[] output) { 166 rungc(); 167 168 final int n = input.length; 169 long start = System.nanoTime(); 170 for (int phase=0 ; phase < 2 ; ++phase) { 171 for (int loop=0 ; loop<loops ; ++loop) { 172 for (int i=0 ; i<n ; ++i) { 173 output[i] = bench.encode(input[i]); 174 } 175 } 176 } 177 return System.nanoTime() - start; 178 } 179 180 static abstract class Bench { 181 final String _name; Bench(String name)182 public Bench(String name) {_name = name;} encode(String input)183 public abstract String encode(String input); toString()184 public String toString() { return _name; } 185 } 186 187 static Bench BASELINE = new Bench("baseline") { 188 @Override 189 public String encode(String input) { 190 StringBuilder buf = new StringBuilder(input.length()); 191 for (int i=0 ; i<input.length() ; ++i) { 192 buf.append(input.charAt(i)); 193 } 194 return buf.toString(); 195 196 // Here is an alternate, faster, baseline. 197 198 // int n = input.length(); 199 // char[] buf = new char[n]; 200 // input.getChars(0, n, buf, 0); 201 // return new String(buf); 202 } 203 }; 204 205 static Bench[] BENCHMARKS = { 206 new Bench("Encode.forXml") { 207 @Override 208 public String encode(String input) { 209 return Encode.forXml(input); 210 } 211 }, 212 new Bench("Encode.forHtmlUnquotedAttribute") { 213 @Override 214 public String encode(String input) { 215 return Encode.forHtmlUnquotedAttribute(input); 216 } 217 }, 218 new Bench("Encode.forJavaScript") { 219 @Override 220 public String encode(String input) { 221 return Encode.forJavaScript(input); 222 } 223 }, 224 new Bench("Encode.forCssString") { 225 @Override 226 public String encode(String input) { 227 return Encode.forCssString(input); 228 } 229 }, 230 new Bench("Encode.forUriComponent") { 231 @Override 232 public String encode(String input) { 233 return Encode.forUriComponent(input); 234 } 235 }, 236 new Bench("Encode.forCDATA") { 237 @Override 238 public String encode(String input) { 239 return Encode.forCDATA(input); 240 } 241 }, 242 new Bench("Encode.forJava") { 243 @Override 244 public String encode(String input) { 245 return Encode.forJava(input); 246 } 247 }, 248 new Bench("Encode.forXmlComment") { 249 @Override 250 public String encode(String input) { 251 return Encode.forXmlComment(input); 252 } 253 }, 254 }; 255 testNothing()256 public void testNothing() throws Exception { 257 System.out.println("Warning: benchmark unit tests have been disabled"); 258 assertTrue(true); 259 } 260 } 261