All Downloads are FREE. Search and download functionalities are using the official Maven repository.

callStack.profiler.ProfileEvent Maven / Gradle / Ivy

Go to download

This profiler keeps track of the method calls and outputs method call hierarchy, allowing developers to quickly comprehend execution time breakdown. Profile your code with negligible performance and memory overhead.

There is a newer version: 1.1.6
Show newest version
/**
 * Copyright 2020 SkillTree
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package callStack.profiler;

import org.apache.commons.lang3.Validate;
import org.joda.time.Period;
import org.joda.time.format.PeriodFormatter;
import org.joda.time.format.PeriodFormatterBuilder;

import java.io.Serializable;
import java.text.NumberFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

import static org.apache.commons.lang3.Validate.notNull;

public class ProfileEvent implements Serializable {
    static final long serialVersionUID = 1l;

    long runtimeInMillis = 0;
    int numOfInvocations = 0;
    String name;

    // interal use only please
    long start = -1;
    // these are display only
    boolean isConcurrent = false;
    boolean isRemote = false;
    ProfileEvent parent;
    // ---------------------------------
    Map childrenAsMap;

    public Collection getChildren() {
        if (childrenAsMap == null) {
            return Collections.emptyList();
        }
        return childrenAsMap.values();
    }

    public synchronized void addChild(ProfileEvent child) {
        notNull(child);
        notNull(child.getName());

        if (childrenAsMap == null) {
            childrenAsMap = new ConcurrentHashMap();
        }
        childrenAsMap.put(child.getName(), child);
        child.setParent(this);
    }

    synchronized void replaceChild(String previousName, ProfileEvent child) {
        childrenAsMap.remove(previousName);
        childrenAsMap.put(child.getName(), child);
    }


    public ProfileEvent getEvent(String str) {
        notNull(str);
        ProfileEvent res = null;
        if (childrenAsMap != null) {
            res = childrenAsMap.get(str);
        }
        return res;
    }

    public void startEvent() {
        if (start != -1) {
            Validate.isTrue(start == -1, "Can not start event twice. Event [" + name + "] has already been started");
        }

        start = System.currentTimeMillis();
    }

    public void endEvent() {
        if (start == -1) {
            throw new IllegalArgumentException("Must call startEvent first");
        }
        numOfInvocations++;
        runtimeInMillis = runtimeInMillis + (System.currentTimeMillis() - start);
        start = -1;
    }

    public String prettyPrint() {
        StringBuilder res = new StringBuilder();
        buildPrettyString(res, this, "");
        return res.toString();
    }

    private final static NumberFormat NUMBER_FORMAT = NumberFormat.getInstance();

    private void buildPrettyString(StringBuilder res, ProfileEvent node, String pre) {
        if (res.length() > 0) {
            res.append("\n");
        }
        StringBuilder preBuilder = new StringBuilder(pre);
        if (node.isConcurrent) {
            preBuilder.append("|");
        }
        if (node.isRemote) {
            preBuilder.append("||");
        }
        preBuilder.append("|");
        res.append(preBuilder.toString());

        res.append("-> ");
        res.append(node.getName());
        res.append(" (");
        res.append(NUMBER_FORMAT.format(node.getNumOfInvocations()));
        res.append(") : ");
        addRuntime(res, node.getRuntimeInMillis());

        boolean hasChildren = node != null && !isEmpty(node.getChildrenAsMap());
        if (hasChildren) {
            handleUnaccountedTime(res, node);
        }
        if (hasChildren) {
            preBuilder.append("     ");
            for (ProfileEvent profileEvent : node.getChildrenAsMap().values()) {
                buildPrettyString(res, profileEvent, preBuilder.toString());
            }
        }
    }

    private static boolean isEmpty(Map map) {
        return map == null || map.isEmpty();
    }

    private void handleUnaccountedTime(StringBuilder res, ProfileEvent node) {
        Collection values = node.getChildrenAsMap().values();

        long childrenSum = 0;
        List syncEvents = values.stream().filter(p-> !isConcurrent(p)).collect(Collectors.toList());
        if(!syncEvents.isEmpty()){
            childrenSum += syncEvents.stream().mapToLong(ProfileEvent::getRuntimeInMillis).sum();
        }
        List asyncEvents = values.stream().filter(p-> isConcurrent(p)).collect(Collectors.toList());
        if(!asyncEvents.isEmpty()){
            childrenSum += asyncEvents.stream().mapToLong(ProfileEvent::getRuntimeInMillis).max().getAsLong();
        }

        long diff = node.getRuntimeInMillis() - childrenSum;
        res.append(" [");
        res.append(periodFormatter.print(new Period(diff)));
        res.append("]");
    }
    private boolean isConcurrent(ProfileEvent p){
        return p.isRemote() || p.isConcurrent();
    }

    private final static long SECOND = 1000;
    private final static long MINUTE = 60 * SECOND;
    private final static long HOUR = 60 * MINUTE;


    private static final PeriodFormatter periodFormatter = new PeriodFormatterBuilder()
            .appendHours()
            .appendSuffix("h")
            .appendSeparatorIfFieldsBefore(" ")
            .appendMinutes()
            .appendSuffix("m")
            .appendSeparatorIfFieldsBefore(" ")
            .appendSeconds()
            .appendSuffix("s")
            .appendSeparatorIfFieldsBefore(" ")
            .appendMillis3Digit()
            .appendSuffix("ms").toFormatter();

    private void addRuntime(StringBuilder res, long runtime) {
        res.append(periodFormatter.print(new Period(runtime)));
    }

    public boolean isEnded() {
        return start == -1;
    }

    @Override
    public String toString() {
        return prettyPrint();
    }

    public long getRuntimeInMillis() {
        return runtimeInMillis;
    }

    public void setRuntimeInMillis(long runtimeInMillis) {
        this.runtimeInMillis = runtimeInMillis;
    }

    public int getNumOfInvocations() {
        return numOfInvocations;
    }

    public void setNumOfInvocations(int numOfInvocations) {
        this.numOfInvocations = numOfInvocations;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public long getStart() {
        return start;
    }

    public void setStart(long start) {
        this.start = start;
    }

    public boolean isConcurrent() {
        return isConcurrent;
    }

    public void setConcurrent(boolean concurrent) {
        isConcurrent = concurrent;
    }

    public boolean isRemote() {
        return isRemote;
    }

    public void setRemote(boolean remote) {
        isRemote = remote;
    }

    public ProfileEvent getParent() {
        return parent;
    }

    public void setParent(ProfileEvent parent) {
        this.parent = parent;
    }

    public Map getChildrenAsMap() {
        return childrenAsMap;
    }

    public void setChildrenAsMap(Map childrenAsMap) {
        this.childrenAsMap = childrenAsMap;
    }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy