org.eclipse.skalli.commons.Statistics.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.skalli.commons.Statistics.java

Source

/*******************************************************************************
 * Copyright (c) 2010-2014 SAP AG and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     SAP AG - initial API and implementation
 *******************************************************************************/
package org.eclipse.skalli.commons;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentSkipListSet;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.StringUtils;

public class Statistics {

    public static class StatisticsInfo implements Comparable<StatisticsInfo> {
        private final String userHash;
        private final long timestamp;
        private final long sequenceNumber;

        public StatisticsInfo(String userId, long sequenceNumber) {
            this.timestamp = System.currentTimeMillis();
            this.sequenceNumber = sequenceNumber;
            if (StringUtils.isNotBlank(userId)) {
                this.userHash = DigestUtils.shaHex(userId);
            } else {
                this.userHash = ANONYMOUS;
            }
        }

        public String getUserHash() {
            return userHash;
        }

        public long getTimestamp() {
            return timestamp;
        }

        public long getSequenceNumber() {
            return sequenceNumber;
        }

        public boolean inRange(long startDate, long endDate) {
            return (startDate <= 0 || startDate <= timestamp) && (endDate <= 0 || timestamp <= endDate);
        }

        @Override
        public int compareTo(StatisticsInfo o) {
            return Long.signum(sequenceNumber - o.sequenceNumber);
        }
    }

    public static class UserInfo extends StatisticsInfo {
        private final String department;
        private final String location;

        public UserInfo(String userId, String department, String location, long id) {
            super(userId, id);
            this.department = department;
            this.location = location;
        }

        public String getDepartment() {
            return department;
        }

        public String getLocation() {
            return location;
        }
    }

    public static class UsageInfo extends StatisticsInfo {
        private final String path;
        private final String referer;

        public UsageInfo(String userId, String path, String referer, long id) {
            super(userId, id);
            this.path = path;
            this.referer = referer;
        }

        public String getPath() {
            return path;
        }

        public String getReferer() {
            return referer;
        }
    }

    public static class RefererInfo extends StatisticsInfo {
        private final String referer;

        public RefererInfo(String userId, String referer, long id) {
            super(userId, id);
            this.referer = referer;
        }

        public String getReferer() {
            return referer;
        }
    }

    public static class BrowserInfo extends StatisticsInfo {
        private final String userAgent;

        public BrowserInfo(String userId, String userAgent, long id) {
            super(userId, id);
            this.userAgent = userAgent;
        }

        public String getUserAgent() {
            return userAgent;
        }
    }

    public static class SearchInfo extends StatisticsInfo {
        private final String queryString;
        private final int resultCount;
        private final long duration;

        public SearchInfo(String userId, String queryString, int resultCount, long duration, long id) {
            super(userId, id);
            this.queryString = queryString;
            this.resultCount = resultCount;
            this.duration = duration;
        }

        public String getQueryString() {
            return queryString;
        }

        public int getResultCount() {
            return resultCount;
        }

        public long getDuration() {
            return duration;
        }
    }

    public static class ResponseTimeInfo extends StatisticsInfo {
        private final String path;
        private final long responseTime; // in milliseconds

        public ResponseTimeInfo(String userId, String path, long responseTime, long id) {
            super(userId, id);
            this.path = path;
            this.responseTime = responseTime > 0 ? responseTime : 1L;
        }

        public String getPath() {
            return path;
        }

        public long getResponseTime() {
            return responseTime;
        }
    }

    private static final String ANONYMOUS = "anonymous"; //$NON-NLS-1$

    // instance startup timestamp
    private final long startupTime;

    // timestamp of the oldest/newest tracked info entry
    private long startDate;
    private long endDate;

    // sequence number to be assigned to the next tracked info entry
    private long sequenceNumber;

    private SortedSet<UserInfo> users = new ConcurrentSkipListSet<UserInfo>();
    private SortedSet<UsageInfo> usages = new ConcurrentSkipListSet<UsageInfo>();
    private SortedSet<RefererInfo> referers = new ConcurrentSkipListSet<RefererInfo>();
    private SortedSet<BrowserInfo> browsers = new ConcurrentSkipListSet<BrowserInfo>();
    private SortedSet<SearchInfo> searches = new ConcurrentSkipListSet<SearchInfo>();
    private SortedSet<ResponseTimeInfo> responseTimes = new ConcurrentSkipListSet<ResponseTimeInfo>();

    private static Statistics instance = new Statistics();

    /**
     * Creates an empty statistics resource.
     * Initializes the {@link #getStartupTime() startup time} to be the current time.
     */
    public Statistics() {
        this(System.currentTimeMillis(), 0, 0, 0);
    }

    /**
     * Creates a statistics resource from a given statistics resource.
     * Copies the {@link #getStartupTime() startup time} from the given
     * statistics resource.
     *
     * @param statistics  the statistics resource to copy from.
     * @param startDate  begin of the time interval to copy
     *                   in milliseconds since January 1, 1970, 00:00:00 GMT.
     * @param endDate  end of the time interval to copy
     *                 in milliseconds since January 1, 1970, 00:00:00 GMT.
     */
    public Statistics(Statistics statistics, long startDate, long endDate) {
        this(statistics.getStartupTime(), 0, 0, 0);
        copy(statistics, startDate, endDate);
        updateAttributes();
    }

    // package protected for testing purposes
    Statistics(long startupTime, long startDate, long endDate, long sequenceNumber) {
        this.startupTime = startupTime;
        this.startDate = startDate;
        this.endDate = endDate;
        this.sequenceNumber = sequenceNumber;
    }

    /**
     * Returns the default statistics resource of this Skalli instance.
     */
    public static Statistics getDefault() {
        return instance;
    }

    /**
     * Returns the startup time of the current Skalli instance,
     * i.e. the begin of the statistics recording.
     *
     * @return the startup time in milliseconds since January 1, 1970, 00:00:00 GMT.
     */
    public long getStartupTime() {
        return startupTime;
    }

    /**
     * Returns the timestamp of the earliest data entry in this statistics resource,
     * or the instance startup time, if no statistics information has been tracked yet.
     *
     * @return the timestamp of the earliest data entry in
     * milliseconds since January 1, 1970, 00:00:00 GMT
     */
    public synchronized long getStartDate() {
        return startDate > 0 ? startDate : startupTime;
    }

    /**
     * Returns the timestamp of the latest data entry in this statistics resource,
     * or the current time, if no statistics information has been tracked yet.
     *
     * @return the timestamp of the latest data entry in
     * milliseconds since January 1, 1970, 00:00:00 GMT
     */
    public synchronized long getEndDate() {
        return endDate > 0 ? endDate : System.currentTimeMillis();
    }

    /**
     * Tracks the path and <code>Referer</code> header of a request.
     *
     * @param userId  the user that issued the request, or <code>null</code>,
     * which is interpreted as the Anonymous user.
     * @param path  the resource path that has been requested.
     * @param referer  the <code>Referer</code> header specified in the request,
     * or <code>null</code>.
     */
    public synchronized void trackUsage(String userId, String path, String referer) {
        UsageInfo entry = new UsageInfo(userId, path, referer, sequenceNumber++);
        usages.add(entry);
        endDate = entry.getTimestamp();
        if (startDate == 0) {
            startDate = endDate;
        }
    }

    /**
     * Tracks the user of a request.
     *
     * @param userId  the user that issued the request, or <code>null</code>,
     * which is interpreted as the Anonymous user.
     * @param department  the department the user belongs to, or <code>null</code>.
     * @param location  the location of the user, or <code>null</code>.
     */
    public synchronized void trackUser(String userId, String department, String location) {
        UserInfo entry = new UserInfo(userId, department, location, sequenceNumber++);
        users.add(entry);
        endDate = entry.getTimestamp();
        if (startDate == 0) {
            startDate = endDate;
        }
    }

    /**
     * Tracks the <code>User-Agent</code> header of a request.
     *
     * @param userId  the user that issued the request, or <code>null</code>,
     * which is interpreted as the Anonymous user.
     * @param userAgent  the <code>User-Agent</code> header specified in the request.
     */
    public synchronized void trackBrowser(String userId, String userAgent) {
        BrowserInfo entry = new BrowserInfo(userId, userAgent, sequenceNumber++);
        browsers.add(entry);
        endDate = entry.getTimestamp();
        if (startDate == 0) {
            startDate = endDate;
        }
    }

    /**
     * Tracks a search request.
     *
     * @param userId  the user that performed the search, or <code>null</code>,
     * which is interpreted as the Anonymous user.
     * @param queryString  the query string entered by the user.
     * @param resultCount  the number of search results.
     * @param duration  the amount of time it took to calculate the search
     * result in milliseconds.
     */
    public synchronized void trackSearch(String userId, String queryString, int resultCount, long duration) {
        SearchInfo entry = new SearchInfo(userId, queryString, resultCount, duration, sequenceNumber++);
        searches.add(entry);
        endDate = entry.getTimestamp();
        if (startDate == 0) {
            startDate = endDate;
        }
    }

    /**
     * Tracks the <code>Referer</code> header of a request.
     *
     * @param userId  the user that issued the request, or <code>null</code>,
     * which is interpreted as the Anonymous user.
     * @param referer  the <code>Referer</code> header specified in the request.
     */
    public synchronized void trackReferer(String userId, String referer) {
        RefererInfo entry = new RefererInfo(userId, referer, sequenceNumber++);
        referers.add(entry);
        endDate = entry.getTimestamp();
        if (startDate == 0) {
            startDate = endDate;
        }
    }

    /**
     * Tracks the response time of a request.
     *
     * @param userId  the user that issued the request, or <code>null</code>,
     * which is interpreted as the Anonymous user.
     * @param path
     * @param responseTime
     */
    public synchronized void trackResponseTime(String userId, String path, long responseTime) {
        ResponseTimeInfo entry = new ResponseTimeInfo(userId, path, responseTime, sequenceNumber++);
        responseTimes.add(entry);
        endDate = entry.getTimestamp();
        if (startDate == 0) {
            startDate = endDate;
        }
    }

    /**
     * Removes all data entries from this statistics resource and resets the
     * {@link #getStartDate() start} and {@link #getEndDate() end} date
     * parameters. The {@link #getStartupTime() startup} timestamp
     * is not touched.
     */
    public synchronized void clear() {
        users.clear();
        usages.clear();
        referers.clear();
        browsers.clear();
        searches.clear();
        responseTimes.clear();
        startDate = 0;
        endDate = 0;
        sequenceNumber = 0;
    }

    /**
     * Removes all data entries from this statistics resource with a timestamp
     * within the given range.
     *
     * @param startDate  begin of the time intervall to remove
     *                   in milliseconds since January 1, 1970, 00:00:00 GMT.
     * @param endDate    end of the time intervall to remove
     *                   in milliseconds since January 1, 1970, 00:00:00 GMT.
     */
    public synchronized void clear(long startDate, long endDate) {
        clear(usages, startDate, endDate);
        clear(users, startDate, endDate);
        clear(browsers, startDate, endDate);
        clear(searches, startDate, endDate);
        clear(referers, startDate, endDate);
        clear(responseTimes, startDate, endDate);
        updateAttributes();
    }

    /**
     * Restores the content of this statistics resource from a given statistics resource, e.g. a backup,
     * but retains the {@link #getStartupTime() startup time} of this statistics resource.
     * Removes all previously stored data entries from this statistics resource and copies all data
     * entries from the backup resource.
     *
     * @param statistics  the statistics backup from which to restore this
     * statistics resource. If the argument is <code>null</code> the method
     * does nothing.
     */
    public synchronized void restore(Statistics statistics) {
        if (statistics != null) {
            clear();
            copy(statistics, statistics.getStartDate(), statistics.getEndDate());
            updateAttributes();
        }
    }

    public Map<String, Long> getUserCount(long startDate, long endDate) {
        Map<String, Long> result = new HashMap<String, Long>();
        for (UserInfo userInfo : users) {
            if (userInfo.inRange(startDate, endDate)) {
                Long count = result.get(userInfo.getUserHash());
                if (count == null) {
                    count = 0L;
                }
                ++count;
                result.put(userInfo.getUserHash(), count);
            }
        }
        return sortedByFrequencyDescending(result);
    }

    public SortedSet<UserInfo> getUserInfo() {
        return Collections.unmodifiableSortedSet(users);
    }

    public Map<String, Long> getDepartmentCount(long startDate, long endDate) {
        Map<String, Long> result = new TreeMap<String, Long>();
        for (UserInfo userInfo : users) {
            String department = userInfo.getDepartment();
            if (userInfo.inRange(startDate, endDate) && StringUtils.isNotBlank(department)) {
                Long count = result.get(department);
                if (count == null) {
                    count = 0L;
                }
                ++count;
                result.put(department, count);
            }
        }
        return sortedByFrequencyDescending(result);
    }

    public Map<String, Long> getLocationCount(long startDate, long endDate) {
        Map<String, Long> result = new TreeMap<String, Long>();
        for (UserInfo userInfo : users) {
            String location = userInfo.getLocation();
            if (userInfo.inRange(startDate, endDate) && StringUtils.isNotBlank(location)) {
                Long count = result.get(location);
                if (count == null) {
                    count = 0L;
                }
                ++count;
                result.put(location, count);
            }
        }
        return sortedByFrequencyDescending(result);
    }

    public Map<String, Long> getBrowserCount(long startDate, long endDate) {
        Map<String, Long> result = new TreeMap<String, Long>();
        for (BrowserInfo browserInfo : browsers) {
            if (browserInfo.inRange(startDate, endDate)) {
                Long count = result.get(browserInfo.getUserAgent());
                if (count == null) {
                    count = 0L;
                }
                ++count;
                result.put(browserInfo.getUserAgent(), count);
            }
        }
        return sortedByFrequencyDescending(result);
    }

    public SortedSet<BrowserInfo> getBrowserInfo() {
        return Collections.unmodifiableSortedSet(browsers);
    }

    public Map<String, Long> getRefererCount(long startDate, long endDate) {
        Map<String, Long> result = new TreeMap<String, Long>();
        for (RefererInfo refererInfo : referers) {
            if (refererInfo.inRange(startDate, endDate)) {
                Long count = result.get(refererInfo.getReferer());
                if (count == null) {
                    count = 0L;
                }
                ++count;
                result.put(refererInfo.getReferer(), count);
            }
        }
        return sortedByFrequencyDescending(result);
    }

    public SortedSet<RefererInfo> getRefererInfo() {
        return Collections.unmodifiableSortedSet(referers);
    }

    public Map<String, Long> getUsageCount(long startDate, long endDate) {
        Map<String, Long> result = new TreeMap<String, Long>();
        for (UsageInfo usageInfo : usages) {
            if (usageInfo.inRange(startDate, endDate)) {
                Long count = result.get(usageInfo.getPath());
                if (count == null) {
                    count = 0L;
                }
                ++count;
                result.put(usageInfo.getPath(), count);
            }
        }
        return sortedByFrequencyDescending(result);
    }

    public SortedSet<UsageInfo> getUsageInfo() {
        return Collections.unmodifiableSortedSet(usages);
    }

    public Map<String, Long> getSearchCount(long startDate, long endDate) {
        Map<String, Long> result = new TreeMap<String, Long>();
        for (SearchInfo searchInfo : searches) {
            if (searchInfo.inRange(startDate, endDate)) {
                Long count = result.get(searchInfo.getQueryString());
                if (count == null) {
                    count = 0L;
                }
                ++count;
                result.put(searchInfo.getQueryString(), count);
            }
        }
        return sortedByFrequencyDescending(result);
    }

    public SortedSet<SearchInfo> getSearchInfo() {
        return Collections.unmodifiableSortedSet(searches);
    }

    public Map<String, Long> getAverageResponseTimes(long startDate, long endDate) {
        Map<String, Long> result = new TreeMap<String, Long>();
        Map<String, Integer> counts = new TreeMap<String, Integer>();

        for (ResponseTimeInfo responseTimeInfo : responseTimes) {
            if (responseTimeInfo.inRange(startDate, endDate)) {
                String path = responseTimeInfo.getPath();

                Long sumResponseTime = result.get(path);
                if (sumResponseTime == null) {
                    sumResponseTime = 0L;
                }
                sumResponseTime += responseTimeInfo.getResponseTime();
                result.put(path, sumResponseTime);

                Integer count = counts.get(path);
                if (count == null) {
                    count = 0;
                }
                ++count;
                counts.put(path, count);
            }
        }
        for (Entry<String, Long> entry : result.entrySet()) {
            Integer count = counts.get(entry.getKey());
            entry.setValue(entry.getValue() / count);
        }
        return sortedByFrequencyDescending(result);
    }

    public SortedSet<ResponseTimeInfo> getResponseTimeInfo() {
        return Collections.unmodifiableSortedSet(responseTimes);
    }

    public Map<String, SortedSet<StatisticsInfo>> getUsageTracks(long startDate, long endDate) {
        Map<String, SortedSet<StatisticsInfo>> tracks = new TreeMap<String, SortedSet<StatisticsInfo>>();
        addAll(tracks, usages, startDate, endDate);
        addAll(tracks, searches, startDate, endDate);
        return tracks;
    }

    long getSequenceNumber() {
        return sequenceNumber;
    }

    void addAll(Map<String, SortedSet<StatisticsInfo>> tracks, SortedSet<? extends StatisticsInfo> entries,
            long startDate, long endDate) {
        for (StatisticsInfo info : entries) {
            if (info.inRange(startDate, endDate)) {
                String userHash = info.getUserHash();
                SortedSet<StatisticsInfo> track = tracks.get(userHash);
                if (track == null) {
                    track = new TreeSet<StatisticsInfo>();
                    tracks.put(userHash, track);
                }
                track.add(info);
            }
        }

    }

    void clear(SortedSet<? extends StatisticsInfo> entries, long startDate, long endDate) {
        Iterator<? extends StatisticsInfo> it = entries.iterator();
        while (it.hasNext()) {
            StatisticsInfo next = it.next();
            if (next.inRange(startDate, endDate)) {
                it.remove();
            }
        }
    }

    /**
     * Copies all statistics entries from the given <code>Statistics</code>.
     */
    void copy(Statistics statistics, long startDate, long endDate) {
        copy(statistics.getUserInfo(), users, startDate, endDate);
        copy(statistics.getUsageInfo(), usages, startDate, endDate);
        copy(statistics.getRefererInfo(), referers, startDate, endDate);
        copy(statistics.getBrowserInfo(), browsers, startDate, endDate);
        copy(statistics.getSearchInfo(), searches, startDate, endDate);
        copy(statistics.getResponseTimeInfo(), responseTimes, startDate, endDate);
    }

    <T extends StatisticsInfo> void copy(SortedSet<T> source, SortedSet<T> target, long startDate, long endDate) {
        for (T next : source) {
            if (next.inRange(startDate, endDate)) {
                target.add(next);
            }
        }
    }

    /**
     * Recalculates <code>sequenceNumber</code>, <code>startDate</code>
     * and <code>endDate</code> from the entries in this dataset.
     */
    void updateAttributes() {
        sequenceNumber = 0;
        startDate = 0;
        endDate = 0;
        updateAttributes(users);
        updateAttributes(usages);
        updateAttributes(referers);
        updateAttributes(browsers);
        updateAttributes(searches);
        updateAttributes(responseTimes);
    }

    <T extends StatisticsInfo> void updateAttributes(SortedSet<T> source) {
        for (T next : source) {
            sequenceNumber = Math.max(sequenceNumber, next.getSequenceNumber() + 1);
            startDate = startDate > 0 ? Math.min(startDate, next.getTimestamp()) : next.getTimestamp();
            endDate = endDate > 0 ? Math.max(endDate, next.getTimestamp()) : next.getTimestamp();
        }
    }

    Map<String, Long> sortedByFrequencyDescending(Map<String, Long> map) {
        if (map == null || map.size() <= 1) {
            return map;
        }
        List<Entry<String, Long>> list = new ArrayList<Entry<String, Long>>(map.entrySet());
        Collections.sort(list, new Comparator<Entry<String, Long>>() {
            @Override
            public int compare(Entry<String, Long> o1, Entry<String, Long> o2) {
                int result = -Integer.signum(o1.getValue().compareTo(o2.getValue()));
                if (result == 0) {
                    result = o1.getKey().compareTo(o2.getKey());
                }
                return result;
            }
        });
        LinkedHashMap<String, Long> sortedMap = new LinkedHashMap<String, Long>();
        for (Entry<String, Long> entry : list) {
            sortedMap.put(entry.getKey(), entry.getValue());
        }
        return sortedMap;
    }
}