Skip to content

Commit

Permalink
Expose Prometheus metrics with Micrometer
Browse files Browse the repository at this point in the history
Signed-off-by: nscuro <nscuro@protonmail.com>
  • Loading branch information
nscuro committed Jul 11, 2022
1 parent f68ff13 commit b13e790
Show file tree
Hide file tree
Showing 11 changed files with 229 additions and 13 deletions.
4 changes: 4 additions & 0 deletions alpine-common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<!-- Test Dependencies -->
<dependency>
<groupId>junit</groupId>
Expand Down
3 changes: 2 additions & 1 deletion alpine-common/src/main/java/alpine/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ public enum AlpineKey implements Key {
LDAP_USERS_SEARCH_FILTER ("alpine.ldap.users.search.filter", null),
LDAP_USER_PROVISIONING ("alpine.ldap.user.provisioning", false),
LDAP_TEAM_SYNCHRONIZATION ("alpine.ldap.team.synchronization", false),
OIDC_ENABLED ("alpine.oidc.enabled", false),
METRICS_ENABLED ("alpine.metrics.enabled", false),
OIDC_ENABLED ("alpine.oidc.enabled", false),
OIDC_ISSUER ("alpine.oidc.issuer", null),
OIDC_CLIENT_ID ("alpine.oidc.client.id", null),
OIDC_USERNAME_CLAIM ("alpine.oidc.username.claim", "sub"),
Expand Down
45 changes: 45 additions & 0 deletions alpine-common/src/main/java/alpine/common/metrics/Metrics.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* This file is part of Alpine.
*
* Licensed 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.
*
* SPDX-License-Identifier: Apache-2.0
* Copyright (c) Steve Springett. All Rights Reserved.
*/
package alpine.common.metrics;

import alpine.Config;
import io.micrometer.core.instrument.binder.jvm.ExecutorServiceMetrics;
import io.micrometer.prometheus.PrometheusConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry;

import java.util.concurrent.ExecutorService;

/**
* @since 2.1.0
*/
public final class Metrics {

private static final PrometheusMeterRegistry registry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);

public static PrometheusMeterRegistry getRegistry() {
return registry;
}

public static void registerExecutorService(final ExecutorService executorService, final String name) {
if (Config.getInstance().getPropertyAsBoolean(Config.AlpineKey.METRICS_ENABLED)) {
new ExecutorServiceMetrics(executorService, name, null).bindTo(registry);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@
package alpine.event.framework;

import alpine.common.logging.Logger;
import alpine.common.metrics.Metrics;
import alpine.model.EventServiceLog;
import alpine.persistence.AlpineQueryManager;
import io.micrometer.core.instrument.Counter;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
Expand Down Expand Up @@ -86,7 +88,7 @@ public void publish(Event event) {
if (event instanceof ChainableEvent) {
if (! addTrackedEvent((ChainableEvent)event)) {
return;
};
}
}

// Check to see if the Event is Unblocked. If so, use a separate executor pool from normal events
Expand Down Expand Up @@ -138,6 +140,7 @@ public void publish(Event event) {

});
}
recordPublishedMetric(event);
}

/**
Expand Down Expand Up @@ -189,6 +192,14 @@ private synchronized void removeTrackedEvent(ChainableEvent event) {
}
}

private void recordPublishedMetric(final Event event) {
Counter.builder("alpine_events_published_total")
.description("Total number of published events")
.tags("event", event.getClass().getName(), "publisher", this.getClass().getName())
.register(Metrics.getRegistry())
.increment();
}

/**
* {@inheritDoc}
* @since 1.0.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import alpine.common.logging.Logger;
import alpine.common.util.ThreadUtil;
import alpine.common.metrics.Metrics;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
Expand All @@ -42,15 +43,17 @@ public final class EventService extends BaseEventService {
private static final EventService INSTANCE = new EventService();
private static final Logger LOGGER = Logger.getLogger(EventService.class);
private static final ExecutorService EXECUTOR;
private static final String EXECUTOR_NAME = "Alpine-EventService";

static {
BasicThreadFactory factory = new BasicThreadFactory.Builder()
.namingPattern("Alpine-EventService-%d")
.namingPattern(EXECUTOR_NAME + "-%d")
.uncaughtExceptionHandler(new LoggableUncaughtExceptionHandler())
.build();
EXECUTOR = Executors.newFixedThreadPool(ThreadUtil.determineNumberOfWorkerThreads(), factory);
INSTANCE.setExecutorService(EXECUTOR);
INSTANCE.setLogger(LOGGER);
Metrics.registerExecutorService(EXECUTOR, EXECUTOR_NAME);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package alpine.event.framework;

import alpine.common.logging.Logger;
import alpine.common.metrics.Metrics;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;

import java.util.concurrent.ExecutorService;
Expand All @@ -40,15 +41,17 @@ public final class SingleThreadedEventService extends BaseEventService {
private static final SingleThreadedEventService INSTANCE = new SingleThreadedEventService();
private static final Logger LOGGER = Logger.getLogger(EventService.class);
private static final ExecutorService EXECUTOR;
private static final String EXECUTOR_NAME = "Alpine-SingleThreadedEventService";

static {
BasicThreadFactory factory = new BasicThreadFactory.Builder()
.namingPattern("Alpine-SingleThreadedEventService")
.namingPattern(EXECUTOR_NAME)
.uncaughtExceptionHandler(new LoggableUncaughtExceptionHandler())
.build();
EXECUTOR = Executors.newSingleThreadExecutor(factory);
INSTANCE.setExecutorService(EXECUTOR);
INSTANCE.setLogger(LOGGER);
Metrics.registerExecutorService(EXECUTOR, EXECUTOR_NAME);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@
*/
package alpine.notification;

import alpine.event.framework.LoggableUncaughtExceptionHandler;
import alpine.common.logging.Logger;
import alpine.common.metrics.Metrics;
import alpine.event.framework.LoggableUncaughtExceptionHandler;
import io.micrometer.core.instrument.Counter;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Map;
Expand All @@ -42,12 +45,18 @@ public final class NotificationService implements INotificationService {
private static final NotificationService INSTANCE = new NotificationService();
private static final Logger LOGGER = Logger.getLogger(NotificationService.class);
private static final Map<Class<? extends Notification>, ArrayList<Subscription>> SUBSCRIPTION_MAP = new ConcurrentHashMap<>();
private static final ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(4,
new BasicThreadFactory.Builder()
.namingPattern("Alpine-NotificationService-%d")
.uncaughtExceptionHandler(new LoggableUncaughtExceptionHandler())
.build()
);
private static final ExecutorService EXECUTOR_SERVICE;
private static final String EXECUTOR_SERVICE_NAME = "Alpine-NotificationService";

static {
EXECUTOR_SERVICE = Executors.newFixedThreadPool(4,
new BasicThreadFactory.Builder()
.namingPattern(EXECUTOR_SERVICE_NAME + "-%d")
.uncaughtExceptionHandler(new LoggableUncaughtExceptionHandler())
.build()
);
Metrics.registerExecutorService(EXECUTOR_SERVICE, EXECUTOR_SERVICE_NAME);
}

/**
* Private constructor
Expand All @@ -70,7 +79,7 @@ public void publish(final Notification notification) {
LOGGER.debug("No subscribers to inform from notification: " + notification.getClass().getName());
return;
}
for (final Subscription subscription: subscriptions) {
for (final Subscription subscription : subscriptions) {
if (subscription.getScope() != null && subscription.getGroup() != null && subscription.getLevel() != null) { // subscription was the most specific
if (subscription.getScope().equals(notification.getScope()) && subscription.getGroup().equals(notification.getGroup()) && subscription.getLevel() == notification.getLevel()) {
alertSubscriber(notification, subscription.getSubscriber());
Expand All @@ -91,19 +100,33 @@ public void publish(final Notification notification) {
alertSubscriber(notification, subscription.getSubscriber());
}
}
recordPublishedMetric(notification);
}

private void alertSubscriber(final Notification notification, final Class<? extends Subscriber> subscriberClass) {
LOGGER.debug("Alerting subscriber " + subscriberClass.getName());
EXECUTOR_SERVICE.execute(() -> {
try {
subscriberClass.getDeclaredConstructor().newInstance().inform(notification);
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException | SecurityException e) {
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException |
IllegalAccessException | SecurityException e) {
LOGGER.error("An error occurred while informing subscriber: " + e);
}
});
}

private void recordPublishedMetric(final Notification notification) {
Counter.builder("alpine_notifications_published_total")
.description("Total number of published notifications")
.tags(
"group", notification.getGroup(),
"level", notification.getLevel().name(),
"scope", notification.getScope()
)
.register(Metrics.getRegistry())
.increment();
}

/**
* {@inheritDoc}
* @since 1.3.0
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* This file is part of Alpine.
*
* Licensed 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.
*
* SPDX-License-Identifier: Apache-2.0
* Copyright (c) Steve Springett. All Rights Reserved.
*/
package alpine.server.metrics;

import alpine.Config;
import alpine.common.logging.Logger;
import alpine.common.metrics.Metrics;
import io.micrometer.core.instrument.binder.jvm.ClassLoaderMetrics;
import io.micrometer.core.instrument.binder.jvm.JvmGcMetrics;
import io.micrometer.core.instrument.binder.jvm.JvmInfoMetrics;
import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics;
import io.micrometer.core.instrument.binder.jvm.JvmThreadMetrics;
import io.micrometer.core.instrument.binder.system.DiskSpaceMetrics;
import io.micrometer.core.instrument.binder.system.ProcessorMetrics;
import io.micrometer.core.instrument.binder.system.UptimeMetrics;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

/**
* @since 2.1.0
*/
public class MetricsInitializer implements ServletContextListener {

private static final Logger LOGGER = Logger.getLogger(MetricsInitializer.class);

@Override
public void contextInitialized(final ServletContextEvent event) {
if (Config.getInstance().getPropertyAsBoolean(Config.AlpineKey.METRICS_ENABLED)) {
LOGGER.info("Registering system metrics");
new ClassLoaderMetrics().bindTo(Metrics.getRegistry());
new DiskSpaceMetrics(Config.getInstance().getDataDirectorty()).bindTo(Metrics.getRegistry());
new JvmGcMetrics().bindTo(Metrics.getRegistry());
new JvmInfoMetrics().bindTo(Metrics.getRegistry());
new JvmMemoryMetrics().bindTo(Metrics.getRegistry());
new JvmThreadMetrics().bindTo(Metrics.getRegistry());
new ProcessorMetrics().bindTo(Metrics.getRegistry());
new UptimeMetrics().bindTo(Metrics.getRegistry());
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* This file is part of Alpine.
*
* Licensed 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.
*
* SPDX-License-Identifier: Apache-2.0
* Copyright (c) Steve Springett. All Rights Reserved.
*/
package alpine.server.servlets;

import alpine.Config;
import alpine.common.metrics.Metrics;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* @since 2.1.0
*/
public class MetricsServlet extends HttpServlet {

private boolean metricsEnabled;

@Override
public void init() throws ServletException {
metricsEnabled = Config.getInstance().getPropertyAsBoolean(Config.AlpineKey.METRICS_ENABLED);
}

@Override
protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws IOException {
if (metricsEnabled) {
Metrics.getRegistry().scrape(resp.getWriter());
} else {
resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
}
}

}
10 changes: 10 additions & 0 deletions example/src/main/webapp/WEB-INF/web.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,14 @@
<url-pattern>/api/*</url-pattern>
</servlet-mapping>

<servlet>
<servlet-name>Metrics</servlet-name>
<servlet-class>alpine.server.servlets.MetricsServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Metrics</servlet-name>
<url-pattern>/metrics</url-pattern>
</servlet-mapping>

</web-app>
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@
<lib.jsr353-spec.version>1.1.4</lib.jsr353-spec.version>
<lib.jstl.version>1.2.5</lib.jstl.version>
<lib.logback.version>1.2.11</lib.logback.version>
<lib.micrometer.version>1.9.1</lib.micrometer.version>
<lib.nimbus-oauth2-oidc-sdk.version>9.37</lib.nimbus-oauth2-oidc-sdk.version>
<lib.owasp.encoder.version>1.2.3</lib.owasp.encoder.version>
<lib.owasp.security-logging.version>1.1.7</lib.owasp.security-logging.version>
Expand Down Expand Up @@ -380,6 +381,12 @@
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>${lib.jackson.version}</version>
</dependency>
<!-- Metrics -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<version>${lib.micrometer.version}</version>
</dependency>
<!-- Logging -->
<!-- Overriding OWASP Security Logging dependencies with newer versions -->
<dependency>
Expand Down

0 comments on commit b13e790

Please sign in to comment.