| /* |
| * 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.catalina.valves; |
| |
| import java.text.SimpleDateFormat; |
| import java.util.Date; |
| |
| import org.junit.Test; |
| |
| /** |
| * Some simple micro-benchmarks to help determine best approach for thread |
| * safety in valves, particularly the {@link AccessLogValve}. Implemented as |
| * JUnit tests to make the simple to execute but does not used Test* as the |
| * class name to avoid being included in the automated unit tests. |
| */ |
| public class Benchmarks { |
| @Test |
| public void testAccessLogGetDate() throws Exception { |
| // Is it better to use a sync or a thread local here? |
| BenchmarkTest benchmark = new BenchmarkTest(); |
| Runnable[] tests = new Runnable[] { new GetDateBenchmarkTest_Sync(), |
| new GetDateBenchmarkTest_Local(), |
| new GetDateBenchmarkTest_LocalMutableLong(), |
| new GetDateBenchmarkTest_LocalStruct() }; |
| benchmark.doTest(5, tests); |
| } |
| |
| private static class GetDateBenchmarkTest_Sync implements Runnable { |
| |
| @Override |
| public String toString() { |
| return "Syncs"; |
| } |
| |
| private volatile long currentMillis = 0; |
| private volatile Date currentDate = null; |
| |
| @Override |
| public void run() { |
| getCurrentDate(); |
| } |
| |
| public Date getCurrentDate() { |
| long systime = System.currentTimeMillis(); |
| if ((systime - currentMillis) > 1000) { |
| synchronized (this) { |
| if ((systime - currentMillis) > 1000) { |
| currentDate = new Date(systime); |
| currentMillis = systime; |
| } |
| } |
| } |
| return currentDate; |
| } |
| } |
| |
| private static class GetDateBenchmarkTest_Local implements Runnable { |
| |
| @Override |
| public String toString() { |
| return "ThreadLocals"; |
| } |
| |
| private ThreadLocal<Long> currentMillisLocal = new ThreadLocal<Long>() { |
| @Override |
| protected Long initialValue() { |
| return Long.valueOf(0); |
| } |
| }; |
| |
| private ThreadLocal<Date> currentDateLocal = new ThreadLocal<>(); |
| |
| @Override |
| public void run() { |
| getCurrentDate(); |
| } |
| |
| public Date getCurrentDate() { |
| long systime = System.currentTimeMillis(); |
| if ((systime - currentMillisLocal.get().longValue()) > 1000) { |
| currentDateLocal.set(new Date(systime)); |
| currentMillisLocal.set(Long.valueOf(systime)); |
| } |
| return currentDateLocal.get(); |
| } |
| } |
| |
| private static class GetDateBenchmarkTest_LocalMutableLong implements |
| Runnable { |
| |
| @Override |
| public String toString() { |
| return "ThreadLocals with a mutable Long"; |
| } |
| |
| private static class MutableLong { |
| long value = 0; |
| } |
| |
| private ThreadLocal<MutableLong> currentMillisLocal = new ThreadLocal<MutableLong>() { |
| @Override |
| protected MutableLong initialValue() { |
| return new MutableLong(); |
| } |
| }; |
| |
| private ThreadLocal<Date> currentDateLocal = new ThreadLocal<>(); |
| |
| @Override |
| public void run() { |
| getCurrentDate(); |
| } |
| |
| public Date getCurrentDate() { |
| long systime = System.currentTimeMillis(); |
| if ((systime - currentMillisLocal.get().value) > 1000) { |
| currentDateLocal.set(new Date(systime)); |
| currentMillisLocal.get().value = systime; |
| } |
| return currentDateLocal.get(); |
| } |
| } |
| |
| private static class GetDateBenchmarkTest_LocalStruct implements Runnable { |
| |
| @Override |
| public String toString() { |
| return "single ThreadLocal"; |
| } |
| |
| // note, that we can avoid (long -> Long) conversion |
| private static class Struct { |
| public long currentMillis = 0; |
| public Date currentDate; |
| } |
| |
| private ThreadLocal<Struct> currentStruct = new ThreadLocal<Struct>() { |
| @Override |
| protected Struct initialValue() { |
| return new Struct(); |
| } |
| }; |
| |
| @Override |
| public void run() { |
| getCurrentDate(); |
| } |
| |
| public Date getCurrentDate() { |
| Struct struct = currentStruct.get(); |
| long systime = System.currentTimeMillis(); |
| if ((systime - struct.currentMillis) > 1000) { |
| struct.currentDate = new Date(systime); |
| struct.currentMillis = systime; |
| } |
| return struct.currentDate; |
| } |
| } |
| |
| @Test |
| public void testAccessLogTimeDateElement() throws Exception { |
| // Is it better to use a sync or a thread local here? |
| BenchmarkTest benchmark = new BenchmarkTest(); |
| Runnable[] tests = new Runnable[] { |
| new TimeDateElementBenchmarkTest_Sync(), |
| new TimeDateElementBenchmarkTest_Local(), |
| new TimeDateElementBenchmarkTest_LocalStruct(), |
| new TimeDateElementBenchmarkTest_LocalStruct_SBuilder() }; |
| benchmark.doTest(5, tests); |
| } |
| |
| private abstract static class TimeDateElementBenchmarkTestBase { |
| protected static final String months[] = { "Jan", "Feb", "Mar", "Apr", |
| "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; |
| |
| protected String lookup(String month) { |
| int index; |
| try { |
| index = Integer.parseInt(month) - 1; |
| } catch (Throwable t) { |
| index = 0; // Can not happen, in theory |
| } |
| return (months[index]); |
| } |
| } |
| |
| private static class TimeDateElementBenchmarkTest_Sync extends |
| TimeDateElementBenchmarkTestBase implements Runnable { |
| |
| @Override |
| public String toString() { |
| return "Syncs"; |
| } |
| |
| private volatile Date currentDate = new Date(); |
| private volatile String currentDateString = null; |
| private SimpleDateFormat dayFormatter = new SimpleDateFormat("dd"); |
| private SimpleDateFormat monthFormatter = new SimpleDateFormat("MM"); |
| private SimpleDateFormat yearFormatter = new SimpleDateFormat("yyyy"); |
| private SimpleDateFormat timeFormatter = new SimpleDateFormat( |
| "hh:mm:ss"); |
| |
| @Override |
| public void run() { |
| printDate(); |
| } |
| |
| public String printDate() { |
| StringBuilder buf = new StringBuilder(); |
| Date date = getDateSync(); |
| if (currentDate != date) { |
| synchronized (this) { |
| if (currentDate != date) { |
| StringBuilder current = new StringBuilder(32); |
| current.append('['); |
| current.append(dayFormatter.format(date)); // Day |
| current.append('/'); |
| current.append(lookup(monthFormatter.format(date))); // Month |
| current.append('/'); |
| current.append(yearFormatter.format(date)); // Year |
| current.append(':'); |
| current.append(timeFormatter.format(date)); // Time |
| current.append(']'); |
| currentDateString = current.toString(); |
| currentDate = date; |
| } |
| } |
| } |
| buf.append(currentDateString); |
| return buf.toString(); |
| } |
| |
| private Date getDateSync() { |
| long systime = System.currentTimeMillis(); |
| if ((systime - currentDate.getTime()) > 1000) { |
| synchronized (this) { |
| if ((systime - currentDate.getTime()) > 1000) { |
| currentDate.setTime(systime); |
| } |
| } |
| } |
| return currentDate; |
| } |
| } |
| |
| private static class TimeDateElementBenchmarkTest_Local extends |
| TimeDateElementBenchmarkTestBase implements Runnable { |
| |
| @Override |
| public String toString() { |
| return "ThreadLocals"; |
| } |
| |
| private ThreadLocal<String> currentDateStringLocal = new ThreadLocal<>(); |
| |
| private ThreadLocal<Date> currentDateLocal = new ThreadLocal<Date>() { |
| @Override |
| protected Date initialValue() { |
| return new Date(); |
| } |
| }; |
| private ThreadLocal<SimpleDateFormat> dayFormatterLocal = new ThreadLocal<SimpleDateFormat>() { |
| @Override |
| protected SimpleDateFormat initialValue() { |
| return new SimpleDateFormat("dd"); |
| } |
| }; |
| private ThreadLocal<SimpleDateFormat> monthFormatterLocal = new ThreadLocal<SimpleDateFormat>() { |
| @Override |
| protected SimpleDateFormat initialValue() { |
| return new SimpleDateFormat("MM"); |
| } |
| }; |
| private ThreadLocal<SimpleDateFormat> yearFormatterLocal = new ThreadLocal<SimpleDateFormat>() { |
| @Override |
| protected SimpleDateFormat initialValue() { |
| return new SimpleDateFormat("yyyy"); |
| } |
| }; |
| private ThreadLocal<SimpleDateFormat> timeFormatterLocal = new ThreadLocal<SimpleDateFormat>() { |
| @Override |
| protected SimpleDateFormat initialValue() { |
| return new SimpleDateFormat("hh:mm:ss"); |
| } |
| }; |
| |
| @Override |
| public void run() { |
| printDate(); |
| } |
| |
| public String printDate() { |
| getDateLocal(); |
| if (currentDateStringLocal.get() == null) { |
| StringBuilder current = new StringBuilder(32); |
| current.append('['); |
| current.append(dayFormatterLocal.get().format( |
| currentDateLocal.get())); // Day |
| current.append('/'); |
| current.append(lookup(monthFormatterLocal.get().format( |
| currentDateLocal.get()))); // Month |
| current.append('/'); |
| current.append(yearFormatterLocal.get().format( |
| currentDateLocal.get())); // Year |
| current.append(':'); |
| current.append(timeFormatterLocal.get().format( |
| currentDateLocal.get())); // Time |
| current.append(']'); |
| currentDateStringLocal.set(current.toString()); |
| } |
| return currentDateStringLocal.get(); |
| } |
| |
| private Date getDateLocal() { |
| long systime = System.currentTimeMillis(); |
| if ((systime - currentDateLocal.get().getTime()) > 1000) { |
| currentDateLocal.get().setTime(systime); |
| currentDateStringLocal.set(null); |
| } |
| return currentDateLocal.get(); |
| } |
| } |
| |
| private static class TimeDateElementBenchmarkTest_LocalStruct extends |
| TimeDateElementBenchmarkTestBase implements Runnable { |
| |
| @Override |
| public String toString() { |
| return "single ThreadLocal"; |
| } |
| |
| private static class Struct { |
| public String currentDateString; |
| public Date currentDate = new Date(); |
| public SimpleDateFormat dayFormatter = new SimpleDateFormat("dd"); |
| public SimpleDateFormat monthFormatter = new SimpleDateFormat("MM"); |
| public SimpleDateFormat yearFormatter = new SimpleDateFormat("yyyy"); |
| public SimpleDateFormat timeFormatter = new SimpleDateFormat( |
| "hh:mm:ss"); |
| } |
| |
| private ThreadLocal<Struct> structLocal = new ThreadLocal<Struct>() { |
| @Override |
| protected Struct initialValue() { |
| return new Struct(); |
| } |
| }; |
| |
| @Override |
| public void run() { |
| printDate(); |
| } |
| |
| public String printDate() { |
| getDateLocal(); |
| Struct struct = structLocal.get(); |
| if (struct.currentDateString == null) { |
| StringBuilder current = new StringBuilder(32); |
| current.append('['); |
| current.append(struct.dayFormatter.format(struct.currentDate)); // Day |
| current.append('/'); |
| current.append(lookup(struct.monthFormatter |
| .format(struct.currentDate))); // Month |
| current.append('/'); |
| current.append(struct.yearFormatter.format(struct.currentDate)); // Year |
| current.append(':'); |
| current.append(struct.timeFormatter.format(struct.currentDate)); // Time |
| current.append(']'); |
| struct.currentDateString = current.toString(); |
| } |
| return struct.currentDateString; |
| } |
| |
| private Date getDateLocal() { |
| Struct struct = structLocal.get(); |
| long systime = System.currentTimeMillis(); |
| if ((systime - struct.currentDate.getTime()) > 1000) { |
| struct.currentDate.setTime(systime); |
| struct.currentDateString = null; |
| } |
| return struct.currentDate; |
| } |
| } |
| |
| private static class TimeDateElementBenchmarkTest_LocalStruct_SBuilder extends |
| TimeDateElementBenchmarkTestBase implements Runnable { |
| |
| @Override |
| public String toString() { |
| return "single ThreadLocal, with StringBuilder"; |
| } |
| |
| private static class Struct { |
| public String currentDateString; |
| public Date currentDate = new Date(); |
| public SimpleDateFormat dayFormatter = new SimpleDateFormat("dd"); |
| public SimpleDateFormat monthFormatter = new SimpleDateFormat("MM"); |
| public SimpleDateFormat yearFormatter = new SimpleDateFormat("yyyy"); |
| public SimpleDateFormat timeFormatter = new SimpleDateFormat( |
| "hh:mm:ss"); |
| } |
| |
| private ThreadLocal<Struct> structLocal = new ThreadLocal<Struct>() { |
| @Override |
| protected Struct initialValue() { |
| return new Struct(); |
| } |
| }; |
| |
| @Override |
| public void run() { |
| printDate(); |
| } |
| |
| public String printDate() { |
| getDateLocal(); |
| Struct struct = structLocal.get(); |
| if (struct.currentDateString == null) { |
| StringBuilder current = new StringBuilder(32); |
| current.append('['); |
| current.append(struct.dayFormatter.format(struct.currentDate)); // Day |
| current.append('/'); |
| current.append(lookup(struct.monthFormatter |
| .format(struct.currentDate))); // Month |
| current.append('/'); |
| current.append(struct.yearFormatter.format(struct.currentDate)); // Year |
| current.append(':'); |
| current.append(struct.timeFormatter.format(struct.currentDate)); // Time |
| current.append(']'); |
| struct.currentDateString = current.toString(); |
| } |
| return struct.currentDateString; |
| } |
| |
| private Date getDateLocal() { |
| Struct struct = structLocal.get(); |
| long systime = System.currentTimeMillis(); |
| if ((systime - struct.currentDate.getTime()) > 1000) { |
| struct.currentDate.setTime(systime); |
| struct.currentDateString = null; |
| } |
| return struct.currentDate; |
| } |
| } |
| |
| private static class BenchmarkTest { |
| public void doTest(int threadCount, Runnable[] tests) throws Exception { |
| for (int iterations = 1000000; iterations < 10000001; iterations += 1000000) { |
| for (int i = 0; i < tests.length; i++) { |
| doTestInternal(threadCount, iterations, tests[i]); |
| } |
| } |
| } |
| |
| private void doTestInternal(int threadCount, int iterations, |
| Runnable test) throws Exception { |
| long start = System.currentTimeMillis(); |
| Thread[] threads = new Thread[threadCount]; |
| for (int i = 0; i < threadCount; i++) { |
| threads[i] = new Thread(new TestThread(iterations, test)); |
| } |
| for (int i = 0; i < threadCount; i++) { |
| threads[i].start(); |
| } |
| for (int i = 0; i < threadCount; i++) { |
| threads[i].join(); |
| } |
| long end = System.currentTimeMillis(); |
| |
| System.out.println(test.getClass().getSimpleName() + ": " |
| + threadCount + " threads and " + iterations |
| + " iterations using " + test + " took " + (end - start) |
| + "ms"); |
| } |
| } |
| |
| private static class TestThread implements Runnable { |
| private int count; |
| private Runnable test; |
| |
| public TestThread(int count, Runnable test) { |
| this.count = count; |
| this.test = test; |
| } |
| |
| @Override |
| public void run() { |
| for (int i = 0; i < count; i++) { |
| test.run(); |
| } |
| } |
| } |
| } |