/******************************************************************************* * This file is part of OpenNMS(R). * * Copyright (C) 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.tools.rrd.converter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.GnuParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.jrobin.core.ArcDef; import org.jrobin.core.RrdBackendFactory; import org.jrobin.core.RrdDb; import org.jrobin.core.RrdDef; import org.jrobin.core.RrdException; import org.opennms.tools.rrd.converter.LogUtils.Level; public class JRobinConverter { private static final int DEFAULT_NUMBER_OF_THREADS = 5; private static final String DEFAULT_JROBIN_FACTORY = "MNIO"; private static final String DEFAULT_LOG_LEVEL = "INFO"; private static final long ONE_YEAR_IN_SECONDS = 60L * 60L * 24L * 366L; private static final AtomicInteger m_count = new AtomicInteger(0); private static final AtomicInteger m_finished = new AtomicInteger(0); private static final AtomicInteger m_total = new AtomicInteger(0); private static final class JRobinConsolidationRunnable implements Runnable { private final File m_rrdFile; private final JRobinConverter m_converter; private JRobinConsolidationRunnable(final File rrdFile, final JRobinConverter converter) { m_rrdFile = rrdFile; m_converter = converter; } public void run() { try { synchronized(m_total) { LogUtils.infof(this, "Starting processing %s (%d/%d Started)", m_rrdFile, m_count.incrementAndGet(), m_total.get()); } final List<String> dsNames = m_converter.getDsNames(m_rrdFile); if (dsNames.size() == 1) { LogUtils.warnf(this, "%s only has one dsName, skipping", m_rrdFile); return; } final File outputRrdFile = m_converter.createTempRrd(m_rrdFile); final File backupRrdFile = new File(m_rrdFile.getAbsolutePath() + ".orig"); final File finishedRrdFile = new File(m_rrdFile.getAbsolutePath() + ".finished"); if (finishedRrdFile.exists()) { LogUtils.warnf(this, "File %s has already been converted, because %s exists! Skipping.", m_rrdFile, finishedRrdFile); return; } LogUtils.debugf(this, "Using %s as temporary file", outputRrdFile); m_converter.consolidateRrdFile(m_rrdFile, outputRrdFile); renameFile(m_rrdFile, backupRrdFile); renameFile(outputRrdFile, m_rrdFile); renameFile(backupRrdFile, finishedRrdFile); LogUtils.infof(this, "Completed processing %s (%d/%d Complete)", m_rrdFile, m_finished.incrementAndGet(), m_total.get()); } catch (final Exception e) { LogUtils.infof(this, e, "Error while converting %s", m_rrdFile); } } private void renameFile(final File source, final File target) { LogUtils.debugf(this, "Renaming %s to %s", source, target); if (target.exists()) { LogUtils.errorf(this, "%s already exists!", target); System.exit(1); } if (!source.renameTo(target)) { LogUtils.errorf(this, "Unable to rename %s to %s", source, target); System.exit(1); } source.delete(); } } /** * @param args * @throws ParseException * @throws ConverterException * @throws RrdException */ public static void main(final String[] args) throws ParseException, ConverterException, RrdException { new JRobinConverter().execute(args); } public void execute(final String[] args) throws ParseException, ConverterException, RrdException { if (args.length == 0) { LogUtils.errorf(this, "no files or directories specified!"); System.exit(1); } final Options options = new Options(); options.addOption("h", "help", false, "This help."); options.addOption("f", "factory", true, "The JRobin factory to use. (Default: " + DEFAULT_JROBIN_FACTORY + ")"); options.addOption("l", "log", true, "The log level to use. (Default: " + DEFAULT_LOG_LEVEL + ")"); options.addOption("t", "threads", true, "Number of threads to start. (Default: " + DEFAULT_NUMBER_OF_THREADS + ")"); final CommandLineParser parser = new GnuParser(); final CommandLine cmd = parser.parse(options, args); LogUtils.setLevel(Level.valueOf(cmd.getOptionValue("l", DEFAULT_LOG_LEVEL))); RrdBackendFactory.setDefaultFactory(cmd.getOptionValue("f", DEFAULT_JROBIN_FACTORY)); final Set<File> rrds = new ConcurrentSkipListSet<File>(); if (cmd.hasOption("h")) { new HelpFormatter().printHelp("jrobin-converter [options] [file-or-directory1] [...file-or-directoryN]", options); System.exit(1); } if (cmd.getArgList().size() == 0) { LogUtils.infof(this, "No files or directories specified! Exiting."); System.exit(0); } int threads = DEFAULT_NUMBER_OF_THREADS; if (cmd.hasOption("t")) { try { threads = Integer.valueOf(cmd.getOptionValue("t")); } catch (final NumberFormatException e) { LogUtils.warnf(JRobinConverter.class, e, "failed to format -t %s to a number", cmd.getOptionValue("t")); } } final ExecutorService executor = Executors.newFixedThreadPool(threads); for (final Object arg : cmd.getArgList()) { LogUtils.infof(this, "Scanning %s for storeByGroup data.", arg); final File f = new File((String)arg); if (f.exists()) { if (f.isDirectory()) { rrds.addAll(findGroupRrds(f)); for (final File rrdFile : findGroupRrds(f)) { consolidateRrd(executor, rrdFile); } } else { consolidateRrd(executor, f); } } } LogUtils.infof(this, "Finished scanning for storeByGroup RRDs. (Total RRD count: %d)", m_total.get()); executor.shutdown(); } private void consolidateRrd(final ExecutorService executor, final File rrdFile) { m_total.incrementAndGet(); executor.execute(new JRobinConsolidationRunnable(rrdFile, this)); } public List<File> getMatchingGroupRrds(final File rrdGroupFile) throws ConverterException { if (rrdGroupFile == null) return Collections.emptyList(); final List<String> dsNames; try { dsNames = getDsNames(rrdGroupFile); } catch (final ConverterException e) { LogUtils.debugf(this, "Unable to get dsNames for %s", rrdGroupFile); return Collections.emptyList(); } final List<File> files = new ArrayList<File>(); for (final File f : rrdGroupFile.getAbsoluteFile().getParentFile().listFiles()) { for (final String dsName : dsNames) { if (f.getName().equals(dsName + ".rrd") || f.getName().equals(dsName + ".jrb")) { files.add(f); } } } return files; } public List<String> getDsNames(final File rrdFile) throws ConverterException { try { final RrdDb db = new RrdDb(rrdFile.getAbsolutePath(), true); return Arrays.asList(db.getDsNames()); } catch (final Exception e) { LogUtils.debugf(JRobinConverter.class, e, "error reading file %s", rrdFile); throw new ConverterException(e); } } public List<String> getRras(final File rrdFile) throws ConverterException { try { final List<String> rras = new ArrayList<String>(); final RrdDb db = new RrdDb(rrdFile.getAbsolutePath(), true); for (final ArcDef def : db.getRrdDef().getArcDefs()) { rras.add(def.dump()); } return rras; } catch (final Exception e) { LogUtils.debugf(JRobinConverter.class, e, "error reading file %s", rrdFile); throw new ConverterException(e); } } public Map<String, Integer> getDsIndexes(final RrdDb rrd) throws RrdException, IOException { final Map<String,Integer> indexes = new HashMap<String,Integer>(); for (final String dsName : rrd.getDsNames()) { indexes.put(dsName, rrd.getDsIndex(dsName)); } return indexes; } public void consolidateRrdFile(final File groupFile, final File outputFile) throws IOException, RrdException, ConverterException { final List<RrdDatabase> rrds = new ArrayList<RrdDatabase>(); rrds.add(new RrdDatabase(new RrdDb(groupFile, true))); for (final File individualFile : getMatchingGroupRrds(groupFile)) { final RrdDb individualRrd = new RrdDb(individualFile, true); rrds.add(new RrdDatabase(individualRrd)); } final TimeSeriesDataSource dataSource = new AggregateTimeSeriesDataSource(rrds); final RrdDb outputRrd = new RrdDb(outputFile); final RrdDatabaseWriter writer = new RrdDatabaseWriter(outputRrd); final long endTime = dataSource.getEndTime(); // 1 year final long startTime = endTime - ONE_YEAR_IN_SECONDS; for (long time = startTime; time <= endTime; time += 300) { final RrdEntry entry = dataSource.getDataAt(time); writer.write(entry); } dataSource.close(); outputRrd.close(); } public List<File> findRrds(final File topDirectory) { final List<File> files = new ArrayList<File>(); findRrds(topDirectory, files); return files; } public List<File> findGroupRrds(final File topDirectory) throws ConverterException { final List<File> files = new ArrayList<File>(); findRrds(topDirectory, files); for (final Iterator<File> it = files.iterator(); it.hasNext(); ) { final File file = it.next(); try { final List<String> dsNames = getDsNames(file); if (dsNames.size() < 2) { it.remove(); } } catch (final ConverterException e) { LogUtils.debugf(this, e, "Unable to get dsNames from %s", file); } } return files; } public void findRrds(final File directory, final List<File> files) { for (final File f : directory.listFiles()) { if (f.isDirectory()) { findRrds(f, files); } else { if (f.getName().endsWith(".rrd") || f.getName().endsWith(".jrb")) { files.add(f); } } } } public File createTempRrd(final File rrdFile) throws IOException, RrdException { final File parentFile = rrdFile.getParentFile(); final File outputFile = new File(parentFile, rrdFile.getName() + ".temp"); outputFile.delete(); // just in case there's an old one lying around parentFile.mkdirs(); // LogUtils.debugf(this, "created temporary RRD: %s", outputFile); final RrdDb oldRrd = new RrdDb(rrdFile.getAbsolutePath(), true); final RrdDef rrdDef = oldRrd.getRrdDef(); rrdDef.setPath(outputFile.getAbsolutePath()); rrdDef.setStartTime(0); final RrdDb newRrd = new RrdDb(rrdDef); newRrd.close(); return outputFile; } public boolean moveFileSafely(final File in, final File out) throws IOException { FileInputStream fis = null; FileOutputStream fos = null; FileChannel inChannel = null; FileChannel outChannel = null; final File tempOut = File.createTempFile("move", ".tmp"); try { fis = new FileInputStream(in); fos = new FileOutputStream(tempOut); inChannel = fis.getChannel(); outChannel = fos.getChannel(); inChannel.transferTo(0, inChannel.size(), outChannel); } finally { try { if (inChannel != null) inChannel.close(); } catch (IOException e) { LogUtils.debugf(JRobinConverter.class, "failed to close channel %s", inChannel); } try { if (outChannel != null) outChannel.close(); } catch (IOException e) { LogUtils.debugf(JRobinConverter.class, "failed to close channel %s", outChannel); } try { if (fis != null) fis.close(); } catch (IOException e) { LogUtils.debugf(JRobinConverter.class, "failed to close stream %s", fis); } try { if (fos != null) fos.close(); } catch (IOException e) { LogUtils.debugf(JRobinConverter.class, "failed to close stream %s", fos); } } out.delete(); if (!out.exists()) { tempOut.renameTo(out); return in.delete(); } return false; } }