Metrics for events (#1601)

Updates legman to version 2, which allows the usage of the MicrometerPlugin. The plugin will collect metrics for subscriber invocations and the underlying executor.

Furthermore this change will fix the usage of wrong subject context in the asynchronous events.
This commit is contained in:
Sebastian Sdorra
2021-03-24 15:54:29 +01:00
committed by GitHub
parent c5720b36b5
commit 8f2272885b
9 changed files with 184 additions and 58 deletions

View File

@@ -27,6 +27,7 @@ import com.hierynomus.gradle.license.tasks.LicenseCheck
import org.gradle.api.Plugin import org.gradle.api.Plugin
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.publish.maven.MavenPublication import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.api.tasks.javadoc.Javadoc import org.gradle.api.tasks.javadoc.Javadoc
import org.gradle.jvm.toolchain.JavaLanguageVersion import org.gradle.jvm.toolchain.JavaLanguageVersion
@@ -45,12 +46,9 @@ class JavaModulePlugin implements Plugin<Project> {
withSourcesJar() withSourcesJar()
} }
project.compileJava { project.tasks.withType(JavaCompile) {
options.release = 8
}
project.compileTestJava {
options.release = 8 options.release = 8
options.encoding = 'UTF-8'
} }
project.tasks.withType(Javadoc) { project.tasks.withType(Javadoc) {

View File

@@ -35,6 +35,7 @@ changelog {
subprojects { s -> subprojects { s ->
repositories { repositories {
mavenLocal()
maven { maven {
url 'https://packages.scm-manager.org/repository/public/' url 'https://packages.scm-manager.org/repository/public/'
} }

View File

@@ -0,0 +1,4 @@
- type: added
description: Metrics for events ([#1601](https://github.com/scm-manager/scm-manager/pull/1601))
- type: fixed
description: Wrong subject context for asynchronous subscriber ([#1601](https://github.com/scm-manager/scm-manager/pull/1601))

View File

@@ -6,6 +6,8 @@ ext {
// TODO upgrade to 2.12.0, but this breaks openapi spec generation // TODO upgrade to 2.12.0, but this breaks openapi spec generation
jacksonVersion = '2.11.3' jacksonVersion = '2.11.3'
legmanVersion = '2.0.0'
mapstructVersion = '1.3.1.Final' mapstructVersion = '1.3.1.Final'
jaxbVersion = '2.3.3' jaxbVersion = '2.3.3'
shiroVersion = '1.7.1' shiroVersion = '1.7.1'
@@ -73,7 +75,9 @@ ext {
mapstructProcessor: "org.mapstruct:mapstruct-processor:${mapstructVersion}", mapstructProcessor: "org.mapstruct:mapstruct-processor:${mapstructVersion}",
// events // events
legman: 'com.github.legman:core:1.6.2', legman: "com.cloudogu.legman:core:${legmanVersion}",
legmanShiro: "com.cloudogu.legman.support:shiro:${legmanVersion}",
legmanMicrometer: "com.cloudogu.legman.support:micrometer:${legmanVersion}",
// xml binding // xml binding
jaxbApi: "jakarta.xml.bind:jakarta.xml.bind-api:${jaxbVersion}", jaxbApi: "jakarta.xml.bind:jakarta.xml.bind-api:${jaxbVersion}",

View File

@@ -21,13 +21,12 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
package sonia.scm.event; package sonia.scm.event;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import com.github.legman.EventBus; import com.github.legman.EventBus;
import com.github.legman.EventBusRegistry;
/** /**
* *
@@ -76,5 +75,5 @@ public class ScmTestEventBus extends ScmEventBus
//~--- fields --------------------------------------------------------------- //~--- fields ---------------------------------------------------------------
/** Field description */ /** Field description */
private final EventBus eventBus = EventBusRegistry.getEventBus(); private final EventBus eventBus = new EventBus("testing");
} }

View File

@@ -113,6 +113,10 @@ dependencies {
// util // util
implementation libraries.commonsCompress implementation libraries.commonsCompress
// events
implementation libraries.legmanShiro
implementation libraries.legmanMicrometer
// lombok // lombok
compileOnly libraries.lombok compileOnly libraries.lombok
testCompileOnly libraries.lombok testCompileOnly libraries.lombok

View File

@@ -21,66 +21,72 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
package sonia.scm.event; package sonia.scm.event;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import com.github.legman.EventBus; import com.github.legman.EventBus;
import com.github.legman.Subscribe; import com.github.legman.Subscribe;
import com.github.legman.micrometer.MicrometerPlugin;
import com.github.legman.shiro.ShiroPlugin;
import com.google.common.annotations.VisibleForTesting;
import io.micrometer.core.instrument.Tag;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sonia.scm.metrics.MeterRegistryProvider;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
/** /**
* *
* @author Sebastian Sdorra * @author Sebastian Sdorra
*/ */
public class LegmanScmEventBus extends ScmEventBus public class LegmanScmEventBus extends ScmEventBus {
{
private static final AtomicLong INSTANCE_COUNTER = new AtomicLong(); private static final AtomicLong INSTANCE_COUNTER = new AtomicLong();
private static final String FORMAT_NAME = "ScmEventBus-%s";
/** Field description */
private static final String NAME = "ScmEventBus-%s";
/** /**
* the logger for LegmanScmEventBus * the logger for LegmanScmEventBus
*/ */
private static final Logger logger = private static final Logger logger = LoggerFactory.getLogger(LegmanScmEventBus.class);
LoggerFactory.getLogger(LegmanScmEventBus.class);
//~--- constructors ---------------------------------------------------------
private String name; private String name;
private EventBus eventBus;
/**
* Constructs ...
*
*/
public LegmanScmEventBus() { public LegmanScmEventBus() {
eventBus = create(); eventBus = create(null);
} }
private EventBus create() { @VisibleForTesting
name = String.format(NAME, INSTANCE_COUNTER.incrementAndGet()); LegmanScmEventBus(Executor executor) {
eventBus = create(executor);
}
private EventBus create(Executor executor) {
name = String.format(FORMAT_NAME, INSTANCE_COUNTER.incrementAndGet());
logger.info("create new event bus {}", name); logger.info("create new event bus {}", name);
return new EventBus(name);
MicrometerPlugin micrometerPlugin = new MicrometerPlugin(MeterRegistryProvider.getStaticRegistry())
.withExecutorTags(Tag.of("type", "fixed"));
ShiroPlugin shiroPlugin = new ShiroPlugin();
EventBus.Builder builder = EventBus.builder()
.withIdentifier(name)
.withPlugins(shiroPlugin, micrometerPlugin);
if (executor != null) {
builder.withExecutor(executor);
}
return builder.build();
} }
//~--- methods --------------------------------------------------------------
/**
* {@inheritDoc}
*
*
* @param event
*/
@Override @Override
public void post(Object event) public void post(Object event) {
{
if (eventBus != null) { if (eventBus != null) {
logger.debug("post {} to event bus {}", event, name); logger.debug("post {} to event bus {}", event, name);
eventBus.post(event); eventBus.post(event);
@@ -93,12 +99,9 @@ public class LegmanScmEventBus extends ScmEventBus
* Registers a object to the eventbus. * Registers a object to the eventbus.
* *
* @param object object to register * @param object object to register
*
* @see {@link #register(java.lang.Object)}
*/ */
@Override @Override
public void register(Object object) public void register(Object object) {
{
if (eventBus != null) { if (eventBus != null) {
logger.trace("register {} to event bus {}", object, name); logger.trace("register {} to event bus {}", object, name);
eventBus.register(object); eventBus.register(object);
@@ -107,15 +110,8 @@ public class LegmanScmEventBus extends ScmEventBus
} }
} }
/**
* {@inheritDoc}
*
*
* @param object
*/
@Override @Override
public void unregister(Object object) public void unregister(Object object) {
{
if (eventBus != null) { if (eventBus != null) {
logger.trace("unregister {} from event bus {}", object, name); logger.trace("unregister {} from event bus {}", object, name);
@@ -147,11 +143,7 @@ public class LegmanScmEventBus extends ScmEventBus
eventBus.shutdown(); eventBus.shutdown();
} }
logger.info("recreate event bus because of received RecreateEventBusEvent"); logger.info("recreate event bus because of received RecreateEventBusEvent");
eventBus = create(); eventBus = create(null);
} }
//~--- fields ---------------------------------------------------------------
/** event bus */
private EventBus eventBus;
} }

View File

@@ -24,6 +24,9 @@
package sonia.scm.metrics; package sonia.scm.metrics;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.binder.jvm.ClassLoaderMetrics; import io.micrometer.core.instrument.binder.jvm.ClassLoaderMetrics;
import io.micrometer.core.instrument.binder.jvm.JvmGcMetrics; import io.micrometer.core.instrument.binder.jvm.JvmGcMetrics;
@@ -37,11 +40,18 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Provider; import javax.inject.Provider;
import javax.inject.Singleton; import javax.inject.Singleton;
import java.util.List;
import java.util.Set; import java.util.Set;
@Singleton @Singleton
public class MeterRegistryProvider implements Provider<MeterRegistry> { public class MeterRegistryProvider implements Provider<MeterRegistry> {
private static final CompositeMeterRegistry staticRegistry = new CompositeMeterRegistry();
public static MeterRegistry getStaticRegistry() {
return staticRegistry;
}
private static final Logger LOG = LoggerFactory.getLogger(MeterRegistryProvider.class); private static final Logger LOG = LoggerFactory.getLogger(MeterRegistryProvider.class);
private final Set<MonitoringSystem> providerSet; private final Set<MonitoringSystem> providerSet;
@@ -49,6 +59,19 @@ public class MeterRegistryProvider implements Provider<MeterRegistry> {
@Inject @Inject
public MeterRegistryProvider(Set<MonitoringSystem> providerSet) { public MeterRegistryProvider(Set<MonitoringSystem> providerSet) {
this.providerSet = providerSet; this.providerSet = providerSet;
resetStaticRegistry();
}
private void resetStaticRegistry() {
Set<MeterRegistry> registries = ImmutableSet.copyOf(staticRegistry.getRegistries());
for (MeterRegistry registry : registries) {
staticRegistry.remove(registry);
}
List<Meter> meters = ImmutableList.copyOf(staticRegistry.getMeters());
for (Meter meter : meters) {
staticRegistry.remove(meter);
}
} }
@Override @Override
@@ -64,6 +87,7 @@ public class MeterRegistryProvider implements Provider<MeterRegistry> {
if (providerSet.size() == 1) { if (providerSet.size() == 1) {
MeterRegistry registry = providerSet.iterator().next().getRegistry(); MeterRegistry registry = providerSet.iterator().next().getRegistry();
LOG.debug("create meter registry from single registration: {}", registry.getClass()); LOG.debug("create meter registry from single registration: {}", registry.getClass());
staticRegistry.add(registry);
return registry; return registry;
} }
return createCompositeRegistry(); return createCompositeRegistry();
@@ -71,13 +95,12 @@ public class MeterRegistryProvider implements Provider<MeterRegistry> {
private CompositeMeterRegistry createCompositeRegistry() { private CompositeMeterRegistry createCompositeRegistry() {
LOG.debug("create composite meter registry"); LOG.debug("create composite meter registry");
CompositeMeterRegistry registry = new CompositeMeterRegistry();
for (MonitoringSystem provider : providerSet) { for (MonitoringSystem provider : providerSet) {
MeterRegistry subRegistry = provider.getRegistry(); MeterRegistry subRegistry = provider.getRegistry();
LOG.debug("register {} as part of composite meter registry", subRegistry.getClass()); LOG.debug("register {} as part of composite meter registry", subRegistry.getClass());
registry.add(subRegistry); staticRegistry.add(subRegistry);
} }
return registry; return staticRegistry;
} }
@SuppressWarnings("java:S2095") // we can't close JvmGcMetrics, but it should be ok @SuppressWarnings("java:S2095") // we can't close JvmGcMetrics, but it should be ok

View File

@@ -0,0 +1,101 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.event;
import com.github.legman.Subscribe;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ThreadContext;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static org.awaitility.Awaitility.await;
@ExtendWith(MockitoExtension.class)
class LegmanScmEventBusTest {
private LegmanScmEventBus eventBus;
@BeforeEach
void setUp() {
DefaultSecurityManager manager = new DefaultSecurityManager();
ThreadContext.bind(manager);
// we have to use a single thread executor to ensure that the
// same thread is used for the test. This allows us to test,
// that even if the same thread is used the correct subject is in place.
eventBus = new LegmanScmEventBus(Executors.newSingleThreadExecutor());
}
@AfterEach
void tearDown() {
eventBus.shutdownEventBus(new ShutdownEventBusEvent());
ThreadContext.unbindSubject();
ThreadContext.unbindSecurityManager();
}
@Test
void shouldPassShiroContext() {
bindSubject("dent");
PrincipalCapturingSubscriber subscriber = new PrincipalCapturingSubscriber();
eventBus.register(subscriber);
eventBus.post("first");
bindSubject("trillian");
eventBus.post("second");
await()
.atMost(1, TimeUnit.SECONDS)
.until(() -> "trillian".equals(subscriber.principal));
}
private void bindSubject(String principal) {
Subject dent = new Subject.Builder()
.authenticated(true)
.principals(new SimplePrincipalCollection(principal, "test"))
.buildSubject();
ThreadContext.bind(dent);
}
static class PrincipalCapturingSubscriber {
private Object principal;
@Subscribe
public void handle(String event) {
principal = SecurityUtils.getSubject().getPrincipal();
}
}
}