From 3193ae8d2d955695ff8870d5006db980cafad7d9 Mon Sep 17 00:00:00 2001 From: wenshao Date: Mon, 13 Aug 2012 08:15:50 +0800 Subject: [PATCH] add sessionStat support --- pom.xml | 6 + .../druid/support/http/WebStatFilter.java | 92 +++++++++-- .../druid/support/http/stat/WebAppStat.java | 97 +++++++---- .../support/http/stat/WebSessionStat.java | 4 + .../java/com/alibaba/druid/util/LRUCache.java | 8 +- .../bvt/support/http/WebStatFilterTest.java | 52 ++++++ .../bvt/support/http/WebStatFilterTest2.java | 150 ++++++++++++++++++ 7 files changed, 361 insertions(+), 48 deletions(-) create mode 100644 src/test/java/com/alibaba/druid/bvt/support/http/WebStatFilterTest.java create mode 100644 src/test/java/com/alibaba/druid/bvt/support/http/WebStatFilterTest2.java diff --git a/pom.xml b/pom.xml index 53c9a26e4d..f4e373ad80 100644 --- a/pom.xml +++ b/pom.xml @@ -343,6 +343,12 @@ ${junit.version} test + + org.springframework + spring-mock + 2.0.8 + test + org.apache.derby derby diff --git a/src/main/java/com/alibaba/druid/support/http/WebStatFilter.java b/src/main/java/com/alibaba/druid/support/http/WebStatFilter.java index ef308ef53f..dc3aa606fc 100644 --- a/src/main/java/com/alibaba/druid/support/http/WebStatFilter.java +++ b/src/main/java/com/alibaba/druid/support/http/WebStatFilter.java @@ -29,18 +29,26 @@ import com.alibaba.druid.util.ServletPathMatcher; public class WebStatFilter implements Filter { - private final static Log LOG = LogFactory.getLog(WebStatFilter.class); + public final static String PARAM_NAME_SESSION_STAT_ENABLE = "sessionStatEnable"; + public final static String PARAM_NAME_SESSION_STAT_MAX_COUNT = "sessionStatMaxCount"; + public static final String PARAM_NAME_EXCLUSIONS = "exclusions"; - private WebAppStat webAppStat = null; - private WebStatFilterContextListener statFilterContextListener = new WebStatFilterContextListener(); + public final static int DEFAULT_MAX_STAT_SESSION_COUNT = 1000 * 100; + + private final static Log LOG = LogFactory.getLog(WebStatFilter.class); + + private WebAppStat webAppStat = null; + private WebStatFilterContextListener statFilterContextListener = new WebStatFilterContextListener(); /** * PatternMatcher used in determining which paths to react to for a given request. */ - protected PatternMatcher pathMatcher = new ServletPathMatcher(); + protected PatternMatcher pathMatcher = new ServletPathMatcher(); - private final String EXCLUSIONS = "exclusions"; private Set excludesPattern; - private boolean createSession = false; + + private boolean sessionStatEnable = true; + private int sessionStatMaxCount = DEFAULT_MAX_STAT_SESSION_COUNT; + private boolean createSession = false; private String contextPath; @@ -86,6 +94,7 @@ public class WebStatFilter implements Filter { if (sessionStat != null) { sessionStat.setLastAccessTimeMillis(startMillis); sessionStat.reacord(nanoSpan); + sessionStat.incrementRequestCount(); } } @@ -94,10 +103,14 @@ public class WebStatFilter implements Filter { } public WebSessionStat getSessionStat(HttpServletRequest request) { + if (!isSessionStatEnable()) { + return null; + } + WebSessionStat sessionStat = null; String sessionId = getSessionId(request); if (sessionId != null) { - sessionStat = webAppStat.getSessionStat(sessionId); + sessionStat = webAppStat.getSessionStat(sessionId, true); } if (sessionStat != null) { @@ -140,10 +153,10 @@ public class WebStatFilter implements Filter { if (excludesPattern == null) { return false; } - + if (contextPath != null && requestURI.startsWith(contextPath)) { requestURI = requestURI.substring(contextPath.length()); - if(!requestURI.startsWith("/")) { + if (!requestURI.startsWith("/")) { requestURI = "/" + requestURI; } } @@ -153,7 +166,7 @@ public class WebStatFilter implements Filter { return true; } } - + return false; } @@ -163,16 +176,41 @@ public class WebStatFilter implements Filter { @Override public void init(FilterConfig config) throws ServletException { - String exclusions = config.getInitParameter(EXCLUSIONS); - if (exclusions != null && exclusions.trim().length() != 0) { - excludesPattern = new HashSet(Arrays.asList(exclusions.split("\\s*,\\s*"))); + { + String exclusions = config.getInitParameter(PARAM_NAME_EXCLUSIONS); + if (exclusions != null && exclusions.trim().length() != 0) { + excludesPattern = new HashSet(Arrays.asList(exclusions.split("\\s*,\\s*"))); + } + } + { + String param = config.getInitParameter(PARAM_NAME_SESSION_STAT_ENABLE); + if (param != null && param.trim().length() != 0) { + param = param.trim(); + if ("true".equals(param)) { + this.sessionStatEnable = true; + } else if ("false".equals(param)) { + this.sessionStatEnable = false; + } else { + LOG.error("WebStatFilter Parameter '" + PARAM_NAME_SESSION_STAT_ENABLE + "' config error"); + } + } + } + { + String param = config.getInitParameter(PARAM_NAME_SESSION_STAT_MAX_COUNT); + if (param != null && param.trim().length() != 0) { + param = param.trim(); + try { + this.sessionStatMaxCount = Integer.parseInt(param); + } catch (NumberFormatException e) { + LOG.error("WebStatFilter Parameter '" + PARAM_NAME_SESSION_STAT_ENABLE + "' config error", e); + } + } } - config.getServletContext().getContextPath(); StatFilterContext.getInstance().addContextListener(statFilterContextListener); this.contextPath = DruidWebUtils.getContextPath(config.getServletContext()); - webAppStat = new WebAppStat(contextPath); + webAppStat = new WebAppStat(contextPath, this.sessionStatMaxCount); WebAppStatManager.getInstance().addWebAppStatSet(webAppStat); } @@ -186,6 +224,30 @@ public class WebStatFilter implements Filter { } } + public boolean isSessionStatEnable() { + return sessionStatEnable; + } + + public void setSessionStatEnable(boolean sessionStatEnable) { + this.sessionStatEnable = sessionStatEnable; + } + + public WebAppStat getWebAppStat() { + return webAppStat; + } + + public String getContextPath() { + return contextPath; + } + + public int getSessionStatMaxCount() { + return sessionStatMaxCount; + } + + public WebStatFilterContextListener getStatFilterContextListener() { + return statFilterContextListener; + } + class WebStatFilterContextListener extends StatFilterContextListenerAdapter { @Override diff --git a/src/main/java/com/alibaba/druid/support/http/stat/WebAppStat.java b/src/main/java/com/alibaba/druid/support/http/stat/WebAppStat.java index f9d9611525..fc705ce184 100644 --- a/src/main/java/com/alibaba/druid/support/http/stat/WebAppStat.java +++ b/src/main/java/com/alibaba/druid/support/http/stat/WebAppStat.java @@ -8,44 +8,56 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; import com.alibaba.druid.support.logging.Log; import com.alibaba.druid.support.logging.LogFactory; +import com.alibaba.druid.util.LRUCache; public class WebAppStat { - private final static Log LOG = LogFactory.getLog(WebAppStat.class); + private final static Log LOG = LogFactory.getLog(WebAppStat.class); - public final static int DEFAULT_MAX_STAT_URI_COUNT = 1000; - public final static int DEFAULT_MAX_STAT_SESSION_COUNT = 1000 * 100; + public final static int DEFAULT_MAX_STAT_URI_COUNT = 1000; + public final static int DEFAULT_MAX_STAT_SESSION_COUNT = 1000 * 100; - private volatile int maxStatUriCount = DEFAULT_MAX_STAT_URI_COUNT; - private volatile int maxStatSessionCount = DEFAULT_MAX_STAT_SESSION_COUNT; + private volatile int maxStatUriCount = DEFAULT_MAX_STAT_URI_COUNT; + private volatile int maxStatSessionCount = DEFAULT_MAX_STAT_SESSION_COUNT; - private final AtomicInteger runningCount = new AtomicInteger(); - private final AtomicInteger concurrentMax = new AtomicInteger(); - private final AtomicLong requestCount = new AtomicLong(0); + private final AtomicInteger runningCount = new AtomicInteger(); + private final AtomicInteger concurrentMax = new AtomicInteger(); + private final AtomicLong requestCount = new AtomicLong(0); - private final static ThreadLocal currentLocal = new ThreadLocal(); + private final static ThreadLocal currentLocal = new ThreadLocal(); - private final ConcurrentMap uriStatMap = new ConcurrentHashMap(); - private final ConcurrentMap sessionStatMap = new ConcurrentHashMap(); + private final ConcurrentMap uriStatMap = new ConcurrentHashMap(); + private final Map sessionStatMap; - private final AtomicLong uriStatMapFullCount = new AtomicLong(); - private final AtomicLong uriSessionMapFullCount = new AtomicLong(); + private final ReadWriteLock sessionStatLock = new ReentrantReadWriteLock(); - private String contextPath; + private final AtomicLong uriStatMapFullCount = new AtomicLong(); + private final AtomicLong uriSessionMapFullCount = new AtomicLong(); + + private String contextPath; public static WebAppStat current() { return currentLocal.get(); } public WebAppStat(){ - + this(null); } public WebAppStat(String contextPath){ + this(contextPath, DEFAULT_MAX_STAT_SESSION_COUNT); + } + + public WebAppStat(String contextPath, int maxStatSessionCount){ this.contextPath = contextPath; + this.maxStatSessionCount = maxStatSessionCount; + + sessionStatMap = new LRUCache(maxStatSessionCount); } public String getContextPath() { @@ -101,26 +113,49 @@ public class WebAppStat { } public WebSessionStat getSessionStat(String sessionId) { - if (sessionStatMap.size() >= this.getMaxStatSessionCount()) { - long fullCount = uriSessionMapFullCount.getAndIncrement(); + return getSessionStat(sessionId, false); + } - if (fullCount == 0) { - LOG.error("sessionStatMap is full"); + public WebSessionStat getSessionStat(String sessionId, boolean create) { + sessionStatLock.readLock().lock(); + try { + WebSessionStat uriStat = sessionStatMap.get(sessionId); + + if (uriStat != null) { + return uriStat; } + } finally { + sessionStatLock.readLock().unlock(); + } + if (!create) { return null; } - WebSessionStat uriStat = sessionStatMap.get(sessionId); + sessionStatLock.writeLock().lock(); + try { + WebSessionStat uriStat = sessionStatMap.get(sessionId); - if (uriStat == null) { - WebSessionStat newStat = new WebSessionStat(sessionId); - - sessionStatMap.putIfAbsent(sessionId, newStat); - uriStat = sessionStatMap.get(sessionId); + if (uriStat == null) { + if (sessionStatMap.size() >= this.getMaxStatSessionCount()) { + long fullCount = uriSessionMapFullCount.getAndIncrement(); + + if (fullCount == 0) { + LOG.error("sessionStatMap is full"); + } + } + + WebSessionStat newStat = new WebSessionStat(sessionId); + + sessionStatMap.put(sessionId, newStat); + + return newStat; + } + + return uriStat; + } finally { + sessionStatLock.writeLock().unlock(); } - - return uriStat; } public void afterInvoke(long nanoSpan) { @@ -193,19 +228,19 @@ public class WebAppStat { } return uriStatDataList; } - + public List> getSessionStatDataList() { List> uriStatDataList = new ArrayList>(this.sessionStatMap.size()); for (WebSessionStat sessionStat : this.sessionStatMap.values()) { Map sessionStatData = sessionStat.getStatData(); - + int runningCount = ((Number) sessionStatData.get("RunningCount")).intValue(); long requestCount = (Long) sessionStatData.get("RequestCount"); - + if (runningCount == 0 && requestCount == 0) { continue; } - + uriStatDataList.add(sessionStatData); } return uriStatDataList; diff --git a/src/main/java/com/alibaba/druid/support/http/stat/WebSessionStat.java b/src/main/java/com/alibaba/druid/support/http/stat/WebSessionStat.java index 8b0e76b558..1c6649da3e 100644 --- a/src/main/java/com/alibaba/druid/support/http/stat/WebSessionStat.java +++ b/src/main/java/com/alibaba/druid/support/http/stat/WebSessionStat.java @@ -112,6 +112,10 @@ public class WebSessionStat { } } + incrementRequestCount(); + } + + public void incrementRequestCount() { requestCount.incrementAndGet(); } diff --git a/src/main/java/com/alibaba/druid/util/LRUCache.java b/src/main/java/com/alibaba/druid/util/LRUCache.java index bedf7443d1..c959968b2f 100644 --- a/src/main/java/com/alibaba/druid/util/LRUCache.java +++ b/src/main/java/com/alibaba/druid/util/LRUCache.java @@ -8,12 +8,16 @@ public class LRUCache extends LinkedHashMap { private static final long serialVersionUID = 1L; private final int maxSize; + public LRUCache(int maxSize){ + this(maxSize, 16, 0.75f, false); + } + public LRUCache(int maxSize, int initialCapacity, float loadFactor, boolean accessOrder){ super(initialCapacity, loadFactor, accessOrder); this.maxSize = maxSize; } - - protected boolean removeEldestEntry(Map.Entry eldest) { + + protected boolean removeEldestEntry(Map.Entry eldest) { return this.size() > this.maxSize; } } diff --git a/src/test/java/com/alibaba/druid/bvt/support/http/WebStatFilterTest.java b/src/test/java/com/alibaba/druid/bvt/support/http/WebStatFilterTest.java new file mode 100644 index 0000000000..2567f75e6e --- /dev/null +++ b/src/test/java/com/alibaba/druid/bvt/support/http/WebStatFilterTest.java @@ -0,0 +1,52 @@ +package com.alibaba.druid.bvt.support.http; + +import junit.framework.TestCase; + +import org.junit.Assert; +import org.springframework.mock.web.MockFilterChain; +import org.springframework.mock.web.MockFilterConfig; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockServletContext; + +import com.alibaba.druid.filter.stat.StatFilterContext; +import com.alibaba.druid.support.http.WebStatFilter; +import com.alibaba.druid.support.http.stat.WebAppStat; +import com.alibaba.druid.support.http.stat.WebAppStatManager; + +public class WebStatFilterTest extends TestCase { + + public void test_sessionStatDisable() throws Exception { + MockServletContext servletContext = new MockServletContext(); + + MockFilterConfig filterConfig = new MockFilterConfig(servletContext); + filterConfig.addInitParameter(WebStatFilter.PARAM_NAME_SESSION_STAT_ENABLE, "false"); + + WebStatFilter filter = new WebStatFilter(); + + Assert.assertNull(filter.getWebAppStat()); + + filter.init(filterConfig); + + WebAppStat appStat = filter.getWebAppStat(); + + Assert.assertFalse(filter.isSessionStatEnable()); + Assert.assertTrue(WebAppStatManager.getInstance().getWebAppStatSet().contains(appStat)); + Assert.assertTrue(StatFilterContext.getInstance().getListeners().contains(filter.getStatFilterContextListener())); + + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + MockFilterChain chain = new MockFilterChain(); + + Assert.assertNull(filter.getSessionStat(request)); + + filter.doFilter(request, response, chain); + + Assert.assertEquals(0, appStat.getSessionStatDataList().size()); + + filter.destroy(); + + Assert.assertFalse(WebAppStatManager.getInstance().getWebAppStatSet().contains(appStat)); + Assert.assertFalse(StatFilterContext.getInstance().getListeners().contains(filter.getStatFilterContextListener())); + } +} diff --git a/src/test/java/com/alibaba/druid/bvt/support/http/WebStatFilterTest2.java b/src/test/java/com/alibaba/druid/bvt/support/http/WebStatFilterTest2.java new file mode 100644 index 0000000000..275d4aa095 --- /dev/null +++ b/src/test/java/com/alibaba/druid/bvt/support/http/WebStatFilterTest2.java @@ -0,0 +1,150 @@ +package com.alibaba.druid.bvt.support.http; + +import org.junit.Assert; +import org.springframework.mock.web.MockFilterChain; +import org.springframework.mock.web.MockFilterConfig; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockHttpSession; +import org.springframework.mock.web.MockServletContext; + +import com.alibaba.druid.support.http.WebStatFilter; +import com.alibaba.druid.support.http.stat.WebAppStat; + +import junit.framework.TestCase; + +public class WebStatFilterTest2 extends TestCase { + + public void test_lru() throws Exception { + MockServletContext servletContext = new MockServletContext(); + + MockFilterConfig filterConfig = new MockFilterConfig(servletContext); + filterConfig.addInitParameter(WebStatFilter.PARAM_NAME_SESSION_STAT_MAX_COUNT, "3"); + + WebStatFilter filter = new WebStatFilter(); + + filter.init(filterConfig); + + WebAppStat appStat = filter.getWebAppStat(); + + Assert.assertEquals(3, filter.getSessionStatMaxCount()); + Assert.assertEquals(0, appStat.getSessionStatDataList().size()); + + final MockHttpSession session_0 = new MockHttpSession(servletContext); + final MockHttpSession session_1 = new MockHttpSession(servletContext); + final MockHttpSession session_2 = new MockHttpSession(servletContext); + final MockHttpSession session_3 = new MockHttpSession(servletContext); + final MockHttpSession session_4 = new MockHttpSession(servletContext); + + // 第一个session请求2次 + { + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + MockFilterChain chain = new MockFilterChain() { + + public void doFilter(javax.servlet.ServletRequest request, javax.servlet.ServletResponse response) { + ((MockHttpServletRequest) request).setSession(session_0); + }; + }; + + filter.doFilter(request, response, chain); + + Assert.assertEquals(1, appStat.getSessionStatDataList().size()); + Assert.assertEquals(1, appStat.getSessionStat(session_0.getId()).getRequestCount()); + } + { + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + MockFilterChain chain = new MockFilterChain() { + + public void doFilter(javax.servlet.ServletRequest request, javax.servlet.ServletResponse response) { + ((MockHttpServletRequest) request).setSession(session_0); + }; + }; + + filter.doFilter(request, response, chain); + + Assert.assertEquals(1, appStat.getSessionStatDataList().size()); + Assert.assertEquals(2, appStat.getSessionStat(session_0.getId()).getRequestCount()); + } + + + // 第2个sesion请求1次 + { + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + MockFilterChain chain = new MockFilterChain() { + + public void doFilter(javax.servlet.ServletRequest request, javax.servlet.ServletResponse response) { + ((MockHttpServletRequest) request).setSession(session_1); + }; + }; + + filter.doFilter(request, response, chain); + + Assert.assertEquals(2, appStat.getSessionStatDataList().size()); + Assert.assertEquals(2, appStat.getSessionStat(session_0.getId()).getRequestCount()); + Assert.assertEquals(1, appStat.getSessionStat(session_1.getId()).getRequestCount()); + } + + // 第3个sesion请求1次 + { + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + MockFilterChain chain = new MockFilterChain() { + + public void doFilter(javax.servlet.ServletRequest request, javax.servlet.ServletResponse response) { + ((MockHttpServletRequest) request).setSession(session_2); + }; + }; + + filter.doFilter(request, response, chain); + + Assert.assertEquals(3, appStat.getSessionStatDataList().size()); + Assert.assertEquals(2, appStat.getSessionStat(session_0.getId()).getRequestCount()); + Assert.assertEquals(1, appStat.getSessionStat(session_1.getId()).getRequestCount()); + Assert.assertEquals(1, appStat.getSessionStat(session_2.getId()).getRequestCount()); + } + + // 第4个sesion请求1次 + { + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + MockFilterChain chain = new MockFilterChain() { + + public void doFilter(javax.servlet.ServletRequest request, javax.servlet.ServletResponse response) { + ((MockHttpServletRequest) request).setSession(session_3); + }; + }; + + filter.doFilter(request, response, chain); + + Assert.assertEquals(3, appStat.getSessionStatDataList().size()); + Assert.assertNull(appStat.getSessionStat(session_0.getId())); + Assert.assertEquals(1, appStat.getSessionStat(session_1.getId()).getRequestCount()); + Assert.assertEquals(1, appStat.getSessionStat(session_2.getId()).getRequestCount()); + Assert.assertEquals(1, appStat.getSessionStat(session_3.getId()).getRequestCount()); + } + + // 第5个sesion请求1次 + { + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + MockFilterChain chain = new MockFilterChain() { + + public void doFilter(javax.servlet.ServletRequest request, javax.servlet.ServletResponse response) { + ((MockHttpServletRequest) request).setSession(session_4); + }; + }; + + filter.doFilter(request, response, chain); + + Assert.assertEquals(3, appStat.getSessionStatDataList().size()); + Assert.assertNull(appStat.getSessionStat(session_0.getId())); + Assert.assertNull(appStat.getSessionStat(session_1.getId())); + Assert.assertEquals(1, appStat.getSessionStat(session_2.getId()).getRequestCount()); + Assert.assertEquals(1, appStat.getSessionStat(session_3.getId()).getRequestCount()); + Assert.assertEquals(1, appStat.getSessionStat(session_4.getId()).getRequestCount()); + } + } +}