/* * 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.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import java.util.concurrent.TimeUnit; import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.ThreadContextBenchmarkAccess; import org.apache.logging.log4j.core.config.Property; import org.apache.logging.log4j.core.ContextDataInjector; import org.apache.logging.log4j.core.impl.ContextDataInjectorFactory; import org.apache.logging.log4j.perf.nogc.OpenHashStringMap; import org.apache.logging.log4j.spi.CopyOnWriteOpenHashMapThreadContextMap; import org.apache.logging.log4j.spi.DefaultThreadContextMap; import org.apache.logging.log4j.spi.GarbageFreeOpenHashMapThreadContextMap; import org.apache.logging.log4j.util.SortedArrayStringMap; import org.apache.logging.log4j.util.StringMap; import org.apache.logging.log4j.spi.ThreadContextMap; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Param; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.TearDown; import org.openjdk.jmh.annotations.Warmup; /** * Compares performance of ThreadContextMap implementations. */ // ============================== 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 ".*ThreadContextBench.*" // // four threads: // java -jar log4j-perf/target/benchmarks.jar ".*ThreadContextBench.*" -f 1 -wi 10 -i 20 -tu ns -bm sample -t 4 // // Usage help: // java -jar log4j-perf/target/benchmarks.jar -help // @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) @Fork(1) @State(Scope.Benchmark) public class ThreadContextBenchmark { private static final String DEFAULT_CONTEXT_MAP = "Default"; private static final String COPY_OPENHASH_MAP = "CopyOpenHash"; private static final String COPY_ARRAY_MAP = "CopySortedArray"; private static final String NO_GC_OPENHASH_MAP = "NoGcOpenHash"; private static final String NO_GC_ARRAY_MAP = "NoGcSortedArray"; private static final Map<String, Class<? extends ThreadContextMap>> IMPLEMENTATIONS = new HashMap<>(); static { IMPLEMENTATIONS.put(DEFAULT_CONTEXT_MAP, DefaultThreadContextMap.class); IMPLEMENTATIONS.put(COPY_OPENHASH_MAP, CopyOnWriteOpenHashMapThreadContextMap.class); IMPLEMENTATIONS.put(COPY_ARRAY_MAP, CopyOnWriteOpenHashMapThreadContextMap.SUPER); //CopyOnWriteSortedArrayThreadContextMap.class); IMPLEMENTATIONS.put(NO_GC_OPENHASH_MAP, GarbageFreeOpenHashMapThreadContextMap.class); IMPLEMENTATIONS.put(NO_GC_ARRAY_MAP, GarbageFreeOpenHashMapThreadContextMap.SUPER); //GarbageFreeSortedArrayThreadContextMap.class); } @Param({ "Default", "CopyOpenHash", "CopySortedArray", "NoGcOpenHash", "NoGcSortedArray"}) //@Param({ "Default", }) // for legecyInject benchmarks public String threadContextMapAlias; @Param({"5", "50", "500"}) public int count; private final int KEY_LENGTH = 16; private String[] keys; private String[] values; private List<Property> propertyList; private ContextDataInjector injector; private StringMap reusableContextData; @Setup public void setup() { System.setProperty("log4j2.threadContextMap", IMPLEMENTATIONS.get(threadContextMapAlias).getName()); ThreadContextBenchmarkAccess.init(); injector = ContextDataInjectorFactory.createInjector(); System.out.println(threadContextMapAlias + ": Injector = " + injector); reusableContextData = threadContextMapAlias.contains("Array") ? new SortedArrayStringMap() : new OpenHashStringMap<>(); keys = new String[count]; values = new String[count]; final Random r = new Random(); for (int j = 0; j < keys.length; j++) { final char[] str = new char[KEY_LENGTH]; for (int i = 0; i < str.length; i++) { str[i] = (char) r.nextInt(); } keys[j] = new String(str); values[j] = new String(str); } final int PROPERTIES_COUNT = 5; // count propertyList = new ArrayList<>(PROPERTIES_COUNT); for (int j = 0; j < PROPERTIES_COUNT; j++) { final char[] str = new char[KEY_LENGTH]; for (int i = 0; i < str.length; i++) { str[i] = (char) r.nextInt(); } propertyList.add(Property.createProperty(new String(str), new String(str))); } clearAndPut(); // ensure ThreadContext contains values } @TearDown public void tearDown() { System.clearProperty("log4j2.threadContextMap"); ThreadContextBenchmarkAccess.init(); } public void clearAndPut() { ThreadContext.clearMap(); for (int i = 0; i < count; i++) { ThreadContext.put(keys[i], values[i]); } } @Benchmark public Object get() { return ThreadContext.get(keys[count - 1]); } @Benchmark public void putAndRemove() { ThreadContext.put("someKey", "someValue"); ThreadContext.remove("someKey"); } @Benchmark public StringMap injectWithoutProperties() { reusableContextData.clear(); return injector.injectContextData(null, reusableContextData); } @Benchmark public StringMap injectWithProperties() { reusableContextData.clear(); return injector.injectContextData(propertyList, reusableContextData); } @Benchmark public Map<String, String> legacyInjectWithoutProperties() { return createMap(null); } @Benchmark public Map<String, String> legacyInjectWithProperties() { return createMap(propertyList); } // from Log4jLogEvent::createMap static Map<String, String> createMap(final List<Property> properties) { final Map<String, String> contextMap = ThreadContext.getImmutableContext(); if (properties == null || properties.isEmpty()) { return contextMap; // may be ThreadContext.EMPTY_MAP but not null } final Map<String, String> map = new HashMap<>(contextMap); for (final Property prop : properties) { if (!map.containsKey(prop.getName())) { map.put(prop.getName(), prop.getValue()); } } return Collections.unmodifiableMap(map); } }