AspectJ用于计算执行时间

huangapple go评论105阅读模式
英文:

AspectJ to calculate execution time

问题

在这种情况下,method1 调用了 method2

在每个方法执行上定义了一个切入点和通知,用于计算方法所花费的时间。

因此,

  • method1 的通知打印出 10 秒;
  • method2 的通知打印出 6 秒;

我想要打印出 method1 所花费的时间,不包括 method2 所花费的时间。也就是说,我想要得到 10-6 = 4 秒 作为结果。

如何使用切面来实现这一点?

期望结果:4 秒

代码片段:

public int method1(String input) {
   User user = null;
   // 做一些事情..
   method2(input, input2);
   return 100;
}

这是切面组件中的检查方法:

@PointCut("....正确的内容")
public void endpoint(ProceedingJoinPoint pjp) throws Throwable {

@Around("endpoint")
public void timeTaken(ProceedingJoinPoint pjp) throws Throwable {
     
     // 计算开始时间的代码
     pjp.proceed();
     printf("总时间:" + (currentTime - startTime));
}

注意:这里的代码内容由于涉及特定的切面和业务逻辑,无法提供具体的代码细节。您需要根据您的项目需求来填写这些内容。

英文:

In this scenario, method1 calls method2.

There is a pointcut and advice defined on each of the method execution to calculate time taken by the method.
So,

   advice of `method1` prints 10 seconds; 

   advice of `method2` prints 6 seconds;

I want to print the time taken by method1 excluding the time taken by method2. That is I want 10-6 = 4 seconds as result

How can I acheive this using aspect ?

Expected : 4 seconds

Code snippet :

public int method1( String input ){
   User user = null;
   // something .. 
   method2(input, input2);
   return 100;
}

Here is the check method in aspect component:

@PointCut("....something correct")
public void endpoint (ProceedingJoinPoint pjp) throws Throwable {

@Around("endpoint")
public void timeTaken(ProceedingJoinPoint pjp) throws Throwable {
     
     //code to calcualte start time
     pjp.proceed();
     printf("the total time " : currentTime - startTime);
}

答案1

得分: 3

可能是有可能的,但通过AOP手段来模拟分析器可能会比较复杂。原因在于你需要计算方法调用树并将其连同执行时间一起记录在单独的数据结构中。在记录日志时,你需要从测量时间中减去子树中所有内容的执行时间。这将消耗CPU周期、内存和大量的维护工作。另一个问题是异步代码,即多线程。

我的建议是退一步思考一下,为什么你觉得自己需要这个。唯一的原因是,你的 method1 将(耗时的)逻辑与调用其他也包含(耗时的)逻辑的方法混合在一起。

你听说过Integration Operation Segregation Principle (IOSP)吗?它基本上说明了有两种类型的方法:

  • operations 包含逻辑
  • integrations 只调用 operations

如果你将这个原则应用到你自己的代码中,你间接地获得了你感兴趣的数字作为副作用。

所以,如果你运行这个切面

package de.scrum_master.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class TimerAspect {
  @Around("execution(* *(..)) && within(de.scrum_master..*) && !within(TimerAspect)")
  public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
    long startTime = System.nanoTime();
    try {
      return joinPoint.proceed();
    }
    finally {
      System.out.println(joinPoint + " -> " + (System.nanoTime() - startTime) / 1000000 + " ms");
    }
  }
}

对这个应用程序

package de.scrum_master.app;

public class Application {
  public static void main(String[] args) throws InterruptedException {
    new Application().method1("foo");
  }

  public int method1(String input) throws InterruptedException {
    Thread.sleep(300);
    method2(input, 11);
    Thread.sleep(100);
    return 100;
  }

  public void method2(String input, int i) throws InterruptedException {
    Thread.sleep(600);
  }
}

你会得到你不喜欢的有问题的输出:

execution(void de.scrum_master.app.Application.method2(String, int)) -> 604 ms
execution(int de.scrum_master.app.Application.method1(String)) -> 1009 ms
execution(void de.scrum_master.app.Application.main(String[])) -> 1010 ms

但是,如果你根据IOSP重构你的应用程序代码,就像这样

package de.scrum_master.app;

public class Application {
  public static void main(String[] args) throws InterruptedException {
    new Application().integration("foo");
  }

  public int integration(String input) throws InterruptedException {
    operation1();
    operation2(input, 11);
    operation3();
    return 100;
  }

  private void operation1() throws InterruptedException {
    Thread.sleep(300);
  }

  public void operation2(String input, int i) throws InterruptedException {
    Thread.sleep(600);
  }

  private void operation3() throws InterruptedException {
    Thread.sleep(100);
  }
}

应用程序仍然执行相同的操作,但所有的逻辑和可能的耗时操作都被提取到它们自己的方法中。operation2 在之前是 method2operation1 是在之前发生的逻辑,operation3 是在 operation2 之后发生的逻辑。现在,你的 integration(以前是 method1)不仅更清晰、更易读,而且你还会得到每个操作的单独计时日志:

execution(void de.scrum_master.app.Application.operation1()) -> 302 ms
execution(void de.scrum_master.app.Application.operation2(String, int)) -> 599 ms
execution(void de.scrum_master.app.Application.operation3()) -> 100 ms
execution(int de.scrum_master.app.Application.integration(String)) -> 1005 ms
execution(void de.scrum_master.app.Application.main(String[])) -> 1005 ms

当然,Thread.sleep 操作只是象征性地表示你可能拥有的任何应用程序逻辑:ifswitch - case 语句,循环,外部资源(文件、数据库)处理,复杂计算或其他任何内容。

底线:进行重构! 如果你想对它们分别进行计时、测试、重构和使用,不要将操作和集成混合在一起。或者,使用一个分析器。

英文:

It might be possible, but complex to emulate a profiler by means of AOP. The reason is that you would need to calculate a method call tree and record it along with the execution times in a separate data structure. When logging, you would have to subtract execution times of everything in a subtree from the measured time. This would cost CPU cycles, memory and lots of maintenance work. One more issue is asynchronous code, i.e. multi-threading.

My recommendation is to take a step back and think about why you even think you need this. The only reason is that your method1 mixes (time-consuming) logic with calling other methods also containing (time-consuming) logic.

Have you ever heard about the Integration Operation Segregation Principle (IOSP)? It basically states that there are two types of methods:

  • operations containing logic
  • integrations only calling operations

If you apply this principle to your own code, you indirectly get the numbers you are interested in as a side effect.

So if you run this aspect

package de.scrum_master.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class TimerAspect {
  @Around("execution(* *(..)) && within(de.scrum_master..*) && !within(TimerAspect)")
  public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
    long startTime = System.nanoTime();
    try {
      return joinPoint.proceed();
    }
    finally {
      System.out.println(joinPoint + " -> " + (System.nanoTime() - startTime) / 1000000 + " ms");
    }
  }
}

against this application

package de.scrum_master.app;

public class Application {
  public static void main(String[] args) throws InterruptedException {
    new Application().method1("foo");
  }

  public int method1(String input) throws InterruptedException {
    Thread.sleep(300);
    method2(input, 11);
    Thread.sleep(100);
    return 100;
  }

  public void method2(String input, int i) throws InterruptedException {
    Thread.sleep(600);
  }
}

you get the problematic output you don't like:

execution(void de.scrum_master.app.Application.method2(String, int)) -> 604 ms
execution(int de.scrum_master.app.Application.method1(String)) -> 1009 ms
execution(void de.scrum_master.app.Application.main(String[])) -> 1010 ms

But if you refactor your application code according to the IOSP like this

package de.scrum_master.app;

public class Application {
  public static void main(String[] args) throws InterruptedException {
    new Application().integration("foo");
  }

  public int integration(String input) throws InterruptedException {
    operation1();
    operation2(input, 11);
    operation3();
    return 100;
  }

  private void operation1() throws InterruptedException {
    Thread.sleep(300);
  }

  public void operation2(String input, int i) throws InterruptedException {
    Thread.sleep(600);
  }

  private void operation3() throws InterruptedException {
    Thread.sleep(100);
  }
}

the application still does the same, but all logical and possible expensive operations are factored out into their own methods. operation2 is the same as method2 before, operation1 is the logic happening before and operation3 is the logic happening after and operation2. Now not only is your integration (formerly method1) cleaner and easier to read but also do you get separate timing logs for each operation:

execution(void de.scrum_master.app.Application.operation1()) -> 302 ms
execution(void de.scrum_master.app.Application.operation2(String, int)) -> 599 ms
execution(void de.scrum_master.app.Application.operation3()) -> 100 ms
execution(int de.scrum_master.app.Application.integration(String)) -> 1005 ms
execution(void de.scrum_master.app.Application.main(String[])) -> 1005 ms

Of course the Thread.sleep operations are just symbolical representations for whatever application logic you might have: if or switch - case statements, loops, external resource (file, database) handling, complex calculations or whatever.

Bottom line: Go refactor! Don't mix operations and integrations if you want to time, test, refactor, use them separately. Alternatively, use a profiler.

huangapple
  • 本文由 发表于 2020年10月16日 15:51:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/64385045.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定