/* * Copyright 2004 - 2011 Christian Sprajc, Dennis Waldherr. All rights reserved. * * This file is part of PowerFolder. * * PowerFolder is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation. * * PowerFolder is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with PowerFolder. If not, see <http://www.gnu.org/licenses/>. * * $Id: BandwidthStatsRecorder.java 7042 2011-01-24 01:17:24Z harry $ */ package de.dal33t.powerfolder.transfer; import de.dal33t.powerfolder.util.DateUtil; import de.dal33t.powerfolder.util.Reject; import de.dal33t.powerfolder.Controller; import de.dal33t.powerfolder.PFComponent; import java.util.*; import java.io.*; /** * Class to allow the transfer manager to record bandwidth stats. */ public class BandwidthStatsRecorder extends PFComponent implements BandwidthStatsListener { /** * Map of stats by info-hour. */ private final Map<StatKey, StatValue> coalescedStats = new HashMap<StatKey, StatValue>(); /** * Constructor. * * @param controller */ public BandwidthStatsRecorder(Controller controller) { super(controller); loadStats(); } /** * Load stats from file. */ @SuppressWarnings("unchecked") private void loadStats() { String filename = getController().getConfigName() + ".stats"; File file = new File(Controller.getMiscFilesLocation(), filename); if (file.exists()) { logFiner("Loading stats"); ObjectInputStream inputStream = null; try { inputStream = new ObjectInputStream( new BufferedInputStream(new FileInputStream(file))); Map<StatKey, StatValue> stats = (Map<StatKey, StatValue>) inputStream .readObject(); coalescedStats.putAll(stats); inputStream.close(); logFine("Loaded " + stats.size() + " stats."); } catch (FileNotFoundException e) { logSevere("FileNotFoundException", e); } catch (IOException e) { logSevere("IOException", e); } catch (ClassNotFoundException e) { logSevere("ClassNotFoundException", e); } catch (ClassCastException e) { logSevere("ClassCastException", e); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { logSevere("IOException", e); } } } } else { logFine("No stats found - probably first start of PF."); } } public void handleBandwidthStat(BandwidthStat stat) { StatKey key = new StatKey(stat.getInfo(), stat.getDate()); // Synchronize on the map so that we do not get concurrent updates to // stats. synchronized (coalescedStats) { StatValue value = coalescedStats.get(key); // Create a new entry if required. if (value == null) { value = new StatValue(); coalescedStats.put(key, value); } // Update the stat data. value.update(stat.getInitialBandwidth(), stat.getResidualBandwidth()); } } /** * Prune stats older than date. */ public void pruneStats(Date date) { int prunedCount = 0; synchronized (coalescedStats) { for (Iterator<StatKey> iterator = coalescedStats.keySet().iterator(); iterator.hasNext();) { StatKey statKey = iterator.next(); if (statKey.date.before(date)) { iterator.remove(); prunedCount++; } } } logFiner("Pruned " + prunedCount + " stats."); } /** * Returns the coalesced stats. * * @return */ public Set<CoalescedBandwidthStat> getBandwidthStats() { synchronized (coalescedStats) { Set<CoalescedBandwidthStat> stats = new TreeSet<CoalescedBandwidthStat>(); for (Map.Entry<StatKey, StatValue> entry : coalescedStats.entrySet()) { CoalescedBandwidthStat stat = new CoalescedBandwidthStat(entry.getKey().getDate(), entry.getKey().getInfo(), entry.getValue().getInitial(), entry.getValue().getResidual(), entry.getValue().getPeak(), entry.getValue().getCount()); stats.add(stat); } return stats; } } public boolean fireInEventDispatchThread() { return false; } public void persistStats() { synchronized (coalescedStats) { String filename = getController().getConfigName() + ".stats"; File file = new File(Controller.getMiscFilesLocation(), filename); ObjectOutputStream outputStream = null; try { logInfo("There are " + coalescedStats.size() + " stats to persist."); outputStream = new ObjectOutputStream(new BufferedOutputStream( new FileOutputStream(file))); outputStream.writeUnshared(coalescedStats); } catch (FileNotFoundException e) { logSevere("FileNotFoundException", e); } catch (IOException e) { logSevere("IOException", e); } finally { if (outputStream != null) { try { outputStream.close(); } catch (IOException e) { logSevere("IOException", e); } } } } } // //////////////// // Inner classes // // //////////////// /** * Inner class to act as a stat-date key limiter by hour. * Note that the date constructor arg is truncated to the nearest hour. */ private static class StatKey implements Serializable { private static final long serialVersionUID = 1L; private final BandwidthLimiterInfo info; private final Date date; private StatKey(BandwidthLimiterInfo info, Date date) { Reject.ifNull(info, "Info is null"); Reject.ifNull(date, "Date is null"); this.info = info; this.date = DateUtil.truncateToHour(date); } public BandwidthLimiterInfo getInfo() { return info; } public Date getDate() { return date; } public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } StatKey that = (StatKey) obj; if (!date.equals(that.date)) { return false; } if (info != that.info) { return false; } return true; } public int hashCode() { int result = info.hashCode(); result = 31 * result + date.hashCode(); return result; } public String toString() { return "StatKey{" + "info=" + info + ", date=" + date + '}'; } } /** * Inner class to hold cumulative stat details. */ private static class StatValue implements Serializable { private static final long serialVersionUID = 1L; private long initial; private long residual; private long peak; private long count; public long getInitial() { return initial; } public long getResidual() { return residual; } public long getPeak() { return peak; } public long getCount() { return count; } public void update(long initialValue, long residualValue) { // Check > 0 to fix for the initial UNLIMITED (-1) stat values. if (initialValue >= 0) { initial += initialValue; } if (residualValue >= 0) { residual += residualValue; } if (initialValue >= 0 && residualValue >= 0) { peak = Math.max(peak, initialValue - residualValue); } count++; } public String toString() { return "StatValue{" + "initial=" + initial + ", residual=" + residual + ", peak=" + peak + ", count=" + count + '}'; } } }