4

A lot of times while writing applications, I wish to profile and measure the time taken for all methods in a stacktrace. What I mean is say:

Method A --> Method B --> Method C ...

A method internally calls B and it might call another. I wish to know the time taken to execute inside each method. This way in a web application, I can precisely know the percentage of time being consumed by what part of the code.

To explain further, most of the times in spring application, I write an aspect which collects information for every method call of a class. Which finally gives me summary. But I hate doing this, its repetitive and verbose and need to keep changing regex to accommodate different classes. Instead I would like this:

@Monitor
public void generateReport(int id){
...
}

Adding some annotation on method will trigger instrumentation api to collect all statistics of time taken by this method and any method later called. And when this method is exited, it stops collection information. I think this should be relatively easy to implement.

The questions is: Are there any reasonable alternatives that lets me do that for general java code? Or any quick way of collection this information. Or even a spring plugin for spring applications?

PS: Exactly like XRebel, it generates beautiful summaries of time take by the security, dao, service etc part of code. But it costs a bomb. If you can afford, you should definitely buy it.

Jatin
  • 31,116
  • 15
  • 98
  • 163
  • Stuck record here. If B and its callees consume 50% of the time, then B is on the stack 50% of the time. If B calling C uses 40% of the time, then 40% of the time the stack will have B above C. Whatever timing information you want to know, as a fraction of running time, you can get by looking at [*10 or so random stacks*](http://stackoverflow.com/a/4390868/23771). It won't show you stuff that doesn't take much time; I bet you're looking for stuff that does take significant time. – Mike Dunlavey Apr 29 '16 at 22:02

4 Answers4

2

You want to write a Java agent. Such an agent allows you to redefine a class when it is loaded. This way, you can implement an aspect without polluting your source code. I have written a library, Byte Buddy, which makes this fairly easy.

For your monitor example, you could use Byte Buddy as follows:

new AgentBuilder.Default()
 .rebase(declaresMethod(isAnnotatedWith(Monitor.class))
 .transform( (builder, type) ->
   builder
     .method(isAnnotatedWith(Monitor.class))
     .intercept(MethodDelegation.to(MonitorInterceptor.class);
  );

class MonitorInterceptor {
  @RuntimeType
  Object intercept(@Origin String method,
                                @SuperCall Callable<?> zuper)
      throws Exception {
    long start = System.currentTimeMillis();
    try {
       return zuper.call();
     } finally {
       System.out.println(method + " took " + (System.currentTimeMillis() - start);
     }
   }
}

The above built agent can than be installed on an instance of the instrumentation interface which is provided to any Java agent.

As an advantage over using Spring, the above agent will work for any Java instance, not only for Spring beans.

Rafael Winterhalter
  • 42,759
  • 13
  • 108
  • 192
  • I tried it with 0.6.8 version. But it printed nothing. Any similar doc for latest version? – Jatin Apr 13 '16 at 09:40
  • Two quick checks: does your `Monitor` annotation declare `RetentionType.RUNTIME`? Did you register an `AgentBuilder.Listener` for looking into potential errors? Also, you should use a recent version. 0.6.8 is really old. – Rafael Winterhalter Apr 13 '16 at 09:43
  • Thanks for the quick response :). here is the link for code: http://pastebin.com/jZnNkgUk used 0.6.8 because above code didnt compile with 1.x series. Thanks. Yes Monitor declared: @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) – Jatin Apr 13 '16 at 09:53
  • 1
    Your `main` method is defined in the class you are intercepting. Therefore, the class is already loaded before the agent is attached. Put your main method into another class and everything should work. The only API change with the current version is that `rebase` was renamed to `type`. – Rafael Winterhalter Apr 13 '16 at 10:24
  • Thanks. It worked now :). Another help needed: I need to find the time take even of all methods that my @ Monitor annotated method calls internally. From above, I only intercept methods called with @Monitor. – Jatin Apr 13 '16 at 10:50
  • That is non-trivial, method calls in Java are dispatched virtual. What you could do would be to set some static ThreadLocal property that marks your tracing state. This would however require you to instrument **all** other methods of your application for reading this property and checking if the call comes from a monitored method. That is quite intense. – Rafael Winterhalter Apr 13 '16 at 13:33
  • I have been mostly able to make it work. I have a threadlocal which stores the current state. When i enter monitor method, I increase the count. If I exit I then decrease the count. This way I keep track if I am nested or not and hence track percentage. – Jatin Apr 13 '16 at 13:58
2

I don't know if theres already a library doing it nor can I give you a ready to use code. But I can give you a description how you can implement it on your own.

First of all i assume its no problem to include AspectJ into your project. Than create an annotation f.e. @Monitor which acts as marker for the time measurment of whatever you like. Than create a simple data strucutre holding the information you wana track. An example for this could be the following :

public class OperationMonitoring {
    boolean active=false;
    List<MethodExecution> methodExecutions = new ArrayList<>();
}

public class MethodExecution {
    MethodExcecution invoker;
    List<MethodExeuction> invocations = new ArrayList<>();
    long startTime;
    long endTime;
}

Than create an Around advice for all methods. On execution check if the called Method is annotated with your Monitoring annotation. If yes started monitoring each method execution in this thread. A simple example code could look like:

@Aspect
public class MonitoringAspect {

    private ThreadLocal<OperationMonitoring> operationMonitorings = new ThreadLocal<>();

    @Around("execution(* *.*(..))")
    public void monitoring(ProceedingJoinPoint pjp) {  
        Method method = extractMethod(pjp);
        if (method != null) {
            OperationMonitoring monitoring = null;
            if(method.isAnnotationPresent(Monitoring.class){
                 monitoring = operationMonitorings.get();
                 if(monitoring!=null){
                     if(!monitoring.active) {
                          monitoring.active=true;
                     }
                 } else {
                     // Create new OperationMonitoring object and set it
                 }
            }
            if(monitoring == null){
                 // this method is not annotated but is the tracking already active?
                 monitoring = operationMonitoring.get();
            }
            if(monitoring!=null && monitoring.active){
               // do monitoring stuff and invoke the called method
            } else {
               // invoke the called method without monitoring
            }
            // Stop the monitoring by setting monitoring.active=false if this method was annotated with Monitoring (and it started the monitoring).
        }
    }


    private Method extractMethod(JoinPoint joinPoint) {
         if (joinPoint.getKind().equals(JoinPoint.METHOD_EXECUTION) && joinPoint.getSignature() instanceof MethodSignature) {
              return ((MethodSignature) joinPoint.getSignature()).getMethod();
         }
         return null;
    }
}

The code above is just a how to. I would also restructure the code but I've written it in a textfield, so please be aware of architectural flaws. As mentioned with a comment at the end. This solution does not supporte multiple annotated methods along the way. But it would be easy to add this. A limitation of this approach is that it fails when you start additional threads during a tracked path. Adding support for starting new threads in a monitored Thread is not that easy. Thats also the reason why IoC frameworks have own features for handling threads to be able to track this.

I hope you understand the general concept of this, if not feel free to ask further questions.

mh-dev
  • 5,264
  • 4
  • 25
  • 23
2

This is the exact reason why I built the open source tool stagemonitor, which uses Byte Buddy to insert profiling code. If you want to monitor a web application you don't have to alter or annotate your code. If you have a standalone application, there is a @MonitorRequests annotation you can use.

Stagemonitor Call Tree

Felix
  • 5,804
  • 4
  • 25
  • 37
  • Thanks for the answer. As co-incidence, I did buitl it even for myself using bytebuddy :). Thanks, will try it over weekend. – Jatin Apr 29 '16 at 15:19
0

You say you want to know the percentage of time taken within each routine on the stack.

I assume you mean inclusive time.

I also assume you mean wall-clock time, on the theory that if one of those lower-level callees happens to do some I/O, locking, etc., you don't want to be blind to that.

So a stack-sampling profiler that samples on wall-clock time will be getting the right kind of information.

The percentage time that A takes is the percentage of samples containing A, same for B, etc.

To get the percentage of A's time used by B, it is the percentage of samples containing A that happen to have B at the next level below.

The information is all in the stack samples, but it may be hard to get the profiler to extract just the information you want.

You also say you want precise percentage. That means you also need a large number of stack samples. For example, if you want to shrink the uncertainty of your measurements by a factor of 10, you need 100 times as many samples.

In my experience finding performance problems, I am willing to tolerate an uncertainty of 10% or more, because my goal is to find big wastage, not to know with precision how bad it is. So I take samples manually, and look at them manually. In fact, if you look at the statistics, you only have to see something wasteful on as few as two samples to know it's bad, and the fewer samples you take before seeing it twice, the worse it is. (Example: If the problem wastes 30% of time, it takes on average 2/30% = 6.67 samples to see it twice. If it wastes 90% of time, it only takes 2.2 samples, on average.)

Mike Dunlavey
  • 40,059
  • 14
  • 91
  • 135