blob: beb145687c211b858245cbb16a40cb5cd37ccdff [file] [log] [blame]
/*
* 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.logging.log4j.perf.jmh;
import java.nio.charset.Charset;
import java.util.Deque;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedDeque;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.ThreadContext.ContextStack;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.DefaultConfiguration;
import org.apache.logging.log4j.core.impl.Log4jLogEvent;
import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
import org.apache.logging.log4j.core.pattern.PatternFormatter;
import org.apache.logging.log4j.core.pattern.PatternParser;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.SimpleMessage;
import org.apache.logging.log4j.util.StringMap;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
/**
* Checks PatternLayout performance when reusing the StringBuilder in a ThreadLocal, an ObjectPool or when creating a
* new instance for each log event.
*/
// ============================== HOW TO RUN THIS TEST: ====================================
// (Quick build: mvn -DskipTests=true clean package -pl log4j-perf -am )
//
// single thread:
// java -jar log4j-perf/target/benchmarks.jar ".*ThreadLocalVsPool.*" -f 1 -wi 10 -i 20 -tu ns -bm sample
//
// four threads:
// java -jar log4j-perf/target/benchmarks.jar ".*ThreadLocalVsPool.*" -f 1 -wi 10 -i 20 -tu ns -bm sample -t 4
//
// Usage help:
// java -jar log4j-perf/target/benchmarks.jar -help
//
@State(Scope.Benchmark)
public class ThreadLocalVsPoolBenchmark {
static final Charset CHARSET_DEFAULT = Charset.defaultCharset();
static final String LOG4JPATTERN = "%d %5p [%t] %c{1} %X{transactionId} - %m%n";
static final int DEFAULT_STRING_BUILDER_SIZE = 1024;
/**
* The LogEvent to serialize.
*/
private final static LogEvent LOG4J2EVENT = createLog4j2Event();
/**
* Initial converter for pattern.
*/
private final static PatternFormatter[] formatters = createFormatters();
private final StringBuilderPool pool = new StringBuilderPool(DEFAULT_STRING_BUILDER_SIZE);
private static ThreadLocal<StringBuilder> threadLocal = new ThreadLocal<>();
/**
*/
private static PatternFormatter[] createFormatters() {
final Configuration config = new DefaultConfiguration();
final PatternParser parser = new PatternParser(config, "Converter", LogEventPatternConverter.class);
final List<PatternFormatter> result = parser.parse(LOG4JPATTERN, false, true);
return result.toArray(new PatternFormatter[result.size()]);
}
@Benchmark
public byte[] newInstance() {
return serializeWithNewInstance(LOG4J2EVENT).getBytes(CHARSET_DEFAULT);
}
@Benchmark
public byte[] threadLocal() {
return serializeWithThreadLocal(LOG4J2EVENT).getBytes(CHARSET_DEFAULT);
}
@Benchmark
public byte[] objectPool() {
return serializeWithPool(LOG4J2EVENT).getBytes(CHARSET_DEFAULT);
}
@Benchmark
public String _stringNewInstance() {
return serializeWithNewInstance(LOG4J2EVENT);
}
@Benchmark
public String _stringThreadLocal() {
return serializeWithThreadLocal(LOG4J2EVENT);
}
@Benchmark
public String _stringObjectPool() {
return serializeWithPool(LOG4J2EVENT);
}
private String serializeWithNewInstance(final LogEvent event) {
final StringBuilder buf = new StringBuilder(DEFAULT_STRING_BUILDER_SIZE);
return serialize(event, buf);
}
private String serializeWithThreadLocal(final LogEvent event) {
StringBuilder buf = threadLocal.get();
if (buf == null) {
buf = new StringBuilder(DEFAULT_STRING_BUILDER_SIZE);
threadLocal.set(buf);
}
buf.setLength(0);
return serialize(event, buf);
}
private String serializeWithPool(final LogEvent event) {
final StringBuilder buf = pool.borrowObject();
try {
buf.setLength(0);
return serialize(event, buf);
} finally {
pool.returnObject(buf);
}
}
private String serialize(final LogEvent event, final StringBuilder buf) {
final int len = formatters.length;
for (int i = 0; i < len; i++) {
formatters[i].format(event, buf);
}
return buf.toString();
}
private static LogEvent createLog4j2Event() {
final Marker marker = null;
final String fqcn = "com.mycom.myproject.mypackage.MyClass";
final Level level = Level.DEBUG;
final String STR = "AB!(%087936DZYXQWEIOP$#^~-=/><nb"; // length=32
final Message message = new SimpleMessage(STR);
final Throwable t = null;
final StringMap mdc = null;
final ContextStack ndc = null;
final String threadName = null;
final StackTraceElement location = null;
final long timestamp = 12345678;
return Log4jLogEvent.newBuilder() //
.setLoggerName("name(ignored)") //
.setMarker(marker) //
.setLoggerFqcn(fqcn) //
.setLevel(level) //
.setMessage(message) //
.setThrown(t) //
.setContextData(mdc) //
.setContextStack(ndc) //
.setThreadName(threadName) //
.setSource(location) //
.setTimeMillis(timestamp) //
.build();
}
}
/**
*
*/
abstract class ObjectPool<T> {
private final Deque<T> pool = new ConcurrentLinkedDeque<>();
public T borrowObject() {
final T object = pool.poll();
return object == null ? createObject() : object;
}
public void returnObject(final T object) {
pool.add(object);
}
protected abstract T createObject();
}
/**
*
*/
class StringBuilderPool extends ObjectPool<StringBuilder> {
private final int initialSize;
public StringBuilderPool(final int stringBuilderSize) {
this.initialSize = stringBuilderSize;
}
@Override
public void returnObject(final StringBuilder stringBuilder) {
stringBuilder.setLength(0);
super.returnObject(stringBuilder);
}
@Override
protected StringBuilder createObject() {
return new StringBuilder(initialSize);
}
}