调用链耗时统计工具Profiler

代码实现

引入了Profiler用于打印日志调用耗时,包括Profiler和ProfilerEntry两个类


package org.apache.james.mailbox.profiler;

/**
 * 耗时统计工具
 *
 * @since 2021-01-20
 */
public class Profiler {

    private static final ThreadLocal<ProfilerEntry> entryStack = new ThreadLocal();

    public Profiler() {
    }

    public static void start() {
        start(null);
    }

    public static void start(String message) {
        entryStack.set(new ProfilerEntry(message, null, null));
    }

    public static void enter(String message) {
        ProfilerEntry currentEntry = getCurrentEntry();
        if (currentEntry != null) {
            currentEntry.enterSubEntry(message);
        }

    }

    public static void release() {
        ProfilerEntry currentEntry = getCurrentEntry();
        if (currentEntry != null) {
            currentEntry.release();
        }

    }

    public static String dump() {
        return dump("", "");
    }

    public static String dump(String prefix1, String prefix2) {
        ProfilerEntry entry = entryStack.get();
        return entry != null ? entry.toString(prefix1, prefix2) : "";
    }

    private static ProfilerEntry getCurrentEntry() {
        ProfilerEntry subEntry = entryStack.get();
        ProfilerEntry entry = null;
        if (subEntry != null) {
            do {
                entry = subEntry;
                subEntry = subEntry.getUnreleasedEntry();
            } while (subEntry != null);
        }

        return entry;
    }
}

package org.apache.james.mailbox.profiler;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.lang3.StringUtils;

/**
 * 耗时统计工具
 *
 * @since 2021-01-20
 */
public final class ProfilerEntry {
    private final List<ProfilerEntry> subEntries = new ArrayList(4);

    private final Object message;

    private final ProfilerEntry parentEntry;

    private final ProfilerEntry firstEntry;

    private final long baseTime;

    private final long startTime;

    private long endTime;

    ProfilerEntry(Object message, ProfilerEntry parentEntry, ProfilerEntry firstEntry) {
        this.message = message;
        this.startTime = System.currentTimeMillis();
        this.parentEntry = parentEntry;
        this.firstEntry = null != firstEntry ? firstEntry : this;
        this.baseTime = firstEntry == null ? 0L : firstEntry.startTime;
    }

    public String getMessage() {
        String messageString = null;
        if (this.message instanceof String) {
            messageString = (String) this.message;
        }

        return (String) StringUtils.defaultIfEmpty(messageString, (CharSequence) null);
    }

    public long getStartTime() {
        return this.baseTime > 0L ? this.startTime - this.baseTime : 0L;
    }

    public long getDuration() {
        return this.endTime < this.startTime ? -1L : this.endTime - this.startTime;
    }

    public long getDurationOfSelf() {
        long duration = this.getDuration();
        if (duration < 0L) {
            return -1L;
        } else if (this.subEntries.isEmpty()) {
            return duration;
        } else {
            ProfilerEntry entry;
            for (Iterator var3 = this.subEntries.iterator(); var3.hasNext(); duration -= entry.getDuration()) {
                entry = (ProfilerEntry) var3.next();
            }

            return duration < 0L ? -1L : duration;
        }
    }

    public double getPercentage() {
        double parentDuration = 0.0D;
        double duration = (double) this.getDuration();
        if (this.parentEntry != null && this.parentEntry.isReleased()) {
            parentDuration = (double) this.parentEntry.getDuration();
        }

        return duration > 0.0D && parentDuration > 0.0D ? duration / parentDuration : 0.0D;
    }

    public double getPercentageOfAll() {
        double firstDuration = 0.0D;
        double duration = (double) this.getDuration();
        if (this.firstEntry != null && this.firstEntry.isReleased()) {
            firstDuration = (double) this.firstEntry.getDuration();
        }

        return duration > 0.0D && firstDuration > 0.0D ? duration / firstDuration : 0.0D;
    }

    public void release() {
        this.endTime = System.currentTimeMillis();
    }

    public boolean isReleased() {
        return this.endTime > 0L;
    }

    public void enterSubEntry(Object message) {
        ProfilerEntry subEntry = new ProfilerEntry(message, this, this.firstEntry);
        this.subEntries.add(subEntry);
    }

    public ProfilerEntry getUnreleasedEntry() {
        ProfilerEntry subEntry = null;
        if (!this.subEntries.isEmpty()) {
            subEntry = this.subEntries.get(this.subEntries.size() - 1);
            if (subEntry.isReleased()) {
                subEntry = null;
            }
        }

        return subEntry;
    }

    @Override
    public String toString() {
        return this.toString("", "");
    }

    public String toString(String prefix1, String prefix2) {
        StringBuffer buffer = new StringBuffer();
        this.toString(buffer, prefix1, prefix2);
        return buffer.toString();
    }

    private void toString(StringBuffer buffer, String prefix1, String prefix2) {
        buffer.append(prefix1);
        String message = this.getMessage();
        long startTime = this.getStartTime();
        long duration = this.getDuration();
        long durationOfSelf = this.getDurationOfSelf();
        double percent = this.getPercentage();
        double percentOfAll = this.getPercentageOfAll();
        Object[] params = new Object[] {message, startTime, duration, durationOfSelf, percent, percentOfAll};
        StringBuffer pattern = new StringBuffer("{1,number} ");
        if (this.isReleased()) {
            pattern.append("[{2,number}ms");
            if (durationOfSelf > 0L && durationOfSelf != duration) {
                pattern.append(" ({3,number}ms)");
            }

            if (percent > 0.0D) {
                pattern.append(", {4,number,##%}");
            }

            if (percentOfAll > 0.0D) {
                pattern.append(", {5,number,##%}");
            }

            pattern.append("]");
        } else {
            pattern.append("[UNRELEASED]");
        }

        if (message != null) {
            pattern.append(" - {0}");
        }

        buffer.append(MessageFormat.format(pattern.toString(), params));

        for (int i = 0; i < this.subEntries.size(); ++i) {
            ProfilerEntry subEntry = (ProfilerEntry) this.subEntries.get(i);
            buffer.append('\n');
            if (i == this.subEntries.size() - 1) {
                subEntry.toString(buffer, prefix2 + "`---", prefix2 + "    ");
            } else if (i == 0) {
                subEntry.toString(buffer, prefix2 + "+---", prefix2 + "|   ");
            } else {
                subEntry.toString(buffer, prefix2 + "+---", prefix2 + "|   ");
            }
        }

    }
}

使用方法

样例


package demo;

import lombok.extern.slf4j.Slf4j;
import profiler.Profiler;


@Slf4j
public class ProfilerDemo {
    public static void main(String[] args) throws InterruptedException {
        Profiler.start("ProfilerDemo");
        Profiler.enter("Enter sayHello");
        ProfilerDemo.sayHello();
        Profiler.release();

        Profiler.enter("Enter sayHello2");
        ProfilerDemo.sayHello2();
        Profiler.release();
        Profiler.release();

        System.out.println(Profiler.dump());
    }

    public static void sayHello() throws InterruptedException {
        Profiler.enter("Enter sayHello");
        Thread.sleep(500);
        ProfilerDemo.sayHello2();
        Profiler.release();
    }

    public static void sayHello2() throws InterruptedException {
        Profiler.enter("Enter sayHello2");
        Thread.sleep(500);
        ProfilerDemo.sayHello3();
        Profiler.release();

    }

    public static void sayHello3() throws InterruptedException {
        Profiler.enter("Enter sayHello3");
        Thread.sleep(500);
        Profiler.release();

    }

}

结果如下

0 [2,546ms, 100%] - ProfilerDemo
+---0 [1,534ms, 60%, 60%] - Enter sayHello
|   `---0 [1,534ms (512ms), 100%, 60%] - Enter sayHello
|       `---512 [1,022ms (515ms), 67%, 40%] - Enter sayHello2
|           `---1,027 [507ms, 50%, 20%] - Enter sayHello3
`---1,534 [1,012ms, 40%, 40%] - Enter sayHello2
    `---1,534 [1,012ms (507ms), 100%, 40%] - Enter sayHello2
        `---2,041 [505ms, 50%, 20%] - Enter sayHello3

使用说明

1、通过Profiler.start("ProfilerDemo");开启日志链

2、调用过程中使用,如下组合,成对调用,否则不成链

Profiler.enter("xxx");
Profiler.release();

3、最后通过Profiler.release();结束全部调用

4、通过日志打印,调用Profiler.dump()方法打出调用耗时链

注意点

  • 成对使用Profiler.enter("xxx");和Profiler.release();
  • 可以嵌套使用
  • 需要在同一线程中使用,因为底层使用了ThreadLocal来统计
  • 可以在上层方法调用Profiler.enter,在后续的调用类中Profiler.release();
posted @ 2021-01-22 14:43  风动静泉  阅读(812)  评论(0编辑  收藏  举报