From 31554fa2e313251b26f16011744c3fbfb6510ec3 Mon Sep 17 00:00:00 2001 From: "kirill.golodnov" Date: Thu, 31 Aug 2017 15:22:33 +0300 Subject: [PATCH 1/2] Added multithreaded caching parser --- pom.xml | 5 + .../ua_parser/MultithreadedCachingParser.java | 127 ++++++++++++++++++ .../MultithreadedCachingParserTest.java | 86 ++++++++++++ 3 files changed, 218 insertions(+) create mode 100644 src/main/java/ua_parser/MultithreadedCachingParser.java create mode 100644 src/test/java/ua_parser/MultithreadedCachingParserTest.java diff --git a/pom.xml b/pom.xml index b1cbbd1..35d50af 100644 --- a/pom.xml +++ b/pom.xml @@ -65,6 +65,11 @@ snakeyaml 1.10 + + com.google.guava + guava + 23.0 + junit junit diff --git a/src/main/java/ua_parser/MultithreadedCachingParser.java b/src/main/java/ua_parser/MultithreadedCachingParser.java new file mode 100644 index 0000000..965507f --- /dev/null +++ b/src/main/java/ua_parser/MultithreadedCachingParser.java @@ -0,0 +1,127 @@ +package ua_parser; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; + +import javax.annotation.ParametersAreNonnullByDefault; +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.TimeUnit; + +/** + * This class is multithreaded version of CachingParser. + * + * @author kyryloholodnov + */ +public class MultithreadedCachingParser extends Parser { + + private static long cacheKeyExpiresAfterAccessMs = 24 * 60L * 60 * 1000; // 24 hours + private static long cacheMaximumSize = 1000; + + private final LoadingCache cacheClient = CacheBuilder.newBuilder() + .expireAfterAccess(cacheKeyExpiresAfterAccessMs, TimeUnit.MILLISECONDS) + .maximumSize(cacheMaximumSize) + .build(new CacheLoader() { + @Override + @ParametersAreNonnullByDefault + public Client load(String agentString) throws Exception { + return MultithreadedCachingParser.super.parse(agentString); + } + }); + + private final LoadingCache cacheUserAgent = CacheBuilder.newBuilder() + .expireAfterAccess(cacheKeyExpiresAfterAccessMs, TimeUnit.MILLISECONDS) + .maximumSize(cacheMaximumSize) + .build(new CacheLoader() { + @Override + @ParametersAreNonnullByDefault + public UserAgent load(String agentString) throws Exception { + return MultithreadedCachingParser.super.parseUserAgent(agentString); + } + }); + + private final LoadingCache cacheDevice = CacheBuilder.newBuilder() + .expireAfterAccess(cacheKeyExpiresAfterAccessMs, TimeUnit.MILLISECONDS) + .maximumSize(cacheMaximumSize) + .build(new CacheLoader() { + @Override + @ParametersAreNonnullByDefault + public Device load(String agentString) throws Exception { + return MultithreadedCachingParser.super.parseDevice(agentString); + } + }); + + private final LoadingCache cacheOS = CacheBuilder.newBuilder() + .expireAfterAccess(cacheKeyExpiresAfterAccessMs, TimeUnit.MILLISECONDS) + .maximumSize(cacheMaximumSize) + .build(new CacheLoader() { + @Override + @ParametersAreNonnullByDefault + public OS load(String agentString) throws Exception { + return MultithreadedCachingParser.super.parseOS(agentString); + } + }); + + public MultithreadedCachingParser() throws IOException { + super(); + } + + public MultithreadedCachingParser(InputStream regexYaml) { + super(regexYaml); + } + + @Override + public Client parse(String agentString) { + if (agentString == null) { + return null; + } + return cacheClient.getUnchecked(agentString); + } + + @Override + public UserAgent parseUserAgent(String agentString) { + if (agentString == null) { + return null; + } + return cacheUserAgent.getUnchecked(agentString); + } + + @Override + public Device parseDevice(String agentString) { + if (agentString == null) { + return null; + } + return cacheDevice.getUnchecked(agentString); + } + + @Override + public OS parseOS(String agentString) { + if (agentString == null) { + return null; + } + return cacheOS.getUnchecked(agentString); + } + + public static long getCacheKeyExpiresAfterAccessMs() { + return cacheKeyExpiresAfterAccessMs; + } + + public static long getCacheMaximumSize() { + return cacheMaximumSize; + } + + public static void setCacheMaximumSize(long cacheMaximumSize) { + if (cacheMaximumSize < 1) { + throw new IllegalArgumentException("Cache size should be positive value"); + } + MultithreadedCachingParser.cacheMaximumSize = cacheMaximumSize; + } + + public static void setCacheKeyExpiresAfterAccessMs(long cacheKeyExpiresAfterAccessMs) { + if (cacheKeyExpiresAfterAccessMs < 1) { + throw new IllegalArgumentException("Cache key expiration should be positive value"); + } + MultithreadedCachingParser.cacheKeyExpiresAfterAccessMs = cacheKeyExpiresAfterAccessMs; + } +} diff --git a/src/test/java/ua_parser/MultithreadedCachingParserTest.java b/src/test/java/ua_parser/MultithreadedCachingParserTest.java new file mode 100644 index 0000000..3b42144 --- /dev/null +++ b/src/test/java/ua_parser/MultithreadedCachingParserTest.java @@ -0,0 +1,86 @@ +package ua_parser; + +import org.junit.Before; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +/** + * These tests really only redo the same tests as in ParserTest but with a + * different Parser subclass Also the same tests will be run several times on + * the same user agents to validate the caching works correctly. + * + * @author kyryloholodnov + */ +public class MultithreadedCachingParserTest extends ParserTest { + + @Before + public void initParser() throws Exception { + parser = new MultithreadedCachingParser(); + } + + @Override + Parser parserFromStringConfig(String configYamlAsString) throws Exception { + InputStream yamlInput = new ByteArrayInputStream( + configYamlAsString.getBytes("UTF8")); + return new CachingParser(yamlInput); + } + + @Test + public void testCachedParseUserAgent() { + super.testParseUserAgent(); + super.testParseUserAgent(); + super.testParseUserAgent(); + } + + @Test + public void testCachedParseOS() { + super.testParseOS(); + super.testParseOS(); + super.testParseOS(); + } + + @Test + public void testCachedParseAdditionalOS() { + super.testParseAdditionalOS(); + super.testParseAdditionalOS(); + super.testParseAdditionalOS(); + } + + @Test + public void testCachedParseDevice() { + super.testParseDevice(); + super.testParseDevice(); + super.testParseDevice(); + } + + @Test + public void testCachedParseFirefox() { + super.testParseFirefox(); + super.testParseFirefox(); + super.testParseFirefox(); + } + + @Test + public void testCachedParsePGTS() { + super.testParsePGTS(); + super.testParsePGTS(); + super.testParsePGTS(); + } + + @Test + public void testCachedParseAll() { + super.testParseAll(); + super.testParseAll(); + super.testParseAll(); + } + + @Test + public void testCachedReplacementQuoting() throws Exception { + super.testReplacementQuoting(); + super.testReplacementQuoting(); + super.testReplacementQuoting(); + } + +} From c29e27bae955a96e10a4a16ed99aaa89671548f4 Mon Sep 17 00:00:00 2001 From: "kirill.golodnov" Date: Thu, 31 Aug 2017 16:29:43 +0300 Subject: [PATCH 2/2] Renamed classes from Multithreaded to Concurrent --- ...rser.java => ConcurrentCachingParser.java} | 20 +++++++++---------- ....java => ConcurrentCachingParserTest.java} | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) rename src/main/java/ua_parser/{MultithreadedCachingParser.java => ConcurrentCachingParser.java} (83%) rename src/test/java/ua_parser/{MultithreadedCachingParserTest.java => ConcurrentCachingParserTest.java} (94%) diff --git a/src/main/java/ua_parser/MultithreadedCachingParser.java b/src/main/java/ua_parser/ConcurrentCachingParser.java similarity index 83% rename from src/main/java/ua_parser/MultithreadedCachingParser.java rename to src/main/java/ua_parser/ConcurrentCachingParser.java index 965507f..e67cbb1 100644 --- a/src/main/java/ua_parser/MultithreadedCachingParser.java +++ b/src/main/java/ua_parser/ConcurrentCachingParser.java @@ -10,11 +10,11 @@ import java.util.concurrent.TimeUnit; /** - * This class is multithreaded version of CachingParser. + * This class is concurrent version of CachingParser. * * @author kyryloholodnov */ -public class MultithreadedCachingParser extends Parser { +public class ConcurrentCachingParser extends Parser { private static long cacheKeyExpiresAfterAccessMs = 24 * 60L * 60 * 1000; // 24 hours private static long cacheMaximumSize = 1000; @@ -26,7 +26,7 @@ public class MultithreadedCachingParser extends Parser { @Override @ParametersAreNonnullByDefault public Client load(String agentString) throws Exception { - return MultithreadedCachingParser.super.parse(agentString); + return ConcurrentCachingParser.super.parse(agentString); } }); @@ -37,7 +37,7 @@ public Client load(String agentString) throws Exception { @Override @ParametersAreNonnullByDefault public UserAgent load(String agentString) throws Exception { - return MultithreadedCachingParser.super.parseUserAgent(agentString); + return ConcurrentCachingParser.super.parseUserAgent(agentString); } }); @@ -48,7 +48,7 @@ public UserAgent load(String agentString) throws Exception { @Override @ParametersAreNonnullByDefault public Device load(String agentString) throws Exception { - return MultithreadedCachingParser.super.parseDevice(agentString); + return ConcurrentCachingParser.super.parseDevice(agentString); } }); @@ -59,15 +59,15 @@ public Device load(String agentString) throws Exception { @Override @ParametersAreNonnullByDefault public OS load(String agentString) throws Exception { - return MultithreadedCachingParser.super.parseOS(agentString); + return ConcurrentCachingParser.super.parseOS(agentString); } }); - public MultithreadedCachingParser() throws IOException { + public ConcurrentCachingParser() throws IOException { super(); } - public MultithreadedCachingParser(InputStream regexYaml) { + public ConcurrentCachingParser(InputStream regexYaml) { super(regexYaml); } @@ -115,13 +115,13 @@ public static void setCacheMaximumSize(long cacheMaximumSize) { if (cacheMaximumSize < 1) { throw new IllegalArgumentException("Cache size should be positive value"); } - MultithreadedCachingParser.cacheMaximumSize = cacheMaximumSize; + ConcurrentCachingParser.cacheMaximumSize = cacheMaximumSize; } public static void setCacheKeyExpiresAfterAccessMs(long cacheKeyExpiresAfterAccessMs) { if (cacheKeyExpiresAfterAccessMs < 1) { throw new IllegalArgumentException("Cache key expiration should be positive value"); } - MultithreadedCachingParser.cacheKeyExpiresAfterAccessMs = cacheKeyExpiresAfterAccessMs; + ConcurrentCachingParser.cacheKeyExpiresAfterAccessMs = cacheKeyExpiresAfterAccessMs; } } diff --git a/src/test/java/ua_parser/MultithreadedCachingParserTest.java b/src/test/java/ua_parser/ConcurrentCachingParserTest.java similarity index 94% rename from src/test/java/ua_parser/MultithreadedCachingParserTest.java rename to src/test/java/ua_parser/ConcurrentCachingParserTest.java index 3b42144..198af63 100644 --- a/src/test/java/ua_parser/MultithreadedCachingParserTest.java +++ b/src/test/java/ua_parser/ConcurrentCachingParserTest.java @@ -13,11 +13,11 @@ * * @author kyryloholodnov */ -public class MultithreadedCachingParserTest extends ParserTest { +public class ConcurrentCachingParserTest extends ParserTest { @Before public void initParser() throws Exception { - parser = new MultithreadedCachingParser(); + parser = new ConcurrentCachingParser(); } @Override