/******************************************************************************* * 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.util.ArrayList; import java.util.Collections; import java.util.List; public class PercentileDataAnalyzer implements DataAnalyzer { private Double m_thresholdMultiplier; private int m_percentileNumber; // e.g. 95 for 95th percentile private double m_percentileValue = Double.NaN; private boolean m_verbose = false; private double m_lowestValue = Double.NaN; private double m_highestValue = Double.NaN; public PercentileDataAnalyzer(List<Double> operands) { setParms(operands); } public List<Integer> findSamplesInViolation(double[] values) { List<Integer> violatorIndices = new ArrayList<Integer>(); calculatePercentile(values); double absThreshold = m_percentileValue * m_thresholdMultiplier; for (int i = 0; i < values.length; i++) { if (Double.toString(values[i]).equals(Double.toString(Double.NaN))) { continue; } if (values[i] > absThreshold) { violatorIndices.add(i); } } return violatorIndices; } public void setParms(List<Double> parms) { m_percentileNumber = parms.get(0).intValue(); m_thresholdMultiplier = parms.get(1); } private void calculatePercentile(double[] values) { if (m_verbose) { SpikeHunter.printToUser("Before removing NaN values, " + values.length + " values are in the set"); } ArrayList<Double> sortedValues = new ArrayList<Double>(); for (double thisVal : values) { if (! Double.toString(thisVal).equals(Double.toString(Double.NaN))) { sortedValues.add(thisVal); } } if (m_verbose) { SpikeHunter.printToUser("After removing NaN values, " + sortedValues.size() + " values are left"); } if (sortedValues.isEmpty()) { m_percentileValue = 0; m_lowestValue = 0; m_highestValue = 0; return; } Collections.sort(sortedValues); float N = new Float(sortedValues.size()); int rankInt = getPercentilePossition(N); if (m_verbose) { SpikeHunter.printToUser("Rank of Nth percentile value (N=" + m_percentileNumber + ") is " + rankInt); } m_percentileValue = sortedValues.get(rankInt); m_lowestValue = sortedValues.get(0); m_highestValue = sortedValues.get(sortedValues.size()-1); } /* * According with the Apache Commons Math (3.0-SNAPSHOT), the percentile should be calculated according with the following rules: * * 1) Let n be the length of the (sorted) array and 0 < p <= 100 be the desired percentile. * 2) If n = 1 return the unique array element (regardless of the value of p); otherwise * 3) Compute the estimated percentile position pos = p * (n + 1) / 100 and the difference, d between pos and floor(pos) (i.e. the fractional part of pos). * 4) If pos < 1 return the smallest element in the array. * 5) Else if pos >= n return the largest element in the array. * 6) Else let lower be the element in position floor(pos) in the array and let upper be the next element in the array. Return lower + d * (upper - lower) * * Source: http://commons.apache.org/math/apidocs/org/apache/commons/math/stat/descriptive/rank/Percentile.html * */ public int getPercentilePossition(float n) { if (n == 0) return 0; if (n == 1) return 1; double pos = m_percentileNumber * (n + 1) / 100; if (pos < 1) { return 0; } if (pos >= n) { return (int)n - 1; } return Math.round(new Float((n / 100) * m_percentileNumber + 0.5)); } public String toString() { return "Nth-percentile analyzer (N=" + m_percentileNumber + ", P_N=" + ((m_percentileValue != Double.NaN) ? m_percentileValue : "not yet calculated") + ", lowest=" + m_lowestValue + ", highest=" + m_highestValue + ")"; } public void setVerbose(boolean v) { m_verbose = v; } }