/* * NOTE: This copyright does *not* cover user programs that use HQ * program services by normal system calls through the application * program interfaces provided as part of the Hyperic Plug-in Development * Kit or the Hyperic Client Development Kit - this is merely considered * normal use of the program, and does *not* fall under the heading of * "derived work". * * Copyright (C) [2004-2008], Hyperic, Inc. * This file is part of HQ. * * HQ is free software; you can redistribute it and/or modify * it under the terms version 2 of the GNU General Public License as * published by the Free Software Foundation. This program 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA. */ package org.hyperic.hq.measurement.server.session; import java.sql.Statement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hibernate.Query; import org.hibernate.SessionFactory; import org.hibernate.type.IntegerType; import org.hyperic.hibernate.dialect.HQDialect; import org.hyperic.hq.authz.server.session.Resource; import org.hyperic.hq.dao.HibernateDAO; import org.hyperic.hq.measurement.MeasurementConstants; import org.hyperic.util.jdbc.DBUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import java.sql.Connection; @Repository public class AvailabilityDataDAO extends HibernateDAO<AvailabilityDataRLE> { private static final String logCtx = AvailabilityDataDAO.class.getName(); private final Log _log = LogFactory.getLog(logCtx); private static final long MAX_TIMESTAMP = AvailabilityDataRLE.getLastTimestamp(); private static final double AVAIL_DOWN = MeasurementConstants.AVAIL_DOWN; private static final String ALIAS_CLAUSE = " upper(t.alias) = '" + MeasurementConstants.CAT_AVAILABILITY.toUpperCase() + "' "; // TOTAL_TIME and TOTAL_UPTIME are used to anchor the start and end values // to // the appropriate time range. They avoid the situation where a query // TOTAL_TIME and TOTAL_UPTIME are used to anchor the start and end values // to // the appropriate time range. They avoid the situation where a query // may result in Long.MAX_VALUE as the endtime and a startime which is < // the user specified value private static final String TOTAL_TIME = "least(rle.endtime,:endtime) " + "- greatest(rle.availabilityDataId.startime,:startime)"; private static final String TOTAL_UPTIME = "(" + TOTAL_TIME + ") * rle.availVal"; private final DBUtil dbUtil; @Autowired public AvailabilityDataDAO(SessionFactory f, DBUtil dbUtil) { super(AvailabilityDataRLE.class, f); this.dbUtil = dbUtil; } @SuppressWarnings("unchecked") List<AvailabilityDataRLE> findLastAvail(List<Integer> mids, long after) { // sort so that the cache has the best opportunity use the query // multiple times mids = new ArrayList<Integer>(mids); Collections.sort(mids); List<AvailabilityDataRLE> rtn = new ArrayList<AvailabilityDataRLE>(mids.size()); if (mids.isEmpty()) { return rtn; // check if sa } String hql = new StringBuilder().append("from AvailabilityDataRLE").append( " WHERE endtime > :endtime").append(" AND availabilityDataId.measurement in (:ids)") .append(" ORDER BY endtime desc").toString(); Query query = getSession().createQuery(hql).setLong("endtime", after); for (int i = 0; i < mids.size(); i += BATCH_SIZE) { int end = Math.min(i + BATCH_SIZE, mids.size()); query.setParameterList("ids", mids.subList(i, end), new IntegerType()); rtn.addAll(query.list()); } return rtn; } @SuppressWarnings("unchecked") List<AvailabilityDataRLE> findLastAvail(List<Integer> mids) { // sort so that the cache has the best opportunity use the query // multiple times mids = new ArrayList<Integer>(mids); Collections.sort(mids); List<AvailabilityDataRLE> rtn = new ArrayList<AvailabilityDataRLE>(mids.size()); if (mids.isEmpty()) { return rtn; } String hql = new StringBuilder().append("from AvailabilityDataRLE").append( " WHERE endtime = :endtime").append(" AND availabilityDataId.measurement in (:ids)") .toString(); // need to do this because of hibernate bug // http://opensource.atlassian.com/projects/hibernate/browse/HHH-1985 Query query = getSession().createQuery(hql).setLong("endtime", MAX_TIMESTAMP); for (int i = 0; i < mids.size(); i += BATCH_SIZE) { int end = Math.min(i + BATCH_SIZE, mids.size()); query.setParameterList("ids", mids.subList(i, end), new IntegerType()); rtn.addAll(query.list()); } return rtn; } @SuppressWarnings("unchecked") AvailabilityDataRLE findAvail(DataPoint state) { String sql = new StringBuilder().append("FROM AvailabilityDataRLE").append( " WHERE availabilityDataId.measurement = :meas").append( " AND availabilityDataId.startime = :startime").toString(); List<AvailabilityDataRLE> list = getSession().createQuery(sql).setLong("startime", state.getTimestamp()).setInteger("meas", state.getMeasurementId().intValue()).list(); if (list.isEmpty()) { return null; } return (AvailabilityDataRLE) list.get(0); } @SuppressWarnings("unchecked") List<AvailabilityDataRLE> findAllAvailsAfter(DataPoint state) { String sql = new StringBuilder().append("FROM AvailabilityDataRLE").append( " WHERE availabilityDataId.measurement = :meas").append( " AND availabilityDataId.startime > :startime").append(" ORDER BY startime asc") .toString(); return getSession().createQuery(sql).setLong("startime", state.getTimestamp()).setInteger( "meas", state.getMeasurementId().intValue()).list(); } @SuppressWarnings("unchecked") AvailabilityDataRLE findAvailAfter(DataPoint state) { String sql = new StringBuilder().append("FROM AvailabilityDataRLE").append( " WHERE availabilityDataId.measurement = :meas").append( " AND availabilityDataId.startime > :startime").append(" ORDER BY startime asc") .toString(); List<AvailabilityDataRLE> list = getSession().createQuery(sql).setLong("startime", state.getTimestamp()).setInteger("meas", state.getMeasurementId().intValue()).setMaxResults( 1).list(); if (list.isEmpty()) { return null; } return (AvailabilityDataRLE) list.get(0); } void updateVal(AvailabilityDataRLE avail, double newVal) { avail.setAvailVal(newVal); save(avail); } @SuppressWarnings("unchecked") AvailabilityDataRLE findAvailBefore(DataPoint state) { String sql = new StringBuilder().append("FROM AvailabilityDataRLE").append( " WHERE availabilityDataId.measurement = :meas").append( " AND availabilityDataId.startime < :startime").append(" ORDER BY startime desc") .toString(); List<AvailabilityDataRLE> list = getSession().createQuery(sql).setLong("startime", state.getTimestamp()).setInteger("meas", state.getMeasurementId().intValue()).setMaxResults( 1).list(); if (list.isEmpty()) { return null; } return (AvailabilityDataRLE) list.get(0); } /** * @return List of AvailabilityDataRLE objs */ @SuppressWarnings("unchecked") List<AvailabilityDataRLE> getHistoricalAvails(Measurement m, long start, long end, boolean descending) { String sql = new StringBuilder().append("FROM AvailabilityDataRLE rle ").append( "WHERE rle.availabilityDataId.measurement = :m AND").append( " (rle.availabilityDataId.startime > :startime").append( " OR rle.endtime > :startime)").append( " AND (rle.availabilityDataId.startime < :endtime").append( " OR rle.endtime < :endtime)") .append(" ORDER BY rle.availabilityDataId.measurement,").append( " rle.availabilityDataId.startime").append(((descending) ? " DESC" : " ASC")) .toString(); return getSession().createQuery(sql).setLong("startime", start).setLong("endtime", end) .setParameter("m", m).list(); } /** * @return List of AvailabilityDataRLE objs */ @SuppressWarnings("unchecked") List<AvailabilityDataRLE> getHistoricalAvails(Integer[] mids, long start, long end, boolean descending) { final List<AvailabilityDataRLE> rtn = new ArrayList<AvailabilityDataRLE>(mids.length); final List<Integer> list = Arrays.asList(mids); final String sql = new StringBuilder().append("FROM AvailabilityDataRLE rle").append( " WHERE rle.availabilityDataId.measurement in (:mids)").append( " AND rle.endtime > :startime").append( " AND rle.availabilityDataId.startime < :endtime").append( " ORDER BY rle.availabilityDataId.measurement,").append( " rle.availabilityDataId.startime").append(((descending) ? " DESC" : " ASC")) .toString(); for (int i=0; i<list.size(); i+=BATCH_SIZE) { final int last = Math.min(i+BATCH_SIZE, list.size()); rtn.addAll(getSession(). createQuery(sql) .setLong("startime", start) .setLong("endtime", end) .setParameterList("mids", list.subList(i, last), new IntegerType()) .list()); } return rtn; } /** * @return {@link Map} of {@link Integer} to ({@link TreeSet} of * {@link AvailabilityDataRLE}). * <p> * The {@link Map} key of {@link Integer} == {@link Measurement} * .getId(). * <p> * The {@link TreeSet}'s comparator sorts by * {@link AvailabilityDataRLE}.getStartime(). */ @SuppressWarnings("unchecked") Map<Integer, TreeSet<AvailabilityDataRLE>> getHistoricalAvailMap(Integer[] mids, final long after, final boolean descending) { if (mids.length <= 0) { return Collections.EMPTY_MAP; } final Comparator<AvailabilityDataRLE> comparator = new Comparator<AvailabilityDataRLE>() { public int compare(AvailabilityDataRLE lhs, AvailabilityDataRLE rhs) { Long lhsStart = new Long(lhs.getStartime()); Long rhsStart = new Long(rhs.getStartime()); if (descending) { return rhsStart.compareTo(lhsStart); } return lhsStart.compareTo(rhsStart); } }; StringBuilder sql = new StringBuilder().append("FROM AvailabilityDataRLE rle").append( " WHERE rle.availabilityDataId.measurement in (:mids)"); if (after > 0) { sql.append(" AND rle.endtime >= :endtime"); } Query query = getSession().createQuery(sql.toString()).setParameterList("mids", mids, new IntegerType()); if (after > 0) { query.setLong("endtime", after); } List<AvailabilityDataRLE> list = query.list(); Map<Integer, TreeSet<AvailabilityDataRLE>> rtn = new HashMap<Integer, TreeSet<AvailabilityDataRLE>>( list.size()); TreeSet<AvailabilityDataRLE> tmp; for (AvailabilityDataRLE rle : list) { Integer mId = rle.getMeasurement().getId(); if (null == (tmp = rtn.get(mId))) { tmp = new TreeSet<AvailabilityDataRLE>(comparator); rtn.put(rle.getMeasurement().getId(), tmp); } tmp.add(rle); } for (int i = 0; i < mids.length; i++) { if (!rtn.containsKey(mids[i])) { rtn.put(mids[i], new TreeSet<AvailabilityDataRLE>(comparator)); } } return rtn; } /** * @return List of AvailabilityDataRLE objs */ @SuppressWarnings("unchecked") List<AvailabilityDataRLE> getHistoricalAvails(Resource res, long start, long end) { String sql = new StringBuilder().append("SELECT rle").append( " FROM AvailabilityDataRLE rle").append(" JOIN rle.availabilityDataId.measurement m") .append(" WHERE m.resource = :resource").append(" AND rle.endtime > :startime").append( " AND rle.availabilityDataId.startime < :endtime").append( " ORDER BY rle.availabilityDataId.startime").toString(); return getSession().createQuery(sql).setParameter("resource", res).setLong("startime", start).setLong("endtime", end).list(); } /** * @return List of Object[]. [0] = Measurement Obj [1] = min(availVal), [2] * = max(availVal), [3] = avg(availVal) [4] = mid count, [5] = total * uptime, [6] = = total time */ @SuppressWarnings("unchecked") List<Object[]> findAggregateAvailability(Integer[] mids, long start, long end) { if (mids.length == 0) { // Nothing to do return new ArrayList<Object[]>(0); } String sql = new StringBuilder().append("SELECT m, min(rle.availVal),").append( " max(rle.availVal),").append(" avg(rle.availVal),").append( " (:endtime - :startime) / m.interval, ").append(" sum(").append(TOTAL_UPTIME).append( "), ").append(" sum(").append(TOTAL_TIME).append(") ").append(" FROM Measurement m") .append(" JOIN m.availabilityData rle").append(" WHERE m in (:mids)").append( " AND (rle.availabilityDataId.startime > :startime").append( " OR rle.endtime > :startime)").append( " AND (rle.availabilityDataId.startime < :endtime").append( " OR rle.endtime < :endtime)") // must group by all columns in query for postgres to work // there is an open bug on this for hibernate to // automatically expand group by's // http://opensource.atlassian.com/projects/hibernate/browse/HHH-2407 .append(" GROUP BY m.id, m._version_, m.instanceId,").append( " m.template, m.mtime,m.enabled,").append(" m.interval, m.formula,m.resource,").append( " rle.endtime").append(" ORDER BY rle.endtime").toString(); final List<Integer> measIds = Arrays.asList(mids); final int size = measIds.size(); final HQDialect dialect = getHQDialect(); final int batchSize = dialect.getMaxExpressions() < 0 ? Integer.MAX_VALUE : dialect.getMaxExpressions(); final List<Object[]> rtn = new ArrayList<Object[]>(size); for (int i=0; i<size; i+=batchSize) { final int last = Math.min(i+batchSize, size); final List sublist = measIds.subList(i, last); rtn.addAll(getSession() .createQuery(sql) .setLong("startime", start) .setLong("endtime", end) .setParameterList("mids", sublist, new IntegerType()) .list()); } return rtn; } @SuppressWarnings("unchecked") Map<Integer,Double> findAggregateAvailabilityUp(final List<Integer> mids, final long start, final long end) throws SQLException { if (mids==null || mids.size() == 0) { return null; } StringBuilder midsSublistStrBuilder = new StringBuilder(); Iterator<Integer> midsItr = mids.iterator(); while (midsItr.hasNext()) { midsSublistStrBuilder.append(',').append(String.valueOf(midsItr.next().intValue())); } String relevantMidsCondStr = "rle.MEASUREMENT_ID in (" + midsSublistStrBuilder.substring(1) + ")"; String sqlBaseAvailInWin = new StringBuilder() .append("SELECT rle.MEASUREMENT_ID, SUM(rle.endtime - rle.startime)") .append(" FROM HQ_AVAIL_DATA_RLE rle") .append(" WHERE ").append(relevantMidsCondStr) .append(" AND rle.startime >= ").append(start) .append(" AND rle.endtime <= ").append(end).toString(); String sqlAllAvailInWin = new StringBuilder() .append(sqlBaseAvailInWin) .append(" GROUP BY rle.MEASUREMENT_ID") .toString(); String sqlAllAvailAtWinEdges = new StringBuilder() .append("SELECT rle.MEASUREMENT_ID, rle.startime, rle.endtime") .append(" FROM HQ_AVAIL_DATA_RLE rle") .append(" WHERE ").append(relevantMidsCondStr) .append(" AND ((rle.startime < ").append(start).append(" AND rle.endtime > ").append(start).append(")") .append(" OR (rle.startime < ").append(end).append(" AND rle.endtime > ").append(end).append("))") .toString(); String sqlAvailUpInWin = new StringBuilder() .append(sqlBaseAvailInWin) .append(" AND rle.availVal = " + MeasurementConstants.AVAIL_UP) .append(" GROUP BY rle.MEASUREMENT_ID") .toString(); String sqlAvailUpAtWinEdges = new StringBuilder() .append(sqlAllAvailAtWinEdges) .append(" AND rle.availVal = " + MeasurementConstants.AVAIL_UP) .toString(); final HQDialect dialect = getHQDialect(); final int batchSize = dialect.getMaxExpressions() < 0 ? Integer.MAX_VALUE : dialect.getMaxExpressions(); Connection conn = null; try { conn = dbUtil.getConnection(); IAvailExtractionStrategy midWinStrtg = new MidWinAvailExtractionStrategy(); IAvailExtractionStrategy winEdgeStrtg = new WinEdgeAvailExtractionStrategy(start, end); Map<Integer,Long> msmtToAllAvailSumTimeInWin = executeAvailQuery(conn,sqlAllAvailInWin,batchSize,midWinStrtg); Map<Integer,Long> msmtToAllAvailInWinEdge = executeAvailQuery(conn,sqlAllAvailAtWinEdges,batchSize,winEdgeStrtg); Map<Integer,Long> msmtToAllAvailSumTime = merge(msmtToAllAvailSumTimeInWin,msmtToAllAvailInWinEdge); Map<Integer,Long> msmtToAvailUpSumTimeInWin = executeAvailQuery(conn,sqlAvailUpInWin,batchSize,midWinStrtg); Map<Integer,Long> msmtToAvailUpAvailInWinEdge = executeAvailQuery(conn,sqlAvailUpAtWinEdges,batchSize,winEdgeStrtg); Map<Integer,Long> msmtToAvailUpSumTime = merge(msmtToAvailUpSumTimeInWin,msmtToAvailUpAvailInWinEdge); Map<Integer,Double> msmtToAvailAvg = calcAvg(msmtToAllAvailSumTime,msmtToAvailUpSumTime); return msmtToAvailAvg; } finally { DBUtil.closeConnection(logCtx,conn); } } protected static interface IAvailExtractionStrategy { public long extract(ResultSet rs) throws SQLException; } protected static class MidWinAvailExtractionStrategy implements IAvailExtractionStrategy { public long extract(ResultSet rs) throws SQLException { return rs.getLong(2); } } protected static class WinEdgeAvailExtractionStrategy implements IAvailExtractionStrategy { protected final long timeFrameStart; protected final long timeFrameEnd; public WinEdgeAvailExtractionStrategy(long timeFrameStart, long timeFrameEnd) { this.timeFrameStart= timeFrameStart; this.timeFrameEnd = timeFrameEnd; } public long extract(ResultSet rs) throws SQLException { long availSectionStart = rs.getLong(2); long availSectionEnd = rs.getLong(3); return Math.min(availSectionEnd, this.timeFrameEnd) - Math.max(availSectionStart, this.timeFrameStart); } } protected Map<Integer,Long> executeAvailQuery(Connection conn, final String sql, final int batchSize, IAvailExtractionStrategy extractStrtg) throws SQLException { Statement stmt = null; ResultSet rs = null; Map<Integer,Long> rtn = new HashMap<Integer, Long>(); try { stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); rs = stmt.executeQuery(sql); rs.setFetchSize(batchSize); Long accumulatedTime; int availId; while (rs.next()) { availId = rs.getInt(1); accumulatedTime = rtn.get(Integer.valueOf(availId)); rtn.put(availId, Long.valueOf(extractStrtg.extract(rs)+(accumulatedTime!=null?accumulatedTime.longValue():0))); } } finally { DBUtil.closeResultSet(logCtx, rs); DBUtil.closeStatement(logCtx, stmt); } return rtn; } protected Map<Integer,Long> merge(Map<Integer,Long> map1, Map<Integer,Long> map2) { Map<Integer,Long> rtn = new HashMap<Integer,Long>(); Set<Integer> globalKeys = new HashSet<Integer>(); globalKeys.addAll(map1.keySet()); globalKeys.addAll(map2.keySet()); for (Integer key : globalKeys) { Long map1Val = map1.get(key); Long map2Val = map2.get(key); rtn.put(key,(map1Val!=null?map1Val:0)+(map2Val!=null?map2Val:0)); } return rtn; } protected Map<Integer, Double> calcAvg(Map<Integer,Long> allAvail, Map<Integer,Long> availUp) { Map<Integer,Double> rtn = new HashMap<Integer,Double>(); Long availUpTime = null; for (Integer availId : allAvail.keySet()) { availUpTime = availUp.get(availId); rtn.put(availId, availUpTime!=null?((double)availUpTime/allAvail.get(availId)):0); } return rtn; } /** * @return List of Object[]. [0] = measurement template id, [1] = * min(availVal), [2] = max(availVal), [3] = avg(availVal) [4] = mid * count, [5] = total uptime, [6] = = total time */ @SuppressWarnings("unchecked") List<Object[]> findAggregateAvailability(Integer[] tids, Integer[] iids, long start, long end) { if (tids.length == 0) { // Nothing to do return new ArrayList<Object[]>(0); } String sql = new StringBuilder().append("SELECT m.template.id, min(rle.availVal),").append( " max(rle.availVal),").append(" avg(rle.availVal),").append(" count(distinct m.id), ") .append(" sum(").append(TOTAL_UPTIME).append("), ").append(" sum(").append(TOTAL_TIME) .append(") ").append(" FROM Measurement m").append(" JOIN m.availabilityData rle") .append(" WHERE m.template in (:tids)").append(" AND m.instanceId in (:iids)").append( " AND (rle.availabilityDataId.startime > :startime").append( " OR rle.endtime > :startime)").append( " AND (rle.availabilityDataId.startime < :endtime").append( " OR rle.endtime < :endtime)").append(" GROUP BY m.template.id, rle.endtime") .append(" ORDER BY rle.endtime").toString(); return getSession().createQuery(sql).setLong("startime", start).setLong("endtime", end) .setParameterList("tids", tids, new IntegerType()).setParameterList("iids", iids, new IntegerType()).list(); } AvailabilityDataRLE create(Measurement meas, long startime, long endtime, double availVal) { AvailabilityDataRLE availObj = new AvailabilityDataRLE(meas, startime, endtime, availVal); getSession().save(availObj); return availObj; } /** * @return List of down Measurements */ @SuppressWarnings("unchecked") List<AvailabilityDataRLE> getDownMeasurements(List<Integer> includes) { StringBuilder sql = new StringBuilder().append("SELECT rle FROM AvailabilityDataRLE rle") .append(" JOIN rle.availabilityDataId.measurement m").append(" JOIN m.template t") .append(" WHERE rle.endtime = ").append(MAX_TIMESTAMP).append( " AND m.resource is not null ").append(" AND rle.availVal = ").append(AVAIL_DOWN) .append(" AND ").append(ALIAS_CLAUSE); final boolean hasIncludes = (includes != null && includes.size() > 0) ? true : false; if (hasIncludes) { sql.append(" AND rle.availabilityDataId.measurement in (:mids)"); } Query query = getSession().createQuery(sql.toString()); if (!hasIncludes) { return query.list(); } List<AvailabilityDataRLE> rtn = new ArrayList<AvailabilityDataRLE>(includes.size()); for (int i = 0; i < includes.size(); i += BATCH_SIZE) { int end = Math.min(i + BATCH_SIZE, includes.size()); query.setParameterList("mids", includes.subList(i, end), new IntegerType()); rtn.addAll(query.list()); } return rtn; } AvailabilityDataRLE create(Measurement meas, long startime, double availVal) { AvailabilityDataRLE availObj = new AvailabilityDataRLE(meas, startime, availVal); if (_log.isDebugEnabled()) { _log.debug("creating Avail: " + availObj); } save(availObj); return availObj; } }