/******************************************************************************* * This file is part of OpenNMS(R). * * Copyright (C) 2006-2011 The OpenNMS Group, Inc. * OpenNMS(R) is Copyright (C) 1999-2011 The OpenNMS Group, Inc. * * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc. * * OpenNMS(R) 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, either version 3 of the License, * or (at your option) any later version. * * OpenNMS(R) 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 OpenNMS(R). If not, see: * http://www.gnu.org/licenses/ * * For more information contact: * OpenNMS(R) Licensing <license@opennms.org> * http://www.opennms.org/ * http://www.opennms.com/ *******************************************************************************/ package org.opennms.netmgt.rrd; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import org.apache.log4j.Logger; /** * Provides queuing implementation of RrdStrategy. * * In order to provide a more scalable collector. We created a queuing * RrdStrategy that enabled the system to amortize the high cost of opening an * round robin database across multiple updates. * * This RrdStrategy implementation enqueues the create and update operations on * a per file basis and maintains a set of threads that process enqueued work * file by file. * * If the I/O system can keep up with the collection threads while performing * only a single update per file then eventually all the data is processed and * the threads sleep until there is more work to do. * * If the I/O system is initially slower than than the collection threads then * work will enqueue here and the write threads will get behind. As this happens * each file will eventually have more than a single update enqueued and * therefore the number of updates pushed thru the system will increase because * more then one will be output per 'open' Eventually, the I/O system and the * collection system will balance out. When this happens all data will be * collected but will not be output to the rrd files until the next time the * file is processed by the write threads. * * As another performance improving strategy. The queue distinguishes between * files with significant vs insignificant updates. Files with only insignificant * updates are put at the lowest priority and are only written when the highest * priority updates have been written * * This implementation delegates all the actual writing to another RrdStrategy * implementation. * * System properties effecting the operation: * * org.opennms.rrd.queuing.writethreads: (default 2) The number of rrd write * threads that process the queue * * org.opennms.rrd.queuing.queueCreates: (default false) indicates whether rrd * file creates should be queued or processed synchronously * * org.opennms.rrd.queuing.maxInsigUpdateSeconds: (default 0) the number of * seconds over which all files with significant updates only should be promoted * onto the significant less. This is to ensure they don't stay unprocessed * forever. Zero means not promotion. * * org.opennms.rrd.queuing.modulus: (default 10000) the number of updates the * get enqueued between statistics output * * org.opennms.rrd.queuing.category: (default "OpenNMS.Queued") the log category * to place the statistics output in * * * * TODO: Promote files when ZeroUpdate operations can't be merged. This may be a * collection miss which we want to push thru. It should also help with memory. * * TODO: Set an upper bound on enqueued operations * * TODO: Provide an event that will write data for a particular file... Say * right before we try to graph it. * * @author ranger * @version $Id: $ */ public class QueuingRrdStrategy implements RrdStrategy<QueuingRrdStrategy.Operation,String>, Runnable { private Properties m_configurationProperties; /** * <p>getConfigurationProperties</p> * * @return a {@link java.util.Properties} object. */ public Properties getConfigurationProperties() { return m_configurationProperties; } /** {@inheritDoc} */ public void setConfigurationProperties(Properties configurationParameters) { this.m_configurationProperties = configurationParameters; } RrdStrategy<Object, Object> m_delegate; static final int UPDATE = 0; static final int CREATE = 1; private int m_writeThreads; private boolean m_queueCreates; private boolean m_prioritizeSignificantUpdates; private long m_inSigHighWaterMark; private long m_sigHighWaterMark; private long m_queueHighWaterMark; private long m_modulus; private String m_category; private long m_maxInsigUpdateSeconds; private long m_writeThreadSleepTime; private long m_writeThreadExitDelay; /** * <p>getWriteThreads</p> * * @return a int. */ public int getWriteThreads() { return m_writeThreads; } /** * <p>setWriteThreads</p> * * @param writeThreads a int. */ public void setWriteThreads(int writeThreads) { m_writeThreads = writeThreads; } /** * <p>queueCreates</p> * * @return a boolean. */ public boolean queueCreates() { return m_queueCreates; } /** * <p>setQueueCreates</p> * * @param queueCreates a boolean. */ public void setQueueCreates(boolean queueCreates) { m_queueCreates = queueCreates; } /** * <p>prioritizeSignificantUpdates</p> * * @return a boolean. */ public boolean prioritizeSignificantUpdates() { return m_prioritizeSignificantUpdates; } /** * <p>setPrioritizeSignificantUpdates</p> * * @param prioritizeSignificantUpdates a boolean. */ public void setPrioritizeSignificantUpdates( boolean prioritizeSignificantUpdates) { m_prioritizeSignificantUpdates = prioritizeSignificantUpdates; } /** * <p>getInSigHighWaterMark</p> * * @return a long. */ public long getInSigHighWaterMark() { return m_inSigHighWaterMark; } /** * <p>setInSigHighWaterMark</p> * * @param inSigHighWaterMark a long. */ public void setInSigHighWaterMark(long inSigHighWaterMark) { m_inSigHighWaterMark = inSigHighWaterMark; } /** * <p>getSigHighWaterMark</p> * * @return a long. */ public long getSigHighWaterMark() { return m_sigHighWaterMark; } /** * <p>setSigHighWaterMark</p> * * @param sigHighWaterMark a long. */ public void setSigHighWaterMark(long sigHighWaterMark) { m_sigHighWaterMark = sigHighWaterMark; } /** * <p>getQueueHighWaterMark</p> * * @return a long. */ public long getQueueHighWaterMark() { return m_queueHighWaterMark; } /** * <p>setQueueHighWaterMark</p> * * @param queueHighWaterMark a long. */ public void setQueueHighWaterMark(long queueHighWaterMark) { m_queueHighWaterMark = queueHighWaterMark; } /** * <p>getModulus</p> * * @return a long. */ public long getModulus() { return m_modulus; } /** * <p>setModulus</p> * * @param modulus a long. */ public void setModulus(long modulus) { m_modulus = modulus; } /** * <p>getCategory</p> * * @return a {@link java.lang.String} object. */ public String getCategory() { return m_category; } /** * <p>setCategory</p> * * @param category a {@link java.lang.String} object. */ public void setCategory(String category) { m_category = category; } /** * <p>getMaxInsigUpdateSeconds</p> * * @return a long. */ public long getMaxInsigUpdateSeconds() { return m_maxInsigUpdateSeconds; } /** * <p>setMaxInsigUpdateSeconds</p> * * @param maxInsigUpdateSeconds a long. */ public void setMaxInsigUpdateSeconds(long maxInsigUpdateSeconds) { m_maxInsigUpdateSeconds = maxInsigUpdateSeconds; } /** * <p>getWriteThreadSleepTime</p> * * @return a long. */ public long getWriteThreadSleepTime() { return m_writeThreadSleepTime; } /** * <p>setWriteThreadSleepTime</p> * * @param writeThreadSleepTime a long. */ public void setWriteThreadSleepTime(long writeThreadSleepTime) { m_writeThreadSleepTime = writeThreadSleepTime; } /** * <p>getWriteThreadExitDelay</p> * * @return a long. */ public long getWriteThreadExitDelay() { return m_writeThreadExitDelay; } /** * <p>setWriteThreadExitDelay</p> * * @param writeThreadExitDelay a long. */ public void setWriteThreadExitDelay(long writeThreadExitDelay) { m_writeThreadExitDelay = writeThreadExitDelay; } LinkedList<String> filesWithSignificantWork = new LinkedList<String>(); LinkedList<String> filesWithInsignificantWork = new LinkedList<String>(); Map<String, LinkedList<Operation>> pendingFileOperations = new HashMap<String, LinkedList<Operation>>(); Map<Thread, String> fileAssignments = new HashMap<Thread, String>(); Set<String> reservedFiles = new HashSet<String>(); private long m_totalOperationsPending = 0; private long m_enqueuedOperations = 0; private long m_dequeuedOperations = 0; private long m_significantOpsEnqueued = 0; private long m_significantOpsDequeued = 0; private long m_significantOpsCompleted = 0; private long m_dequeuedItems = 0; private long m_createsCompleted = 0; private long m_updatesCompleted = 0; private long m_errors = 0; int threadsRunning = 0; private long m_startTime = 0; private long m_promotionCount = 0; long lastLap = System.currentTimeMillis(); long lastStatsTime = 0; long lastEnqueued = 0; long lastDequeued = 0; long lastSignificantEnqueued = 0; long lastSignificantDequeued = 0; long lastSignificantCompleted = 0; long lastDequeuedItems = 0; long lastOpsPending = 0; /** * This is the base class for an enqueue able operation */ static abstract class Operation { String fileName; int type; Object data; boolean significant; Operation(String fileName, int type, Object data, boolean significant) { this.fileName = fileName; this.type = type; this.data = data; this.significant = significant; } int getCount() { return 1; } String getFileName() { return this.fileName; } int getType() { return this.type; } Object getData() { return this.data; } boolean isSignificant() { return significant; } void addToPendingList(LinkedList<Operation> pendingOperations) { pendingOperations.add(this); } abstract Object process(Object rrd) throws Exception; } /** * This class represents an operation to create an rrd file */ public class CreateOperation extends Operation { CreateOperation(String fileName, Object rrdDef) { super(fileName, CREATE, rrdDef, true); } Object process(Object rrd) throws Exception { // if the rrd is already open we are confused if (rrd != null) { log().debug("WHAT! rrd open but not created?"); m_delegate.closeFile(rrd); rrd = null; } // create the file m_delegate.createFile(getData()); // keep stats setCreatesCompleted(getCreatesCompleted() + 1); // return the file return rrd; } } /** * Represents an update to a rrd file. */ public class UpdateOperation extends Operation { UpdateOperation(String fileName, String data) { super(fileName, UPDATE, data, true); } UpdateOperation(String fileName, String data, boolean significant) { super(fileName, UPDATE, data, significant); } Object process(Object rrd) throws Exception { // open the file if we need to if (rrd == null) rrd = m_delegate.openFile(getFileName()); final String update = (String) getData(); try { // process the update m_delegate.updateFile(rrd, "", update); } catch (final Throwable e) { final String error = String.format("Error processing update for file %s: %s", getFileName(), update); if (log().isDebugEnabled()) { log().debug(error, e); } throw new Exception(error, e); } // keep stats setUpdatesCompleted(getUpdatesCompleted() + 1); if (getUpdatesCompleted() % m_modulus == 0) { logStats(); } // return the open rrd for further processing return rrd; } } /** * Represents an update whose value is 0. These operations can be merged * together and take up less memory */ public class ZeroUpdateOperation extends UpdateOperation { long timeStamp; long interval = 0; int count; ZeroUpdateOperation(String fileName, long intitialTimeStamp) { super(fileName, "0", false); timeStamp = intitialTimeStamp; count = 1; } Object process(Object rrd) throws Exception { long ts = getFirstTimeStamp(); for (int i = 0; i < count; i++) { // open the file if we need to if (rrd == null) rrd = m_delegate.openFile(getFileName()); String update = ts + ":0"; try { // process the update m_delegate.updateFile(rrd, "", update); } catch (Throwable e) { throw new Exception("Error processing update " + i + " for file " + getFileName() + ": " + update, e); } ts += getInterval(); // keep stats setUpdatesCompleted(getUpdatesCompleted() + 1); if (getUpdatesCompleted() % m_modulus == 0) { logStats(); } } return rrd; } public int getCount() { return count; } public void setCount(int newCount) { this.count = newCount; } public long getFirstTimeStamp() { return timeStamp; } public long getLastTimeStamp() { return timeStamp + interval * (count - 1); } public long getInterval() { return interval; } public void setInterval(long newInterval) { interval = newInterval; } public void mergeUpdates(ZeroUpdateOperation op) throws IllegalArgumentException { long opSpacing = op.getFirstTimeStamp() - getLastTimeStamp(); long tolerance = getInterval() / 5; if (opSpacing == 0) { throw new IllegalArgumentException("unable to merge op because the spacing " + opSpacing + " is 0"); } if (getInterval() > 0 && Math.abs(opSpacing - getInterval()) >= tolerance) { throw new IllegalArgumentException("unable to merge op because the spacing " + opSpacing + " is different than the current interval " + getInterval()); } if (getInterval() > 0 && op.getInterval() > 0 && Math.abs(op.getInterval() - getInterval()) >= tolerance) { throw new IllegalArgumentException("unable to merge op because the new op interval " + op.getInterval() + " is different than the current interval " + getInterval()); } int newCount = getCount() + op.getCount(); long newInterval = ((getCount() - 1) * getInterval() + (op.getCount() - 1) + op.getInterval() + opSpacing) / (newCount - 1); setCount(newCount); setInterval(newInterval); } void addToPendingList(LinkedList<Operation> pendingOperations) { if (pendingOperations.size() > 0 && pendingOperations.getLast() instanceof ZeroUpdateOperation) { ZeroUpdateOperation zeroOp = (ZeroUpdateOperation) pendingOperations.getLast(); try { zeroOp.mergeUpdates(this); } catch (IllegalArgumentException e) { log().debug(e.getMessage()); super.addToPendingList(pendingOperations); } } else { super.addToPendingList(pendingOperations); } } } /** * <p>makeCreateOperation</p> * * @param fileName a {@link java.lang.String} object. * @param rrdDef a {@link java.lang.Object} object. * @return a {@link org.opennms.netmgt.rrd.QueuingRrdStrategy.Operation} object. */ public Operation makeCreateOperation(String fileName, Object rrdDef) { return new CreateOperation(fileName, rrdDef); } /** * <p>makeUpdateOperation</p> * * @param fileName a {@link java.lang.String} object. * @param owner a {@link java.lang.String} object. * @param update a {@link java.lang.String} object. * @return a {@link org.opennms.netmgt.rrd.QueuingRrdStrategy.Operation} object. */ public Operation makeUpdateOperation(String fileName, String owner, String update) { try { int colon = update.indexOf(':'); if ((colon >= 0) && (Double.parseDouble(update.substring(colon + 1)) == 0.0)) { long initialTimeStamp = Long.parseLong(update.substring(0, colon)); if (initialTimeStamp == 0) log().debug("ZERO ERROR: created a zero update with ts=0 for file: " + fileName + " data: " + update); return new ZeroUpdateOperation(fileName, initialTimeStamp); } } catch (NumberFormatException e) { } return new UpdateOperation(fileName, update); } // // Queue management functions. // // TODO: Put this all in a class of its own. This is really ugly. // /** * Add an operation to the queue. * * @param op a {@link org.opennms.netmgt.rrd.QueuingRrdStrategy.Operation} object. */ public void addOperation(Operation op) { synchronized (this) { if (queueIsFull()) { log().error("RRD Data Queue is Full!! Discarding operation for file "+op.getFileName()); return; } if (op.isSignificant() && sigQueueIsFull()) { log().error("RRD Data Significant Queue is Full!! Discarding operation for file "+op.getFileName()); return; } if (!op.isSignificant() && inSigQueueIsFull()) { log().error("RRD Insignificant Data Queue is Full!! Discarding operation for file "+op.getFileName()); return; } storeAssignment(op); setTotalOperationsPending(getTotalOperationsPending() + 1); setEnqueuedOperations(getEnqueuedOperations() + 1); if (op.isSignificant()) setSignificantOpsEnqueued(getSignificantOpsEnqueued() + 1); notifyAll(); ensureThreadsStarted(); } } private Logger log() { return Logger.getLogger(m_category); } private boolean queueIsFull() { if (m_queueHighWaterMark <= 0) return false; else return getTotalOperationsPending() >= m_queueHighWaterMark; } private boolean sigQueueIsFull() { if (m_sigHighWaterMark <= 0) return false; else return getTotalOperationsPending() >= m_sigHighWaterMark; } private boolean inSigQueueIsFull() { if (m_inSigHighWaterMark <= 0) return false; else return getTotalOperationsPending() >= m_inSigHighWaterMark; } /** * Ensure that we have threads started to process the queue. */ public synchronized void ensureThreadsStarted() { if (threadsRunning < m_writeThreads) { threadsRunning++; new Thread(this, this.getClass().getSimpleName() + "-" + threadsRunning).start(); } } /** * Get the operations for the next file that should be worked on. * * @return a linkedList of operations to be processed all for the same file. */ public LinkedList<Operation> getNext() { LinkedList<Operation> ops = null; synchronized (this) { // turn in our previous assignment completeAssignment(); String newAssignment; // wait until there is work to do while ((newAssignment = selectNewAssignment()) == null) { try { wait(); } catch (InterruptedException e) { } } // initialize start time for stats if (getStartTime() == 0) setStartTime(System.currentTimeMillis()); // reserve the assignment and take work items ops = takeAssignment(newAssignment); // keep stats if (ops != null) { for(Operation op : ops) { setTotalOperationsPending(getTotalOperationsPending()-op.getCount()); setDequeuedOperations(getDequeuedOperations() + op.getCount()); if (op.isSignificant()) { setSignificantOpsDequeued(getSignificantOpsDequeued() + op.getCount()); } } setDequeuedItems(getDequeuedItems() + 1); } } return ops; } /** * We need to track which files are being processed by which threads so that * we don't try to process updates for the same file on more than one * thread. */ private synchronized void storeAssignment(Operation op) { // look and see if there a pending ops list for this file LinkedList<Operation> pendingOperations = pendingFileOperations.get(op.getFileName()); // if not then we create an ops list for the file and add the file to // the work items list if (pendingOperations == null) { pendingOperations = new LinkedList<Operation>(); pendingFileOperations.put(op.getFileName(), pendingOperations); // add the file to the correct list based on what type of work we // are adding. (if we aren't prioritizing then every file is counted as // signficant if (!m_prioritizeSignificantUpdates || op.isSignificant()) filesWithSignificantWork.addLast(op.getFileName()); else filesWithInsignificantWork.addLast(op.getFileName()); } else if (m_prioritizeSignificantUpdates && op.isSignificant() && hasOnlyInsignificant(pendingOperations)) { // only do this when we are prioritizing as this bumps files from inSig // up to insig // promote the file to the significant list if this is the first // significant filesWithSignificantWork.addLast(op.getFileName()); } promoteAgedFiles(); op.addToPendingList(pendingOperations); } /** * Ensure that files with insignificant changes are getting promoted if * necessary * */ private synchronized void promoteAgedFiles() { // no need to do this is we aren't prioritizing if (!m_prioritizeSignificantUpdates) return; // the num seconds to update files is 0 then use unfair prioritization if (m_maxInsigUpdateSeconds == 0 || filesWithInsignificantWork.isEmpty()) return; // calculate the elapsed time we first queued updates long now = System.currentTimeMillis(); long elapsedMillis = Math.max(now - getStartTime(), 1); // calculate the milliseconds between promotions necessary to age // insignificant files into // the significant queue double millisPerPromotion = ((m_maxInsigUpdateSeconds * 1000.0) / filesWithInsignificantWork.size()); // calculate the number of millis since start until the next file needs // to be promotoed long nextPromotionMillis = (long) (millisPerPromotion * getPromotionCount()); // if more time has elapsed than the next promotion time then promote a // file if (elapsedMillis > nextPromotionMillis) { String file = filesWithInsignificantWork.removeFirst(); filesWithSignificantWork.addFirst(file); setPromotionCount(getPromotionCount() + 1); } } /** {@inheritDoc} */ public synchronized void promoteEnqueuedFiles(Collection<String> rrdFiles) { filesWithSignificantWork.addAll(0, rrdFiles); m_delegate.promoteEnqueuedFiles(rrdFiles); } /** * Return true if and only if all the operations in the list are * insignificant */ private boolean hasOnlyInsignificant(LinkedList<Operation> pendingOps) { for(Operation op : pendingOps) { if (op.isSignificant()) { return false; } } return true; } /** * register the file that the currentThread is be working on. This enables * us to ensure that another thread doesn't try to work on operations for * that file. Note: this is not synchronized as it is called from getNext which * is thread safe */ private LinkedList<Operation> takeAssignment(String newAssignment) { // make the file as reserved by the current thread fileAssignments.put(Thread.currentThread(), newAssignment); reservedFiles.add(newAssignment); // get the assignments work list and return it return pendingFileOperations.remove(newAssignment); } /** * Return the name of the next file with available work */ private String selectNewAssignment() { for (Iterator<String> it = filesWithSignificantWork.iterator(); it.hasNext();) { String fn = it.next(); if (!reservedFiles.contains(fn)) { it.remove(); return fn; } } for (Iterator<String> it = filesWithInsignificantWork.iterator(); it.hasNext();) { String fn = it.next(); if (!reservedFiles.contains(fn)) { it.remove(); return fn; } } return null; } /** * Record that fact that the current thread has finished process operations * for its current assignment */ private synchronized void completeAssignment() { // remove any existing reservation of the current thread String previousAssignment = fileAssignments.remove(Thread.currentThread()); if (previousAssignment != null) reservedFiles.remove(previousAssignment); } /** * <p>Constructor for QueuingRrdStrategy.</p> * * @param delegate a {@link org.opennms.netmgt.rrd.RrdStrategy} object. */ public QueuingRrdStrategy(RrdStrategy<Object, Object> delegate) { m_delegate = delegate; } /** * <p>getDelegate</p> * * @return a {@link org.opennms.netmgt.rrd.RrdStrategy} object. */ public RrdStrategy<Object, Object> getDelegate() { return m_delegate; } // // RrdStrategy Implementation.. These methods just enqueue the calls as // operations // /* * (non-Javadoc) * * @see RrdStrategy#closeFile(java.lang.Object) */ /** * <p>closeFile</p> * * @param rrd a {@link java.lang.String} object. * @throws java.lang.Exception if any. */ public void closeFile(String rrd) throws Exception { // no need to do anything here } /* * (non-Javadoc) * * @see RrdStrategy#createDefinition(java.lang.String) */ /** * <p>createDefinition</p> * * @param creator a {@link java.lang.String} object. * @param directory a {@link java.lang.String} object. * @param dsName a {@link java.lang.String} object. * @param step a int. * @param dsType a {@link java.lang.String} object. * @param dsHeartbeat a int. * @param dsMin a {@link java.lang.String} object. * @param dsMax a {@link java.lang.String} object. * @param rraList a {@link java.util.List} object. * @return a {@link org.opennms.netmgt.rrd.QueuingRrdStrategy.Operation} object. * @throws java.lang.Exception if any. */ public Operation createDefinition(String creator, String directory, String dsName, int step, String dsType, int dsHeartbeat, String dsMin, String dsMax, List<String> rraList) throws Exception { return createDefinition(creator, directory, dsName, step, Collections.singletonList(new RrdDataSource(dsName, dsType, dsHeartbeat, dsMin, dsMax)), rraList); } /** {@inheritDoc} */ public Operation createDefinition(String creator, String directory, String rrdName, int step, List<RrdDataSource> dataSources, List<String> rraList) throws Exception { String fileName = directory + File.separator + rrdName + RrdUtils.getExtension(); Object def = m_delegate.createDefinition(creator, directory, rrdName, step, dataSources, rraList); return makeCreateOperation(fileName, def); } /* * (non-Javadoc) * * @see RrdStrategy#createFile(java.lang.Object) */ /** * <p>createFile</p> * * @param op a {@link org.opennms.netmgt.rrd.QueuingRrdStrategy.Operation} object. * @throws java.lang.Exception if any. */ public void createFile(Operation op) throws Exception { if (m_queueCreates) addOperation(op); else { m_delegate.createFile(op.getData()); } } /* * (non-Javadoc) * * @see RrdStrategy#openFile(java.lang.String) */ /** {@inheritDoc} */ public String openFile(String fileName) throws Exception { return fileName; } /* * (non-Javadoc) * * @see RrdStrategy#updateFile(java.lang.Object, java.lang.String, java.lang.String) */ /** {@inheritDoc} */ public void updateFile(String rrdFile, String owner, String data) throws Exception { addOperation(makeUpdateOperation((String) rrdFile, owner, data)); } /** {@inheritDoc} */ public Double fetchLastValue(String rrdFile, String ds, int interval) throws NumberFormatException, RrdException { // TODO: handle queued values with fetch. Fetch could pull values off // the queue or force // an immediate file update. return m_delegate.fetchLastValue(rrdFile, ds, interval); } /** {@inheritDoc} */ public Double fetchLastValue(String rrdFile, String ds, String consolidationFunction, int interval) throws NumberFormatException, RrdException { // TODO: handle queued values with fetch. Fetch could pull values off // the queue or force // an immediate file update. return m_delegate.fetchLastValue(rrdFile, ds, consolidationFunction, interval); } /** {@inheritDoc} */ public Double fetchLastValueInRange(String rrdFile, String ds, int interval, int range) throws NumberFormatException, RrdException { // TODO: handle queued values with fetch. Fetch could pull values off // the queue or force // an immediate file update. return m_delegate.fetchLastValueInRange(rrdFile, ds, interval, range); } /** {@inheritDoc} */ public InputStream createGraph(String command, File workDir) throws IOException, RrdException { return m_delegate.createGraph(command, workDir); } // // These methods are run by the write threads the process the queues. // /** * <p>run</p> */ public void run() { try { long waitStart = -1L; long delayed = 0; while (delayed < m_writeThreadExitDelay) { if (getTotalOperationsPending() > 0) { delayed = 0; waitStart = -1L; processPendingOperations(); } else { if (waitStart < 0) { waitStart = System.currentTimeMillis(); } try { Thread.sleep(m_writeThreadSleepTime); } catch (InterruptedException e) { } long now = System.currentTimeMillis(); delayed = now - waitStart; } } } finally { synchronized (this) { threadsRunning--; completeAssignment(); } } } /** * Actually process the operations be calling the underlying delegate * strategy */ private void processPendingOperations() { Object rrd = null; String fileName = null; try { LinkedList<Operation> ops = getNext(); if (ops == null) return; // update stats correctly we update them even if an exception occurs // while we are processing for(Operation op : ops) { if (op.isSignificant()) { setSignificantOpsCompleted(getSignificantOpsCompleted() + 1); } } // now we actually process the events for(Operation op : ops) { fileName = op.getFileName(); rrd = op.process(rrd); } } catch (Throwable e) { setErrors(getErrors() + 1); logLapTime("Error updating file " + fileName + ": " + e.getMessage()); log().debug("Error upading file " + fileName + ": " + e.getMessage(), e); } finally { processClose(rrd); } } /** * close the rrd file */ private void processClose(Object rrd) { if (rrd != null) { try { m_delegate.closeFile(rrd); } catch (Throwable t) { logLapTime("Throwable received while closing file", t); } } } /** * Print queue statistics. * * @return a {@link java.lang.String} object. */ public String getStats() { long now = System.currentTimeMillis(); long currentElapsedMillis = Math.max(now - lastStatsTime, 1); long totalElapsedMillis = Math.max(now - getStartTime(), 1); long currentEnqueuedOps = (getEnqueuedOperations() - lastEnqueued); long currentDequeuedOps = (getDequeuedOperations() - lastDequeued); long currentDequeuedItems = (getDequeuedItems() - lastDequeuedItems); long currentSigOpsEnqueued = (getSignificantOpsEnqueued() - lastSignificantEnqueued); long currentSigOpsDequeued = (getSignificantOpsDequeued() - lastSignificantDequeued); //long currentSigOpsCompleted = (significantOpsCompleted - lastSignificantCompleted); long currentEnqueueRate = (long) (currentEnqueuedOps * 1000.0 / currentElapsedMillis); long currentSigEnqueueRate = (long) (currentSigOpsEnqueued * 1000.0 / currentElapsedMillis); long currentInsigEnqueueRate = (long) ((currentEnqueuedOps - currentSigOpsEnqueued) * 1000.0 / currentElapsedMillis); long overallEnqueueRate = (long) (getEnqueuedOperations() * 1000.0 / totalElapsedMillis); long overallSigEnqueueRate = (long) (getSignificantOpsEnqueued() * 1000.0 / totalElapsedMillis); long overallInsigEnqueueRate = (long) ((getEnqueuedOperations() - getSignificantOpsEnqueued()) * 1000.0 / totalElapsedMillis); long currentDequeueRate = (long) (currentDequeuedOps * 1000.0 / currentElapsedMillis); long currentSigDequeueRate = (long) (currentSigOpsDequeued * 1000.0 / currentElapsedMillis); long currentInsigDequeueRate = (long) ((currentDequeuedOps - currentSigOpsDequeued) * 1000.0 / currentElapsedMillis); long overallDequeueRate = (long) (getDequeuedOperations() * 1000.0 / totalElapsedMillis); long overallSigDequeueRate = (long) (getSignificantOpsDequeued() * 1000.0 / totalElapsedMillis); long overallInsigDequeueRate = (long) ((getDequeuedOperations() - getSignificantOpsDequeued()) * 1000.0 / totalElapsedMillis); long currentItemDequeueRate = (long) (currentDequeuedItems * 1000.0 / currentElapsedMillis); long overallItemDequeueRate = (long) (getDequeuedItems() * 1000.0 / totalElapsedMillis); String stats = "\nQS:\t" + "totalOperationsPending=" + getTotalOperationsPending() + ", significantOpsPending=" + (getSignificantOpsEnqueued() - getSignificantOpsCompleted()) + ", filesWithSignificantWork=" + filesWithSignificantWork.size() + ", filesWithInsignificantWork=" + filesWithInsignificantWork.size() + "\nQS:\t" + ", createsCompleted=" + getCreatesCompleted() + ", updatesCompleted=" + getUpdatesCompleted() + ", errors=" + getErrors() + ", promotionRate=" + ((double) (getPromotionCount() * 1000.0 / totalElapsedMillis)) + ", promotionCount=" + getPromotionCount() + "\nQS:\t" + ", currentEnqueueRates=(" + currentSigEnqueueRate + "/" + currentInsigEnqueueRate + "/" + currentEnqueueRate + ")" + ", currentDequeueRate=(" + currentSigDequeueRate + "/" + currentInsigDequeueRate + "/" + currentDequeueRate + ")" + ", currentItemDequeRate=" + currentItemDequeueRate + ", currentOpsPerUpdate=" + (currentDequeuedOps / Math.max(currentDequeuedItems, 1.0)) + ", currentPrcntSignificant=" + (currentSigOpsEnqueued * 100.0 / Math.max(currentEnqueuedOps, 1.0)) + "%" + ", elapsedTime=" + ((currentElapsedMillis + 500) / 1000) + "\nQS:\t" + ", overallEnqueueRate=(" + overallSigEnqueueRate + "/" + overallInsigEnqueueRate + "/" + overallEnqueueRate + ")" + ", overallDequeueRate=(" + overallSigDequeueRate + "/" + overallInsigDequeueRate + "/" + overallDequeueRate + ")" + ", overallItemDequeRate=" + overallItemDequeueRate + ", overallOpsPerUpdate=" + (getDequeuedOperations() / Math.max(getDequeuedItems(), 1.0)) + ", overallPrcntSignificant=" + (getSignificantOpsEnqueued() * 100.0 / Math.max(getEnqueuedOperations(), 1.0)) + "%" + ", totalElapsedTime=" + ((totalElapsedMillis + 500) / 1000); lastStatsTime = now; lastEnqueued = getEnqueuedOperations(); lastDequeued = getDequeuedOperations(); lastDequeuedItems = getDequeuedItems(); lastSignificantEnqueued = getSignificantOpsEnqueued(); lastSignificantDequeued = getSignificantOpsDequeued(); lastSignificantCompleted = getSignificantOpsCompleted(); lastOpsPending = getTotalOperationsPending(); return stats; } /** * <p>logStats</p> */ public void logStats() { // TODO: Seth 2010-05-21: Change this so that it avoids the overhead of // calling getStats() unless debug logging is enabled? logLapTime(getStats()); } void logLapTime(String message) { log().debug(message + " " + getLapTime()); } void logLapTime(String message, Throwable t) { log().debug(message + " " + getLapTime(), t); } /** * <p>getLapTime</p> * * @return a {@link java.lang.String} object. */ public String getLapTime() { long newLap = System.currentTimeMillis(); double seconds = (newLap - lastLap) / 1000.0; lastLap = newLap; return "[" + seconds + " sec]"; } /** * <p>getGraphLeftOffset</p> * * @return a int. */ public int getGraphLeftOffset() { return m_delegate.getGraphLeftOffset(); } /** * <p>getGraphRightOffset</p> * * @return a int. */ public int getGraphRightOffset() { return m_delegate.getGraphRightOffset(); } /** * <p>getGraphTopOffsetWithText</p> * * @return a int. */ public int getGraphTopOffsetWithText() { return m_delegate.getGraphTopOffsetWithText(); } /** * <p>getDefaultFileExtension</p> * * @return a {@link java.lang.String} object. */ public String getDefaultFileExtension() { return m_delegate.getDefaultFileExtension(); } /** {@inheritDoc} */ public RrdGraphDetails createGraphReturnDetails(String command, File workDir) throws IOException, RrdException { return m_delegate.createGraphReturnDetails(command, workDir); } /** * <p>getTotalOperationsPending</p> * * @return a long. */ public long getTotalOperationsPending() { return m_totalOperationsPending; } /** * <p>setTotalOperationsPending</p> * * @param totalOperationsPending a long. */ public void setTotalOperationsPending(long totalOperationsPending) { m_totalOperationsPending = totalOperationsPending; } /** * <p>getCreatesCompleted</p> * * @return a long. */ public long getCreatesCompleted() { return m_createsCompleted; } /** * <p>setCreatesCompleted</p> * * @param createsCompleted a long. */ public void setCreatesCompleted(long createsCompleted) { m_createsCompleted = createsCompleted; } /** * <p>getUpdatesCompleted</p> * * @return a long. */ public long getUpdatesCompleted() { return m_updatesCompleted; } /** * <p>setUpdatesCompleted</p> * * @param updatesCompleted a long. */ public void setUpdatesCompleted(long updatesCompleted) { m_updatesCompleted = updatesCompleted; } /** * <p>getErrors</p> * * @return a long. */ public long getErrors() { return m_errors; } /** * <p>setErrors</p> * * @param errors a long. */ public void setErrors(long errors) { m_errors = errors; } /** * <p>getPromotionCount</p> * * @return a long. */ public long getPromotionCount() { return m_promotionCount; } /** * <p>setPromotionCount</p> * * @param promotionCount a long. */ public void setPromotionCount(long promotionCount) { m_promotionCount = promotionCount; } /** * <p>getSignificantOpsEnqueued</p> * * @return a long. */ public long getSignificantOpsEnqueued() { return m_significantOpsEnqueued; } /** * <p>setSignificantOpsEnqueued</p> * * @param significantOpsEnqueued a long. */ public void setSignificantOpsEnqueued(long significantOpsEnqueued) { m_significantOpsEnqueued = significantOpsEnqueued; } /** * <p>getSignificantOpsDequeued</p> * * @return a long. */ public long getSignificantOpsDequeued() { return m_significantOpsDequeued; } /** * <p>setSignificantOpsDequeued</p> * * @param significantOpsDequeued a long. */ public void setSignificantOpsDequeued(long significantOpsDequeued) { m_significantOpsDequeued = significantOpsDequeued; } /** * <p>getEnqueuedOperations</p> * * @return a long. */ public long getEnqueuedOperations() { return m_enqueuedOperations; } /** * <p>setEnqueuedOperations</p> * * @param enqueuedOperations a long. */ public void setEnqueuedOperations(long enqueuedOperations) { m_enqueuedOperations = enqueuedOperations; } /** * <p>getDequeuedOperations</p> * * @return a long. */ public long getDequeuedOperations() { return m_dequeuedOperations; } /** * <p>setDequeuedOperations</p> * * @param dequeuedOperations a long. */ public void setDequeuedOperations(long dequeuedOperations) { m_dequeuedOperations = dequeuedOperations; } /** * <p>getDequeuedItems</p> * * @return a long. */ public long getDequeuedItems() { return m_dequeuedItems; } /** * <p>setDequeuedItems</p> * * @param dequeuedItems a long. */ public void setDequeuedItems(long dequeuedItems) { m_dequeuedItems = dequeuedItems; } /** * <p>getSignificantOpsCompleted</p> * * @return a long. */ public long getSignificantOpsCompleted() { return m_significantOpsCompleted; } /** * <p>setSignificantOpsCompleted</p> * * @param significantOpsCompleted a long. */ public void setSignificantOpsCompleted(long significantOpsCompleted) { m_significantOpsCompleted = significantOpsCompleted; } /** * <p>getStartTime</p> * * @return a long. */ public long getStartTime() { return m_startTime; } /** * <p>setStartTime</p> * * @param updateStart a long. */ public void setStartTime(long updateStart) { m_startTime = updateStart; } }