Skip to content

Commit

Permalink
[#9522] Support URI statistics for Spring Webflux
Browse files Browse the repository at this point in the history
  • Loading branch information
ga-ram committed Dec 21, 2022
1 parent 7089c1b commit 2f706e2
Show file tree
Hide file tree
Showing 12 changed files with 220 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ public class ReactorNettyPluginTestController {

@RequestMapping(value = "/client/echo", method = RequestMethod.GET)
@ResponseBody
public String clientEcho() {
public String clientEcho(HttpServletRequest request) {
request.setAttribute("pinpoint.metric.uri-template", "/test");
return "Welcome";
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@RestController
public class SpringWebfluxPluginController {
@GetMapping("/server/welcome")
public Mono<String> welcome() {
@GetMapping("/server/welcome/**")
public Mono<String> welcome(ServerWebExchange exchange) {
exchange.getAttributes().put("pinpoint.metric.uri-template", "/test");
return Mono.just("Welcome Home");
}

Expand All @@ -50,7 +52,8 @@ public Mono<String> clientPost() {
}

@GetMapping("/client/get")
public Mono<String> clientGet() {
public Mono<String> clientGet(ServerWebExchange exchange) {
exchange.getAttributes().put("pinpoint.metric.uri-template", "/test");
WebClient client = WebClient.create("http://www.google.com");
WebClient.ResponseSpec response = client.method(HttpMethod.GET)
.uri("").retrieve();
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion agent/src/main/resources/profiles/local/pinpoint.config
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,8 @@ profiler.uri.stat.spring.webmvc.enable=true
profiler.uri.stat.spring.webmvc.useuserinput=false
profiler.uri.stat.vertx.enable=true
profiler.uri.stat.vertx.useuserinput=false

profiler.uri.stat.spring.webflux.enable=true
profiler.uri.stat.spring.webflux.useuserinput=false
###########################################################
# TOMCAT #
###########################################################
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,7 @@
public class SpringWebFluxConstants {
public static final ServiceType SPRING_WEBFLUX = ServiceTypeProvider.getByName("SPRING_WEBFLUX");
public static final ServiceType SPRING_WEBFLUX_CLIENT = ServiceTypeProvider.getByName("SPRING_WEBFLUX_CLIENT");
public static final String SPRING_WEBFLUX_DEFAULT_URI_ATTRIBUTE_KEYS[] = {"org.springframework.web.reactive.HandlerMapping.bestMatchingPattern"};
public static final String[] SPRING_WEBFLUX_URI_USER_INPUT_ATTRIBUTE_KEYS = {"pinpoint.metric.uri-template"};

}
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,13 @@
import com.navercorp.pinpoint.plugin.spring.webflux.interceptor.DispatchHandlerInvokeHandlerMethodInterceptor;
import com.navercorp.pinpoint.plugin.spring.webflux.interceptor.ExchangeFunctionMethodInterceptor;
import com.navercorp.pinpoint.plugin.spring.webflux.interceptor.InvocableHandlerMethodInterceptor;
import com.navercorp.pinpoint.plugin.spring.webflux.interceptor.AbstractHandlerMethodMappingInterceptor;
import com.navercorp.pinpoint.plugin.spring.webflux.interceptor.AbstractUrlHandlerMappingInterceptor;

import java.security.ProtectionDomain;

import static com.navercorp.pinpoint.common.util.VarArgs.va;

/**
* @author jaehong.kim
*/
Expand All @@ -59,7 +63,7 @@ public void setup(ProfilerPluginSetupContext context) {

logger.info("{} version range=[5.0.0.RELEASE, 5.2.1.RELEASE], config:{}", this.getClass().getSimpleName(), config);
// Server
transformTemplate.transform("org.springframework.web.reactive.DispatcherHandler", DispatchHandlerTransform.class);
transformTemplate.transform("org.springframework.web.reactive.DispatcherHandler", DispatchHandlerTransform.class, new Object[]{config.isUriStatEnable(), config.isUriStatUseUserInput()}, new Class[]{Boolean.class, Boolean.class});
final Matcher invokeMatcher = Matchers.newPackageBasedMatcher("org.springframework.web.reactive.DispatcherHandler$$Lambda$");
transformTemplate.transform(invokeMatcher, DispatchHandlerInvokeHandlerTransform.class);

Expand All @@ -73,6 +77,12 @@ public void setup(ProfilerPluginSetupContext context) {
transformTemplate.transform("org.springframework.web.reactive.function.client.ExchangeFunctions$DefaultExchangeFunction", ExchangeFunctionTransform.class);
transformTemplate.transform("org.springframework.web.reactive.function.client.DefaultClientRequestBuilder$BodyInserterRequest", BodyInserterRequestTransform.class);
}

// uri stat
if (config.isUriStatEnable()) {
transformTemplate.transform("org.springframework.web.reactive.result.method.AbstractHandlerMethodMapping", AbstractHandlerMethodMappingTransform.class);
transformTemplate.transform("org.springframework.web.reactive.handler.AbstractUrlHandlerMapping", AbstractUrlHandlerMappingTransform.class);
}
}

@Override
Expand All @@ -81,6 +91,14 @@ public void setTransformTemplate(MatchableTransformTemplate transformTemplate) {
}

public static class DispatchHandlerTransform implements TransformCallback {
private final Boolean uriStatEnable;
private final Boolean uriStatUseUserInput;

public DispatchHandlerTransform(Boolean uriStatEnable, Boolean uriStatUseUserInput) {
this.uriStatEnable = uriStatEnable;
this.uriStatUseUserInput = uriStatUseUserInput;
}

@Override
public byte[] doInTransform(Instrumentor instrumentor, ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
final InstrumentClass target = instrumentor.getInstrumentClass(loader, className, classfileBuffer);
Expand All @@ -92,12 +110,12 @@ public byte[] doInTransform(Instrumentor instrumentor, ClassLoader loader, Strin
// Invoke
final InstrumentMethod invokerHandlerMethod = target.getDeclaredMethod("invokeHandler", "org.springframework.web.server.ServerWebExchange", "java.lang.Object");
if (invokerHandlerMethod != null) {
invokerHandlerMethod.addInterceptor(DispatchHandlerInvokeHandlerMethodInterceptor.class);
invokerHandlerMethod.addInterceptor(DispatchHandlerInvokeHandlerMethodInterceptor.class, va(this.uriStatEnable, Boolean.valueOf(false)));
}
// Result
final InstrumentMethod handleResultMethod = target.getDeclaredMethod("handleResult", "org.springframework.web.server.ServerWebExchange", "org.springframework.web.reactive.HandlerResult");
if (handleResultMethod != null) {
handleResultMethod.addInterceptor(DispatchHandlerInvokeHandlerMethodInterceptor.class);
handleResultMethod.addInterceptor(DispatchHandlerInvokeHandlerMethodInterceptor.class, va(this.uriStatEnable, this.uriStatUseUserInput));
}

return target.toBytecode();
Expand Down Expand Up @@ -200,4 +218,33 @@ public byte[] doInTransform(Instrumentor instrumentor, ClassLoader loader, Strin
return target.toBytecode();
}
}

public static class AbstractHandlerMethodMappingTransform implements TransformCallback {
@Override
public byte[] doInTransform(Instrumentor instrumentor, ClassLoader classLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
final InstrumentClass target = instrumentor.getInstrumentClass(classLoader, className, classfileBuffer);

// Add attribute listener.
final InstrumentMethod lookupHandlerMethod = target.getDeclaredMethod("lookupHandlerMethod", "org.springframework.web.server.ServerWebExchange");
if (lookupHandlerMethod != null) {
lookupHandlerMethod.addInterceptor(AbstractHandlerMethodMappingInterceptor.class);
}
return target.toBytecode();
}
}

public static class AbstractUrlHandlerMappingTransform implements TransformCallback {
@Override
public byte[] doInTransform(Instrumentor instrumentor, ClassLoader classLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
final InstrumentClass target = instrumentor.getInstrumentClass(classLoader, className, classfileBuffer);

// Add attribute listener.
final InstrumentMethod exposePathWithinMapping = target.getDeclaredMethod("lookupHandler", "org.springframework.http.server.PathContainer", "org.springframework.web.server.ServerWebExchange");
if (exposePathWithinMapping != null) {
exposePathWithinMapping.addInterceptor(AbstractUrlHandlerMappingInterceptor.class);
}
return target.toBytecode();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ public class SpringWebFluxPluginConfig {
private final HttpDumpConfig httpDumpConfig;
private final boolean clientEnable;

private final boolean uriStatEnable;
private final boolean uriStatUseUserInput;

public SpringWebFluxPluginConfig(ProfilerConfig config) {
Objects.requireNonNull(config, "config");

Expand All @@ -44,6 +47,8 @@ public SpringWebFluxPluginConfig(ProfilerConfig config) {
int cookieSamplingRate = config.readInt("profiler.spring.webflux.client.cookie.sampling.rate", 1);
int cookieDumpSize = config.readInt("profiler.spring.webflux.client.cookie.dumpsize", 1024);
this.httpDumpConfig = HttpDumpConfig.get(cookie, cookieDumpType, cookieSamplingRate, cookieDumpSize, false, cookieDumpType, 1, 1024);
this.uriStatEnable = config.readBoolean("profiler.uri.stat.spring.webflux.enable", false);
this.uriStatUseUserInput = config.readBoolean("profiler.uri.stat.spring.webflux.useuserinput", false);
}

public boolean isEnable() {
Expand All @@ -62,12 +67,22 @@ public boolean isClientEnable() {
return clientEnable;
}

public boolean isUriStatEnable() {
return uriStatEnable;
}

public boolean isUriStatUseUserInput() {
return uriStatUseUserInput;
}

@Override
public String toString() {
final StringBuilder sb = new StringBuilder("SpringWebFluxPluginConfig{");
sb.append("enable=").append(enable);
sb.append(", param=").append(param);
sb.append(", httpDumpConfig=").append(httpDumpConfig);
sb.append(", uriStatEnable=").append(uriStatEnable);
sb.append(", uriStatUseUserInput=").append(uriStatUseUserInput);
sb.append('}');
return sb.toString();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.navercorp.pinpoint.plugin.spring.webflux.interceptor;

import com.navercorp.pinpoint.bootstrap.context.SpanRecorder;
import com.navercorp.pinpoint.bootstrap.context.Trace;
import com.navercorp.pinpoint.bootstrap.context.TraceContext;
import com.navercorp.pinpoint.bootstrap.interceptor.AroundInterceptor;
import com.navercorp.pinpoint.common.util.ArrayArgumentUtils;
import com.navercorp.pinpoint.common.util.StringUtils;
import com.navercorp.pinpoint.plugin.spring.webflux.SpringWebFluxConstants;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.pattern.PathPattern;

public class AbstractHandlerMethodMappingInterceptor implements AroundInterceptor {
private final TraceContext traceContext;

public AbstractHandlerMethodMappingInterceptor(final TraceContext traceContext) {
this.traceContext = traceContext;
}

@Override
public void before(Object target, Object[] args) {

}

@Override
public void after(Object target, Object[] args, Object result, Throwable throwable) {
final Trace trace = traceContext.currentRawTraceObject();
if (trace != null) {
ServerWebExchange webExchange = ArrayArgumentUtils.getArgument(args, 0, ServerWebExchange.class);
String uri = extractAttribute(webExchange, SpringWebFluxConstants.SPRING_WEBFLUX_DEFAULT_URI_ATTRIBUTE_KEYS);
if (uri != null) {
SpanRecorder spanRecorder = trace.getSpanRecorder();
spanRecorder.recordUriTemplate(uri, false);
}
}
}

private String extractAttribute(ServerWebExchange webExchange, String[] keys) {
for (String attributeName : keys) {
Object uriMapping = webExchange.getAttribute(attributeName);
if (!(uriMapping instanceof PathPattern)) {
continue;
}

String uriTemplate = ((PathPattern) uriMapping).getPatternString();
if (StringUtils.hasLength(uriTemplate)) {
return uriTemplate;
}
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.navercorp.pinpoint.plugin.spring.webflux.interceptor;

import com.navercorp.pinpoint.bootstrap.context.SpanRecorder;
import com.navercorp.pinpoint.bootstrap.context.Trace;
import com.navercorp.pinpoint.bootstrap.context.TraceContext;
import com.navercorp.pinpoint.bootstrap.interceptor.AroundInterceptor;
import com.navercorp.pinpoint.common.util.ArrayArgumentUtils;
import com.navercorp.pinpoint.common.util.StringUtils;
import com.navercorp.pinpoint.plugin.spring.webflux.SpringWebFluxConstants;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.pattern.PathPattern;

public class AbstractUrlHandlerMappingInterceptor implements AroundInterceptor {
private final TraceContext traceContext;

public AbstractUrlHandlerMappingInterceptor(TraceContext traceContext) {
this.traceContext = traceContext;
}

@Override
public void before(Object target, Object[] args) {

}

@Override
public void after(Object target, Object[] args, Object result, Throwable throwable) {
final Trace trace = traceContext.currentRawTraceObject();
if (trace != null) {
ServerWebExchange webExchange = ArrayArgumentUtils.getArgument(args, 1, ServerWebExchange.class);
String uri = extractAttribute(webExchange, SpringWebFluxConstants.SPRING_WEBFLUX_DEFAULT_URI_ATTRIBUTE_KEYS);
if (uri != null) {
SpanRecorder spanRecorder = trace.getSpanRecorder();
spanRecorder.recordUriTemplate(uri, false);
}
}
}

private String extractAttribute(ServerWebExchange webExchange, String[] keys) {
for (String attributeName : keys) {
Object uriMapping = webExchange.getAttribute(attributeName);
if (!(uriMapping instanceof PathPattern)) {
continue;
}

String uriTemplate = ((PathPattern) uriMapping).getPatternString();
if (StringUtils.hasLength(uriTemplate)) {
return uriTemplate;
}
}
return null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,28 @@
import com.navercorp.pinpoint.bootstrap.context.MethodDescriptor;
import com.navercorp.pinpoint.bootstrap.context.SpanEventRecorder;
import com.navercorp.pinpoint.bootstrap.context.TraceContext;
import com.navercorp.pinpoint.bootstrap.context.Trace;
import com.navercorp.pinpoint.bootstrap.context.SpanRecorder;
import com.navercorp.pinpoint.bootstrap.interceptor.AsyncContextSpanEventSimpleAroundInterceptor;
import com.navercorp.pinpoint.common.util.ArrayArgumentUtils;
import com.navercorp.pinpoint.common.util.ArrayUtils;
import com.navercorp.pinpoint.common.util.StringUtils;
import com.navercorp.pinpoint.plugin.spring.webflux.SpringWebFluxConstants;
import org.springframework.web.server.ServerWebExchange;

/**
* @author jaehong.kim
*/
public class DispatchHandlerInvokeHandlerMethodInterceptor extends AsyncContextSpanEventSimpleAroundInterceptor {
private TraceContext traceContext;
private final Boolean uriStatEnable;
private final Boolean uriStatUseUserInput;

public DispatchHandlerInvokeHandlerMethodInterceptor(TraceContext traceContext, MethodDescriptor methodDescriptor) {
public DispatchHandlerInvokeHandlerMethodInterceptor(TraceContext traceContext, MethodDescriptor methodDescriptor, Boolean uriStatEnable, Boolean uriStatUseUserInput) {
super(traceContext, methodDescriptor);
this.traceContext = traceContext;
this.uriStatEnable = uriStatEnable;
this.uriStatUseUserInput = uriStatUseUserInput;
}

// BEFORE
Expand All @@ -46,6 +57,28 @@ public AsyncContext getAsyncContext(Object target, Object[] args) {

@Override
public void doInBeforeTrace(SpanEventRecorder recorder, AsyncContext asyncContext, Object target, Object[] args) {
if (uriStatEnable && uriStatUseUserInput) {
Trace trace = traceContext.currentRawTraceObject();
if (trace == null) {
return;
}

ServerWebExchange exchange = ArrayArgumentUtils.getArgument(args, 0, ServerWebExchange.class);
for (String attributeName : SpringWebFluxConstants.SPRING_WEBFLUX_URI_USER_INPUT_ATTRIBUTE_KEYS) {
Object uriMapping = exchange.getAttribute(attributeName);
if (!(uriMapping instanceof String)) {
continue;
}

String uriTemplate = (String) uriMapping;

if (StringUtils.hasLength(uriTemplate)) {
SpanRecorder spanRecorder = trace.getSpanRecorder();
spanRecorder.recordUriTemplate(uriTemplate, true);
}
}

}
}

// AFTER
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public void after(Object target, Object[] args, Object result, Throwable throwab
if (trace != null) {
String url = ArrayArgumentUtils.getArgument(args, 0, String.class);
SpanRecorder spanRecorder = trace.getSpanRecorder();
spanRecorder.recordUriTemplate(url);
spanRecorder.recordUriTemplate(url, false);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public void after(Object target, Object[] args, Object result, Throwable throwab
String path = route.getPath();
logger.debug("vertx uriTemplate:{}", path);
SpanRecorder spanRecorder = trace.getSpanRecorder();
spanRecorder.recordUriTemplate(path);
spanRecorder.recordUriTemplate(path, false);
}
}
}
Expand All @@ -61,7 +61,7 @@ private boolean recordWithUserInput(RoutingContext routingContext, Trace trace)
String userUriTemplate = (String) value;
if (StringUtils.hasLength(userUriTemplate)) {
SpanRecorder spanRecorder = trace.getSpanRecorder();
spanRecorder.recordUriTemplate(userUriTemplate);
spanRecorder.recordUriTemplate(userUriTemplate, true);
return true;
}
}
Expand Down

0 comments on commit 2f706e2

Please sign in to comment.