/*******************************************************************************
* This file is part of OpenNMS(R).
*
* Copyright (C) 2008-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.util.spikehunter;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.PosixParser;
import org.jrobin.core.Archive;
import org.jrobin.core.FetchData;
import org.jrobin.core.Robin;
import org.jrobin.core.RrdDb;
import org.jrobin.core.RrdException;
/**
* @author Jeff Gehlbach <jeffg@opennms.org>
*
*/
public class SpikeHunter {
static RrdDb m_rrdFile;
static String m_rrdFileName; // RRD file to open
static String m_dsNames; // Data source names, e.g. "ifInOctets,ifOutOctets"
static int m_analysisStrategy; // Maps into ANALYSIS_STRATEGIES enum
static List<Double> m_operands; // Passed into the chosen analysis strategy
static int m_replacementStrategy; // Maps into REPLACEMENT_STRATEGIES enum
static boolean m_dryRun;
static boolean m_dumpContents;
static boolean m_quiet;
static boolean m_verbose;
protected static Options m_options = new Options();
protected static CommandLine m_commandLine;
private static PrintStream m_out;
private static enum ANALYSIS_STRATEGIES {
PERCENTILE_STRATEGY
}
private static enum REPLACEMENT_STRATEGIES {
NAN_STRATEGY,
PREVIOUS_STRATEGY,
NEXT_STRATEGY
}
/**
* @param args
*/
public static void main(String[] args) {
m_out = System.out;
try {
parseCmdLine(args);
} catch (Throwable e) {
System.out.println("Error parsing command line arguments: " + e.getMessage());
}
try {
m_rrdFile = openRrd();
} catch (IOException ioe) {
System.out.println("IO Exception trying to open RRD file: " + ioe.getMessage());
System.exit(-1);
} catch (RrdException rrde) {
System.out.println("RRD Exception trying to open RRD file: " + rrde.getMessage());
System.exit(-1);
}
if (m_dumpContents) {
dumpContents();
System.exit(0);
}
doReplacement();
closeRrd();
}
public static void parseCmdLine(String[] argv) throws Exception {
m_options.addOption("h", "help", false, "This help text");
m_options.addOption("f", "file", true, "JRobin disk file on which to operate");
m_options.addOption("d", "ds-name", true, "Data source names on which to operate, comma-separated. If unspecified, operate on all DSes.");
m_options.addOption("a", "analysis-strategy", true, "Data analysis strategy. Defaults to percentile.");
m_options.addOption("o", "operands", true, "Operands (numeric, comma-separated) for the selected analysis strategy. Defaults to 95,5.");
m_options.addOption("r", "replacement-strategy", true, "Strategy for replacing spike samples, one of nan|previous|next, defaults to nan");
m_options.addOption("n", "dry-run", false, "Just report spikes, do not make any changes to the JRobin disk file.");
m_options.addOption("p", "dump-contents", false, "Just dump the DSes and RRAs in the JRobin disk file.");
m_options.addOption("q", "quiet", false, "Do not print any informational output");
m_options.addOption("v", "verbose", false, "Print plenty of informational output");
CommandLineParser parser = new PosixParser();
m_commandLine = parser.parse(m_options, argv);
if (m_commandLine.hasOption("h")) {
usage(m_options, m_commandLine);
System.exit(0);
}
Map<String,Integer> analysisStrategies = new HashMap<String,Integer>();
analysisStrategies.put("percentile", ANALYSIS_STRATEGIES.PERCENTILE_STRATEGY.ordinal());
Map<String,Integer> replacementStrategies = new HashMap<String,Integer>();
replacementStrategies.put("nan", REPLACEMENT_STRATEGIES.NAN_STRATEGY.ordinal());
replacementStrategies.put("previous", REPLACEMENT_STRATEGIES.PREVIOUS_STRATEGY.ordinal());
replacementStrategies.put("next", REPLACEMENT_STRATEGIES.NEXT_STRATEGY.ordinal());
m_rrdFileName = m_commandLine.getOptionValue("f");
m_dsNames = m_commandLine.getOptionValue("d", null);
m_operands = new ArrayList<Double>();
for (String operandStr : m_commandLine.getOptionValue("o", "95,5").split(",")) {
m_operands.add(Double.parseDouble(operandStr));
}
m_analysisStrategy = analysisStrategies.get(m_commandLine.getOptionValue("l", "percentile").toLowerCase());
m_replacementStrategy = replacementStrategies.get(m_commandLine.getOptionValue("r", "nan").toLowerCase());
m_dryRun = m_commandLine.hasOption("n");
m_dumpContents = m_commandLine.hasOption("p");
m_quiet = m_commandLine.hasOption("q");
m_verbose = m_commandLine.hasOption("v");
}
private static void usage(Options options, CommandLine cmd) {
usage(options, cmd, null, null);
}
private static void usage(Options options, CommandLine cmd, String error, Exception e) {
HelpFormatter formatter = new HelpFormatter();
PrintWriter pw = new PrintWriter(m_out);
if (error != null) {
pw.println("An error occurred: " + error + "\n");
}
formatter.printHelp("usage: spike-hunter [options]", options);
if (e != null) {
pw.println(e.getMessage());
e.printStackTrace(pw);
}
pw.close();
System.exit(0);
}
public static void printToUser(String msg) {
if (m_quiet) {
return;
}
m_out.println(msg);
}
private static RrdDb openRrd() throws IOException, RrdException {
return new RrdDb(m_rrdFileName, m_dryRun);
}
private static void closeRrd() {
try {
m_rrdFile.close();
} catch (IOException ioe) {
System.out.println("IO Exception trying to close RRD file: " + ioe.getMessage());
}
}
private static void dumpContents() {
System.out.println("Number of archives: " + m_rrdFile.getArcCount());
for (int i = 0; i < m_rrdFile.getArcCount(); i++) {
org.jrobin.core.Archive arc = m_rrdFile.getArchive(i);
String consolFun = "";
double xff = Double.NaN;
long arcStep = 0;
int steps = 0, rows = 0;
try {
consolFun = arc.getConsolFun();
xff = arc.getXff();
arcStep = arc.getArcStep();
steps = arc.getSteps();
rows = arc.getRows();
} catch (IOException e) {
System.out.println("IO Exception trying to dump RRD file contents: " + e.getMessage());
}
System.out.println("\t" + consolFun + ":" + xff + ":" + steps + ":" + rows + " (Step size: " + arcStep + ")");
}
System.out.println();
System.out.println("Number of data sources: " + m_rrdFile.getDsCount());
try {
for (String dsName : m_rrdFile.getDsNames()) {
System.out.println("\t" + dsName);
}
} catch (IOException e) {
System.out.println("IO Exception trying to enumerate data source names: " + e.getMessage());
}
}
private static DataAnalyzer getDataAnalyzer() {
DataAnalyzer analyzer = new PercentileDataAnalyzer(m_operands);
return analyzer;
}
private static DataReplacer getDataReplacer() {
DataReplacer replacer;
if (m_replacementStrategy == REPLACEMENT_STRATEGIES.PREVIOUS_STRATEGY.ordinal()) {
replacer = new PreviousDataReplacer();
} else if (m_replacementStrategy == REPLACEMENT_STRATEGIES.NEXT_STRATEGY.ordinal()) {
replacer = new NextDataReplacer();
} else {
replacer = new NanDataReplacer();
}
return replacer;
}
private static void doReplacement() {
if (m_dryRun) {
printToUser("Running in dry-run mode, no modifications will be made to the specified file");
}
int arcCount = m_rrdFile.getArcCount();
for (int arcIndex = 0; arcIndex < arcCount; arcIndex++) {
org.jrobin.core.Archive thisArc = m_rrdFile.getArchive(arcIndex);
replaceInArchive(thisArc);
}
}
private static void replaceInArchive(org.jrobin.core.Archive arc) {
String consolFun = "";
int arcSteps = 0;
long startTime = 0;
long endTime = 0;
FetchData data = null;
Robin robin = null;
try {
consolFun = arc.getConsolFun();
arcSteps = arc.getSteps();
startTime = arc.getStartTime();
endTime = arc.getEndTime();
} catch (IOException e) {
System.out.println("IO Exception trying to get archive information from RRD file: " + e.getMessage());
System.exit(-1);
}
printToUser("Operating on archive with CF " + consolFun + ", " + arcSteps + " steps");
try {
data = m_rrdFile.createFetchRequest(consolFun, startTime, endTime).fetchData();
} catch (RrdException rrde) {
System.out.println("RRD Exception trying to create fetch request: " + rrde.getMessage());
System.exit(-1);
} catch (IOException ioe) {
System.out.println("IO Exception trying to create fetch request: " + ioe.getMessage());
System.exit(-1);
}
String[] dsNames;
if (m_dsNames == null) {
dsNames = data.getDsNames();
} else {
dsNames = m_dsNames.split(",");
}
for (String dsName : dsNames) {
replaceInDs(arc, data, dsName);
}
}
private static void replaceInDs(Archive arc, FetchData data, String dsName) {
printToUser(" Operating on DS " + dsName);
double[] origValues = null;
try {
origValues = data.getValues(dsName);
} catch (RrdException e) {
System.out.println("RRD Exception trying to get values from RRD file: " + e.getMessage());
System.exit(-1);
}
DataAnalyzer analyzer = getDataAnalyzer();
analyzer.setVerbose(m_verbose);
List<Integer> violatorIndices = analyzer.findSamplesInViolation(origValues);
if (m_verbose) {
printToUser(" Number of values: " + origValues.length);
printToUser(" Data analyzer: " + analyzer);
printToUser(" Samples found in violation: " + violatorIndices.size());
}
DataReplacer replacer = getDataReplacer();
double[] newValues = replacer.replaceValues(origValues, violatorIndices);
printReplacementsToUser(data, dsName, newValues, violatorIndices);
if (!m_dryRun) {
replaceInFile(arc, data, dsName, newValues, violatorIndices);
}
}
private static void printReplacementsToUser(FetchData data, String dsName, double[] newValues, List<Integer> violatorIndices) {
long timestamps[] = data.getTimestamps();
double origValues[] = null;
try {
origValues = data.getValues(dsName);
} catch (RrdException e) {
System.out.println("RRD Exception trying to get values from RRD file: " + e.getMessage());
}
for (int i : violatorIndices) {
Date sampleDate = new Date(timestamps[i] * 1000);
printToUser(" Sample with timestamp " + sampleDate + " and value " + origValues[i] + " replaced by value " + newValues[i]);
}
}
private static void replaceInFile(Archive arc, FetchData data, String dsName, double[] newValues, List<Integer> violatorIndices) {
Robin robin = null;
try {
robin = arc.getRobin(m_rrdFile.getDsIndex(dsName));
} catch (RrdException rrde) {
System.out.println("RRD Exception trying to retrieve Robin from RRD file: " + rrde.getMessage());
System.exit(-1);
} catch (IOException ioe) {
System.out.println("RRD Exception trying to retrieve Robin from RRD file: " + ioe.getMessage());
System.exit(-1);
}
//m_rrdFile.getArchive(int arcIndex)
//m_rrdFile.getArchive(String consolFun, int steps)
for (int i : violatorIndices) {
try {
robin.setValue(i, newValues[i]);
} catch (IOException e) {
System.out.println("IO Exception trying to set value for index " + i + " to " + newValues[i]);
}
}
}
}