Merge remote-tracking branch 'origin/master'

This commit is contained in:
Olga Maciaszek-Sharma
2020-12-18 17:58:35 +01:00
24 changed files with 873 additions and 88 deletions

View File

@@ -33,11 +33,14 @@ import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle;
import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycleValidator;
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestAdapter;
import org.springframework.cloud.client.loadbalancer.LoadBalancerUriTools;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.client.loadbalancer.ResponseData;
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.ReflectionUtils;
import static org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer.REQUEST;
@@ -65,7 +68,7 @@ public class BlockingLoadBalancerClient implements LoadBalancerClient {
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
String hint = getHint(serviceId);
DefaultRequest<DefaultRequestContext> lbRequest = new DefaultRequest<>(
LoadBalancerRequestAdapter<T, DefaultRequestContext> lbRequest = new LoadBalancerRequestAdapter<>(request,
new DefaultRequestContext(request, hint));
Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator
.getSupportedLifecycleProcessors(
@@ -74,11 +77,11 @@ public class BlockingLoadBalancerClient implements LoadBalancerClient {
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
ServiceInstance serviceInstance = choose(serviceId, lbRequest);
if (serviceInstance == null) {
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
.onComplete(new CompletionContext<>(CompletionContext.Status.DISCARD, new EmptyResponse())));
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(
new CompletionContext<>(CompletionContext.Status.DISCARD, lbRequest, new EmptyResponse())));
throw new IllegalStateException("No instances available for " + serviceId);
}
return execute(serviceId, serviceInstance, request);
return execute(serviceId, serviceInstance, lbRequest);
}
@Override
@@ -89,25 +92,45 @@ public class BlockingLoadBalancerClient implements LoadBalancerClient {
.getSupportedLifecycleProcessors(
loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),
DefaultRequestContext.class, Object.class, ServiceInstance.class);
Request lbRequest = request instanceof Request ? (Request) request : new DefaultRequest<>();
supportedLifecycleProcessors
.forEach(lifecycle -> lifecycle.onStartRequest(lbRequest, new DefaultResponse(serviceInstance)));
try {
T response = request.apply(serviceInstance);
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
.onComplete(new CompletionContext<>(CompletionContext.Status.SUCCESS, defaultResponse, response)));
Object clientResponse = getClientResponse(response);
supportedLifecycleProcessors
.forEach(lifecycle -> lifecycle.onComplete(new CompletionContext<>(CompletionContext.Status.SUCCESS,
lbRequest, defaultResponse, clientResponse)));
return response;
}
catch (IOException iOException) {
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(
new CompletionContext<>(CompletionContext.Status.FAILED, iOException, defaultResponse)));
new CompletionContext<>(CompletionContext.Status.FAILED, iOException, lbRequest, defaultResponse)));
throw iOException;
}
catch (Exception exception) {
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
.onComplete(new CompletionContext<>(CompletionContext.Status.FAILED, exception, defaultResponse)));
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(
new CompletionContext<>(CompletionContext.Status.FAILED, exception, lbRequest, defaultResponse)));
ReflectionUtils.rethrowRuntimeException(exception);
}
return null;
}
private <T> Object getClientResponse(T response) {
ClientHttpResponse clientHttpResponse = null;
if (response instanceof ClientHttpResponse) {
clientHttpResponse = (ClientHttpResponse) response;
}
if (clientHttpResponse != null) {
try {
return new ResponseData(clientHttpResponse, null);
}
catch (IOException ignored) {
}
}
return response;
}
@Override
public URI reconstructURI(ServiceInstance serviceInstance, URI original) {
return LoadBalancerUriTools.reconstructURI(serviceInstance, original);

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.loadbalancer.config;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.loadbalancer.stats.MicrometerStatsLoadBalancerLifecycle;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Autoconfiguration that provides a {@link MicrometerStatsLoadBalancerLifecycle} bean.
*
* @author Olga Maciaszek-Sharma
* @since 3.0.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(MeterRegistry.class)
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.stats.micrometer.enabled", havingValue = "true")
public class LoadBalancerStatsAutoConfiguration {
@Bean
@ConditionalOnBean(MeterRegistry.class)
public MicrometerStatsLoadBalancerLifecycle micrometerStatsLifecycle(MeterRegistry meterRegistry) {
return new MicrometerStatsLoadBalancerLifecycle(meterRegistry);
}
}

View File

@@ -0,0 +1,160 @@
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.loadbalancer.stats;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.CompletionContext;
import org.springframework.cloud.client.loadbalancer.RequestData;
import org.springframework.cloud.client.loadbalancer.RequestDataContext;
import org.springframework.cloud.client.loadbalancer.ResponseData;
import org.springframework.util.StringUtils;
/**
* Utility class for building metrics tags for load-balanced calls.
*
* @author Olga Maciaszek-Sharma
* @since 3.0.0
*/
final class LoadBalancerTags {
static final String UNKNOWN = "UNKNOWN";
private LoadBalancerTags() {
throw new UnsupportedOperationException("Cannot instantiate utility class");
}
static Iterable<Tag> buildSuccessRequestTags(CompletionContext<Object, ServiceInstance, Object> completionContext) {
ServiceInstance serviceInstance = completionContext.getLoadBalancerResponse().getServer();
Tags tags = Tags.of(buildServiceInstanceTags(serviceInstance));
Object clientResponse = completionContext.getClientResponse();
if (clientResponse instanceof ResponseData) {
ResponseData responseData = (ResponseData) clientResponse;
RequestData requestData = responseData.getRequestData();
if (requestData != null) {
tags = tags.and(valueOrUnknown("method", requestData.getHttpMethod()),
valueOrUnknown("uri", getPath(requestData)));
}
else {
tags = tags.and(Tag.of("method", UNKNOWN), Tag.of("uri", UNKNOWN));
}
tags = tags.and(Tag.of("outcome", forStatus(statusValue(responseData))),
valueOrUnknown("status", statusValue(responseData)));
}
else {
tags = tags.and(Tag.of("method", UNKNOWN), Tag.of("uri", UNKNOWN), Tag.of("outcome", UNKNOWN),
Tag.of("status", UNKNOWN));
}
return tags;
}
// In keeping with the way null HttpStatus is handled in Actuator
private static int statusValue(ResponseData responseData) {
return responseData.getHttpStatus() != null ? responseData.getHttpStatus().value() : 200;
}
private static String getPath(RequestData requestData) {
return requestData.getUrl() != null ? requestData.getUrl().getPath() : UNKNOWN;
}
static Iterable<Tag> buildDiscardedRequestTags(
CompletionContext<Object, ServiceInstance, Object> completionContext) {
if (completionContext.getLoadBalancerRequest().getContext() instanceof RequestDataContext) {
RequestData requestData = ((RequestDataContext) completionContext.getLoadBalancerRequest().getContext())
.getClientRequest();
if (requestData != null) {
return Tags.of(valueOrUnknown("method", requestData.getHttpMethod()),
valueOrUnknown("uri", getPath(requestData)), valueOrUnknown("serviceId", getHost(requestData)));
}
}
return Tags.of(valueOrUnknown("method", UNKNOWN), valueOrUnknown("uri", UNKNOWN),
valueOrUnknown("serviceId", UNKNOWN));
}
private static String getHost(RequestData requestData) {
return requestData.getUrl() != null ? requestData.getUrl().getHost() : UNKNOWN;
}
static Iterable<Tag> buildFailedRequestTags(CompletionContext<Object, ServiceInstance, Object> completionContext) {
ServiceInstance serviceInstance = completionContext.getLoadBalancerResponse().getServer();
Tags tags = Tags.of(buildServiceInstanceTags(serviceInstance)).and(exception(completionContext.getThrowable()));
if (completionContext.getLoadBalancerRequest().getContext() instanceof RequestDataContext) {
RequestData requestData = ((RequestDataContext) completionContext.getLoadBalancerRequest().getContext())
.getClientRequest();
if (requestData != null) {
return tags.and(Tags.of(valueOrUnknown("method", requestData.getHttpMethod()),
valueOrUnknown("uri", getPath(requestData))));
}
}
return tags.and(Tags.of(valueOrUnknown("method", UNKNOWN), valueOrUnknown("uri", UNKNOWN)));
}
static Iterable<Tag> buildServiceInstanceTags(ServiceInstance serviceInstance) {
return Tags.of(valueOrUnknown("serviceId", serviceInstance.getServiceId()),
valueOrUnknown("serviceInstance.instanceId", serviceInstance.getInstanceId()),
valueOrUnknown("serviceInstance.host", serviceInstance.getHost()),
valueOrUnknown("serviceInstance.port", String.valueOf(serviceInstance.getPort())));
}
private static Tag valueOrUnknown(String key, String value) {
if (value != null) {
return Tag.of(key, value);
}
return Tag.of(key, UNKNOWN);
}
private static Tag valueOrUnknown(String key, Object value) {
if (value != null) {
return Tag.of(key, String.valueOf(value));
}
return Tag.of(key, UNKNOWN);
}
private static Tag exception(Throwable exception) {
if (exception != null) {
String simpleName = exception.getClass().getSimpleName();
return Tag.of("exception", StringUtils.hasText(simpleName) ? simpleName : exception.getClass().getName());
}
return Tag.of("exception", "None");
}
// Logic from Actuator's `Outcome` class. Copied in here to avoid adding Actuator
// dependency.
public static String forStatus(int status) {
if (status >= 100 && status < 200) {
return "INFORMATIONAL";
}
else if (status >= 200 && status < 300) {
return "SUCCESS";
}
else if (status >= 300 && status < 400) {
return "REDIRECTION";
}
else if (status >= 400 && status < 500) {
return "CLIENT_ERROR";
}
else if (status >= 500 && status < 600) {
return "SERVER_ERROR";
}
return UNKNOWN;
}
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.loadbalancer.stats;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.CompletionContext;
import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.client.loadbalancer.TimedRequestContext;
import static org.springframework.cloud.loadbalancer.stats.LoadBalancerTags.buildDiscardedRequestTags;
import static org.springframework.cloud.loadbalancer.stats.LoadBalancerTags.buildFailedRequestTags;
import static org.springframework.cloud.loadbalancer.stats.LoadBalancerTags.buildServiceInstanceTags;
import static org.springframework.cloud.loadbalancer.stats.LoadBalancerTags.buildSuccessRequestTags;
/**
* An implementation of {@link LoadBalancerLifecycle} that records metrics for
* load-balanced calls.
*
* @author Olga Maciaszek-Sharma
* @since 3.0.0
*/
public class MicrometerStatsLoadBalancerLifecycle implements LoadBalancerLifecycle<Object, Object, ServiceInstance> {
private final MeterRegistry meterRegistry;
private final ConcurrentHashMap<ServiceInstance, AtomicLong> activeRequestsPerInstance = new ConcurrentHashMap<>();
public MicrometerStatsLoadBalancerLifecycle(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
@Override
public boolean supports(Class requestContextClass, Class responseClass, Class serverTypeClass) {
return ServiceInstance.class.isAssignableFrom(serverTypeClass);
}
@Override
public void onStart(Request<Object> request) {
// do nothing
}
@Override
public void onStartRequest(Request<Object> request, Response<ServiceInstance> lbResponse) {
if (request.getContext() instanceof TimedRequestContext) {
((TimedRequestContext) request.getContext()).setRequestStartTime(System.nanoTime());
}
if (!lbResponse.hasServer()) {
return;
}
ServiceInstance serviceInstance = lbResponse.getServer();
AtomicLong activeRequestsCounter = activeRequestsPerInstance.computeIfAbsent(serviceInstance, instance -> {
AtomicLong createdCounter = new AtomicLong();
Gauge.builder("loadbalancer.requests.active", () -> createdCounter)
.tags(buildServiceInstanceTags(serviceInstance)).register(meterRegistry);
return createdCounter;
});
activeRequestsCounter.incrementAndGet();
}
@Override
public void onComplete(CompletionContext<Object, ServiceInstance, Object> completionContext) {
long requestFinishedTimestamp = System.nanoTime();
if (CompletionContext.Status.DISCARD.equals(completionContext.status())) {
Counter.builder("loadbalancer.requests.discard").tags(buildDiscardedRequestTags(completionContext))
.register(meterRegistry).increment();
return;
}
ServiceInstance serviceInstance = completionContext.getLoadBalancerResponse().getServer();
AtomicLong activeRequestsCounter = activeRequestsPerInstance.get(serviceInstance);
if (activeRequestsCounter != null) {
activeRequestsCounter.decrementAndGet();
}
Object loadBalancerRequestContext = completionContext.getLoadBalancerRequest().getContext();
if (requestHasBeenTimed(loadBalancerRequestContext)) {
if (CompletionContext.Status.FAILED.equals(completionContext.status())) {
Timer.builder("loadbalancer.requests.failed").tags(buildFailedRequestTags(completionContext))
.register(meterRegistry)
.record(requestFinishedTimestamp
- ((TimedRequestContext) loadBalancerRequestContext).getRequestStartTime(),
TimeUnit.NANOSECONDS);
return;
}
Timer.builder("loadbalancer.requests.success").tags(buildSuccessRequestTags(completionContext))
.register(meterRegistry)
.record(requestFinishedTimestamp
- ((TimedRequestContext) loadBalancerRequestContext).getRequestStartTime(),
TimeUnit.NANOSECONDS);
}
}
private boolean requestHasBeenTimed(Object loadBalancerRequestContext) {
return loadBalancerRequestContext instanceof TimedRequestContext
&& (((TimedRequestContext) loadBalancerRequestContext).getRequestStartTime() != 0L);
}
}

View File

@@ -3,4 +3,5 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration,\
org.springframework.cloud.loadbalancer.config.BlockingLoadBalancerClientAutoConfiguration,\
org.springframework.cloud.loadbalancer.config.LoadBalancerCacheAutoConfiguration,\
org.springframework.cloud.loadbalancer.security.OAuth2LoadBalancerClientAutoConfiguration
org.springframework.cloud.loadbalancer.security.OAuth2LoadBalancerClientAutoConfiguration,\
org.springframework.cloud.loadbalancer.config.LoadBalancerStatsAutoConfiguration

View File

@@ -20,6 +20,7 @@ import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@@ -173,12 +174,18 @@ class BlockingLoadBalancerClientTests {
Collection<Request<Object>> lifecycleLogRequests = ((TestLoadBalancerLifecycle) factory
.getInstances("myservice", LoadBalancerLifecycle.class).get("loadBalancerLifecycle")).getStartLog()
.values();
Collection<CompletionContext<Object, ServiceInstance>> anotherLifecycleLogRequests = ((AnotherLoadBalancerLifecycle) factory
Collection<Request<Object>> lifecycleLogStartedRequests = ((TestLoadBalancerLifecycle) factory
.getInstances("myservice", LoadBalancerLifecycle.class).get("loadBalancerLifecycle"))
.getStartRequestLog().values();
Collection<CompletionContext<Object, ServiceInstance, Object>> anotherLifecycleLogRequests = ((AnotherLoadBalancerLifecycle) factory
.getInstances("myservice", LoadBalancerLifecycle.class).get("anotherLoadBalancerLifecycle"))
.getCompleteLog().values();
assertThat(actualResult).isEqualTo(result);
assertThat(lifecycleLogRequests).extracting(request -> ((DefaultRequestContext) request.getContext()).getHint())
.contains(callbackTestHint);
assertThat(lifecycleLogStartedRequests)
.extracting(request -> ((DefaultRequestContext) request.getContext()).getHint())
.contains(callbackTestHint);
assertThat(anotherLifecycleLogRequests).extracting(CompletionContext::getClientResponse).contains(result);
}
@@ -231,9 +238,11 @@ class BlockingLoadBalancerClientTests {
protected static class TestLoadBalancerLifecycle implements LoadBalancerLifecycle<Object, Object, ServiceInstance> {
final ConcurrentHashMap<String, Request<Object>> startLog = new ConcurrentHashMap<>();
final Map<String, Request<Object>> startLog = new ConcurrentHashMap<>();
final ConcurrentHashMap<String, CompletionContext<Object, ServiceInstance>> completeLog = new ConcurrentHashMap<>();
final Map<String, Request<Object>> startRequestLog = new ConcurrentHashMap<>();
final Map<String, CompletionContext<Object, ServiceInstance, Object>> completeLog = new ConcurrentHashMap<>();
@Override
public void onStart(Request<Object> request) {
@@ -241,18 +250,27 @@ class BlockingLoadBalancerClientTests {
}
@Override
public void onComplete(CompletionContext<Object, ServiceInstance> completionContext) {
public void onStartRequest(Request<Object> request, Response<ServiceInstance> lbResponse) {
startRequestLog.put(getName() + UUID.randomUUID(), request);
}
@Override
public void onComplete(CompletionContext<Object, ServiceInstance, Object> completionContext) {
completeLog.put(getName() + UUID.randomUUID(), completionContext);
}
ConcurrentHashMap<String, Request<Object>> getStartLog() {
Map<String, Request<Object>> getStartLog() {
return startLog;
}
ConcurrentHashMap<String, CompletionContext<Object, ServiceInstance>> getCompleteLog() {
Map<String, CompletionContext<Object, ServiceInstance, Object>> getCompleteLog() {
return completeLog;
}
Map<String, Request<Object>> getStartRequestLog() {
return startRequestLog;
}
protected String getName() {
return this.getClass().getSimpleName();
}

View File

@@ -0,0 +1,182 @@
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.loadbalancer.stats;
import java.net.URI;
import java.util.HashMap;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.jupiter.api.Test;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.CompletionContext;
import org.springframework.cloud.client.loadbalancer.DefaultRequest;
import org.springframework.cloud.client.loadbalancer.DefaultRequestContext;
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.EmptyResponse;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.RequestData;
import org.springframework.cloud.client.loadbalancer.RequestDataContext;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.client.loadbalancer.ResponseData;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.util.MultiValueMapAdapter;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.cloud.loadbalancer.stats.LoadBalancerTags.UNKNOWN;
/**
* Tests for {@link MicrometerStatsLoadBalancerLifecycle}.
*
* @author Olga Maciaszek-Sharma
*/
class MicrometerStatsLoadBalancerLifecycleTests {
MeterRegistry meterRegistry = new SimpleMeterRegistry();
MicrometerStatsLoadBalancerLifecycle statsLifecycle = new MicrometerStatsLoadBalancerLifecycle(meterRegistry);
@Test
void shouldRecordSuccessfulTimedRequest() {
RequestData requestData = new RequestData(HttpMethod.GET, URI.create("http://test.org/test"), new HttpHeaders(),
new HttpHeaders(), new HashMap<>());
Request<Object> lbRequest = new DefaultRequest<>(new RequestDataContext(requestData));
Response<ServiceInstance> lbResponse = new DefaultResponse(
new DefaultServiceInstance("test-1", "test", "test.org", 8080, false, new HashMap<>()));
ResponseData responseData = new ResponseData(HttpStatus.OK, new HttpHeaders(),
new MultiValueMapAdapter<>(new HashMap<>()), requestData);
statsLifecycle.onStartRequest(lbRequest, lbResponse);
assertThat(meterRegistry.get("loadbalancer.requests.active").gauge().value()).isEqualTo(1);
statsLifecycle.onComplete(
new CompletionContext<>(CompletionContext.Status.SUCCESS, lbRequest, lbResponse, responseData));
assertThat(meterRegistry.getMeters()).hasSize(2);
assertThat(meterRegistry.get("loadbalancer.requests.active").gauge().value()).isEqualTo(0);
assertThat(meterRegistry.get("loadbalancer.requests.success").timers()).hasSize(1);
assertThat(meterRegistry.get("loadbalancer.requests.success").timer().count()).isEqualTo(1);
assertThat(meterRegistry.get("loadbalancer.requests.success").timer().getId().getTags()).contains(
Tag.of("method", "GET"), Tag.of("outcome", "SUCCESS"), Tag.of("serviceId", "test"),
Tag.of("serviceInstance.host", "test.org"), Tag.of("serviceInstance.instanceId", "test-1"),
Tag.of("serviceInstance.port", "8080"), Tag.of("status", "200"), Tag.of("uri", "/test"));
}
@Test
void shouldRecordFailedTimedRequest() {
RequestData requestData = new RequestData(HttpMethod.GET, URI.create("http://test.org/test"), new HttpHeaders(),
new HttpHeaders(), new HashMap<>());
Request<Object> lbRequest = new DefaultRequest<>(new RequestDataContext(requestData));
Response<ServiceInstance> lbResponse = new DefaultResponse(
new DefaultServiceInstance("test-1", "test", "test.org", 8080, false, new HashMap<>()));
statsLifecycle.onStartRequest(lbRequest, lbResponse);
assertThat(meterRegistry.get("loadbalancer.requests.active").gauge().value()).isEqualTo(1);
statsLifecycle.onComplete(new CompletionContext<>(CompletionContext.Status.FAILED, new IllegalStateException(),
lbRequest, lbResponse));
assertThat(meterRegistry.getMeters()).hasSize(2);
assertThat(meterRegistry.get("loadbalancer.requests.active").gauge().value()).isEqualTo(0);
assertThat(meterRegistry.get("loadbalancer.requests.failed").timers()).hasSize(1);
assertThat(meterRegistry.get("loadbalancer.requests.failed").timer().count()).isEqualTo(1);
assertThat(meterRegistry.get("loadbalancer.requests.failed").timer().getId().getTags()).contains(
Tag.of("exception", "IllegalStateException"), Tag.of("method", "GET"), Tag.of("serviceId", "test"),
Tag.of("serviceInstance.host", "test.org"), Tag.of("serviceInstance.instanceId", "test-1"),
Tag.of("serviceInstance.port", "8080"), Tag.of("uri", "/test"));
}
@Test
void shouldNotRecordDiscardedRequest() {
RequestData requestData = new RequestData(HttpMethod.GET, URI.create("http://test.org/test"), new HttpHeaders(),
new HttpHeaders(), new HashMap<>());
Request<Object> lbRequest = new DefaultRequest<>(new RequestDataContext(requestData));
Response<ServiceInstance> lbResponse = new EmptyResponse();
statsLifecycle.onStartRequest(lbRequest, lbResponse);
statsLifecycle.onComplete(new CompletionContext<>(CompletionContext.Status.DISCARD, lbRequest, lbResponse));
assertThat(meterRegistry.getMeters()).hasSize(1);
assertThat(meterRegistry.get("loadbalancer.requests.discard").counter().count()).isEqualTo(1);
}
@Test
void shouldNotRecordUnTimedRequest() {
Request<Object> lbRequest = new DefaultRequest<>(new StatsTestContext());
Response<ServiceInstance> lbResponse = new DefaultResponse(
new DefaultServiceInstance("test-1", "test", "test.org", 8080, false, new HashMap<>()));
ResponseData responseData = new ResponseData(HttpStatus.OK, new HttpHeaders(),
new MultiValueMapAdapter<>(new HashMap<>()), null);
statsLifecycle.onStartRequest(lbRequest, lbResponse);
assertThat(meterRegistry.get("loadbalancer.requests.active").gauge().value()).isEqualTo(1);
statsLifecycle.onComplete(
new CompletionContext<>(CompletionContext.Status.SUCCESS, lbRequest, lbResponse, responseData));
assertThat(meterRegistry.getMeters()).hasSize(1);
assertThat(meterRegistry.get("loadbalancer.requests.active").gauge().value()).isEqualTo(0);
}
@Test
void shouldNotCreateNullTagsWhenNullDataObjects() {
Request<Object> lbRequest = new DefaultRequest<>(new DefaultRequestContext());
Response<ServiceInstance> lbResponse = new DefaultResponse(new DefaultServiceInstance());
statsLifecycle.onStartRequest(lbRequest, lbResponse);
assertThat(meterRegistry.get("loadbalancer.requests.active").gauge().value()).isEqualTo(1);
statsLifecycle
.onComplete(new CompletionContext<>(CompletionContext.Status.SUCCESS, lbRequest, lbResponse, null));
assertThat(meterRegistry.getMeters()).hasSize(2);
assertThat(meterRegistry.get("loadbalancer.requests.active").gauge().value()).isEqualTo(0);
assertThat(meterRegistry.get("loadbalancer.requests.success").timers()).hasSize(1);
assertThat(meterRegistry.get("loadbalancer.requests.success").timer().count()).isEqualTo(1);
assertThat(meterRegistry.get("loadbalancer.requests.success").timer().getId().getTags()).contains(
Tag.of("method", UNKNOWN), Tag.of("outcome", UNKNOWN), Tag.of("serviceId", UNKNOWN),
Tag.of("serviceInstance.host", UNKNOWN), Tag.of("serviceInstance.instanceId", UNKNOWN),
Tag.of("serviceInstance.port", "0"), Tag.of("status", UNKNOWN), Tag.of("uri", UNKNOWN));
}
@Test
void shouldNotCreateNullTagsWhenEmptyDataObjects() {
RequestData requestData = new RequestData(null, null, null, null, null);
Request<Object> lbRequest = new DefaultRequest<>(new RequestDataContext());
Response<ServiceInstance> lbResponse = new DefaultResponse(new DefaultServiceInstance());
ResponseData responseData = new ResponseData(null, null, null, requestData);
statsLifecycle.onStartRequest(lbRequest, lbResponse);
assertThat(meterRegistry.get("loadbalancer.requests.active").gauge().value()).isEqualTo(1);
statsLifecycle.onComplete(
new CompletionContext<>(CompletionContext.Status.SUCCESS, lbRequest, lbResponse, responseData));
assertThat(meterRegistry.getMeters()).hasSize(2);
assertThat(meterRegistry.get("loadbalancer.requests.active").gauge().value()).isEqualTo(0);
assertThat(meterRegistry.get("loadbalancer.requests.success").timers()).hasSize(1);
assertThat(meterRegistry.get("loadbalancer.requests.success").timer().count()).isEqualTo(1);
assertThat(meterRegistry.get("loadbalancer.requests.success").timer().getId().getTags()).contains(
Tag.of("method", UNKNOWN), Tag.of("outcome", "SUCCESS"), Tag.of("serviceId", UNKNOWN),
Tag.of("serviceInstance.host", UNKNOWN), Tag.of("serviceInstance.instanceId", UNKNOWN),
Tag.of("serviceInstance.port", "0"), Tag.of("status", "200"), Tag.of("uri", UNKNOWN));
}
private static class StatsTestContext {
}
}