执行方法堆栈中所有方法所花费的时间

时间:2015-05-01 13:26:57

标签: java spring profiling

在编写应用程序时,我希望分析和测量 stacktrace 中所有方法所花费的时间。我的意思是说:

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

A方法在内部调用B,它可能会调用另一个。我想知道在每个方法中执行所花费的时间。这样在Web应用程序中,我可以准确地知道代码的哪个部分所消耗的时间百分比。

为了进一步解释,在spring应用程序的大多数时候,我都会编写一个方面来收集类的每个方法调用的信息。这最后给了我总结。但我讨厌这样做,它重复和冗长,需要不断改变正则表达式以适应不同的类。相反,我想这样:

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

在方法上添加一些注释将触发instrumentation api收集此方法和稍后调用的任何方法所花费的所有时间统计信息。当退出此方法时,它会停止收集信息。我认为这应该相对容易实现。

问题是:是否有任何合理的替代方案可以让我为普通的Java代码执行此操作?或任何快速收集此信息的方式。甚至是用于弹簧应用的弹簧插件?

PS:与XRebel完全相同,它会生成安全,dao,服务等部分代码的精美时间摘要。但它需要炸弹。如果你负担得起,你一定要买。

4 个答案:

答案 0 :(得分:2)

你想写一个Java agent。这样的代理允许您在加载类时重新定义类。这样,您可以在不污染源代码的情况下实现方面。我写了一个库Byte Buddy,这使得这很容易。

对于您的监视器示例,您可以按如下方式使用Byte Buddy:

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

上面构建的代理可以安装在提供给任何Java代理的检测接口的实例上。

作为使用Spring的优势,上述代理适用于任何Java实例,不仅适用于Spring bean。

答案 1 :(得分:2)

我不知道是否已经有一个库在做这件事,我也不能给你准备好使用代码。但我可以告诉你如何自己实现它。

首先,我假设将AspectJ包含在您的项目中没有问题。比创建注释f.e. @Monitor充当你喜欢的时间测量的标记。 比创建一个简单的数据结构持有你wana跟踪的信息。 这方面的一个例子如下:

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

为所有方法创建一个Around建议。在执行时检查调用的Method是否使用Monitoring annotation进行注释。如果是,则开始监视此线程中的每个方法执行。一个简单的示例代码可能如下所示:

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

上面的代码只是一个方法。我也会重构代码,但我已经在文本字段中编写了代码,所以请注意架构缺陷。正如最后提到的评论所述。此解决方案在此过程中不支持多个带注释的方法。但是添加它会很容易。 此方法的一个限制是,当您在跟踪路径中启动其他线程时,它会失败。添加对在受监视的线程中启动新线程的支持并不容易。这也是IoC框架有自己的功能来处理线程以便能够跟踪它的原因。

我希望你能理解这个的一般概念,如果不随意提出进一步的问题。

答案 2 :(得分:2)

这就是我构建开源工具stagemonitor的确切原因,它使用Byte Buddy插入分析代码。如果要监视Web应用程序,则不必更改或注释代码。如果您有独立应用程序,则可以使用@MonitorRequests注释。

Stagemonitor Call Tree

答案 3 :(得分:0)

你说你想知道堆栈中每个例程的时间百分比。

我假设您的意思是包含时间。

我还假设你的意思是挂钟时间,理论上如果其中一个较低级别的被调用者碰巧做了一些I / O,锁定等,你就不会这样做。我想对此视而不见。

因此,在挂钟时间采样的堆栈采样分析器将获得正确的信息。

A所用的百分比时间是包含A的样本的百分比,对于B等相同。

要获得B使用的A时间的百分比,它是包含A的样本在下一级别碰巧有B的百分比。

信息全部在堆栈样本中,但可能很难让分析器只提取您想要的信息。

您还说您想要精确百分比。 这意味着您还需要大量的堆栈样本。 例如,如果您想将测量的不确定性缩小10倍,则需要100倍的样本。

根据我发现性能问题的经验,我愿意容忍10%或更多的不确定性,因为我的目标是找到大量的浪费,而不是精确地知道它有多糟糕。 所以我手动采样,并手动查看。 事实上,如果你查看统计数据,你只需要看到与两个样本一样少的东西就知道它很糟糕,并且在看到它之前你采取的样本越少,越糟糕。 (例如:如果问题浪费了30%的时间,平均需要2/30%= 6.67个样本才能看到它两次。如果它浪费了90%的时间,平均只需要2.2个样本。)