mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-02-21 22:16:55 +01:00
Core metrics (#1586)
Expose metrics for http requests and executor services.
This commit is contained in:
@@ -25,6 +25,7 @@
|
||||
package sonia.scm.admin;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -57,7 +58,7 @@ class ReleaseFeedParserTest {
|
||||
|
||||
@BeforeEach
|
||||
void createSut() {
|
||||
releaseFeedParser = new ReleaseFeedParser(client, 500);
|
||||
releaseFeedParser = new ReleaseFeedParser(client, new SimpleMeterRegistry(), 500);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -28,6 +28,7 @@ import com.github.sdorra.shiro.ShiroRule;
|
||||
import com.github.sdorra.shiro.SubjectAware;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.io.Resources;
|
||||
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||
import org.apache.shiro.subject.SimplePrincipalCollection;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.jboss.resteasy.mock.MockHttpRequest;
|
||||
@@ -178,7 +179,7 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
||||
RepositoryCollectionToDtoMapper repositoryCollectionToDtoMapper = new RepositoryCollectionToDtoMapper(repositoryToDtoMapper, resourceLinks);
|
||||
super.repositoryCollectionResource = new RepositoryCollectionResource(repositoryManager, repositoryCollectionToDtoMapper, dtoToRepositoryMapper, resourceLinks, repositoryInitializer);
|
||||
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());
|
||||
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
|
||||
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.SCMContext;
|
||||
import sonia.scm.ScmConstraintViolationException;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.event.ScmEventBus;
|
||||
import sonia.scm.repository.api.HookContext;
|
||||
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
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.schedule;
|
||||
|
||||
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -32,7 +33,6 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
@@ -56,7 +56,7 @@ class CronSchedulerTest {
|
||||
@Test
|
||||
void shouldScheduleWithClass() {
|
||||
when(task.hasNextRun()).thenReturn(true);
|
||||
try (CronScheduler scheduler = new CronScheduler(taskFactory)) {
|
||||
try (CronScheduler scheduler = new CronScheduler(taskFactory, new SimpleMeterRegistry())) {
|
||||
scheduler.schedule("vep", TestingRunnable.class);
|
||||
verify(task).setFuture(any(Future.class));
|
||||
}
|
||||
@@ -65,7 +65,7 @@ class CronSchedulerTest {
|
||||
@Test
|
||||
void shouldScheduleWithRunnable() {
|
||||
when(task.hasNextRun()).thenReturn(true);
|
||||
try (CronScheduler scheduler = new CronScheduler(taskFactory)) {
|
||||
try (CronScheduler scheduler = new CronScheduler(taskFactory, new SimpleMeterRegistry())) {
|
||||
scheduler.schedule("vep", new TestingRunnable());
|
||||
verify(task).setFuture(any(Future.class));
|
||||
}
|
||||
@@ -73,7 +73,7 @@ class CronSchedulerTest {
|
||||
|
||||
@Test
|
||||
void shouldSkipSchedulingWithoutNextRun(){
|
||||
try (CronScheduler scheduler = new CronScheduler(taskFactory)) {
|
||||
try (CronScheduler scheduler = new CronScheduler(taskFactory, new SimpleMeterRegistry())) {
|
||||
scheduler.schedule("vep", new TestingRunnable());
|
||||
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
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.template;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.Test;
|
||||
import sonia.scm.plugin.PluginLoader;
|
||||
@@ -64,10 +65,13 @@ public class MustacheTemplateEngineTest extends TemplateEngineTestBase
|
||||
when(loader.getUberClassLoader()).thenReturn(
|
||||
Thread.currentThread().getContextClassLoader());
|
||||
|
||||
MustacheTemplateEngine.PluginLoaderHolder holder = new MustacheTemplateEngine.PluginLoaderHolder();
|
||||
holder.pluginLoader = loader;
|
||||
MustacheTemplateEngine.PluginLoaderHolder pluginLoaderHolder = new MustacheTemplateEngine.PluginLoaderHolder();
|
||||
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 ----------------------------------------------------------
|
||||
@@ -119,14 +123,15 @@ public class MustacheTemplateEngineTest extends TemplateEngineTestBase
|
||||
@Test
|
||||
public void testCreateEngineWithoutPluginLoader() throws IOException {
|
||||
ServletContext context = mock(ServletContext.class);
|
||||
MustacheTemplateEngine.PluginLoaderHolder holder = new MustacheTemplateEngine.PluginLoaderHolder();
|
||||
MustacheTemplateEngine engine = new MustacheTemplateEngine(context, holder);
|
||||
MustacheTemplateEngine.PluginLoaderHolder pluginLoaderHolder = new MustacheTemplateEngine.PluginLoaderHolder();
|
||||
MustacheTemplateEngine.MeterRegistryHolder meterRegistryHolder = new MustacheTemplateEngine.MeterRegistryHolder();
|
||||
MustacheTemplateEngine engine = new MustacheTemplateEngine(context, pluginLoaderHolder, meterRegistryHolder);
|
||||
|
||||
Template template = engine.getTemplate(getTemplateResource());
|
||||
|
||||
StringWriter writer = new StringWriter();
|
||||
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