mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-10 15:35:49 +01:00
Core metrics (#1586)
Expose metrics for http requests and executor services.
This commit is contained in:
4
gradle/changelog/core_metrics.yaml
Normal file
4
gradle/changelog/core_metrics.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
- type: added
|
||||||
|
description: Metrics for http requests ([#1586](https://github.com/scm-manager/scm-manager/issues/1586))
|
||||||
|
- type: added
|
||||||
|
description: Metrics for executor services ([#1586](https://github.com/scm-manager/scm-manager/issues/1586))
|
||||||
60
scm-core/src/main/java/sonia/scm/metrics/Metrics.java
Normal file
60
scm-core/src/main/java/sonia/scm/metrics/Metrics.java
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* 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.metrics;
|
||||||
|
|
||||||
|
import io.micrometer.core.instrument.MeterRegistry;
|
||||||
|
import io.micrometer.core.instrument.Tag;
|
||||||
|
import io.micrometer.core.instrument.binder.jvm.ExecutorServiceMetrics;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Util methods to collect metrics from known apis.
|
||||||
|
*
|
||||||
|
* @since 2.16.0
|
||||||
|
*/
|
||||||
|
public final class Metrics {
|
||||||
|
|
||||||
|
private Metrics() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect metrics from an {@link ExecutorService}.
|
||||||
|
*
|
||||||
|
* @param registry meter registry
|
||||||
|
* @param executorService executor service to monitor
|
||||||
|
* @param name name of executor service
|
||||||
|
* @param type type of executor service e.g.: cached, fixed, etc.
|
||||||
|
*/
|
||||||
|
public static void executor(MeterRegistry registry, ExecutorService executorService, String name, String type) {
|
||||||
|
new ExecutorServiceMetrics(
|
||||||
|
executorService,
|
||||||
|
name,
|
||||||
|
Collections.singleton(Tag.of("type", type))
|
||||||
|
).bindTo(registry);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -25,8 +25,10 @@
|
|||||||
package sonia.scm.web;
|
package sonia.scm.web;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||||
|
import io.micrometer.core.instrument.MeterRegistry;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sonia.scm.metrics.Metrics;
|
||||||
import sonia.scm.repository.RepositoryHookType;
|
import sonia.scm.repository.RepositoryHookType;
|
||||||
import sonia.scm.repository.spi.GitHookContextProvider;
|
import sonia.scm.repository.spi.GitHookContextProvider;
|
||||||
import sonia.scm.repository.spi.HookEventFacade;
|
import sonia.scm.repository.spi.HookEventFacade;
|
||||||
@@ -59,9 +61,9 @@ public class GitHookEventFacade implements Closeable {
|
|||||||
private final ExecutorService internalThreadHookHandler;
|
private final ExecutorService internalThreadHookHandler;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public GitHookEventFacade(HookEventFacade hookEventFacade) {
|
public GitHookEventFacade(HookEventFacade hookEventFacade, MeterRegistry registry) {
|
||||||
this.hookEventFacade = hookEventFacade;
|
this.hookEventFacade = hookEventFacade;
|
||||||
this.internalThreadHookHandler = createInternalThreadHookHandlerPool();
|
this.internalThreadHookHandler = createInternalThreadHookHandlerPool(registry);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void fire(RepositoryHookType type, GitHookContextProvider context) {
|
public void fire(RepositoryHookType type, GitHookContextProvider context) {
|
||||||
@@ -122,12 +124,14 @@ public class GitHookEventFacade implements Closeable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
private ExecutorService createInternalThreadHookHandlerPool() {
|
private ExecutorService createInternalThreadHookHandlerPool(MeterRegistry registry) {
|
||||||
return Executors.newCachedThreadPool(
|
ExecutorService executorService = Executors.newCachedThreadPool(
|
||||||
new ThreadFactoryBuilder()
|
new ThreadFactoryBuilder()
|
||||||
.setNameFormat("GitInternalThreadHookHandler-%d")
|
.setNameFormat("GitInternalThreadHookHandler-%d")
|
||||||
.build()
|
.build()
|
||||||
);
|
);
|
||||||
|
Metrics.executor(registry, executorService, "GitInternalHookHandler", "cached");
|
||||||
|
return executorService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
package sonia.scm.repository.spi;
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||||
import org.eclipse.jgit.transport.ScmTransportProtocol;
|
import org.eclipse.jgit.transport.ScmTransportProtocol;
|
||||||
import org.eclipse.jgit.transport.Transport;
|
import org.eclipse.jgit.transport.Transport;
|
||||||
import org.junit.rules.ExternalResource;
|
import org.junit.rules.ExternalResource;
|
||||||
@@ -49,7 +50,7 @@ class BindTransportProtocolRule extends ExternalResource {
|
|||||||
@Override
|
@Override
|
||||||
protected void before() {
|
protected void before() {
|
||||||
HookContextFactory hookContextFactory = new HookContextFactory(mock(PreProcessorUtil.class));
|
HookContextFactory hookContextFactory = new HookContextFactory(mock(PreProcessorUtil.class));
|
||||||
hookEventFacade = new GitHookEventFacade(new HookEventFacade(of(repositoryManager), hookContextFactory));
|
hookEventFacade = new GitHookEventFacade(new HookEventFacade(of(repositoryManager), hookContextFactory), new SimpleMeterRegistry());
|
||||||
GitRepositoryHandler gitRepositoryHandler = mock(GitRepositoryHandler.class);
|
GitRepositoryHandler gitRepositoryHandler = mock(GitRepositoryHandler.class);
|
||||||
scmTransportProtocol = new ScmTransportProtocol(of(GitTestHelper.createConverterFactory()), of(hookEventFacade), of(gitRepositoryHandler));
|
scmTransportProtocol = new ScmTransportProtocol(of(GitTestHelper.createConverterFactory()), of(hookEventFacade), of(gitRepositoryHandler));
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
package sonia.scm.repository.spi;
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||||
import org.eclipse.jgit.api.Git;
|
import org.eclipse.jgit.api.Git;
|
||||||
import org.eclipse.jgit.api.Status;
|
import org.eclipse.jgit.api.Status;
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
@@ -67,7 +68,7 @@ public class SimpleGitWorkingCopyFactoryTest extends AbstractGitCommandTestBase
|
|||||||
@Before
|
@Before
|
||||||
public void bindScmProtocol() throws IOException {
|
public void bindScmProtocol() throws IOException {
|
||||||
HookContextFactory hookContextFactory = new HookContextFactory(mock(PreProcessorUtil.class));
|
HookContextFactory hookContextFactory = new HookContextFactory(mock(PreProcessorUtil.class));
|
||||||
hookEventFacade = new GitHookEventFacade(new HookEventFacade(of(mock(RepositoryManager.class)), hookContextFactory));
|
hookEventFacade = new GitHookEventFacade(new HookEventFacade(of(mock(RepositoryManager.class)), hookContextFactory), new SimpleMeterRegistry());
|
||||||
GitRepositoryHandler gitRepositoryHandler = mock(GitRepositoryHandler.class);
|
GitRepositoryHandler gitRepositoryHandler = mock(GitRepositoryHandler.class);
|
||||||
proto = new ScmTransportProtocol(of(GitTestHelper.createConverterFactory()), of(hookEventFacade), of(gitRepositoryHandler));
|
proto = new ScmTransportProtocol(of(GitTestHelper.createConverterFactory()), of(hookEventFacade), of(gitRepositoryHandler));
|
||||||
Transport.register(proto);
|
Transport.register(proto);
|
||||||
|
|||||||
@@ -25,11 +25,13 @@
|
|||||||
package sonia.scm.repository.hooks;
|
package sonia.scm.repository.hooks;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||||
|
import io.micrometer.core.instrument.MeterRegistry;
|
||||||
import org.apache.shiro.SecurityUtils;
|
import org.apache.shiro.SecurityUtils;
|
||||||
import org.apache.shiro.mgt.SecurityManager;
|
import org.apache.shiro.mgt.SecurityManager;
|
||||||
import org.apache.shiro.util.ThreadContext;
|
import org.apache.shiro.util.ThreadContext;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sonia.scm.metrics.Metrics;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -48,6 +50,7 @@ public class HookServer implements AutoCloseable {
|
|||||||
private static final Logger LOG = LoggerFactory.getLogger(HookServer.class);
|
private static final Logger LOG = LoggerFactory.getLogger(HookServer.class);
|
||||||
|
|
||||||
private final HookHandlerFactory handlerFactory;
|
private final HookHandlerFactory handlerFactory;
|
||||||
|
private final MeterRegistry registry;
|
||||||
|
|
||||||
private ExecutorService acceptor;
|
private ExecutorService acceptor;
|
||||||
private ExecutorService workerPool;
|
private ExecutorService workerPool;
|
||||||
@@ -55,8 +58,9 @@ public class HookServer implements AutoCloseable {
|
|||||||
private SecurityManager securityManager;
|
private SecurityManager securityManager;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public HookServer(HookHandlerFactory handlerFactory) {
|
public HookServer(HookHandlerFactory handlerFactory, MeterRegistry registry) {
|
||||||
this.handlerFactory = handlerFactory;
|
this.handlerFactory = handlerFactory;
|
||||||
|
this.registry = registry;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int start() throws IOException {
|
public int start() throws IOException {
|
||||||
@@ -110,15 +114,19 @@ public class HookServer implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ExecutorService createAcceptor() {
|
private ExecutorService createAcceptor() {
|
||||||
return Executors.newSingleThreadExecutor(
|
ExecutorService executorService = Executors.newSingleThreadExecutor(
|
||||||
createThreadFactory("HgHookAcceptor")
|
createThreadFactory("HgHookAcceptor")
|
||||||
);
|
);
|
||||||
|
Metrics.executor(registry, executorService, "HgHookServerAcceptor", "single");
|
||||||
|
return executorService;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExecutorService createWorkerPool() {
|
private ExecutorService createWorkerPool() {
|
||||||
return Executors.newCachedThreadPool(
|
ExecutorService executorService = Executors.newCachedThreadPool(
|
||||||
createThreadFactory("HgHookWorker-%d")
|
createThreadFactory("HgHookWorker-%d")
|
||||||
);
|
);
|
||||||
|
Metrics.executor(registry, executorService, "HgHookServerWorker", "cached");
|
||||||
|
return executorService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
package sonia.scm.repository.hooks;
|
package sonia.scm.repository.hooks;
|
||||||
|
|
||||||
|
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.apache.shiro.SecurityUtils;
|
import org.apache.shiro.SecurityUtils;
|
||||||
@@ -74,7 +75,7 @@ class HookServerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Response send(Request request) throws IOException {
|
private Response send(Request request) throws IOException {
|
||||||
try (HookServer server = new HookServer(HelloHandler::new)) {
|
try (HookServer server = new HookServer(HelloHandler::new, new SimpleMeterRegistry())) {
|
||||||
int port = server.start();
|
int port = server.start();
|
||||||
try (
|
try (
|
||||||
Socket socket = new Socket(InetAddress.getLoopbackAddress(), port);
|
Socket socket = new Socket(InetAddress.getLoopbackAddress(), port);
|
||||||
|
|||||||
@@ -26,8 +26,11 @@ package sonia.scm.admin;
|
|||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||||
|
import io.micrometer.core.instrument.MeterRegistry;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sonia.scm.metrics.Metrics;
|
||||||
import sonia.scm.net.ahc.AdvancedHttpClient;
|
import sonia.scm.net.ahc.AdvancedHttpClient;
|
||||||
import sonia.scm.version.Version;
|
import sonia.scm.version.Version;
|
||||||
|
|
||||||
@@ -46,7 +49,7 @@ public class ReleaseFeedParser {
|
|||||||
public static final int DEFAULT_TIMEOUT_IN_MILLIS = 5000;
|
public static final int DEFAULT_TIMEOUT_IN_MILLIS = 5000;
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ReleaseFeedParser.class);
|
private static final Logger LOG = LoggerFactory.getLogger(ReleaseFeedParser.class);
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final String SPAN_KIND = "Release Feed";
|
static final String SPAN_KIND = "Release Feed";
|
||||||
|
|
||||||
@@ -56,14 +59,24 @@ public class ReleaseFeedParser {
|
|||||||
private Future<Optional<UpdateInfo>> updateInfoFuture;
|
private Future<Optional<UpdateInfo>> updateInfoFuture;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ReleaseFeedParser(AdvancedHttpClient client) {
|
public ReleaseFeedParser(AdvancedHttpClient client, MeterRegistry registry) {
|
||||||
this(client, DEFAULT_TIMEOUT_IN_MILLIS);
|
this(client, registry, DEFAULT_TIMEOUT_IN_MILLIS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReleaseFeedParser(AdvancedHttpClient client, long timeoutInMillis) {
|
public ReleaseFeedParser(AdvancedHttpClient client, MeterRegistry registry, long timeoutInMillis) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.timeoutInMillis = timeoutInMillis;
|
this.timeoutInMillis = timeoutInMillis;
|
||||||
this.executorService = Executors.newSingleThreadExecutor();
|
this.executorService = createExecutorService(registry);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExecutorService createExecutorService(MeterRegistry registry) {
|
||||||
|
ExecutorService executor = Executors.newSingleThreadExecutor(
|
||||||
|
new ThreadFactoryBuilder()
|
||||||
|
.setNameFormat("ReleaseFeedParser")
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
Metrics.executor(registry, executor, "ReleaseFeedParser", "single");
|
||||||
|
return executor;
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<UpdateInfo> findLatestRelease(String url) {
|
Optional<UpdateInfo> findLatestRelease(String url) {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ package sonia.scm.api.v2.resources;
|
|||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
import io.micrometer.core.instrument.MeterRegistry;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.media.Content;
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
@@ -43,6 +44,7 @@ import sonia.scm.importexport.ExportFileExtensionResolver;
|
|||||||
import sonia.scm.importexport.ExportService;
|
import sonia.scm.importexport.ExportService;
|
||||||
import sonia.scm.importexport.FullScmRepositoryExporter;
|
import sonia.scm.importexport.FullScmRepositoryExporter;
|
||||||
import sonia.scm.importexport.RepositoryImportExportEncryption;
|
import sonia.scm.importexport.RepositoryImportExportEncryption;
|
||||||
|
import sonia.scm.metrics.Metrics;
|
||||||
import sonia.scm.repository.NamespaceAndName;
|
import sonia.scm.repository.NamespaceAndName;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.RepositoryManager;
|
import sonia.scm.repository.RepositoryManager;
|
||||||
@@ -104,7 +106,10 @@ public class RepositoryExportResource {
|
|||||||
RepositoryImportExportEncryption repositoryImportExportEncryption,
|
RepositoryImportExportEncryption repositoryImportExportEncryption,
|
||||||
ExportService exportService,
|
ExportService exportService,
|
||||||
RepositoryExportInformationToDtoMapper informationToDtoMapper,
|
RepositoryExportInformationToDtoMapper informationToDtoMapper,
|
||||||
ExportFileExtensionResolver fileExtensionResolver, ResourceLinks resourceLinks) {
|
ExportFileExtensionResolver fileExtensionResolver,
|
||||||
|
ResourceLinks resourceLinks,
|
||||||
|
MeterRegistry registry
|
||||||
|
) {
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
this.serviceFactory = serviceFactory;
|
this.serviceFactory = serviceFactory;
|
||||||
this.fullScmRepositoryExporter = fullScmRepositoryExporter;
|
this.fullScmRepositoryExporter = fullScmRepositoryExporter;
|
||||||
@@ -113,7 +118,7 @@ public class RepositoryExportResource {
|
|||||||
this.informationToDtoMapper = informationToDtoMapper;
|
this.informationToDtoMapper = informationToDtoMapper;
|
||||||
this.fileExtensionResolver = fileExtensionResolver;
|
this.fileExtensionResolver = fileExtensionResolver;
|
||||||
this.resourceLinks = resourceLinks;
|
this.resourceLinks = resourceLinks;
|
||||||
this.repositoryExportHandler = this.createExportHandlerPool();
|
this.repositoryExportHandler = this.createExportHandlerPool(registry);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -518,12 +523,14 @@ public class RepositoryExportResource {
|
|||||||
return Instant.now().toString().replace(":", "-").split("\\.")[0];
|
return Instant.now().toString().replace(":", "-").split("\\.")[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExecutorService createExportHandlerPool() {
|
private ExecutorService createExportHandlerPool(MeterRegistry registry) {
|
||||||
return Executors.newCachedThreadPool(
|
ExecutorService executorService = Executors.newCachedThreadPool(
|
||||||
new ThreadFactoryBuilder()
|
new ThreadFactoryBuilder()
|
||||||
.setNameFormat("RepositoryExportHandler-%d")
|
.setNameFormat("RepositoryExportHandler-%d")
|
||||||
.build()
|
.build()
|
||||||
);
|
);
|
||||||
|
Metrics.executor(registry, executorService, "RepositoryExport", "cached");
|
||||||
|
return executorService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* 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.metrics;
|
||||||
|
|
||||||
|
import io.micrometer.core.instrument.MeterRegistry;
|
||||||
|
import io.micrometer.core.instrument.Tag;
|
||||||
|
import io.micrometer.core.instrument.Tags;
|
||||||
|
import io.micrometer.core.instrument.Timer;
|
||||||
|
import io.micrometer.core.instrument.binder.http.DefaultHttpServletRequestTagsProvider;
|
||||||
|
import io.micrometer.core.instrument.binder.http.HttpServletRequestTagsProvider;
|
||||||
|
import sonia.scm.Priority;
|
||||||
|
import sonia.scm.filter.Filters;
|
||||||
|
import sonia.scm.filter.WebElement;
|
||||||
|
import sonia.scm.web.filter.HttpFilter;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Provider;
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@WebElement(Filters.PATTERN_ALL)
|
||||||
|
@Priority(Filters.PRIORITY_PRE_BASEURL)
|
||||||
|
public class HttpMetricsFilter extends HttpFilter {
|
||||||
|
|
||||||
|
static final String METRIC_DURATION = "scm.http.requests";
|
||||||
|
|
||||||
|
private final HttpServletRequestTagsProvider tagsProvider = new DefaultHttpServletRequestTagsProvider();
|
||||||
|
|
||||||
|
private final Provider<MeterRegistry> registryProvider;
|
||||||
|
private final RequestCategoryDetector detector;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public HttpMetricsFilter(Provider<MeterRegistry> registryProvider, RequestCategoryDetector detector) {
|
||||||
|
this.registryProvider = registryProvider;
|
||||||
|
this.detector = detector;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||||
|
MeterRegistry registry = registryProvider.get();
|
||||||
|
Timer.Sample sample = Timer.start(registry);
|
||||||
|
try {
|
||||||
|
chain.doFilter(request, response);
|
||||||
|
} finally {
|
||||||
|
Tags tags = tags(request, response);
|
||||||
|
sample.stop(timer(registry, tags));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Timer timer(MeterRegistry registry, Tags tags) {
|
||||||
|
return Timer.builder(METRIC_DURATION)
|
||||||
|
.description("Duration of an http request")
|
||||||
|
.tags(tags)
|
||||||
|
.register(registry);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Tags tags(HttpServletRequest request, HttpServletResponse response) {
|
||||||
|
Iterable<Tag> tags = tagsProvider.getTags(request, response);
|
||||||
|
return Tags.concat(tags, "category", detector.detect(request).name());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,6 +31,8 @@ import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics;
|
|||||||
import io.micrometer.core.instrument.binder.jvm.JvmThreadMetrics;
|
import io.micrometer.core.instrument.binder.jvm.JvmThreadMetrics;
|
||||||
import io.micrometer.core.instrument.binder.system.ProcessorMetrics;
|
import io.micrometer.core.instrument.binder.system.ProcessorMetrics;
|
||||||
import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
|
import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Provider;
|
import javax.inject.Provider;
|
||||||
@@ -40,6 +42,8 @@ import java.util.Set;
|
|||||||
@Singleton
|
@Singleton
|
||||||
public class MeterRegistryProvider implements Provider<MeterRegistry> {
|
public class MeterRegistryProvider implements Provider<MeterRegistry> {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(MeterRegistryProvider.class);
|
||||||
|
|
||||||
private final Set<MonitoringSystem> providerSet;
|
private final Set<MonitoringSystem> providerSet;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@@ -58,15 +62,20 @@ public class MeterRegistryProvider implements Provider<MeterRegistry> {
|
|||||||
|
|
||||||
private MeterRegistry createRegistry() {
|
private MeterRegistry createRegistry() {
|
||||||
if (providerSet.size() == 1) {
|
if (providerSet.size() == 1) {
|
||||||
return providerSet.iterator().next().getRegistry();
|
MeterRegistry registry = providerSet.iterator().next().getRegistry();
|
||||||
|
LOG.debug("create meter registry from single registration: {}", registry.getClass());
|
||||||
|
return registry;
|
||||||
}
|
}
|
||||||
return createCompositeRegistry();
|
return createCompositeRegistry();
|
||||||
}
|
}
|
||||||
|
|
||||||
private CompositeMeterRegistry createCompositeRegistry() {
|
private CompositeMeterRegistry createCompositeRegistry() {
|
||||||
|
LOG.debug("create composite meter registry");
|
||||||
CompositeMeterRegistry registry = new CompositeMeterRegistry();
|
CompositeMeterRegistry registry = new CompositeMeterRegistry();
|
||||||
for (MonitoringSystem provider : providerSet) {
|
for (MonitoringSystem provider : providerSet) {
|
||||||
registry.add(provider.getRegistry());
|
MeterRegistry subRegistry = provider.getRegistry();
|
||||||
|
LOG.debug("register {} as part of composite meter registry", subRegistry.getClass());
|
||||||
|
registry.add(subRegistry);
|
||||||
}
|
}
|
||||||
return registry;
|
return registry;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* 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.metrics;
|
||||||
|
|
||||||
|
|
||||||
|
public enum RequestCategory {
|
||||||
|
|
||||||
|
UI, API, PROTOCOL, STATIC, UNKNOWN
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* 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.metrics;
|
||||||
|
|
||||||
|
import sonia.scm.util.HttpUtil;
|
||||||
|
import sonia.scm.web.UserAgent;
|
||||||
|
import sonia.scm.web.UserAgentParser;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
public final class RequestCategoryDetector {
|
||||||
|
|
||||||
|
private final UserAgentParser userAgentParser;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public RequestCategoryDetector(UserAgentParser userAgentParser) {
|
||||||
|
this.userAgentParser = userAgentParser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RequestCategory detect(HttpServletRequest request) {
|
||||||
|
String uri = HttpUtil.getStrippedURI(request);
|
||||||
|
if (isStatic(uri)) {
|
||||||
|
return RequestCategory.STATIC;
|
||||||
|
} else if (HttpUtil.isWUIRequest(request)) {
|
||||||
|
return RequestCategory.UI;
|
||||||
|
} else if (uri.startsWith("/api/")) {
|
||||||
|
return RequestCategory.API;
|
||||||
|
} else if (uri.startsWith("/repo/") && isScmClient(request)) {
|
||||||
|
return RequestCategory.PROTOCOL;
|
||||||
|
}
|
||||||
|
return RequestCategory.UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isStatic(String uri) {
|
||||||
|
return uri.startsWith("/assets")
|
||||||
|
|| uri.endsWith(".js")
|
||||||
|
|| uri.endsWith(".css")
|
||||||
|
|| uri.endsWith(".jpg")
|
||||||
|
|| uri.endsWith(".jpeg")
|
||||||
|
|| uri.endsWith(".png")
|
||||||
|
|| uri.endsWith(".gif")
|
||||||
|
|| uri.endsWith(".svg")
|
||||||
|
|| uri.endsWith(".html");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isScmClient(HttpServletRequest request) {
|
||||||
|
UserAgent agent = userAgentParser.parse(request);
|
||||||
|
return agent != null && agent.isScmClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -26,10 +26,8 @@ package sonia.scm.repository;
|
|||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
import org.apache.shiro.concurrent.SubjectAwareExecutorService;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import sonia.scm.ConfigurationException;
|
import sonia.scm.ConfigurationException;
|
||||||
@@ -56,9 +54,6 @@ import java.util.HashSet;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.ThreadFactory;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
@@ -75,10 +70,7 @@ import static sonia.scm.NotFoundException.notFound;
|
|||||||
@Singleton
|
@Singleton
|
||||||
public class DefaultRepositoryManager extends AbstractRepositoryManager {
|
public class DefaultRepositoryManager extends AbstractRepositoryManager {
|
||||||
|
|
||||||
private static final String THREAD_NAME = "Hook-%s";
|
private static final Logger logger = LoggerFactory.getLogger(DefaultRepositoryManager.class);
|
||||||
private static final Logger logger =
|
|
||||||
LoggerFactory.getLogger(DefaultRepositoryManager.class);
|
|
||||||
private final ExecutorService executorService;
|
|
||||||
private final Map<String, RepositoryHandler> handlerMap;
|
private final Map<String, RepositoryHandler> handlerMap;
|
||||||
private final KeyGenerator keyGenerator;
|
private final KeyGenerator keyGenerator;
|
||||||
private final RepositoryDAO repositoryDAO;
|
private final RepositoryDAO repositoryDAO;
|
||||||
@@ -94,12 +86,6 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
|
|||||||
this.repositoryDAO = repositoryDAO;
|
this.repositoryDAO = repositoryDAO;
|
||||||
this.namespaceStrategyProvider = namespaceStrategyProvider;
|
this.namespaceStrategyProvider = namespaceStrategyProvider;
|
||||||
|
|
||||||
ThreadFactory factory = new ThreadFactoryBuilder()
|
|
||||||
.setNameFormat(THREAD_NAME).build();
|
|
||||||
this.executorService = new SubjectAwareExecutorService(
|
|
||||||
Executors.newCachedThreadPool(factory)
|
|
||||||
);
|
|
||||||
|
|
||||||
handlerMap = new HashMap<>();
|
handlerMap = new HashMap<>();
|
||||||
types = new HashSet<>();
|
types = new HashSet<>();
|
||||||
|
|
||||||
@@ -109,11 +95,8 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
|
|||||||
managerDaoAdapter = new ManagerDaoAdapter<>(repositoryDAO);
|
managerDaoAdapter = new ManagerDaoAdapter<>(repositoryDAO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
executorService.shutdown();
|
|
||||||
|
|
||||||
for (RepositoryHandler handler : handlerMap.values()) {
|
for (RepositoryHandler handler : handlerMap.values()) {
|
||||||
IOUtil.close(handler);
|
IOUtil.close(handler);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,9 +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.repository;
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||||
|
import io.micrometer.core.instrument.MeterRegistry;
|
||||||
|
import sonia.scm.metrics.Metrics;
|
||||||
import sonia.scm.repository.spi.SyncAsyncExecutor;
|
import sonia.scm.repository.spi.SyncAsyncExecutor;
|
||||||
import sonia.scm.repository.spi.SyncAsyncExecutorProvider;
|
import sonia.scm.repository.spi.SyncAsyncExecutorProvider;
|
||||||
|
|
||||||
@@ -48,8 +51,19 @@ public class DefaultSyncAsyncExecutorProvider implements SyncAsyncExecutorProvid
|
|||||||
private final int defaultMaxAsyncAbortSeconds;
|
private final int defaultMaxAsyncAbortSeconds;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public DefaultSyncAsyncExecutorProvider() {
|
public DefaultSyncAsyncExecutorProvider(MeterRegistry registry) {
|
||||||
this(Executors.newFixedThreadPool(getProperty(NUMBER_OF_THREADS_PROPERTY, DEFAULT_NUMBER_OF_THREADS)));
|
this(createExecutorService(registry, getProperty(NUMBER_OF_THREADS_PROPERTY, DEFAULT_NUMBER_OF_THREADS)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ExecutorService createExecutorService(MeterRegistry registry, int fixed) {
|
||||||
|
ExecutorService executorService = Executors.newFixedThreadPool(
|
||||||
|
fixed,
|
||||||
|
new ThreadFactoryBuilder()
|
||||||
|
.setNameFormat("SyncAsyncExecutorProvider-%d")
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
Metrics.executor(registry, executorService, "SyncAsyncExecutorProvider", "fixed");
|
||||||
|
return executorService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DefaultSyncAsyncExecutorProvider(ExecutorService executor) {
|
public DefaultSyncAsyncExecutorProvider(ExecutorService executor) {
|
||||||
|
|||||||
@@ -21,11 +21,13 @@
|
|||||||
* 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.schedule;
|
package sonia.scm.schedule;
|
||||||
|
|
||||||
|
import io.micrometer.core.instrument.MeterRegistry;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sonia.scm.metrics.Metrics;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
@@ -44,14 +46,16 @@ public class CronScheduler implements Scheduler {
|
|||||||
private final CronThreadFactory threadFactory;
|
private final CronThreadFactory threadFactory;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public CronScheduler(CronTaskFactory taskFactory) {
|
public CronScheduler(CronTaskFactory taskFactory, MeterRegistry registry) {
|
||||||
this.taskFactory = taskFactory;
|
this.taskFactory = taskFactory;
|
||||||
this.threadFactory = new CronThreadFactory();
|
this.threadFactory = new CronThreadFactory();
|
||||||
this.executorService = createExecutor();
|
this.executorService = createExecutor(registry);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ScheduledExecutorService createExecutor() {
|
private ScheduledExecutorService createExecutor(MeterRegistry registry) {
|
||||||
return Executors.newScheduledThreadPool(2, threadFactory);
|
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2, threadFactory);
|
||||||
|
Metrics.executor(registry, executor, "Cron", "scheduled");
|
||||||
|
return executor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
* 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.template;
|
package sonia.scm.template;
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
@@ -32,16 +32,19 @@ import com.google.common.base.Throwables;
|
|||||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||||
import com.google.common.util.concurrent.UncheckedExecutionException;
|
import com.google.common.util.concurrent.UncheckedExecutionException;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
import io.micrometer.core.instrument.MeterRegistry;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import sonia.scm.Default;
|
import sonia.scm.Default;
|
||||||
|
import sonia.scm.metrics.Metrics;
|
||||||
import sonia.scm.plugin.PluginLoader;
|
import sonia.scm.plugin.PluginLoader;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import javax.servlet.ServletContext;
|
import javax.servlet.ServletContext;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ThreadFactory;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
@@ -61,37 +64,43 @@ public class MustacheTemplateEngine implements TemplateEngine
|
|||||||
@Inject(optional = true) PluginLoader pluginLoader;
|
@Inject(optional = true) PluginLoader pluginLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to implement optional injection for the MeterRegistry.
|
||||||
|
* @see <a href="https://github.com/google/guice/wiki/FrequentlyAskedQuestions#how-can-i-inject-optional-parameters-into-a-constructor">Optional Injection</a>
|
||||||
|
*/
|
||||||
|
static class MeterRegistryHolder {
|
||||||
|
@Inject(optional = true) MeterRegistry registry;
|
||||||
|
}
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
public static final TemplateType TYPE = new TemplateType("mustache",
|
public static final TemplateType TYPE = new TemplateType("mustache",
|
||||||
"Mustache", "mustache");
|
"Mustache", "mustache");
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private static final String THREAD_NAME = "Mustache-%s";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the logger for MustacheTemplateEngine
|
* the logger for MustacheTemplateEngine
|
||||||
*/
|
*/
|
||||||
private static final Logger logger =
|
private static final Logger logger =
|
||||||
LoggerFactory.getLogger(MustacheTemplateEngine.class);
|
LoggerFactory.getLogger(MustacheTemplateEngine.class);
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param context
|
|
||||||
* @param pluginLoaderHolder
|
|
||||||
*/
|
|
||||||
@Inject
|
@Inject
|
||||||
public MustacheTemplateEngine(@Default ServletContext context, PluginLoaderHolder pluginLoaderHolder)
|
public MustacheTemplateEngine(@Default ServletContext context, PluginLoaderHolder pluginLoaderHolder, MeterRegistryHolder registryHolder)
|
||||||
{
|
{
|
||||||
factory = new ServletMustacheFactory(context, createClassLoader(pluginLoaderHolder.pluginLoader));
|
factory = new ServletMustacheFactory(context, createClassLoader(pluginLoaderHolder.pluginLoader));
|
||||||
|
factory.setExecutorService(createExecutorService(registryHolder.registry));
|
||||||
|
}
|
||||||
|
|
||||||
ThreadFactory threadFactory =
|
private static ExecutorService createExecutorService(@Nullable MeterRegistry registry) {
|
||||||
new ThreadFactoryBuilder().setNameFormat(THREAD_NAME).build();
|
ExecutorService executorService = Executors.newCachedThreadPool(
|
||||||
|
new ThreadFactoryBuilder()
|
||||||
|
.setNameFormat("MustacheTemplateEngine-%d")
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
|
||||||
factory.setExecutorService(Executors.newCachedThreadPool(threadFactory));
|
if (registry != null) {
|
||||||
|
Metrics.executor(registry, executorService,"MustacheTemplateEngine", "cached" );
|
||||||
|
}
|
||||||
|
|
||||||
|
return executorService;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ClassLoader createClassLoader(PluginLoader pluginLoader) {
|
private ClassLoader createClassLoader(PluginLoader pluginLoader) {
|
||||||
|
|||||||
@@ -21,23 +21,24 @@
|
|||||||
* 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.web.cgi;
|
package sonia.scm.web.cgi;
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||||
|
import io.micrometer.core.instrument.MeterRegistry;
|
||||||
import sonia.scm.config.ScmConfiguration;
|
import sonia.scm.config.ScmConfiguration;
|
||||||
|
import sonia.scm.metrics.Metrics;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
|
|
||||||
import javax.servlet.ServletContext;
|
import javax.servlet.ServletContext;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -50,13 +51,19 @@ public class DefaultCGIExecutorFactory implements CGIExecutorFactory, AutoClosea
|
|||||||
* Constructs ...
|
* Constructs ...
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public DefaultCGIExecutorFactory()
|
@Inject
|
||||||
{
|
public DefaultCGIExecutorFactory(MeterRegistry registry) {
|
||||||
//J-
|
this.executor = createExecutor(registry);
|
||||||
this.executor = Executors.newCachedThreadPool(
|
}
|
||||||
new ThreadFactoryBuilder().setNameFormat("cgi-pool-%d").build()
|
|
||||||
|
private ExecutorService createExecutor(MeterRegistry registry) {
|
||||||
|
ExecutorService executorService = Executors.newCachedThreadPool(
|
||||||
|
new ThreadFactoryBuilder()
|
||||||
|
.setNameFormat("cgi-pool-%d")
|
||||||
|
.build()
|
||||||
);
|
);
|
||||||
//J+
|
Metrics.executor(registry, executorService, "CGI", "cached");
|
||||||
|
return executorService;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
//~--- methods --------------------------------------------------------------
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
package sonia.scm.admin;
|
package sonia.scm.admin;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
@@ -57,7 +58,7 @@ class ReleaseFeedParserTest {
|
|||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void createSut() {
|
void createSut() {
|
||||||
releaseFeedParser = new ReleaseFeedParser(client, 500);
|
releaseFeedParser = new ReleaseFeedParser(client, new SimpleMeterRegistry(), 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import com.github.sdorra.shiro.ShiroRule;
|
|||||||
import com.github.sdorra.shiro.SubjectAware;
|
import com.github.sdorra.shiro.SubjectAware;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.io.Resources;
|
import com.google.common.io.Resources;
|
||||||
|
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||||
import org.apache.shiro.subject.SimplePrincipalCollection;
|
import org.apache.shiro.subject.SimplePrincipalCollection;
|
||||||
import org.apache.shiro.subject.Subject;
|
import org.apache.shiro.subject.Subject;
|
||||||
import org.jboss.resteasy.mock.MockHttpRequest;
|
import org.jboss.resteasy.mock.MockHttpRequest;
|
||||||
@@ -178,7 +179,7 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
RepositoryCollectionToDtoMapper repositoryCollectionToDtoMapper = new RepositoryCollectionToDtoMapper(repositoryToDtoMapper, resourceLinks);
|
RepositoryCollectionToDtoMapper repositoryCollectionToDtoMapper = new RepositoryCollectionToDtoMapper(repositoryToDtoMapper, resourceLinks);
|
||||||
super.repositoryCollectionResource = new RepositoryCollectionResource(repositoryManager, repositoryCollectionToDtoMapper, dtoToRepositoryMapper, resourceLinks, repositoryInitializer);
|
super.repositoryCollectionResource = new RepositoryCollectionResource(repositoryManager, repositoryCollectionToDtoMapper, dtoToRepositoryMapper, resourceLinks, repositoryInitializer);
|
||||||
super.repositoryImportResource = new RepositoryImportResource(dtoToRepositoryMapper, resourceLinks, fullScmRepositoryImporter, new RepositoryImportDtoToRepositoryImportParametersMapperImpl(), repositoryImportExportEncryption, fromUrlImporter, fromBundleImporter, importLoggerFactory);
|
super.repositoryImportResource = new RepositoryImportResource(dtoToRepositoryMapper, resourceLinks, fullScmRepositoryImporter, new RepositoryImportDtoToRepositoryImportParametersMapperImpl(), repositoryImportExportEncryption, fromUrlImporter, fromBundleImporter, importLoggerFactory);
|
||||||
super.repositoryExportResource = new RepositoryExportResource(repositoryManager, serviceFactory, fullScmRepositoryExporter, repositoryImportExportEncryption, exportService, exportInformationToDtoMapper, fileExtensionResolver, resourceLinks);
|
super.repositoryExportResource = new RepositoryExportResource(repositoryManager, serviceFactory, fullScmRepositoryExporter, repositoryImportExportEncryption, exportService, exportInformationToDtoMapper, fileExtensionResolver, resourceLinks, new SimpleMeterRegistry());
|
||||||
dispatcher.addSingletonResource(getRepositoryRootResource());
|
dispatcher.addSingletonResource(getRepositoryRootResource());
|
||||||
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
|
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
|
||||||
doReturn(ImmutableSet.of(new CustomNamespaceStrategy()).iterator()).when(strategies).iterator();
|
doReturn(ImmutableSet.of(new CustomNamespaceStrategy()).iterator()).when(strategies).iterator();
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
* 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.metrics;
|
||||||
|
|
||||||
|
import com.google.inject.util.Providers;
|
||||||
|
import io.micrometer.core.instrument.MeterRegistry;
|
||||||
|
import io.micrometer.core.instrument.Timer;
|
||||||
|
import io.micrometer.core.instrument.binder.http.Outcome;
|
||||||
|
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
class HttpMetricsFilterTest {
|
||||||
|
|
||||||
|
private MeterRegistry registry;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUpRegistry() {
|
||||||
|
registry = new SimpleMeterRegistry();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCollectMetrics() throws IOException, ServletException {
|
||||||
|
filter("GET", HttpServletResponse.SC_OK);
|
||||||
|
filter("GET", HttpServletResponse.SC_OK);
|
||||||
|
|
||||||
|
Timer timer = timer("GET", HttpServletResponse.SC_OK, Outcome.SUCCESS);
|
||||||
|
assertThat(timer.count()).isEqualTo(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCollectDifferentMetrics() throws IOException, ServletException {
|
||||||
|
filter("GET", HttpServletResponse.SC_OK);
|
||||||
|
filter("POST", HttpServletResponse.SC_CREATED);
|
||||||
|
filter("DELETE", HttpServletResponse.SC_NOT_FOUND);
|
||||||
|
|
||||||
|
Timer ok = timer("GET", HttpServletResponse.SC_OK, Outcome.SUCCESS);
|
||||||
|
Timer created = timer("POST", HttpServletResponse.SC_CREATED, Outcome.SUCCESS);
|
||||||
|
Timer notFound = timer("DELETE", HttpServletResponse.SC_NOT_FOUND, Outcome.CLIENT_ERROR);
|
||||||
|
|
||||||
|
assertThat(ok.count()).isEqualTo(1);
|
||||||
|
assertThat(created.count()).isEqualTo(1);
|
||||||
|
assertThat(notFound.count()).isEqualTo(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Timer timer(String method, int status, Outcome outcome) {
|
||||||
|
return registry.get(HttpMetricsFilter.METRIC_DURATION)
|
||||||
|
.tags("category", "UNKNOWN", "method", method, "outcome", outcome.name(), "status", String.valueOf(status))
|
||||||
|
.timer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void filter(String requestMethod, int responseStatus) throws IOException, ServletException {
|
||||||
|
HttpServletRequest request = request(requestMethod);
|
||||||
|
HttpServletResponse response = response(responseStatus);
|
||||||
|
FilterChain chain = chain();
|
||||||
|
filter(request, response, chain);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void filter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException, IOException, ServletException {
|
||||||
|
RequestCategoryDetector detector = mock(RequestCategoryDetector.class);
|
||||||
|
when(detector.detect(request)).thenReturn(RequestCategory.UNKNOWN);
|
||||||
|
HttpMetricsFilter filter = new HttpMetricsFilter(Providers.of(registry), detector);
|
||||||
|
filter.doFilter(request, response, chain);
|
||||||
|
}
|
||||||
|
|
||||||
|
private FilterChain chain() {
|
||||||
|
return mock(FilterChain.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpServletRequest request(String method) {
|
||||||
|
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||||
|
when(request.getMethod()).thenReturn(method);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpServletResponse response(int status) {
|
||||||
|
HttpServletResponse response = mock(HttpServletResponse.class);
|
||||||
|
when(response.getStatus()).thenReturn(status);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* 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.metrics;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import sonia.scm.util.HttpUtil;
|
||||||
|
import sonia.scm.web.UserAgent;
|
||||||
|
import sonia.scm.web.UserAgentParser;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class RequestCategoryDetectorTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private UserAgentParser userAgentParser;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private RequestCategoryDetector detector;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnStatic() {
|
||||||
|
assertThat(category("/assets/bla")).isEqualTo(RequestCategory.STATIC);
|
||||||
|
assertThat(category("/assets/bla/foo/bar")).isEqualTo(RequestCategory.STATIC);
|
||||||
|
assertThat(category("/some/path.jpg")).isEqualTo(RequestCategory.STATIC);
|
||||||
|
assertThat(category("/some/path.css")).isEqualTo(RequestCategory.STATIC);
|
||||||
|
assertThat(category("/some/path.js")).isEqualTo(RequestCategory.STATIC);
|
||||||
|
assertThat(category("/my.png")).isEqualTo(RequestCategory.STATIC);
|
||||||
|
assertThat(category("/images/loading.svg")).isEqualTo(RequestCategory.STATIC);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnUi() {
|
||||||
|
RequestCategory category = category("/", HttpUtil.HEADER_SCM_CLIENT, HttpUtil.SCM_CLIENT_WUI);
|
||||||
|
assertThat(category).isEqualTo(RequestCategory.UI);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnApi() {
|
||||||
|
assertThat(category("/api/v2")).isEqualTo(RequestCategory.API);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnProtocol() {
|
||||||
|
HttpServletRequest request = request("/repo/my/repo");
|
||||||
|
when(userAgentParser.parse(request)).thenReturn(UserAgent.scmClient("MySCM").build());
|
||||||
|
assertThat(detector.detect(request)).isEqualTo(RequestCategory.PROTOCOL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnUnknown() {
|
||||||
|
assertThat(category("/unknown")).isEqualTo(RequestCategory.UNKNOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RequestCategory category(String uri) {
|
||||||
|
HttpServletRequest request = request(uri);
|
||||||
|
return detector.detect(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpServletRequest request(String uri) {
|
||||||
|
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||||
|
when(request.getContextPath()).thenReturn("/scm");
|
||||||
|
when(request.getRequestURI()).thenReturn("/scm" + uri);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RequestCategory category(String uri, String header, String value) {
|
||||||
|
HttpServletRequest request = request(uri);
|
||||||
|
when(request.getHeader(header)).thenReturn(value);
|
||||||
|
return detector.detect(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -48,7 +48,6 @@ import sonia.scm.NoChangesMadeException;
|
|||||||
import sonia.scm.NotFoundException;
|
import sonia.scm.NotFoundException;
|
||||||
import sonia.scm.SCMContext;
|
import sonia.scm.SCMContext;
|
||||||
import sonia.scm.ScmConstraintViolationException;
|
import sonia.scm.ScmConstraintViolationException;
|
||||||
import sonia.scm.config.ScmConfiguration;
|
|
||||||
import sonia.scm.event.ScmEventBus;
|
import sonia.scm.event.ScmEventBus;
|
||||||
import sonia.scm.repository.api.HookContext;
|
import sonia.scm.repository.api.HookContext;
|
||||||
import sonia.scm.repository.api.HookContextFactory;
|
import sonia.scm.repository.api.HookContextFactory;
|
||||||
|
|||||||
@@ -21,9 +21,10 @@
|
|||||||
* 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.schedule;
|
package sonia.scm.schedule;
|
||||||
|
|
||||||
|
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
@@ -32,7 +33,6 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
|||||||
|
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
@@ -56,7 +56,7 @@ class CronSchedulerTest {
|
|||||||
@Test
|
@Test
|
||||||
void shouldScheduleWithClass() {
|
void shouldScheduleWithClass() {
|
||||||
when(task.hasNextRun()).thenReturn(true);
|
when(task.hasNextRun()).thenReturn(true);
|
||||||
try (CronScheduler scheduler = new CronScheduler(taskFactory)) {
|
try (CronScheduler scheduler = new CronScheduler(taskFactory, new SimpleMeterRegistry())) {
|
||||||
scheduler.schedule("vep", TestingRunnable.class);
|
scheduler.schedule("vep", TestingRunnable.class);
|
||||||
verify(task).setFuture(any(Future.class));
|
verify(task).setFuture(any(Future.class));
|
||||||
}
|
}
|
||||||
@@ -65,7 +65,7 @@ class CronSchedulerTest {
|
|||||||
@Test
|
@Test
|
||||||
void shouldScheduleWithRunnable() {
|
void shouldScheduleWithRunnable() {
|
||||||
when(task.hasNextRun()).thenReturn(true);
|
when(task.hasNextRun()).thenReturn(true);
|
||||||
try (CronScheduler scheduler = new CronScheduler(taskFactory)) {
|
try (CronScheduler scheduler = new CronScheduler(taskFactory, new SimpleMeterRegistry())) {
|
||||||
scheduler.schedule("vep", new TestingRunnable());
|
scheduler.schedule("vep", new TestingRunnable());
|
||||||
verify(task).setFuture(any(Future.class));
|
verify(task).setFuture(any(Future.class));
|
||||||
}
|
}
|
||||||
@@ -73,7 +73,7 @@ class CronSchedulerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldSkipSchedulingWithoutNextRun(){
|
void shouldSkipSchedulingWithoutNextRun(){
|
||||||
try (CronScheduler scheduler = new CronScheduler(taskFactory)) {
|
try (CronScheduler scheduler = new CronScheduler(taskFactory, new SimpleMeterRegistry())) {
|
||||||
scheduler.schedule("vep", new TestingRunnable());
|
scheduler.schedule("vep", new TestingRunnable());
|
||||||
verify(task, never()).setFuture(any(Future.class));
|
verify(task, never()).setFuture(any(Future.class));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,12 +21,13 @@
|
|||||||
* 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.template;
|
package sonia.scm.template;
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||||
import org.assertj.core.api.Assertions;
|
import org.assertj.core.api.Assertions;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import sonia.scm.plugin.PluginLoader;
|
import sonia.scm.plugin.PluginLoader;
|
||||||
@@ -64,10 +65,13 @@ public class MustacheTemplateEngineTest extends TemplateEngineTestBase
|
|||||||
when(loader.getUberClassLoader()).thenReturn(
|
when(loader.getUberClassLoader()).thenReturn(
|
||||||
Thread.currentThread().getContextClassLoader());
|
Thread.currentThread().getContextClassLoader());
|
||||||
|
|
||||||
MustacheTemplateEngine.PluginLoaderHolder holder = new MustacheTemplateEngine.PluginLoaderHolder();
|
MustacheTemplateEngine.PluginLoaderHolder pluginLoaderHolder = new MustacheTemplateEngine.PluginLoaderHolder();
|
||||||
holder.pluginLoader = loader;
|
pluginLoaderHolder.pluginLoader = loader;
|
||||||
|
|
||||||
return new MustacheTemplateEngine(context, holder);
|
MustacheTemplateEngine.MeterRegistryHolder meterRegistryHolder = new MustacheTemplateEngine.MeterRegistryHolder();
|
||||||
|
meterRegistryHolder.registry = new SimpleMeterRegistry();
|
||||||
|
|
||||||
|
return new MustacheTemplateEngine(context, pluginLoaderHolder, meterRegistryHolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
//~--- get methods ----------------------------------------------------------
|
||||||
@@ -119,14 +123,15 @@ public class MustacheTemplateEngineTest extends TemplateEngineTestBase
|
|||||||
@Test
|
@Test
|
||||||
public void testCreateEngineWithoutPluginLoader() throws IOException {
|
public void testCreateEngineWithoutPluginLoader() throws IOException {
|
||||||
ServletContext context = mock(ServletContext.class);
|
ServletContext context = mock(ServletContext.class);
|
||||||
MustacheTemplateEngine.PluginLoaderHolder holder = new MustacheTemplateEngine.PluginLoaderHolder();
|
MustacheTemplateEngine.PluginLoaderHolder pluginLoaderHolder = new MustacheTemplateEngine.PluginLoaderHolder();
|
||||||
MustacheTemplateEngine engine = new MustacheTemplateEngine(context, holder);
|
MustacheTemplateEngine.MeterRegistryHolder meterRegistryHolder = new MustacheTemplateEngine.MeterRegistryHolder();
|
||||||
|
MustacheTemplateEngine engine = new MustacheTemplateEngine(context, pluginLoaderHolder, meterRegistryHolder);
|
||||||
|
|
||||||
Template template = engine.getTemplate(getTemplateResource());
|
Template template = engine.getTemplate(getTemplateResource());
|
||||||
|
|
||||||
StringWriter writer = new StringWriter();
|
StringWriter writer = new StringWriter();
|
||||||
template.execute(writer, ImmutableMap.of("name", "World"));
|
template.execute(writer, ImmutableMap.of("name", "World"));
|
||||||
|
|
||||||
Assertions.assertThat(writer.toString()).isEqualTo("Hello World!");
|
Assertions.assertThat(writer).hasToString("Hello World!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user