psiprobe.model.stats.StatsCollection.java Source code

Java tutorial

Introduction

Here is the source code for psiprobe.model.stats.StatsCollection.java

Source

/**
 * Licensed under the GPL License. You may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 *
 * THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
 * WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE.
 */
package psiprobe.model.stats;

import com.thoughtworks.xstream.XStream;

import org.jfree.data.xy.XYDataItem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.web.context.WebApplicationContext;

import psiprobe.tools.UpdateCommitLock;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
 * The Class StatsCollection.
 *
 * @author Vlad Ilyushchenko
 * @author Andy Shapoval
 * @author Mark Lewis
 */
public class StatsCollection implements InitializingBean, DisposableBean, ApplicationContextAware {

    /** The logger. */
    private static final Logger logger = LoggerFactory.getLogger(StatsCollection.class);

    /** The stats data. */
    private Map<String, List<XYDataItem>> statsData = new TreeMap<>();

    /** The swap file name. */
    private String swapFileName;

    /** The storage path. */
    private String storagePath;

    /** The context temp dir. */
    private File contextTempDir;

    /** The max files. */
    private int maxFiles = 2;

    /** The lock. */
    private final UpdateCommitLock lock = new UpdateCommitLock();

    /**
     * Gets the swap file name.
     *
     * @return the swap file name
     */
    public String getSwapFileName() {
        return swapFileName;
    }

    /**
     * Sets the swap file name.
     *
     * @param swapFileName the new swap file name
     */
    public void setSwapFileName(String swapFileName) {
        this.swapFileName = swapFileName;
    }

    /**
     * Gets the storage path.
     *
     * @return the storage path
     */
    public String getStoragePath() {
        return storagePath;
    }

    /**
     * Sets the storage path.
     *
     * @param storagePath the new storage path
     */
    public void setStoragePath(String storagePath) {
        this.storagePath = storagePath;
    }

    /**
     * Checks if is collected.
     *
     * @param statsName the stats name
     * @return true, if is collected
     */
    public boolean isCollected(String statsName) {
        return statsData.get(statsName) != null;
    }

    /**
     * Gets the max files.
     *
     * @return the max files
     */
    public int getMaxFiles() {
        return maxFiles;
    }

    /**
     * Sets the max files.
     *
     * @param maxFiles the new max files
     */
    public void setMaxFiles(int maxFiles) {
        this.maxFiles = maxFiles > 0 ? maxFiles : 2;
    }

    /**
     * New stats.
     *
     * @param name the name
     * @param maxElements the max elements
     * @return the list
     */
    public synchronized List<XYDataItem> newStats(String name, int maxElements) {
        List<XYDataItem> stats = Collections.synchronizedList(new ArrayList<XYDataItem>(maxElements));
        statsData.put(name, stats);
        return stats;
    }

    /**
     * Reset stats.
     *
     * @param name the name
     */
    public synchronized void resetStats(String name) {
        List<XYDataItem> stats = getStats(name);
        if (stats != null) {
            stats.clear();
        }
    }

    /**
     * Gets the stats.
     *
     * @param name the name
     * @return the stats
     */
    public synchronized List<XYDataItem> getStats(String name) {
        return statsData.get(name);
    }

    /**
     * Gets the last value for stat.
     *
     * @param statName the stat name
     * @return the last value for stat
     */
    public long getLastValueForStat(String statName) {
        long statValue = 0;

        List<XYDataItem> stats = getStats(statName);
        if (stats != null && !stats.isEmpty()) {
            XYDataItem xy = stats.get(stats.size() - 1);
            if (xy != null && xy.getY() != null) {
                statValue = xy.getY().longValue();
            }
        }

        return statValue;
    }

    /**
     * Returns series if stat name starts with the prefix.
     * 
     * @param statNamePrefix they key under which the stats are stored
     * @return a Map of matching stats. Map keys are stat names and map values are corresponding
     *         series.
     */
    public synchronized Map<String, List<XYDataItem>> getStatsByPrefix(String statNamePrefix) {
        Map<String, List<XYDataItem>> map = new HashMap<>();
        for (Map.Entry<String, List<XYDataItem>> en : statsData.entrySet()) {
            if (en.getKey().startsWith(statNamePrefix)) {
                map.put(en.getKey(), en.getValue());
            }
        }
        return map;
    }

    /**
     * Make file.
     *
     * @return the file
     */
    private File makeFile() {
        return storagePath == null ? new File(contextTempDir, swapFileName) : new File(storagePath, swapFileName);
    }

    /**
     * Shift files.
     *
     * @param index the index
     */
    private void shiftFiles(int index) {
        if (index >= maxFiles - 1) {
            new File(makeFile().getAbsolutePath() + "." + index).delete();
        } else {
            shiftFiles(index + 1);
            File srcFile = index == 0 ? makeFile() : new File(makeFile().getAbsolutePath() + "." + index);
            File destFile = new File(makeFile().getAbsolutePath() + "." + (index + 1));
            srcFile.renameTo(destFile);
        }
    }

    /**
     * Writes stats data to file on disk.
     *
     * @throws InterruptedException if a lock cannot be obtained
     */
    public synchronized void serialize() throws InterruptedException {
        lock.lockForCommit();
        long start = System.currentTimeMillis();
        try {
            shiftFiles(0);
            try (OutputStream os = new FileOutputStream(makeFile())) {
                new XStream().toXML(statsData, os);
            }
        } catch (Exception e) {
            logger.error("Could not write stats data to '{}'", makeFile().getAbsolutePath(), e);
        } finally {
            lock.releaseCommitLock();
            logger.debug("stats serialized in {}ms", (System.currentTimeMillis() - start));
        }
    }

    /**
     * Deserialize.
     *
     * @param file the file
     * @return the map
     */
    private Map<String, List<XYDataItem>> deserialize(File file) {
        Map<String, List<XYDataItem>> stats = null;
        if (file.exists() && file.canRead()) {
            long start = System.currentTimeMillis();
            try {
                try (FileInputStream fis = new FileInputStream(file)) {
                    stats = (Map<String, List<XYDataItem>>) (new XStream().fromXML(fis));

                    if (stats != null) {
                        // adjust stats data so that charts look realistic.
                        // we do that by ending the previous stats group with 0 value
                        // and starting the current stats group also with 0
                        // thus giving the chart nice plunge to zero indicating downtime
                        // and lets not bother about rotating stats;
                        // regular stats collection cycle will do it

                        for (String key : stats.keySet()) {
                            List<XYDataItem> list = stats.get(key);
                            if (!list.isEmpty()) {
                                XYDataItem xy = list.get(list.size() - 1);
                                list.add(new XYDataItem(xy.getX().longValue() + 1, 0));
                                list.add(new XYDataItem(System.currentTimeMillis(), 0));
                            }
                        }
                    }
                }
                logger.debug("stats data read in {}ms", (System.currentTimeMillis() - start));
            } catch (Exception e) {
                logger.error("Could not read stats data from '{}'", file.getAbsolutePath(), e);
            }
        }

        return stats;
    }

    /**
     * Lock for update.
     *
     * @throws InterruptedException the interrupted exception
     */
    public void lockForUpdate() throws InterruptedException {
        lock.lockForUpdate();
    }

    /**
     * Release lock.
     */
    public void releaseLock() {
        lock.releaseUpdateLock();
    }

    /**
     * Reads stats data from file on disk.
     */
    @Override
    public synchronized void afterPropertiesSet() {
        int index = 0;
        Map<String, List<XYDataItem>> stats;

        while (true) {
            File file = index == 0 ? makeFile() : new File(makeFile().getAbsolutePath() + "." + index);
            stats = deserialize(file);
            index += 1;
            if (stats != null || index >= maxFiles - 1) {
                break;
            }
        }

        if (stats != null) {
            statsData = stats;
        } else {
            logger.debug("Stats data file not found. Empty file assumed.");
        }

    }

    @Override
    public void destroy() throws Exception {
        serialize();
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        WebApplicationContext wac = (WebApplicationContext) applicationContext;
        contextTempDir = (File) wac.getServletContext().getAttribute("javax.servlet.context.tempdir");
    }

}