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

Posted

技术标签:

【中文标题】执行方法堆栈中的所有方法所花费的时间【英文标题】:Time taken to execute all methods in a method stack 【发布时间】:2015-05-01 13:26:56 【问题描述】:

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

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

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

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

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

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

问题是:是否有任何合理的替代方案可以让我为一般的 java 代码做到这一点?或任何快速收集此信息的方法。甚至是 Spring 应用程序的 Spring 插件?

PS:与XRebel 完全一样,它会生成漂亮的安全、dao、服务等部分代码所花费的时间摘要。但它需要一个炸弹。如果你买得起,你一定要买。

【问题讨论】:

记录在这里。如果 B 和它的被调用者消耗了 50% 的时间,那么 B 有 50% 的时间在堆栈上。如果 B 调用 C 使用 40% 的时间,那么堆栈有 40% 的时间将 B 在 C 之上。无论您想知道什么时间信息,作为运行时间的一小部分,您可以通过查看 10 or so random stacks 获得。它不会向您展示不需要太多时间的东西;我敢打赌,您正在寻找需要大量时间的东西。 【参考方案1】:

你想写一个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。

【讨论】:

我用 0.6.8 版本试了一下。但它什么也没打印。有最新版本的类似文档吗? 两个快速检查:您的Monitor 注释是否声明RetentionType.RUNTIME?您是否注册了AgentBuilder.Listener 以查看潜在错误?此外,您应该使用最新版本。 0.6.8 真的很老了。 感谢您的快速回复:)。这是代码的链接:pastebin.com/jZnNkgUk 使用 0.6.8,因为上面的代码没有用 1.x 系列编译。谢谢。是监视器声明:@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) 您的main 方法是在您正在拦截的类中定义的。因此,在附加代理之前已经加载了该类。将您的主要方法放入另一个类中,一切都应该正常工作。当前版本的唯一 API 变化是 rebase 被重命名为 type 谢谢。它现在工作了:)。需要另一个帮助:我需要找出我的 @Monitor 注释方法在内部调用的所有方法的时间。从上面,我只拦截使用@Monitor 调用的方法。【参考方案2】:

我不知道是否已经有一个库在做这件事,也不能给你一个现成的代码。但我可以给你一个描述,你可以如何自己实现它。

首先,我认为将 AspectJ 包含到您的项目中没有问题。比创建注释 f.e. @Monitor 充当您喜欢的任何时间测量的标记。 比创建一个简单的数据结构来保存您想要跟踪的信息。 一个例子如下:

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;

比为所有方法创建一个环绕建议。在执行时检查被调用的方法是否用您的 Monitoring 注释进行了注释。如果是,则开始监视此线程中的每个方法执行。一个简单的示例代码如下所示:

@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 框架有自己的特性来处理线程以便能够跟踪它的原因。

我希望您了解这个的一般概念,如果不欢迎进一步提问。

【讨论】:

【参考方案3】:

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

【讨论】:

感谢您的回答。巧合的是,我什至使用 bytebuddy 为自己构建了它 :)。谢谢,周末试试。【参考方案4】:

你说你想知道堆栈上每个例程所花费的时间百分比。

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

我还假设您的意思是 wall-clock 时间,理论上,如果其中一个较低级别的被调用者碰巧进行了一些 I/O、锁定等,您不希望对此视而不见。

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

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

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

所有信息都在堆栈样本中,但可能很难让分析器仅提取您想要的信息。

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

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

【讨论】:

以上是关于执行方法堆栈中的所有方法所花费的时间的主要内容,如果未能解决你的问题,请参考以下文章

Ruby 方法的测量和基准时间

处理请求时每行执行所花费的 Rails 分析时间

如何计算算法复杂度

Spring Transaction - 记录新事务的方法执行时间

监控java程序的执行时间分布

测量 Angular 渲染指令所花费的时间