From 5cf079e72bc49ebbdd4e430f0765a068f9cfe5c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Cocula?= Date: Thu, 12 Jan 2017 21:53:52 +0100 Subject: [PATCH 1/8] Add metrics filter --- libresonic-main/pom.xml | 1 - .../libresonic/player/boot/Application.java | 15 ++++-- .../player/filter/MetricsFilter.java | 46 +++++++++++++++++++ .../src/main/resources/application.properties | 1 + 4 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 libresonic-main/src/main/java/org/libresonic/player/filter/MetricsFilter.java diff --git a/libresonic-main/pom.xml b/libresonic-main/pom.xml index ac1fe759..53820f2c 100644 --- a/libresonic-main/pom.xml +++ b/libresonic-main/pom.xml @@ -38,7 +38,6 @@ io.dropwizard.metrics metrics-core ${metrics.version} - test diff --git a/libresonic-main/src/main/java/org/libresonic/player/boot/Application.java b/libresonic-main/src/main/java/org/libresonic/player/boot/Application.java index fa8cfe93..6a396c17 100644 --- a/libresonic-main/src/main/java/org/libresonic/player/boot/Application.java +++ b/libresonic-main/src/main/java/org/libresonic/player/boot/Application.java @@ -4,11 +4,7 @@ import javax.servlet.Filter; import javax.servlet.ServletContextListener; import net.sf.ehcache.constructs.web.ShutdownListener; import org.directwebremoting.servlet.DwrServlet; -import org.libresonic.player.filter.BootstrapVerificationFilter; -import org.libresonic.player.filter.ParameterDecodingFilter; -import org.libresonic.player.filter.RESTFilter; -import org.libresonic.player.filter.RequestEncodingFilter; -import org.libresonic.player.filter.ResponseHeaderFilter; +import org.libresonic.player.filter.*; import org.libresonic.player.spring.AdditionalPropertySourceConfigurer; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration; @@ -140,6 +136,15 @@ public class Application extends SpringBootServletInitializer { return registration; } + @Bean + public FilterRegistrationBean metricsFilterRegistration() { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setFilter(new MetricsFilter()); + registration.setOrder(7); + return registration; + } + + @Bean public Filter noCacheFilter() { return new ResponseHeaderFilter(); diff --git a/libresonic-main/src/main/java/org/libresonic/player/filter/MetricsFilter.java b/libresonic-main/src/main/java/org/libresonic/player/filter/MetricsFilter.java new file mode 100644 index 00000000..eb58b7bb --- /dev/null +++ b/libresonic-main/src/main/java/org/libresonic/player/filter/MetricsFilter.java @@ -0,0 +1,46 @@ +package org.libresonic.player.filter; + +import com.codahale.metrics.ConsoleReporter; +import com.codahale.metrics.JmxReporter; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Timer; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +/** + * Created by remi on 12/01/17. + */ +public class MetricsFilter implements Filter { + + private final MetricRegistry metrics = new MetricRegistry(); + JmxReporter reporter; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + reporter = JmxReporter.forRegistry(metrics) + .convertRatesTo(TimeUnit.SECONDS.SECONDS) + .convertDurationsTo(TimeUnit.MILLISECONDS) + .build(); + reporter.start(); + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + + HttpServletRequest httpServletRequest = (HttpServletRequest)request; + Timer requestTimer = metrics.timer(MetricRegistry.name(MetricsFilter.class, httpServletRequest.getRequestURI())); + + Timer.Context requestTimerContext = requestTimer.time(); + + chain.doFilter(request, response); + requestTimerContext.stop(); + } + + @Override + public void destroy() { + reporter.stop(); + } +} diff --git a/libresonic-main/src/main/resources/application.properties b/libresonic-main/src/main/resources/application.properties index d8a35e3b..52c2cb5c 100644 --- a/libresonic-main/src/main/resources/application.properties +++ b/libresonic-main/src/main/resources/application.properties @@ -1,3 +1,4 @@ spring.mvc.view.prefix: /WEB-INF/jsp/ spring.mvc.view.suffix: .jsp server.error.includeStacktrace: ALWAYS +logging.level.org.springframework.web=DEBUG \ No newline at end of file From b8b511e1912e60163b4c3d73edad4a56ab74e512 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Cocula?= Date: Tue, 17 Jan 2017 23:30:54 +0100 Subject: [PATCH 2/8] WIP metrics --- .../player/filter/MetricsFilter.java | 15 ++- .../player/monitor/MetricsManager.java | 119 ++++++++++++++++++ 2 files changed, 128 insertions(+), 6 deletions(-) create mode 100644 libresonic-main/src/main/java/org/libresonic/player/monitor/MetricsManager.java diff --git a/libresonic-main/src/main/java/org/libresonic/player/filter/MetricsFilter.java b/libresonic-main/src/main/java/org/libresonic/player/filter/MetricsFilter.java index eb58b7bb..f3f87d74 100644 --- a/libresonic-main/src/main/java/org/libresonic/player/filter/MetricsFilter.java +++ b/libresonic-main/src/main/java/org/libresonic/player/filter/MetricsFilter.java @@ -1,12 +1,12 @@ package org.libresonic.player.filter; -import com.codahale.metrics.ConsoleReporter; import com.codahale.metrics.JmxReporter; import com.codahale.metrics.MetricRegistry; -import com.codahale.metrics.Timer; +import org.libresonic.player.monitor.MetricsManager; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; + import java.io.IOException; import java.util.concurrent.TimeUnit; @@ -31,12 +31,15 @@ public class MetricsFilter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest)request; - Timer requestTimer = metrics.timer(MetricRegistry.name(MetricsFilter.class, httpServletRequest.getRequestURI())); - Timer.Context requestTimerContext = requestTimer.time(); + String timerName = httpServletRequest.getRequestURI(); + try (MetricsManager.Timer t = MetricsManager.buildTimer(this,timerName).condition(timerName.contains("main.view")).timer()) { + chain.doFilter(request, response); + } - chain.doFilter(request, response); - requestTimerContext.stop(); + /* MetricsManager.buildTimer(MetricsFilter.class,timerName).condition(timerName.contains("main.view")).exec(() -> { + chain.doFilter(request,response); + }); */ } @Override diff --git a/libresonic-main/src/main/java/org/libresonic/player/monitor/MetricsManager.java b/libresonic-main/src/main/java/org/libresonic/player/monitor/MetricsManager.java new file mode 100644 index 00000000..86512022 --- /dev/null +++ b/libresonic-main/src/main/java/org/libresonic/player/monitor/MetricsManager.java @@ -0,0 +1,119 @@ +package org.libresonic.player.monitor; + +import com.codahale.metrics.JmxReporter; +import com.codahale.metrics.MetricRegistry; + +import java.util.concurrent.TimeUnit; + +/** + * Created by remi on 17/01/17. + */ +public class MetricsManager { + + private static final MetricRegistry metrics = new MetricRegistry(); + private static JmxReporter reporter; + private static final NullTimer nullTimerSingleton = new NullTimer(null); + + static { + reporter = JmxReporter.forRegistry(metrics) + .convertRatesTo(TimeUnit.SECONDS.SECONDS) + .convertDurationsTo(TimeUnit.MILLISECONDS) + .build(); + reporter.start(); + } + + + public static TimerBuilder buildTimer(Class clazz, String name) { + return new TimerBuilder(clazz,name); + } + + public static TimerBuilder buildTimer(Object ref, String name) { + return new TimerBuilder(ref.getClass(),name); + } + + public interface TimerExecutor { + void doWithTimer() throws Exception; + } + + + public static class TimerBuilder { + private Timer theTimer; + private Class clazz; + private String name; + + public TimerBuilder() { + } + + public TimerBuilder(Timer theTimer) { + this.theTimer = theTimer; + } + + public TimerBuilder(Class clazz, String name) { + this.clazz = clazz; + this.name = name; + } + + public TimerBuilder condition(boolean ifTrue) { + if (ifTrue == false) { + theTimer = nullTimerSingleton; + } + return this; + } + + public Timer timer() { + if (theTimer == null) { + com.codahale.metrics.Timer t = metrics.timer(MetricRegistry.name(clazz,name)); + com.codahale.metrics.Timer.Context tContext = t.time(); + theTimer = new Timer(tContext); + } + return theTimer; + } + + public void exec(TimerExecutor executor) throws Exception { + if (theTimer == null) { + com.codahale.metrics.Timer t = metrics.timer(MetricRegistry.name(clazz, name)); + com.codahale.metrics.Timer.Context tContext = t.time(); + theTimer = new Timer(tContext); + } + + executor.doWithTimer(); + + theTimer.close(); + } + } + + /** + * + */ + public static class Timer implements AutoCloseable { + + private com.codahale.metrics.Timer.Context timerContext; + + protected Timer(com.codahale.metrics.Timer.Context timerContext) { + this.timerContext = timerContext; + } + + @Override + public void close() { + timerContext.stop(); + } + + public Timer condition() { + return null; + } + } + + private static class NullTimer extends Timer { + + protected NullTimer(com.codahale.metrics.Timer.Context timerContext) { + super(timerContext); + } + + @Override + public void close() { + // Does nothing + } + } + + +} From 844b5de4270a98337552a63645caf6082de134a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Cocula?= Date: Wed, 18 Jan 2017 00:07:32 +0100 Subject: [PATCH 3/8] WIP metrics --- .../player/filter/MetricsFilter.java | 2 +- .../player/monitor/MetricsManager.java | 68 +++++++++---------- 2 files changed, 33 insertions(+), 37 deletions(-) diff --git a/libresonic-main/src/main/java/org/libresonic/player/filter/MetricsFilter.java b/libresonic-main/src/main/java/org/libresonic/player/filter/MetricsFilter.java index f3f87d74..59539ff2 100644 --- a/libresonic-main/src/main/java/org/libresonic/player/filter/MetricsFilter.java +++ b/libresonic-main/src/main/java/org/libresonic/player/filter/MetricsFilter.java @@ -33,7 +33,7 @@ public class MetricsFilter implements Filter { HttpServletRequest httpServletRequest = (HttpServletRequest)request; String timerName = httpServletRequest.getRequestURI(); - try (MetricsManager.Timer t = MetricsManager.buildTimer(this,timerName).condition(timerName.contains("main.view")).timer()) { + try (MetricsManager.Timer t = MetricsManager.condition(timerName.contains("main.view")).timer(this,timerName)) { chain.doFilter(request, response); } diff --git a/libresonic-main/src/main/java/org/libresonic/player/monitor/MetricsManager.java b/libresonic-main/src/main/java/org/libresonic/player/monitor/MetricsManager.java index 86512022..d5632a55 100644 --- a/libresonic-main/src/main/java/org/libresonic/player/monitor/MetricsManager.java +++ b/libresonic-main/src/main/java/org/libresonic/player/monitor/MetricsManager.java @@ -13,6 +13,7 @@ public class MetricsManager { private static final MetricRegistry metrics = new MetricRegistry(); private static JmxReporter reporter; private static final NullTimer nullTimerSingleton = new NullTimer(null); + private static ConditionFalseTimerBuilder conditionFalseTimerBuilderSingleton = new ConditionFalseTimerBuilder(); static { reporter = JmxReporter.forRegistry(metrics) @@ -22,54 +23,46 @@ public class MetricsManager { reporter.start(); } - - public static TimerBuilder buildTimer(Class clazz, String name) { - return new TimerBuilder(clazz,name); + public static Timer timer(Class clazz, String name) { + return new TimerBuilder().timer(clazz,name); } - public static TimerBuilder buildTimer(Object ref, String name) { - return new TimerBuilder(ref.getClass(),name); + public static Timer timer(Object ref, String name) { + return timer(ref.getClass(),name); } - public interface TimerExecutor { - void doWithTimer() throws Exception; + public static TimerBuilder condition(boolean ifTrue) { + if (ifTrue == false) { + return conditionFalseTimerBuilderSingleton; + } + return new TimerBuilder(); } + /* public interface TimerExecutor { + void doWithTimer() throws Exception; + } */ public static class TimerBuilder { - private Timer theTimer; - private Class clazz; - private String name; - - public TimerBuilder() { - } - public TimerBuilder(Timer theTimer) { - this.theTimer = theTimer; - } - - public TimerBuilder(Class clazz, String name) { - this.clazz = clazz; - this.name = name; - } - - public TimerBuilder condition(boolean ifTrue) { + /* public TimerBuilder condition(boolean ifTrue) { if (ifTrue == false) { theTimer = nullTimerSingleton; } return this; + } */ + + public Timer timer(Class clazz, String name) { + com.codahale.metrics.Timer t = metrics.timer(MetricRegistry.name(clazz,name)); + com.codahale.metrics.Timer.Context tContext = t.time(); + return new Timer(tContext); } - public Timer timer() { - if (theTimer == null) { - com.codahale.metrics.Timer t = metrics.timer(MetricRegistry.name(clazz,name)); - com.codahale.metrics.Timer.Context tContext = t.time(); - theTimer = new Timer(tContext); - } - return theTimer; + public Timer timer(Object ref, String name) { + return timer(ref.getClass(),name); } - public void exec(TimerExecutor executor) throws Exception { + + /* public void exec(TimerExecutor executor) throws Exception { if (theTimer == null) { com.codahale.metrics.Timer t = metrics.timer(MetricRegistry.name(clazz, name)); com.codahale.metrics.Timer.Context tContext = t.time(); @@ -80,6 +73,14 @@ public class MetricsManager { theTimer.close(); } + */ + } + + private static class ConditionFalseTimerBuilder extends TimerBuilder { + @Override + public Timer timer(Class clazz, String name) { + return nullTimerSingleton; + } } /** @@ -98,9 +99,6 @@ public class MetricsManager { timerContext.stop(); } - public Timer condition() { - return null; - } } private static class NullTimer extends Timer { @@ -114,6 +112,4 @@ public class MetricsManager { // Does nothing } } - - } From 1f9d11c5a58dc69695184ca25f104813e51a347d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Cocula?= Date: Wed, 18 Jan 2017 00:32:31 +0100 Subject: [PATCH 4/8] Code cleanup and comment. --- .../player/filter/MetricsFilter.java | 5 -- .../player/monitor/MetricsManager.java | 79 +++++++++++-------- 2 files changed, 45 insertions(+), 39 deletions(-) diff --git a/libresonic-main/src/main/java/org/libresonic/player/filter/MetricsFilter.java b/libresonic-main/src/main/java/org/libresonic/player/filter/MetricsFilter.java index 59539ff2..ec88be54 100644 --- a/libresonic-main/src/main/java/org/libresonic/player/filter/MetricsFilter.java +++ b/libresonic-main/src/main/java/org/libresonic/player/filter/MetricsFilter.java @@ -29,17 +29,12 @@ public class MetricsFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - HttpServletRequest httpServletRequest = (HttpServletRequest)request; String timerName = httpServletRequest.getRequestURI(); try (MetricsManager.Timer t = MetricsManager.condition(timerName.contains("main.view")).timer(this,timerName)) { chain.doFilter(request, response); } - - /* MetricsManager.buildTimer(MetricsFilter.class,timerName).condition(timerName.contains("main.view")).exec(() -> { - chain.doFilter(request,response); - }); */ } @Override diff --git a/libresonic-main/src/main/java/org/libresonic/player/monitor/MetricsManager.java b/libresonic-main/src/main/java/org/libresonic/player/monitor/MetricsManager.java index d5632a55..8f2d358f 100644 --- a/libresonic-main/src/main/java/org/libresonic/player/monitor/MetricsManager.java +++ b/libresonic-main/src/main/java/org/libresonic/player/monitor/MetricsManager.java @@ -10,10 +10,11 @@ import java.util.concurrent.TimeUnit; */ public class MetricsManager { + // Main metrics registry private static final MetricRegistry metrics = new MetricRegistry(); + + // Potential metrics reporters private static JmxReporter reporter; - private static final NullTimer nullTimerSingleton = new NullTimer(null); - private static ConditionFalseTimerBuilder conditionFalseTimerBuilderSingleton = new ConditionFalseTimerBuilder(); static { reporter = JmxReporter.forRegistry(metrics) @@ -23,14 +24,36 @@ public class MetricsManager { reporter.start(); } + /** + * Creates a {@link Timer} whose name is based on a class name and a + * qualified name. + * @param clazz + * @param name + * @return + */ public static Timer timer(Class clazz, String name) { return new TimerBuilder().timer(clazz,name); } + /** + * Creates a {@link Timer} whose name is based on an object's class name and a + * qualified name. + * @param ref + * @param name + * @return + */ public static Timer timer(Object ref, String name) { return timer(ref.getClass(),name); } + /** + * Initiate a {@link TimerBuilder} using a condition. + * If the condition is false, a void {@link Timer} will finally be built thus + * no timer will be registered in the Metrics registry. + * + * @param ifTrue + * @return + */ public static TimerBuilder condition(boolean ifTrue) { if (ifTrue == false) { return conditionFalseTimerBuilderSingleton; @@ -38,19 +61,11 @@ public class MetricsManager { return new TimerBuilder(); } - /* public interface TimerExecutor { - void doWithTimer() throws Exception; - } */ - + /** + * A class that builds a {@link Timer} + */ public static class TimerBuilder { - /* public TimerBuilder condition(boolean ifTrue) { - if (ifTrue == false) { - theTimer = nullTimerSingleton; - } - return this; - } */ - public Timer timer(Class clazz, String name) { com.codahale.metrics.Timer t = metrics.timer(MetricRegistry.name(clazz,name)); com.codahale.metrics.Timer.Context tContext = t.time(); @@ -61,30 +76,11 @@ public class MetricsManager { return timer(ref.getClass(),name); } - - /* public void exec(TimerExecutor executor) throws Exception { - if (theTimer == null) { - com.codahale.metrics.Timer t = metrics.timer(MetricRegistry.name(clazz, name)); - com.codahale.metrics.Timer.Context tContext = t.time(); - theTimer = new Timer(tContext); - } - - executor.doWithTimer(); - - theTimer.close(); - } - */ - } - - private static class ConditionFalseTimerBuilder extends TimerBuilder { - @Override - public Timer timer(Class clazz, String name) { - return nullTimerSingleton; - } } /** - * + * A class that holds a Metrics timer context implementing {@link AutoCloseable} + * thus it can be used in a try-with-resources statement. */ public static class Timer implements AutoCloseable { @@ -101,6 +97,13 @@ public class MetricsManager { } + + // ----------------------------------------------------------------- + // Convenient singletons to avoid creating useless objects instances + // ----------------------------------------------------------------- + private static final NullTimer nullTimerSingleton = new NullTimer(null); + private static final ConditionFalseTimerBuilder conditionFalseTimerBuilderSingleton = new ConditionFalseTimerBuilder(); + private static class NullTimer extends Timer { protected NullTimer(com.codahale.metrics.Timer.Context timerContext) { @@ -112,4 +115,12 @@ public class MetricsManager { // Does nothing } } + + private static class ConditionFalseTimerBuilder extends TimerBuilder { + @Override + public Timer timer(Class clazz, String name) { + return nullTimerSingleton; + } + } + } From 9a64777edeae7b18d864967b22ccc312963d8585 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Cocula?= Date: Wed, 25 Jan 2017 21:39:18 +0100 Subject: [PATCH 5/8] Add jmeter first test plan --- .../jMeter/libresonicMainTestPlan.jmx | 226 ++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100755 libresonic-main/src/test/resources/jMeter/libresonicMainTestPlan.jmx diff --git a/libresonic-main/src/test/resources/jMeter/libresonicMainTestPlan.jmx b/libresonic-main/src/test/resources/jMeter/libresonicMainTestPlan.jmx new file mode 100755 index 00000000..213721d8 --- /dev/null +++ b/libresonic-main/src/test/resources/jMeter/libresonicMainTestPlan.jmx @@ -0,0 +1,226 @@ + + + + + + false + false + + + + + + + + + false + standard + org.apache.jmeter.protocol.http.control.HC4CookieHandler + + + + continue + + false + 1 + + 1 + + 1459196515000 + 1459196515000 + false + + + + + + + + + + + localhost + 8080 + + + + + /login + GET + true + false + true + false + false + + + + + LOGIN_CSRF_TOKEN + input[name=_csrf] + value + toto + false + + JSOUP + ${LOGIN_CSRF_TOKEN} + + + + + + + + false + ${LOGIN_CSRF_TOKEN} + = + true + _csrf + + + false + admin + = + true + j_username + + + false + admin + = + true + j_password + + + + localhost + 8080 + + + + + /login + POST + true + false + true + false + false + + + + + + true + -1 + + + + 5000 + + + + + + + false + 2 + = + true + id + + + + localhost + 8080 + + + + + /main.view + GET + true + false + true + false + false + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + + From 88e820d8d13365e6def0a25f34d0ab06eee35f76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Cocula?= Date: Wed, 25 Jan 2017 22:53:13 +0100 Subject: [PATCH 6/8] MetricsManager is now configurable. --- .../libresonic/player/boot/Application.java | 7 +- .../player/filter/MetricsFilter.java | 16 ++--- .../player/monitor/MetricsManager.java | 67 ++++++++++++++----- .../resources/applicationContext-service.xml | 4 ++ 4 files changed, 66 insertions(+), 28 deletions(-) diff --git a/libresonic-main/src/main/java/org/libresonic/player/boot/Application.java b/libresonic-main/src/main/java/org/libresonic/player/boot/Application.java index a5492d81..7e35eeb5 100644 --- a/libresonic-main/src/main/java/org/libresonic/player/boot/Application.java +++ b/libresonic-main/src/main/java/org/libresonic/player/boot/Application.java @@ -146,10 +146,15 @@ public class Application extends SpringBootServletInitializer { return registration; } + @Bean + public Filter metricsFilter() { + return new MetricsFilter(); + } + @Bean public FilterRegistrationBean metricsFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); - registration.setFilter(new MetricsFilter()); + registration.setFilter(metricsFilter()); registration.setOrder(7); return registration; } diff --git a/libresonic-main/src/main/java/org/libresonic/player/filter/MetricsFilter.java b/libresonic-main/src/main/java/org/libresonic/player/filter/MetricsFilter.java index ec88be54..528e6039 100644 --- a/libresonic-main/src/main/java/org/libresonic/player/filter/MetricsFilter.java +++ b/libresonic-main/src/main/java/org/libresonic/player/filter/MetricsFilter.java @@ -1,30 +1,23 @@ package org.libresonic.player.filter; -import com.codahale.metrics.JmxReporter; -import com.codahale.metrics.MetricRegistry; import org.libresonic.player.monitor.MetricsManager; +import org.springframework.beans.factory.annotation.Autowired; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; -import java.util.concurrent.TimeUnit; /** * Created by remi on 12/01/17. */ public class MetricsFilter implements Filter { - private final MetricRegistry metrics = new MetricRegistry(); - JmxReporter reporter; + @Autowired + private MetricsManager metricsManager; @Override public void init(FilterConfig filterConfig) throws ServletException { - reporter = JmxReporter.forRegistry(metrics) - .convertRatesTo(TimeUnit.SECONDS.SECONDS) - .convertDurationsTo(TimeUnit.MILLISECONDS) - .build(); - reporter.start(); } @Override @@ -32,13 +25,12 @@ public class MetricsFilter implements Filter { HttpServletRequest httpServletRequest = (HttpServletRequest)request; String timerName = httpServletRequest.getRequestURI(); - try (MetricsManager.Timer t = MetricsManager.condition(timerName.contains("main.view")).timer(this,timerName)) { + try (MetricsManager.Timer t = metricsManager.condition(timerName.contains("main.view")).timer(this,timerName)) { chain.doFilter(request, response); } } @Override public void destroy() { - reporter.stop(); } } diff --git a/libresonic-main/src/main/java/org/libresonic/player/monitor/MetricsManager.java b/libresonic-main/src/main/java/org/libresonic/player/monitor/MetricsManager.java index 8f2d358f..31c5f8fb 100644 --- a/libresonic-main/src/main/java/org/libresonic/player/monitor/MetricsManager.java +++ b/libresonic-main/src/main/java/org/libresonic/player/monitor/MetricsManager.java @@ -2,6 +2,7 @@ package org.libresonic.player.monitor; import com.codahale.metrics.JmxReporter; import com.codahale.metrics.MetricRegistry; +import org.libresonic.player.service.ApacheCommonsConfigurationService; import java.util.concurrent.TimeUnit; @@ -10,18 +11,41 @@ import java.util.concurrent.TimeUnit; */ public class MetricsManager { + private ApacheCommonsConfigurationService configurationService; + // Main metrics registry private static final MetricRegistry metrics = new MetricRegistry(); + private static Boolean metricsActivatedByConfiguration = null; + private static Object _lock = new Object(); + // Potential metrics reporters private static JmxReporter reporter; - static { - reporter = JmxReporter.forRegistry(metrics) - .convertRatesTo(TimeUnit.SECONDS.SECONDS) - .convertDurationsTo(TimeUnit.MILLISECONDS) - .build(); - reporter.start(); + private void configureMetricsActivation() { + if (configurationService.containsKey("Metrics")) { + metricsActivatedByConfiguration = Boolean.TRUE; + + // Start a Metrics JMX reporter + reporter = JmxReporter.forRegistry(metrics) + .convertRatesTo(TimeUnit.SECONDS.SECONDS) + .convertDurationsTo(TimeUnit.MILLISECONDS) + .build(); + reporter.start(); + } else { + metricsActivatedByConfiguration = Boolean.FALSE; + } + } + + private boolean metricsActivatedByConfiguration() { + if (metricsActivatedByConfiguration == null) { + synchronized (_lock) { + if (metricsActivatedByConfiguration == null) { + configureMetricsActivation(); + } + } + } + return metricsActivatedByConfiguration.booleanValue(); } /** @@ -31,8 +55,12 @@ public class MetricsManager { * @param name * @return */ - public static Timer timer(Class clazz, String name) { - return new TimerBuilder().timer(clazz,name); + public Timer timer(Class clazz, String name) { + if (metricsActivatedByConfiguration()) { + return new TimerBuilder().timer(clazz, name); + } else { + return nullTimerSingleton; + } } /** @@ -42,7 +70,7 @@ public class MetricsManager { * @param name * @return */ - public static Timer timer(Object ref, String name) { + public Timer timer(Object ref, String name) { return timer(ref.getClass(),name); } @@ -54,11 +82,19 @@ public class MetricsManager { * @param ifTrue * @return */ - public static TimerBuilder condition(boolean ifTrue) { - if (ifTrue == false) { - return conditionFalseTimerBuilderSingleton; + public TimerBuilder condition(boolean ifTrue) { + if (metricsActivatedByConfiguration()) { + if (ifTrue == false) { + return conditionFalseTimerBuilderSingleton; + } + return new TimerBuilder(); + } else { + return nullTimerBuilderSingleton; } - return new TimerBuilder(); + } + + public void setConfigurationService(ApacheCommonsConfigurationService configurationService) { + this.configurationService = configurationService; } /** @@ -102,7 +138,8 @@ public class MetricsManager { // Convenient singletons to avoid creating useless objects instances // ----------------------------------------------------------------- private static final NullTimer nullTimerSingleton = new NullTimer(null); - private static final ConditionFalseTimerBuilder conditionFalseTimerBuilderSingleton = new ConditionFalseTimerBuilder(); + private static final NullTimerBuilder conditionFalseTimerBuilderSingleton = new NullTimerBuilder(); + private static final NullTimerBuilder nullTimerBuilderSingleton = new NullTimerBuilder(); private static class NullTimer extends Timer { @@ -116,7 +153,7 @@ public class MetricsManager { } } - private static class ConditionFalseTimerBuilder extends TimerBuilder { + private static class NullTimerBuilder extends TimerBuilder { @Override public Timer timer(Class clazz, String name) { return nullTimerSingleton; diff --git a/libresonic-main/src/main/resources/applicationContext-service.xml b/libresonic-main/src/main/resources/applicationContext-service.xml index a966a9cf..300bbd52 100644 --- a/libresonic-main/src/main/resources/applicationContext-service.xml +++ b/libresonic-main/src/main/resources/applicationContext-service.xml @@ -92,6 +92,10 @@ + + + + From aec131a3d1c4903f82bd87c5cb63f9a4b3999fc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Cocula?= Date: Wed, 25 Jan 2017 23:18:52 +0100 Subject: [PATCH 7/8] A bit of documentation. --- .../developer/Performance-Metrics.md | 18 ++++++++++++++++++ .../developer/metrics-visualvm-screenshot.png | Bin 0 -> 27829 bytes .../player/filter/MetricsFilter.java | 1 + 3 files changed, 19 insertions(+) create mode 100644 documentation/developer/Performance-Metrics.md create mode 100644 documentation/developer/metrics-visualvm-screenshot.png diff --git a/documentation/developer/Performance-Metrics.md b/documentation/developer/Performance-Metrics.md new file mode 100644 index 00000000..eca7bdb3 --- /dev/null +++ b/documentation/developer/Performance-Metrics.md @@ -0,0 +1,18 @@ + +The Libresonic framework contains a convenient class (called MetricsManager) to add inner metrics that constructs in real time some performance indicators. + +The use of MetricsManager is illustrated in the org.libresonic.player.filter.MetricsFilter class. + +The MetricsFilter adds a metric based on the time spent by each /main.view HTTP request. This is interesting as the main.view request is invoqued when something is displayed in the main Libresonic web frame. + +By default, the MetricsManager is deactivated; it does nothing. +It can be activated only by adding the following line inside the livresonic.properties configuration file : + +``` +Metrics= +``` + +Once the MetricsManager as been activated this way, each metric can be inspected using a jmx console like VisualVM. +Each metric is registered as a MBean as shown below. + +![](metrics-visualvm-screenshot.png) \ No newline at end of file diff --git a/documentation/developer/metrics-visualvm-screenshot.png b/documentation/developer/metrics-visualvm-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..d9a52c7c5c3f0b8d431128503195f3d6dd0971af GIT binary patch literal 27829 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 literal 0 HcmV?d00001 diff --git a/libresonic-main/src/main/java/org/libresonic/player/filter/MetricsFilter.java b/libresonic-main/src/main/java/org/libresonic/player/filter/MetricsFilter.java index 528e6039..18e886f8 100644 --- a/libresonic-main/src/main/java/org/libresonic/player/filter/MetricsFilter.java +++ b/libresonic-main/src/main/java/org/libresonic/player/filter/MetricsFilter.java @@ -25,6 +25,7 @@ public class MetricsFilter implements Filter { HttpServletRequest httpServletRequest = (HttpServletRequest)request; String timerName = httpServletRequest.getRequestURI(); + // Add a metric that measures the time spent for each http request for the /main.view url. try (MetricsManager.Timer t = metricsManager.condition(timerName.contains("main.view")).timer(this,timerName)) { chain.doFilter(request, response); } From 8508581db3e6cc2236e03ce4a0446db50dd52cc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Cocula?= Date: Mon, 13 Feb 2017 22:08:48 +0100 Subject: [PATCH 8/8] Reserve jmeter case test for future PR. --- .../jMeter/libresonicMainTestPlan.jmx | 226 ------------------ 1 file changed, 226 deletions(-) delete mode 100755 libresonic-main/src/test/resources/jMeter/libresonicMainTestPlan.jmx diff --git a/libresonic-main/src/test/resources/jMeter/libresonicMainTestPlan.jmx b/libresonic-main/src/test/resources/jMeter/libresonicMainTestPlan.jmx deleted file mode 100755 index 213721d8..00000000 --- a/libresonic-main/src/test/resources/jMeter/libresonicMainTestPlan.jmx +++ /dev/null @@ -1,226 +0,0 @@ - - - - - - false - false - - - - - - - - - false - standard - org.apache.jmeter.protocol.http.control.HC4CookieHandler - - - - continue - - false - 1 - - 1 - - 1459196515000 - 1459196515000 - false - - - - - - - - - - - localhost - 8080 - - - - - /login - GET - true - false - true - false - false - - - - - LOGIN_CSRF_TOKEN - input[name=_csrf] - value - toto - false - - JSOUP - ${LOGIN_CSRF_TOKEN} - - - - - - - - false - ${LOGIN_CSRF_TOKEN} - = - true - _csrf - - - false - admin - = - true - j_username - - - false - admin - = - true - j_password - - - - localhost - 8080 - - - - - /login - POST - true - false - true - false - false - - - - - - true - -1 - - - - 5000 - - - - - - - false - 2 - = - true - id - - - - localhost - 8080 - - - - - /main.view - GET - true - false - true - false - false - - - - - - - false - - saveConfig - - - true - true - true - - true - true - true - true - false - true - true - false - false - false - true - false - false - false - true - 0 - true - true - true - true - true - - - - - - - false - - saveConfig - - - true - true - true - - true - true - true - true - false - true - true - false - false - false - true - false - false - false - true - 0 - true - true - true - true - true - - - - - - - -