在编写应用程序时,我希望分析和测量 stacktrace 中所有方法所花费的时间。我的意思是说:
Method A --> Method B --> Method C ...
A
方法在内部调用B
,它可能会调用另一个。我想知道在每个方法中执行所花费的时间。这样在Web应用程序中,我可以准确地知道代码的哪个部分所消耗的时间百分比。
为了进一步解释,在spring应用程序的大多数时候,我都会编写一个方面来收集类的每个方法调用的信息。这最后给了我总结。但我讨厌这样做,它重复和冗长,需要不断改变正则表达式以适应不同的类。相反,我想这样:
@Monitor
public void generateReport(int id){
...
}
在方法上添加一些注释将触发instrumentation api收集此方法和稍后调用的任何方法所花费的所有时间统计信息。当退出此方法时,它会停止收集信息。我认为这应该相对容易实现。
问题是:是否有任何合理的替代方案可以让我为普通的Java代码执行此操作?或任何快速收集此信息的方式。甚至是用于弹簧应用的弹簧插件?
PS:与XRebel完全相同,它会生成安全,dao,服务等部分代码的精美时间摘要。但它需要炸弹。如果你负担得起,你一定要买。
答案 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
注释。
答案 3 :(得分:0)
你说你想知道堆栈中每个例程的时间百分比。
我假设您的意思是包含时间。
我还假设你的意思是挂钟时间,理论上如果其中一个较低级别的被调用者碰巧做了一些I / O,锁定等,你就不会这样做。我想对此视而不见。
因此,在挂钟时间采样的堆栈采样分析器将获得正确的信息。
A所用的百分比时间是包含A的样本的百分比,对于B等相同。
要获得B使用的A时间的百分比,它是包含A的样本在下一级别碰巧有B的百分比。
信息全部在堆栈样本中,但可能很难让分析器只提取您想要的信息。
您还说您想要精确百分比。 这意味着您还需要大量的堆栈样本。 例如,如果您想将测量的不确定性缩小10倍,则需要100倍的样本。
根据我发现性能问题的经验,我愿意容忍10%或更多的不确定性,因为我的目标是找到大量的浪费,而不是精确地知道它有多糟糕。 所以我手动采样,并手动查看。 事实上,如果你查看统计数据,你只需要看到与两个样本一样少的东西就知道它很糟糕,并且在看到它之前你采取的样本越少,越糟糕。 (例如:如果问题浪费了30%的时间,平均需要2/30%= 6.67个样本才能看到它两次。如果它浪费了90%的时间,平均只需要2.2个样本。)