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

com.github.endoscope.core.Stats Maven / Gradle / Ivy

The newest version!
package com.github.endoscope.core;

import java.beans.Transient;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong;

import com.github.endoscope.properties.Properties;

@com.fasterxml.jackson.annotation.JsonPropertyOrder({ "statsLeft", "lost", "fatalError", "startDate", "endDate", "map" })
public class Stats {
    private Map map = new HashMap<>();
    private long statsLeft = Properties.getMaxStatCount();
    private AtomicLong lost = new AtomicLong(0);
    private String fatalError = null;
    private Date startDate;
    private Date endDate;
    private String info;

    //do not get it from Properties here as we could loose data by accident by calculating Stats on machine with Property turned off
    private boolean aggregateSubCalls = true;

    public Stats(){}

    /**
     * Set aggregateSubCalls to false to collect entry point's only.
     * It limits Endoscope functionality a bit but significantly reduces amount of data and might be really
     * handy in case of bigger applications.
     *
     * @param aggregateSubCalls
     */
    public Stats(boolean aggregateSubCalls){
        this.aggregateSubCalls = aggregateSubCalls;
    }

    private Stat getOrAddParent(Context context) {
        Stat parentStat = map.get(context.getId());
        if( parentStat == null && statsLeft > 0 ){
            parentStat = new Stat();
            statsLeft--;
            map.put(context.getId(), parentStat);
        }
        return parentStat;
    }

    public void store(Context context){
        store(context, true);
    }

    private void store(Context context, boolean firstPass){
        if( !firstPass && !aggregateSubCalls ){
            return;
        }

        Stat root = getOrAddParent(context);
        if( root != null ){
            root.update(context.getTime());
            root.updateErr(context.isErr());
            store(context, root, firstPass);
        }
    }

    private void store(Context context, final Stat parentStat, boolean firstPass){
        if( context.getChildren() != null ){
            //first collect number of calls per parent
            context.getChildren().stream().forEach( child -> {
                //update child stats
                Stat childStat = parentStat.getChild(child.getId());
                if( childStat == null && statsLeft > 0 ){
                    childStat = parentStat.createChild(child.getId());
                    statsLeft--;
                }
                if( childStat != null ){
                    childStat.update(child.getTime());
                    childStat.updateErr(child.isErr());

                    //recurse and update next level of child stats
                    store(child, childStat, firstPass);

                    //stats for child calls are collected in two places:
                    //- in context of parent
                    //- separately as root stats
                    //Context like this:
                    // a -> b -> c
                    //Results in following stats:
                    // a -> b -> c
                    // b -> c
                    // c

                    //recurse and update top level stats for this child
                    if( firstPass ) {
                        store(child, false);
                    }
                }
            });
        }
    }

    @Transient
    public Stats deepCopy(){
        return deepCopy(true);
    }

    @Transient
    public Stats deepCopy(boolean withChildren){
        Stats s = new Stats();

        s.statsLeft = statsLeft;
        s.lost.set(lost.get());
        s.fatalError = fatalError;
        s.startDate = startDate;
        s.endDate = endDate;

        map.forEach((k, v) -> s.map.put(k, v.deepCopy(withChildren)));

        return s;
    }

    @Transient
    public void merge(Stats inc, boolean withChildren){
        //too much hassle with merging statsLeft

        lost.set(lost.get() + inc.lost.get());
        if( inc.fatalError != null && fatalError == null ){
            fatalError = inc.fatalError;
        }
        if( startDate == null || (inc.startDate != null && inc.startDate.before(startDate)) ){
            startDate = inc.startDate;
        }
        if( endDate == null || (inc.endDate != null && inc.endDate.after(endDate)) ){
            endDate = inc.endDate;
        }

        inc.map.forEach((k, v) -> {
            Stat s = map.get(k);
            if( s == null ){
                map.put(k, v.deepCopy(withChildren) );
            } else {
                s.merge(v, withChildren);
            }
        });
    }

    /**
     * This method is thread safe.
     */
    @Transient
    public void threadSafeIncrementLost() {
        lost.incrementAndGet();
    }

    public Map getMap() {
        return map;
    }

    public void setMap(Map map) {
        this.map = map;
    }

    public long getLost() {
        return lost.get();
    }

    public void setLost(long lost) {
        this.lost.set(lost);
    }

    public long getStatsLeft() {
        return statsLeft;
    }

    public void setStatsLeft(long statsLeft) {
        this.statsLeft = statsLeft;
    }

    public String getFatalError() {
        return fatalError;
    }

    public void setFatalError(String fatalError) {
        this.fatalError = fatalError;
    }

    public Date getStartDate() {
        return startDate;
    }

    public void setStartDate(Date startDate) {
        this.startDate = startDate;
    }

    public Date getEndDate() {
        return endDate;
    }

    public void setEndDate(Date endDate) {
        this.endDate = endDate;
    }


    /**
     * Implementation specific information.
     * @return
     */
    public String getInfo() {
        return info;
    }

    /**
     * Implementation specific information.
     * @param info
     */
    public void setInfo(String info) {
        this.info = info;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Stats)) return false;
        Stats stats = (Stats) o;
        return statsLeft == stats.statsLeft &&
                lost.get() == stats.lost.get() &&
                aggregateSubCalls == stats.aggregateSubCalls &&
                Objects.equals(map, stats.map) &&
                Objects.equals(fatalError, stats.fatalError) &&
                Objects.equals(startDate, stats.startDate) &&
                Objects.equals(endDate, stats.endDate) &&
                Objects.equals(info, stats.info);
    }

    @Override
    public int hashCode() {
        return Objects.hash(map, statsLeft, lost.get(), fatalError, startDate, endDate, info, aggregateSubCalls);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy