Core metrics (#1586)

Expose metrics for http requests and executor services.
This commit is contained in:
Sebastian Sdorra
2021-03-17 11:09:52 +01:00
committed by GitHub
parent 4dbcc019b7
commit 26b65582ce
25 changed files with 632 additions and 93 deletions

View File

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

View File

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

View File

@@ -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;
}
}

View File

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

View File

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

View File

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

View File

@@ -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!");
}
}