Our company has used 7 years of code execution time-consuming statistical functions, so elegant! !

Click on the blue font above and select "Star Official Account"

High-quality articles, delivered immediately

Follow the official account backstage to reply to pay or mall to obtain actual project information + video

Author: Jitwxs

Source| https://jitwxs.cn/5aa91d10.html

I. Introduction

Code time-consuming statistics is a very common requirement in daily development, especially when you need to find code performance bottlenecks.

It may also be limited by the language features of Java, and I always feel that the code is not elegant enough to write, and a large amount of time-consuming statistical code interferes with business logic. Especially when developing functions, I have a feeling that the code is refreshing and elegant just after the development. As a result, after adding a lot of auxiliary code, the whole code becomes bloated, and it is very uncomfortable to look at it myself. Therefore, I always wonder if I can write this piece more elegantly. Today, this article will try to discuss the “code time-consuming statistics” piece.

Before starting the main text, let me talk about the premise that "code time-consuming statistics" is not the time-consuming of a certain method, but the time-consuming between arbitrary code segments. This code segment may be a few lines of code in a method, or it may be from a line of this method to a line of another called method, so this requirement cannot be achieved through AOP.

Second, the conventional method

2.1 Time difference statistics

This method is the simplest method. Record the start time, record the end time, and calculate the time difference.

public class TimeDiffTest {
    public static void main(String[] args) throws InterruptedException {
        final long startMs = TimeUtils.nowMs();

        TimeUnit.SECONDS.sleep(5); // 模拟业务代码

        System.out.println("timeCost: " + TimeUtils.diffMs(startMs));
    }
}

/* output: 
timeCost: 5005
*/
public class TimeUtils {
    /**
     * @return 当前毫秒数
     */
    public static long nowMs() {
        return System.currentTimeMillis();
    }

    /**
     * 当前毫秒与起始毫秒差
     * @param startMillis 开始纳秒数
     * @return 时间差
     */
    public static long diffMs(long startMillis) {
       return diffMs(startMillis, nowMs());
    }
}

The advantage of this method is that it is simple to implement and easy to understand; the disadvantage is that it is more intrusive to the code, looks foolish, and is not elegant.

2.2 StopWatch

The second way is to refer to StopWatch. StopWatch is usually used as a time-consuming statistical code. Each framework and Common package has its own implementation.

public class TraceWatchTest {
    public static void main(String[] args) throws InterruptedException {
        TraceWatch traceWatch = new TraceWatch();

        traceWatch.start("function1");
        TimeUnit.SECONDS.sleep(1); // 模拟业务代码
        traceWatch.stop();

        traceWatch.start("function2");
        TimeUnit.SECONDS.sleep(1); // 模拟业务代码
        traceWatch.stop();

        traceWatch.record("function1", 1); // 直接记录耗时

        System.out.println(JSON.toJSONString(traceWatch.getTaskMap()));
    }
}

/* output: 
{"function2":[{"data":1000,"taskName":"function2"}],"function1":[{"data":1000,"taskName":"function1"},{"data":1,"taskName":"function1"}]}
*/

public class TraceWatch {
    /** Start time of the current task. */
    private long startMs;

    /** Name of the current task. */
    @Nullable
    private String currentTaskName;

    @Getter
    private final Map<String, List<TaskInfo>> taskMap = new HashMap<>();

    /**
     * 开始时间差类型指标记录,如果需要终止,请调用 {@link #stop()}
     *
     * @param taskName 指标名
     */
    public void start(String taskName) throws IllegalStateException {
        if (this.currentTaskName != null) {
            throw new IllegalStateException("Can't start TraceWatch: it's already running");
        }
        this.currentTaskName = taskName;
        this.startMs = TimeUtils.nowMs();
    }

    /**
     * 终止时间差类型指标记录,调用前请确保已经调用
     */
    public void stop() throws IllegalStateException {
        if (this.currentTaskName == null) {
            throw new IllegalStateException("Can't stop TraceWatch: it's not running");
        }
        long lastTime = TimeUtils.nowMs() - this.startMs;

        TaskInfo info = new TaskInfo(this.currentTaskName, lastTime);

        this.taskMap.computeIfAbsent(this.currentTaskName, e -> new LinkedList<>()).add(info);

        this.currentTaskName = null;
    }

    /**
     * 直接记录指标数据,不局限于时间差类型
     *  @param taskName 指标名
     * @param data 指标数据
     */
    public void record(String taskName, Object data) {
        TaskInfo info = new TaskInfo(taskName, data);

        this.taskMap.computeIfAbsent(taskName, e -> new LinkedList<>()).add(info);
    }

    @Getter
    @AllArgsConstructor
    public static final class TaskInfo {
        private final String taskName;

        private final Object data;
    }
}

I imitated  org.springframework.util.StopWatch the implementation and wrote the TraceWatch class. This method provides two time-consuming statistical methods:

 Perform time-consuming statistics by calling  Start(name) and  Stop()methods. By calling  Record(name, timeCost), method, directly record time-consuming information. This method is essentially the same as "time difference statistics", except that one layer is extracted, which is a little more elegant.

Note: You can modify the TraceWatch internal data structure by yourself according to your business needs.  I am here to keep it simple. The internal data structure is just an example.

Three, advanced methods

The two methods mentioned in the second section are "straight-forward" in the vernacular. We can also try to write the code a little easier.

3.1 Function

In jdk 1.8, a java.util.function package was introduced  . Through the interface provided by this class, the function of executing additional code in the context of a specified code segment can be realized.

public class TraceHolderTest {
    public static void main(String[] args) {
        TraceWatch traceWatch = new TraceWatch();

        TraceHolder.run(traceWatch, "function1", i -> {
            try {
                TimeUnit.SECONDS.sleep(1); // 模拟业务代码
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        String result = TraceHolder.run(traceWatch, "function2", () -> {
            try {
                TimeUnit.SECONDS.sleep(1); // 模拟业务代码
                return "YES";
            } catch (InterruptedException e) {
                e.printStackTrace();
                return "NO";
            }
        });

        TraceHolder.run(traceWatch, "function1", i -> {
            try {
                TimeUnit.SECONDS.sleep(1); // 模拟业务代码
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        System.out.println(JSON.toJSONString(traceWatch.getTaskMap()));
    }
}

/* output: 
{"function2":[{"data":1004,"taskName":"function2"}],"function1":[{"data":1001,"taskName":"function1"},{"data":1002,"taskName":"function1"}]}
*/

public class TraceHolder {
    /**
     * 有返回值调用
     */
    public static <T> T run(TraceWatch traceWatch, String taskName, Supplier<T> supplier) {
        try {
            traceWatch.start(taskName);

            return supplier.get();
        } finally {
            traceWatch.stop();
        }
    }

    /**
     * 无返回值调用
     */
    public static void run(TraceWatch traceWatch, String taskName, IntConsumer function) {
        try {
            traceWatch.start(taskName);

            function.accept(0);
        } finally {
            traceWatch.stop();
        }
    }
}

Here I use the  Supplier and  IntConsumer interface to provide externally with return value and no return worth calling. In the TraceHolder class, before and after the core code block, the previous TraceWatch methods are respectively called to realize the time-consuming statistics function.

3.2 AutoCloseable

In addition to Function the features used, we can also use  the features of jdk 1.7  AutoCloseable . It is said  AutoCloseable that some students may not have heard of it, but if you show the following code, you will immediately understand what it is.

// 未使用 AutoCloseable
public static String readFirstLingFromFile(String path) throws IOException {
    BufferedReader br = null;
    try {
        br = new BufferedReader(new FileReader(path));
        return br.readLine();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (br != null) {
            br.close();
        }
    }
    return null;
}

// 使用 AutoCloseable
public static String readFirstLineFromFile(String path) throws IOException {
    try (BufferedReader br = new BufferedReader(new FileReader(path))) {
        return br.readLine();
    }
}

In the  try back, you can load an AutoCloseable object that implements the  interface, which acts on the entire try statement block, and calls back the AutoCloseable#close() method after the execution is complete  .

Let's modify the TraceWatch class:

Implement  AutoCloseable interface, implement  close() interface:

@Override
public void close() {
    this.stop();
}

Modify the  start() method to support chain call:

public TraceWatch start(String taskName) throws IllegalStateException {
    if (this.currentTaskName != null) {
        throw new IllegalStateException("Can't start TraceWatch: it's already running");
    }
    this.currentTaskName = taskName;
    this.startMs = TimeUtils.nowMs();
    
    return this;
}

public class AutoCloseableTest {
    public static void main(String[] args) {
        TraceWatch traceWatch = new TraceWatch();

        try(TraceWatch ignored = traceWatch.start("function1")) {
            try {
                TimeUnit.SECONDS.sleep(1); // 模拟业务代码
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        try(TraceWatch ignored = traceWatch.start("function2")) {
            try {
                TimeUnit.SECONDS.sleep(1); // 模拟业务代码
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        try(TraceWatch ignored = traceWatch.start("function1")) {
            try {
                TimeUnit.SECONDS.sleep(1); // 模拟业务代码
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println(JSON.toJSONString(traceWatch.getTaskMap()));
    }
}

/* output: 
{"function2":[{"data":1001,"taskName":"function2"}],"function1":[{"data":1002,"taskName":"function1"},{"data":1002,"taskName":"function1"}]}
*/

Four, summary

This article lists four ways to count code time-consuming:

  • Time difference statistics

  • StopWatch

  • Function

  • AutoCloseable

The schemes listed are what I can think of at present.


There are hot recommendations???

19 pictures overview Spring Cloud

The new CTO prohibits the separation of front and back ends, and also said a lot of advantages and disadvantages!

It is not difficult to realize SpringBoot's simple read-write separation from scratch!

Use VS Code to develop Spring Boot, Zhenni Ma show!

Stupidly indistinguishable Cookie, Session, Token, JWT

Dry goods|SpringBoot integrated Jiguang push complete implementation code (recommended collection)

Zhihu Gaozan: Which one to choose between Pinduoduo and State Grid Offer?

Don't use SELECT *

In-depth comparison of Jackson and Fastjson, in the end I still chose...

The ultimate guide to getting started with Docker, this is the best tutorial I have ever seen!

Click to read the original text, go to learn SpringCloud actual combat project

Guess you like

Origin blog.csdn.net/qq_17231297/article/details/115302101