Merge pull request #249 from biconou/biconou_develop_PR_metrics

Performance metrics
master
Tom Powell 8 years ago committed by GitHub
commit 760d261275
  1. 18
      documentation/developer/Performance-Metrics.md
  2. BIN
      documentation/developer/metrics-visualvm-screenshot.png
  3. 1
      libresonic-main/pom.xml
  4. 14
      libresonic-main/src/main/java/org/libresonic/player/boot/Application.java
  5. 37
      libresonic-main/src/main/java/org/libresonic/player/filter/MetricsFilter.java
  6. 163
      libresonic-main/src/main/java/org/libresonic/player/monitor/MetricsManager.java
  7. 4
      libresonic-main/src/main/resources/applicationContext-service.xml

@ -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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

@ -38,7 +38,6 @@
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
<version>${metrics.version}</version>
<scope>test</scope>
</dependency>
<!-- END Metrics -->

@ -153,6 +153,20 @@ public class Application extends SpringBootServletInitializer implements Embedde
return registration;
}
@Bean
public Filter metricsFilter() {
return new MetricsFilter();
}
@Bean
public FilterRegistrationBean metricsFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(metricsFilter());
registration.setOrder(7);
return registration;
}
@Bean
public Filter noCacheFilter() {
return new ResponseHeaderFilter();

@ -0,0 +1,37 @@
package org.libresonic.player.filter;
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;
/**
* Created by remi on 12/01/17.
*/
public class MetricsFilter implements Filter {
@Autowired
private MetricsManager metricsManager;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
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);
}
}
@Override
public void destroy() {
}
}

@ -0,0 +1,163 @@
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;
/**
* Created by remi on 17/01/17.
*/
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;
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();
}
/**
* Creates a {@link Timer} whose name is based on a class name and a
* qualified name.
* @param clazz
* @param name
* @return
*/
public Timer timer(Class clazz, String name) {
if (metricsActivatedByConfiguration()) {
return new TimerBuilder().timer(clazz, name);
} else {
return nullTimerSingleton;
}
}
/**
* Creates a {@link Timer} whose name is based on an object's class name and a
* qualified name.
* @param ref
* @param name
* @return
*/
public 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 TimerBuilder condition(boolean ifTrue) {
if (metricsActivatedByConfiguration()) {
if (ifTrue == false) {
return conditionFalseTimerBuilderSingleton;
}
return new TimerBuilder();
} else {
return nullTimerBuilderSingleton;
}
}
public void setConfigurationService(ApacheCommonsConfigurationService configurationService) {
this.configurationService = configurationService;
}
/**
* A class that builds a {@link Timer}
*/
public static class TimerBuilder {
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(Object ref, String name) {
return timer(ref.getClass(),name);
}
}
/**
* 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 {
private com.codahale.metrics.Timer.Context timerContext;
protected Timer(com.codahale.metrics.Timer.Context timerContext) {
this.timerContext = timerContext;
}
@Override
public void close() {
timerContext.stop();
}
}
// -----------------------------------------------------------------
// Convenient singletons to avoid creating useless objects instances
// -----------------------------------------------------------------
private static final NullTimer nullTimerSingleton = new NullTimer(null);
private static final NullTimerBuilder conditionFalseTimerBuilderSingleton = new NullTimerBuilder();
private static final NullTimerBuilder nullTimerBuilderSingleton = new NullTimerBuilder();
private static class NullTimer extends Timer {
protected NullTimer(com.codahale.metrics.Timer.Context timerContext) {
super(timerContext);
}
@Override
public void close() {
// Does nothing
}
}
private static class NullTimerBuilder extends TimerBuilder {
@Override
public Timer timer(Class clazz, String name) {
return nullTimerSingleton;
}
}
}

@ -92,6 +92,10 @@
<bean id="configurationService" class="org.libresonic.player.service.ApacheCommonsConfigurationService" />
<bean id="metricsManager" class="org.libresonic.player.monitor.MetricsManager">
<property name="configurationService" ref="configurationService" />
</bean>
<bean id="settingsService" class="org.libresonic.player.service.SettingsService" init-method="init">
<property name="internetRadioDao" ref="internetRadioDao"/>
<property name="musicFolderDao" ref="musicFolderDao"/>

Loading…
Cancel
Save