Skip to content

Create spans for forward/include#557

Closed
videnkz wants to merge 8 commits intoelastic:masterfrom
videnkz:task/429-servlet-transaction
Closed

Create spans for forward/include#557
videnkz wants to merge 8 commits intoelastic:masterfrom
videnkz:task/429-servlet-transaction

Conversation

@videnkz
Copy link
Contributor

@videnkz videnkz commented Apr 2, 2019

closes #384

@codecov-io
Copy link

codecov-io commented Apr 3, 2019

Codecov Report

Merging #557 into master will decrease coverage by 0.67%.
The diff coverage is 32.5%.

Impacted file tree graph

@@             Coverage Diff              @@
##             master     #557      +/-   ##
============================================
- Coverage      63.7%   63.03%   -0.68%     
  Complexity       68       68              
============================================
  Files           189      182       -7     
  Lines          7431     6836     -595     
  Branches        871      786      -85     
============================================
- Hits           4734     4309     -425     
+ Misses         2433     2264     -169     
+ Partials        264      263       -1
Impacted Files Coverage Δ Complexity Δ
...ic/apm/agent/servlet/ServletTransactionHelper.java 83.73% <100%> (+7.51%) 0 <0> (ø) ⬇️
...orwardIncludeRequestDispatcherInstrumentation.java 25% <25%> (ø) 0 <0> (?)
...gent/servlet/RequestDispatcherInstrumentation.java 45.45% <45.45%> (ø) 0 <0> (?)
...m/agent/impl/async/SpanInScopeCallableWrapper.java 62.96% <0%> (-37.04%) 0% <0%> (ø)
...m/agent/impl/async/SpanInScopeRunnableWrapper.java 64.28% <0%> (-35.72%) 0% <0%> (ø)
.../agent/report/processor/ProcessorEventHandler.java 44.44% <0%> (-19.2%) 0% <0%> (ø)
...n/java/co/elastic/apm/agent/web/BodyProcessor.java 69.23% <0%> (-14.11%) 0% <0%> (ø)
...lastic/apm/agent/impl/transaction/Transaction.java 73.77% <0%> (-11.23%) 0% <0%> (ø)
...bci/methodmatching/TraceMethodInstrumentation.java 59.57% <0%> (-5.37%) 0% <0%> (ø)
...tic/apm/agent/configuration/CoreConfiguration.java 96.12% <0%> (-3.88%) 0% <0%> (ø)
... and 51 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 17f6985...0c6d70c. Read the comment docs.

Copy link
Member

@felixbarny felixbarny left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see how this is related to #429 I think this tackles #384, right?

We should also add a test case to the integration tests to be sure that it works on all supported servers.

@felixbarny felixbarny changed the title Task/429 servlet transaction Apr 3, 2019
@videnkz
Copy link
Contributor Author

videnkz commented May 1, 2019

@felixbarny please check pull request, i deleted instrumentation on RequestDispatcher.
And added integration tests on forward, include.

String pathInfo = "";

if (FORWARD.equalsIgnoreCase(method)) {
servletPath = (String) httpServletRequest.getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't you say these properties are not set yet? Or are they only set on the real application servers but were not on the unit tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested with Apache Tomcat Version 8.5.5, and those properties were not there.
They were not on the unit tests, too.
I don't know, if I should instrument RequestDispatcher .

Copy link
Contributor

@eyalkoren eyalkoren May 5, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there was a confusion here- the attribute RequestDispatcher.FORWARD_SERVLET_PATH provides the servlet path of the original request in the new request created in the new forwarded/included request, but here we are interested in the new request paths. So maybe we can create and activate the span here but set it only when the next service will run simply through HttpServletRequest#getServletPath and HttpServletRequest#getPathInfo.

@felixbarny
Copy link
Member

Some ideas about alternatives which enable the capturing of the path:

  • Just instrument javax.servlet.ServletRequest#getRequestDispatcher and return a RequestDispatcherWrapper. Similar to RequestStreamRecordingInstrumentation.
    • Pro: easy to implement
    • Con: Allocates memory, does not capture ERROR requests
  • Enhance ServletApiAdvice to create spans for DispatcherTypes FORWARD, INCLUDE and potentially ERROR
    • Pro: we can simply read the path from the request.getServletPath() and request.getPathInfo()
    • Con: Makes the complex ServletApiAdvice even more complex

Another option is to just omit the path (the current state of the PR).

@kananindzya @eyalkoren WDYT?

Copy link
Contributor

@eyalkoren eyalkoren left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your great contribution!
A few comments, mainly about the span naming that seems to include the original path, but we probably prefer using the forwarded/included servlet path and path info.
Also, some notes about start/end and activate/deactivate maintenance.

Without regard, I know you already know our code quite well, but did you see https://www.elastic.co/blog/a-cookbook-for-contributing-a-plugin-to-the-elastic-apm-java-agent? Maybe this can be informative even for you 🙂.

if (parent instanceof AbstractSpan) {
AbstractSpan parentSpan = (AbstractSpan) parent;
if (parentSpan.getName().toString().equals(span.getName().toString())) {
return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is too late- the span is already created and started, just not activated.
This check needs to be done at the beginning of the method, before span creation and start. Then you will have to avoid object allocation for the string creation, so you could use a ThreadLocal StringBuilder for example, or check whether parent span's name contains the pathInfo and servletPath.
@felixbarny do you think scenarios of multiple forwards/includes for same path and servlet are a valid concern?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. I don't really know. Theoretically, that could happen, although not very likely.


public class ForwardIncludeRequestDispatcherInstrumentation extends ElasticApmInstrumentation {

private static final String SPAN_TYPE = "servlet";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@felixbarny the type servlet seems right here, but I wonder if it is a good fit in the overall APM context. All the other span main types are such that we try to keep somewhat aligned between agents and with limited cardinality. I would say sub-type is better to use for Java-specific term, but not sure which major span type is a good fit instead. Any idea?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think app might be a good type. For breakdown graphs, the transaction's self-time will also be attributed as app and it's also the default type for the @CaptureSpan annotation.

}
}

span.activate();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the span is started, it should be activated as well

@Advice.Thrown @Nullable Throwable t) {
if (span != null) {
span.captureException(t)
.deactivate()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may cause deactivation of non-activated span, which should always be checked against and avoided. I think the code of this method should remain, and the proper way to ensure span activation state correctness is making sure it's either null or non-null-and-activated in beforeExecute

String pathInfo = "";

if (FORWARD.equalsIgnoreCase(method)) {
servletPath = (String) httpServletRequest.getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH);
Copy link
Contributor

@eyalkoren eyalkoren May 5, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there was a confusion here- the attribute RequestDispatcher.FORWARD_SERVLET_PATH provides the servlet path of the original request in the new request created in the new forwarded/included request, but here we are interested in the new request paths. So maybe we can create and activate the span here but set it only when the next service will run simply through HttpServletRequest#getServletPath and HttpServletRequest#getPathInfo.

@eyalkoren
Copy link
Contributor

@felixbarny I just saw your comment now.

What I thought when reviewing was to create the span as it is created now in this PR with the basic name only, but update its name in the service that is ran for the forwarded/included servlet, either by setting the span as a Request attribute and assume the servlet container will copy it to the new request it generates (needs to be tested) or by looking for the active span and if it is the FORWARD/INCLUDE one update its name (would not work if spans are created in between).

However,

Enhance ServletApiAdvice to create spans for DispatcherTypes FORWARD, INCLUDE and potentially ERROR

may actually be a better option - you mean that we rely on the RequestDispather#FORWARD_XXX/INCLUDE_XXX/ERROR_XXX attributes to decide if and which type of span this is?

Con: Makes the complex ServletApiAdvice even more complex

I don't think so. Seems pretty straightforward.

@felixbarny
Copy link
Member

you mean that we rely on the RequestDispather#FORWARD_XXX/INCLUDE_XXX/ERROR_XXX attributes to decide if and which type of span this is?

No, I meant relying on javax.servlet.ServletRequest#getDispatcherType.

But that can easily become messy - especially when there's a filter chain which may or may not invoke the target servlet and taking into account that there may be multiple forwards/includes.

@eyalkoren
Copy link
Contributor

@kananindzya So let's do this:
Instead of instrumenting RequestDispatcher, we will use our existing doFilter and service instrumentation, ie do everything through ServletApiAdvice.

On method enter and exit, add a @Nullabale @Advice.Local("span") Span span.
On method enter, we will rely on javax.servlet.ServletRequest#getDispatcherType to decide if this is a transaction, or a forward/include/error span. If the DispatcherType is FORWARD, INCLUDE or ERROR, AND tracer.currentTransaction()==null, we will start and activate a span, unless the current span has the same servletPath and pathInfo.
On method exit, if the span is not null, deactivate it and end it.

WDYT?

@eyalkoren
Copy link
Contributor

@kananindzya we would like to implement that based on the logic described in the last comment.
Is it OK if I close this PR?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

6 participants