/* * RHQ Management Platform * Copyright (C) 2005-2014 Red Hat, Inc. * All rights reserved. * * This program 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 version 2 of the License. * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ package org.rhq.enterprise.server.measurement; import java.lang.reflect.UndeclaredThrowableException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; 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 java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import javax.ejb.Asynchronous; import javax.ejb.EJB; import javax.ejb.Stateless; import javax.ejb.TransactionAttribute; import javax.ejb.TransactionAttributeType; import javax.persistence.EntityManager; import javax.persistence.FlushModeType; import javax.persistence.NoResultException; import javax.persistence.PersistenceContext; import javax.persistence.Query; import javax.sql.DataSource; import com.google.common.base.Stopwatch; import com.google.common.util.concurrent.FutureCallback; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jboss.util.NoSuchMethodException; import org.jetbrains.annotations.Nullable; import org.jboss.remoting.CannotConnectException; import org.rhq.core.domain.auth.Subject; import org.rhq.core.domain.common.EntityContext; import org.rhq.core.domain.criteria.MeasurementDataTraitCriteria; import org.rhq.core.domain.criteria.MeasurementScheduleCriteria; import org.rhq.core.domain.measurement.DataType; import org.rhq.core.domain.measurement.DisplayType; import org.rhq.core.domain.measurement.MeasurementAggregate; import org.rhq.core.domain.measurement.MeasurementData; import org.rhq.core.domain.measurement.MeasurementDataNumeric; import org.rhq.core.domain.measurement.MeasurementDataTrait; import org.rhq.core.domain.measurement.MeasurementDefinition; import org.rhq.core.domain.measurement.MeasurementReport; import org.rhq.core.domain.measurement.MeasurementSchedule; import org.rhq.core.domain.measurement.MeasurementScheduleRequest; import org.rhq.core.domain.measurement.composite.MeasurementDataNumericHighLowComposite; import org.rhq.core.domain.measurement.ui.MetricDisplaySummary; import org.rhq.core.domain.resource.Agent; import org.rhq.core.domain.resource.Resource; import org.rhq.core.domain.resource.ResourceType; import org.rhq.core.domain.resource.composite.ResourceIdWithAgentComposite; import org.rhq.core.domain.resource.group.ResourceGroup; import org.rhq.core.domain.server.PersistenceUtility; import org.rhq.core.domain.util.OrderingField; import org.rhq.core.domain.util.PageControl; import org.rhq.core.domain.util.PageList; import org.rhq.core.domain.util.PageOrdering; import org.rhq.core.util.collection.ArrayUtils; import org.rhq.core.util.exception.ThrowableUtil; import org.rhq.core.util.jdbc.JDBCUtil; import org.rhq.enterprise.server.RHQConstants; import org.rhq.enterprise.server.agentclient.AgentClient; import org.rhq.enterprise.server.alert.AlertManagerLocal; import org.rhq.enterprise.server.alert.engine.AlertConditionCacheManagerLocal; import org.rhq.enterprise.server.alert.engine.AlertConditionCacheStats; import org.rhq.enterprise.server.auth.SubjectManagerLocal; import org.rhq.enterprise.server.authz.AuthorizationManagerLocal; import org.rhq.enterprise.server.authz.PermissionException; import org.rhq.enterprise.server.core.AgentManagerLocal; import org.rhq.enterprise.server.measurement.instrumentation.MeasurementMonitor; import org.rhq.enterprise.server.measurement.util.MeasurementDataManagerUtility; import org.rhq.enterprise.server.resource.group.ResourceGroupManagerLocal; import org.rhq.enterprise.server.rest.ResourceHandlerBean; import org.rhq.enterprise.server.storage.StorageClientManager; import org.rhq.enterprise.server.util.CriteriaQueryGenerator; import org.rhq.enterprise.server.util.CriteriaQueryRunner; import org.rhq.server.metrics.MetricsServer; import org.rhq.server.metrics.domain.AggregateNumericMetric; import org.rhq.server.metrics.domain.RawNumericMetric; /** * A manager for {@link MeasurementData}s. * * @author Heiko W. Rupp * @author Greg Hinkle * @author Ian Springer */ @Stateless public class MeasurementDataManagerBean implements MeasurementDataManagerLocal, MeasurementDataManagerRemote { // time_stamp, schedule_id, value, schedule_id, schedule_id, value, value, value, value private static final String TRAIT_INSERT_STATEMENT = "INSERT INTO RHQ_measurement_data_trait \n" + " SELECT ?, ?, ? FROM RHQ_numbers n \n" + " WHERE n.i = 42 \n" + " AND NOT EXISTS \n" + " ( \n" + " SELECT 1 \n" + " FROM (SELECT dt2.value as v \n" + " FROM RHQ_measurement_data_trait dt2 \n" + " WHERE dt2.schedule_id = ? \n" + " AND dt2.time_stamp = \n" + " (SELECT max(dt3.time_stamp) FROM RHQ_measurement_data_trait dt3 WHERE dt3.schedule_id = ?)) lastValue \n" + " WHERE NOT ((? is null AND lastValue.v is not null) \n" + " OR (? is not null AND lastValue.v is null) \n" + " OR (? is not null AND lastValue.v is not null AND ? <> lastValue.v)) \n" + " )"; private static final int TRAIT_VALUE_SIZE_IN_DB = 4000; private final Log log = LogFactory.getLog(MeasurementDataManagerBean.class); @PersistenceContext(unitName = RHQConstants.PERSISTENCE_UNIT_NAME) private EntityManager entityManager; @javax.annotation.Resource(name = "RHQ_DS", mappedName = RHQConstants.DATASOURCE_JNDI_NAME) private DataSource rhqDs; @EJB private AuthorizationManagerLocal authorizationManager; @EJB private AlertConditionCacheManagerLocal alertConditionCacheManager; @EJB private AlertManagerLocal alertManager; @EJB //@IgnoreDependency private AgentManagerLocal agentClientManager; @EJB private ResourceGroupManagerLocal resourceGroupManager; @EJB private CallTimeDataManagerLocal callTimeDataManager; @EJB private MeasurementDataManagerLocal measurementDataManager; @EJB //@IgnoreDependency private MeasurementDefinitionManagerLocal measurementDefinitionManager; @EJB private StorageClientManager storageClientManager; @EJB private MeasurementScheduleManagerLocal measurementScheduleManager; @EJB private SubjectManagerLocal subjectManager; @Override @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) public void mergeMeasurementReport(MeasurementReport report) { long start = System.currentTimeMillis(); // TODO GH: Deal with offset (this is only for situations where the clock doesn't match on the agent) /* * even if these methods check for null/empty collections, they cross the EJB boundary and so unnecessarily * start transactions. by checking the null/emptiness of a collection here, by only create transactions * when real work will be done; */ if (report.getNumericData() != null && !report.getNumericData().isEmpty()) { this.measurementDataManager.addNumericData(report.getNumericData()); } if (report.getTraitData() != null && !report.getTraitData().isEmpty()) { this.measurementDataManager.addTraitData(report.getTraitData()); } if (report.getCallTimeData() != null && !report.getCallTimeData().isEmpty()) { this.callTimeDataManager.addCallTimeData(report.getCallTimeData()); } long time = System.currentTimeMillis() - start; MeasurementMonitor.getMBean().incrementMeasurementInsertTime(time); MeasurementMonitor.getMBean().incrementMeasurementsInserted(report.getDataCount()); if (log.isDebugEnabled()) { log.debug("Measurement storage for [" + report.getDataCount() + "] took " + time + "ms"); } } /** * Add metrics data to the database. Data that is passed can come from several Schedules, but needs to be of only * one type of MeasurementGathering. For good performance it is important that the agent sends batches as big as * possible (ok, perhaps not more than 100 items at a time). * * @param data the actual data points */ @Override @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) public void addNumericData(final Set<MeasurementDataNumeric> data) { if ((data == null) || (data.isEmpty())) { return; } MetricsServer metricsServer = storageClientManager.getMetricsServer(); metricsServer.addNumericData(data, new FutureCallback<Void>() { @Override public void onSuccess(@Nullable Void result) { Set<MeasurementData> insertedData = new TreeSet<MeasurementData>(new Comparator<MeasurementData>() { // Note, if the logic in this comparator is updated please ensure that you also // update MeasurementDataTest.testComparator(), where this logic is tested. @Override public int compare(MeasurementData d1, MeasurementData d2) { int c = Integer.valueOf(d1.getScheduleId()).compareTo(d2.getScheduleId()); if (c != 0) { return c; } return Long.valueOf(d1.getTimestamp()).compareTo(d2.getTimestamp()); } }); insertedData.addAll(data); measurementDataManager.updateAlertConditionCache("mergeMeasurementReport", insertedData.toArray(new MeasurementData[insertedData.size()])); } @Override public void onFailure(Throwable throwable) { // This should cause rollback and agent to resubmit the raw data throw new MeasurementStorageException(throwable); } }); } @Override @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void addTraitData(Set<MeasurementDataTrait> data) { if ((data == null) || (data.isEmpty())) { return; } Connection conn = null; PreparedStatement ps = null; try { conn = rhqDs.getConnection(); ps = conn.prepareStatement(TRAIT_INSERT_STATEMENT); for (MeasurementDataTrait aData : data) { // time_stamp, schedule_id, value, schedule_id, schedule_id, value, value, value, value // There's a limitation of 4000 database characters in Oracle, try to overcome the issue // The character set in Oracle could vary, using UTF-8 for "worst-case" if (aData.getValue() != null) { while (aData.getValue().getBytes("UTF-8").length > TRAIT_VALUE_SIZE_IN_DB) { aData.setValue(aData.getValue().substring(0, aData.getValue().length() - 1)); } } ps.setLong(1, aData.getTimestamp()); ps.setInt(2, aData.getScheduleId()); ps.setString(3, aData.getValue()); ps.setInt(4, aData.getScheduleId()); ps.setInt(5, aData.getScheduleId()); ps.setString(6, aData.getValue()); ps.setString(7, aData.getValue()); ps.setString(8, aData.getValue()); ps.setString(9, aData.getValue()); ps.addBatch(); } int[] res = ps.executeBatch(); if (res.length != data.size()) { throw new MeasurementStorageException("Failure to store measurement trait data."); // It is expected that some of these batch updates didn't update anything as the previous value was the same } notifyAlertConditionCacheManager("mergeMeasurementReport", data.toArray(new MeasurementData[data.size()])); } catch (SQLException e) { log.warn("Failure saving measurement trait data:\n" + ThrowableUtil.getAllMessages(e)); } catch (Exception e) { log.error("Error persisting trait data", e); } finally { JDBCUtil.safeClose(conn, ps, null); } } /** * Return a map of <resource id, List<MetricDisplaySummary>>, where the list contains the * {@link MetricDisplaySummary} for the (enabled) schedules of the resource * * @param subject Subject of the caller * @param resourceTypeId ResourceTypeId of the child resources * @param parentId ID of the common parent resource * @param resourceIds List of primary keys of the resources we are interested in * @param begin begin time * @param end end time */ @Override @SuppressWarnings("unchecked") public Map<Integer, List<MetricDisplaySummary>> findNarrowedMetricDisplaySummariesForResourcesAndParent( Subject subject, int resourceTypeId, int parentId, List<Integer> resourceIds, long begin, long end) { Map<Integer, List<MetricDisplaySummary>> sumMap = new HashMap<Integer, List<MetricDisplaySummary>>(); if ((parentId <= 0) || (resourceIds == null) || (resourceIds.isEmpty()) || (end < begin)) { return sumMap; } /* * Get the schedule(ids) for the passed resources and types and stuff them in a MapMap to easier access them * afterwards. */ Query q = entityManager.createNamedQuery(MeasurementSchedule.FIND_ENABLED_BY_RESOURCE_IDS_AND_RESOURCE_TYPE_ID); q.setFlushMode(FlushModeType.COMMIT); q.setParameter("resourceTypeId", resourceTypeId); q.setParameter("resourceIds", resourceIds); // <schedId, resId, defId> List<Object[]> triples = q.getResultList(); Map<Integer, Map<Integer, Integer>> resDefSchedMap = new HashMap<Integer, Map<Integer, Integer>>(); List<Integer> scheduleIds = new ArrayList<Integer>(triples.size()); for (Object[] triple : triples) { int sid = (Integer) triple[0]; scheduleIds.add(sid); int res = (Integer) triple[1]; int def = (Integer) triple[2]; Map<Integer, Integer> defSchedMap; if (!resDefSchedMap.containsKey(res)) { defSchedMap = new HashMap<Integer, Integer>(); resDefSchedMap.put(res, defSchedMap); } else { defSchedMap = resDefSchedMap.get(res); } defSchedMap.put(def, sid); } Map<Integer, Integer> alerts = alertManager.getAlertCountForSchedules(begin, end, scheduleIds); List<MeasurementDefinition> definitions = measurementDefinitionManager .findMeasurementDefinitionsByResourceType(subject, resourceTypeId, DataType.MEASUREMENT, null); Map<Integer, MeasurementDefinition> defMap = new HashMap<Integer, MeasurementDefinition>(definitions.size()); for (MeasurementDefinition def : definitions) { defMap.put(def.getId(), def); } /* * Now that we have the data, loop over the data and fill in the MetricDisplaySummaries. */ for (int resourceId : resourceIds) { List<MetricDisplaySummary> summaries = new ArrayList<MetricDisplaySummary>(); if (resDefSchedMap.containsKey(resourceId)) { Map<Integer, Integer> defSchedMap = resDefSchedMap.get(resourceId); for (int defId : defSchedMap.keySet()) { if (defMap.get(defId) == null) { // This is not a DataType.MEASUREMENT type measurement continue; } int sid = defSchedMap.get(defId); MetricDisplaySummary mds = new MetricDisplaySummary(); mds.setAlertCount(alerts.get(sid)); mds.setBeginTimeFrame(begin); mds.setEndTimeFrame(end); mds.setDefinitionId(defId); mds.setMetricName(defMap.get(defId).getName()); mds.setLabel(defMap.get(defId).getDisplayName()); mds.setParentId(parentId); mds.setChildTypeId(resourceTypeId); summaries.add(mds); } } sumMap.put(resourceId, summaries); } return sumMap; } /* (non-Javadoc) * @see * org.rhq.enterprise.server.measurement.MeasurementDataManagerLocal#getNarrowedMetricsDisplaySummaryForCompGroup(org.jboss.on.domain.auth.Subject, * int) */ @Override public Map<Integer, List<MetricDisplaySummary>> findNarrowedMetricsDisplaySummariesForCompGroup(Subject subject, ResourceGroup group, long beginTime, long endTime) { group = entityManager.merge(group); Set<Resource> resources = group.getExplicitResources(); Map<Integer, List<MetricDisplaySummary>> resMap = findNarrowedMetricDisplaySummariesForCompatibleResources( subject, resources, beginTime, endTime); // loop over the map entries and set the group Id on each list element for (List<MetricDisplaySummary> summaries : resMap.values()) { for (MetricDisplaySummary sum : summaries) { sum.setGroupId(group.getId()); } } return resMap; } @Override public Map<Integer, List<MetricDisplaySummary>> findNarrowedMetricsDisplaySummariesForAutoGroup(Subject subject, int parentId, int cType, long beginTime, long endTime) { List<Resource> resources = resourceGroupManager.findResourcesForAutoGroup(subject, parentId, cType); Set<Resource> resSet = new HashSet<Resource>(resources.size()); Map<Integer, List<MetricDisplaySummary>> resMap = findNarrowedMetricDisplaySummariesForCompatibleResources( subject, resSet, beginTime, endTime); // loop over the map entries and set the group Id on each list element for (List<MetricDisplaySummary> summaries : resMap.values()) { for (MetricDisplaySummary sum : summaries) { sum.setChildTypeId(cType); sum.setParentId(parentId); } } return resMap; } /** * Get the {@link MetricDisplaySummary}s for the resources passed in, that all need to be of the same * {@link ResourceType}. Summaries only contain a basic selection of fields for the purpose of filling the Child * resource popups. */ @Override @SuppressWarnings("unchecked") public Map<Integer, List<MetricDisplaySummary>> findNarrowedMetricDisplaySummariesForCompatibleResources( Subject subject, Collection<Resource> resources, long beginTime, long endTime) { Map<Integer, List<MetricDisplaySummary>> resMap = new HashMap<Integer, List<MetricDisplaySummary>>(); if ((resources == null) || (resources.isEmpty())) { return resMap; } /* * Get the resource type and make sure all resources are of the same type */ Iterator<Resource> it = resources.iterator(); ResourceType type = it.next().getResourceType(); boolean found = false; while (it.hasNext()) { ResourceType tmp = it.next().getResourceType(); if (tmp != type) { found = true; break; } } if (found) { throw new IllegalArgumentException("Resources were of different type: " + resources); } Set<MeasurementDefinition> defs = type.getMetricDefinitions(); // get all schedules that are collecting (=enabled) Query q = entityManager.createNamedQuery(MeasurementSchedule.FIND_ENABLED_BY_RESOURCES_AND_RESOURCE_TYPE); q.setFlushMode(FlushModeType.COMMIT); q.setParameter("resourceType", type); q.setParameter("resources", resources); q.setParameter("dataType", DataType.MEASUREMENT); // <schedId, resId, defId> List<Object[]> schedules = q.getResultList(); Map<Integer, Map<Integer, Integer>> resDefSchedMap = new HashMap<Integer, Map<Integer, Integer>>(); List<Integer> scheduleIds = new ArrayList<Integer>(schedules.size()); for (Object[] sched : schedules) { int sid = (Integer) sched[0]; scheduleIds.add(sid); int res = (Integer) sched[1]; int def = (Integer) sched[2]; Map<Integer, Integer> defSchedMap; if (!resDefSchedMap.containsKey(res)) { defSchedMap = new HashMap<Integer, Integer>(); resDefSchedMap.put(res, defSchedMap); } else { defSchedMap = resDefSchedMap.get(res); } defSchedMap.put(def, sid); } Map<Integer, Integer> alerts = alertManager.getAlertCountForSchedules(beginTime, endTime, scheduleIds); /* * Loop over the resources and populate the map with the schedules for the definitions we have There won't be a * schedule for each combination, as the list above only contains schedules that are actually collecting. Also * if the schedule is not collecting, we don't need to add it to the result. */ for (Resource res : resources) { List<MetricDisplaySummary> summaries = new ArrayList<MetricDisplaySummary>(); for (MeasurementDefinition def : defs) { MetricDisplaySummary sum = new MetricDisplaySummary(); sum.setDefinitionId(def.getId()); sum.setMetricName(def.getName()); sum.setLabel(def.getDisplayName()); sum.setBeginTimeFrame(beginTime); sum.setEndTimeFrame(endTime); int resId = res.getId(); if (resDefSchedMap.containsKey(resId)) { Map<Integer, Integer> defSched = resDefSchedMap.get(resId); if (defSched.containsKey(def.getId())) { int sid = defSched.get(def.getId()); sum.setScheduleId(sid); sum.setAlertCount(alerts.get(sid)); summaries.add(sum); } } } resMap.put(res.getId(), summaries); } return resMap; } /** * Helper to fill the name of the trait from the passed array into the MeasurementDataTrait object. The input is a * tuple [MeasurementDataTrait,String name, Short displayOrder]. * * @param objs Tuple {@link MeasurementDataTrait},String,Short * * @return {@link MeasurementDataTrait} where the name property is set. Or null if the input was null. */ private MeasurementDataTrait fillMeasurementDataTraitFromObjectArray(Object[] objs) { if (objs == null) { return null; } MeasurementDataTrait mdt = (MeasurementDataTrait) objs[0]; String name = (String) objs[1]; mdt.setName(name); return mdt; } /** * Return the current trait value for the passed schedule * * @param scheduleId id of a MeasurementSchedule that 'points' to a Trait * * @return One trait or null if nothing was found in the db. */ @Override @Nullable public MeasurementDataTrait getCurrentTraitForSchedule(int scheduleId) { Query q = entityManager.createNamedQuery(MeasurementDataTrait.FIND_CURRENT_FOR_SCHEDULES); q.setParameter("scheduleIds", Collections.singletonList(scheduleId)); Object[] res; try { res = (Object[]) q.getSingleResult(); MeasurementDataTrait trait = fillMeasurementDataTraitFromObjectArray(res); return trait; } catch (NoResultException nre) { if (log.isDebugEnabled()) { log.debug("No current trait data for schedule with id [" + scheduleId + "] found"); } return null; } } @Override @Nullable public MeasurementDataNumeric getCurrentNumericForSchedule(int scheduleId) { MetricsServer metricsServer = storageClientManager.getMetricsServer(); RawNumericMetric metric = metricsServer.findLatestValueForResource(scheduleId); if(null != metric) { return new MeasurementDataNumeric(metric.getTimestamp(), scheduleId, metric.getValue()); }else { return new MeasurementDataNumeric(System.currentTimeMillis(), scheduleId, Double.NaN); } } @Asynchronous @Override public void updateAlertConditionCache(String callingMethod, MeasurementData[] data) { AlertConditionCacheStats stats = alertConditionCacheManager.checkConditions(data); log.debug(callingMethod + ": " + stats.toString()); } private void notifyAlertConditionCacheManager(String callingMethod, MeasurementData[] data) { AlertConditionCacheStats stats = alertConditionCacheManager.checkConditions(data); log.debug(callingMethod + ": " + stats.toString()); } @Deprecated @Override @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) public org.rhq.enterprise.server.measurement.MeasurementAggregate getAggregate(Subject subject, int scheduleId, long startTime, long endTime) { MeasurementScheduleCriteria criteria = new MeasurementScheduleCriteria(); criteria.addFilterId(scheduleId); criteria.fetchResource(true); PageList<MeasurementSchedule> schedules = measurementScheduleManager.findSchedulesByCriteria( subjectManager.getOverlord(), criteria); if (schedules.isEmpty()) { throw new MeasurementException("Could not fine MeasurementSchedule with the id[" + scheduleId + "]"); } MeasurementSchedule schedule = schedules.get(0); if (authorizationManager.canViewResource(subject, schedule.getResource().getId()) == false) { throw new PermissionException("User[" + subject.getName() + "] does not have permission to view schedule[id=" + scheduleId + "]"); } if (schedule.getDefinition().getDataType() != DataType.MEASUREMENT) { throw new IllegalArgumentException(schedule + " is not about numerical values. Can't compute aggregates"); } if (startTime > endTime) { throw new IllegalArgumentException("Start date " + startTime + " is not before " + endTime); } MetricsServer metricsServer = storageClientManager.getMetricsServer(); AggregateNumericMetric summary = metricsServer.getSummaryAggregate(scheduleId, startTime, endTime); return new org.rhq.enterprise.server.measurement.MeasurementAggregate(summary.getMin(), summary.getAvg(), summary.getMax()); } @Override public MeasurementAggregate getMeasurementAggregate(Subject subject, int scheduleId, long startTime, long endTime) { Stopwatch stopwatch = new Stopwatch().start(); try { MeasurementScheduleCriteria criteria = new MeasurementScheduleCriteria(); criteria.addFilterId(scheduleId); criteria.fetchResource(true); PageList<MeasurementSchedule> schedules = measurementScheduleManager.findSchedulesByCriteria( subjectManager.getOverlord(), criteria); if (schedules.isEmpty()) { throw new MeasurementException("Could not fine MeasurementSchedule with the id[" + scheduleId + "]"); } MeasurementSchedule schedule = schedules.get(0); if (authorizationManager.canViewResource(subject, schedule.getResource().getId()) == false) { throw new PermissionException("User[" + subject.getName() + "] does not have permission to view schedule[id=" + scheduleId + "]"); } if (schedule.getDefinition().getDataType() != DataType.MEASUREMENT) { throw new IllegalArgumentException(schedule + " is not about numerical values. Can't compute aggregates"); } if (startTime > endTime) { throw new IllegalArgumentException("Start date " + startTime + " is not before " + endTime); } MetricsServer metricsServer = storageClientManager.getMetricsServer(); AggregateNumericMetric summary = metricsServer.getSummaryAggregate(scheduleId, startTime, endTime); return new MeasurementAggregate(summary.getMin(), summary.getAvg(), summary.getMax()); } finally { stopwatch.stop(); log.debug("Finished loading measurement aggregate in " + stopwatch.elapsed(TimeUnit.MILLISECONDS)); } } @Override @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) public MeasurementAggregate getAggregate(Subject subject, int groupId, int definitionId, long startTime, long endTime) { if (authorizationManager.canViewGroup(subject, groupId) == false) { throw new PermissionException("User[" + subject.getName() + "] does not have permission to calculate measurement aggregate for group[id=" + groupId + "], definition[id=" + definitionId + "]"); } MeasurementDefinition def = measurementDefinitionManager.getMeasurementDefinition(subject, definitionId); if (def.getDataType() != DataType.MEASUREMENT) { throw new IllegalArgumentException(def + " is not about numerical values. Can't compute aggregates"); } if (startTime > endTime) { throw new IllegalArgumentException("Start date " + startTime + " is not before " + endTime); } MeasurementScheduleCriteria criteria = new MeasurementScheduleCriteria(); criteria.addFilterResourceGroupId(groupId); criteria.addFilterDefinitionIds(definitionId); criteria.setPageControl(PageControl.getUnlimitedInstance()); PageList<MeasurementSchedule> schedules = measurementScheduleManager.findSchedulesByCriteria(subject, criteria); MetricsServer metricsServer = storageClientManager.getMetricsServer(); AggregateNumericMetric summary = metricsServer.getSummaryAggregate(map(schedules), startTime, endTime); return new MeasurementAggregate(summary.getMin(), summary.getAvg(), summary.getMax()); } /** * Return the Traits for the passed resource. This method will for each trait only return the 'youngest' entry. If * there are no traits found for that resource, an empty list is returned. If displayType is null, no displayType is * honoured, else the traits will be filtered for the given displayType * * @param resourceId Id of the resource we are interested in * @param displayType A display type for filtering or null for all traits. * * @return a List of MeasurementDataTrait */ @Override @SuppressWarnings("unchecked") public List<MeasurementDataTrait> findCurrentTraitsForResource(Subject subject, int resourceId, DisplayType displayType) { if (authorizationManager.canViewResource(subject, resourceId) == false) { throw new PermissionException("User[" + subject.getName() + "] does not have permission to view traits for resource[id=" + resourceId + "]"); } Query query; List<Object[]> qres; if (displayType == null) { // query = entityManager.createNamedQuery(MeasurementDataTrait.FIND_CURRENT_FOR_RESOURCE); query = PersistenceUtility.createQueryWithOrderBy(entityManager, MeasurementDataTrait.FIND_CURRENT_FOR_RESOURCE, new OrderingField("d.displayOrder", PageOrdering.ASC)); } else { query = PersistenceUtility.createQueryWithOrderBy(entityManager, MeasurementDataTrait.FIND_CURRENT_FOR_RESOURCE_AND_DISPLAY_TYPE, new OrderingField("d.displayOrder", PageOrdering.ASC)); query.setParameter("displayType", displayType); } query.setParameter("resourceId", resourceId); qres = query.getResultList(); /* * Now that we have everything from the query (it returns a tuple <MeasurementDataTrait,DislayName> of the * definition), lets create the method output. */ List<MeasurementDataTrait> result = new ArrayList<MeasurementDataTrait>(qres.size()); for (Object[] objs : qres) { MeasurementDataTrait mdt = fillMeasurementDataTraitFromObjectArray(objs); result.add(mdt); } if (log.isDebugEnabled()) { log.debug("getCurrentTraitsForResource(" + resourceId + ") -> result is " + result); } return result; } @Override @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) public List<List<MeasurementDataNumericHighLowComposite>> findDataForCompatibleGroup(Subject subject, int groupId, int definitionId, long beginTime, long endTime, int numPoints) { List<List<MeasurementDataNumericHighLowComposite>> ret = findDataForContext(subject, EntityContext.forGroup(groupId), definitionId, beginTime, endTime, numPoints); return ret; } @Override @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) public List<List<MeasurementDataNumericHighLowComposite>> findDataForContext(Subject subject, EntityContext context, int definitionId, long beginTime, long endTime, int numDataPoints) { MetricsServer metricsServer = storageClientManager.getMetricsServer(); if (context.type == EntityContext.Type.Resource) { if (!authorizationManager.canViewResource(subject, context.resourceId)) { throw new PermissionException("User [" + subject.getName() + "] does not have permission to view measurement data for resource[id=" + context.resourceId + "]"); } MeasurementSchedule schedule = measurementScheduleManager.getSchedule(subject, context.getResourceId(), definitionId, false); List<List<MeasurementDataNumericHighLowComposite>> data = new ArrayList<List<MeasurementDataNumericHighLowComposite>>(); List<MeasurementDataNumericHighLowComposite> tempList = new ArrayList<MeasurementDataNumericHighLowComposite>(); for (MeasurementDataNumericHighLowComposite object : metricsServer.findDataForResource(schedule.getId(), beginTime, endTime, numDataPoints)) { tempList.add(object); } data.add(tempList); return data; } else if (context.type == EntityContext.Type.ResourceGroup) { if (!authorizationManager.canViewGroup(subject, context.groupId)) { throw new PermissionException("User [" + subject.getName() + "] does not have permission to view measurement data for resourceGroup[id=" + context.groupId + "]"); } MeasurementScheduleCriteria criteria = new MeasurementScheduleCriteria(); criteria.addFilterResourceGroupId(context.getGroupId()); criteria.addFilterDefinitionIds(definitionId); criteria.setPageControl(PageControl.getUnlimitedInstance()); PageList<MeasurementSchedule> schedules = measurementScheduleManager.findSchedulesByCriteria(subject, criteria); List<List<MeasurementDataNumericHighLowComposite>> data = new ArrayList<List<MeasurementDataNumericHighLowComposite>>(); List<MeasurementDataNumericHighLowComposite> tempList = new ArrayList<MeasurementDataNumericHighLowComposite>(); for (MeasurementDataNumericHighLowComposite object : metricsServer.findDataForGroup(map(schedules), beginTime, endTime,numDataPoints)) { tempList.add(object); } data.add(tempList); return data; } else { throw new UnsupportedOperationException("The findDataForContext method does not support " + context); } } private List<Integer> map(List<MeasurementSchedule> schedules) { List<Integer> scheduleIds = new ArrayList<Integer>(schedules.size()); for (MeasurementSchedule schedule : schedules) { scheduleIds.add(schedule.getId()); } return scheduleIds; } @Override @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) public List<List<MeasurementDataNumericHighLowComposite>> findDataForResource(Subject subject, int resourceId, int[] definitionIds, long beginTime, long endTime, int numDataPoints) { if (!authorizationManager.canViewResource(subject, resourceId)) { throw new PermissionException("User[" + subject.getName() + "] does not have permission to view measurement data for resource[id=" + resourceId + "]"); } MetricsServer metricsServer = storageClientManager.getMetricsServer(); List<List<MeasurementDataNumericHighLowComposite>> results = new ArrayList<List<MeasurementDataNumericHighLowComposite>>(); for (int nextDefinitionId : definitionIds) { MeasurementSchedule schedule = measurementScheduleManager.getSchedule(subject, resourceId, nextDefinitionId, false); List<MeasurementDataNumericHighLowComposite> tempList = new ArrayList<MeasurementDataNumericHighLowComposite>(); for(MeasurementDataNumericHighLowComposite object : metricsServer.findDataForResource(schedule.getId(), beginTime, endTime,numDataPoints) ){ tempList.add(object); } results.add(tempList); } return results; } @Override public Set<MeasurementData> findLiveData(Subject subject, int resourceId, int[] definitionIds) { // use default timeout return findLiveData(subject, resourceId, definitionIds, null); } @Override @SuppressWarnings("unchecked") public Set<MeasurementData> findLiveData(Subject subject, int resourceId, int[] definitionIds, Long timeout) { if (authorizationManager.canViewResource(subject, resourceId) == false) { throw new PermissionException("User[" + subject.getName() + "] does not have permission to view live measurement data for resource[id=" + resourceId + "]"); } // return an empty collection if no definition ids were provided if (definitionIds == null || definitionIds.length == 0) { return Collections.<MeasurementData>emptySet(); } Query query = entityManager.createNamedQuery(Agent.QUERY_FIND_BY_RESOURCE_ID); query.setParameter("resourceId", resourceId); Agent agent = (Agent) query.getSingleResult(); // return empty data if the agent is the dummy one if (agent.getName().startsWith(ResourceHandlerBean.DUMMY_AGENT_NAME_PREFIX) && agent.getAgentToken().startsWith(ResourceHandlerBean.DUMMY_AGENT_TOKEN_PREFIX)) { return Collections.<MeasurementData> emptySet(); } query = entityManager.createNamedQuery(MeasurementSchedule.FIND_BY_RESOURCE_IDS_AND_DEFINITION_IDS); query.setParameter("definitionIds", ArrayUtils.wrapInList(definitionIds)); query.setParameter("resourceIds", Arrays.asList(resourceId)); List<MeasurementSchedule> schedules = query.getResultList(); Set<MeasurementScheduleRequest> requests = new HashSet<MeasurementScheduleRequest>(schedules.size()); for (MeasurementSchedule schedule : schedules) { requests.add(new MeasurementScheduleRequest(schedule)); } Set<MeasurementData> result = null; try { AgentClient ac = agentClientManager.getAgentClient(agent); result = ac.getMeasurementAgentService(timeout).getRealTimeMeasurementValue(resourceId, requests); } catch (RuntimeException e) { if (e instanceof CannotConnectException // || (null != e.getCause() && (e.getCause() instanceof TimeoutException))) { // ignore timeouts and connect issue, just return an empty result and keep the logs clean } else if (e instanceof UndeclaredThrowableException || (e.getCause() != null && e.getCause() instanceof NoSuchMethodException)) { // also ignore NoSuchMethodException } else { throw e; } } if (result != null && !result.isEmpty()) { //we just got data from the agent so let's push them through the alerting pushToAlertSubsystem(result); } //[BZ 760139] always return non-null value even when there are errors on the server side. Avoids cryptic // Global UI Exceptions when attempting to serialize null responses. if (null == result) { result = Collections.<MeasurementData>emptySet(); } return result; } @Override @SuppressWarnings("unchecked") public Set<MeasurementData> findLiveDataForGroup(Subject subject, int groupId, int resourceIds[], int[] definitionIds) { if (authorizationManager.canViewGroup(subject, groupId) == false) { throw new PermissionException("User [" + subject.getName() + "] does not have permission to view measurement data for resourceGroup[id=" + groupId + "]"); } // return an empty collection if no definition ids were provided if (definitionIds == null || definitionIds.length == 0) { return Collections.<MeasurementData>emptySet(); } Set<MeasurementData> values = new HashSet<MeasurementData>(); if (resourceIds != null) { Query query = entityManager.createNamedQuery(Agent.QUERY_FIND_RESOURCE_IDS_WITH_AGENTS_BY_RESOURCE_IDS); query.setParameter("resourceIds", ArrayUtils.wrapInList(resourceIds)); List<ResourceIdWithAgentComposite> resourceIdsWithAgents = query.getResultList(); for (ResourceIdWithAgentComposite resourceIdWithAgent : resourceIdsWithAgents) { // return empty data if the agent is the dummy one if (resourceIdWithAgent.getAgent().getName().startsWith(ResourceHandlerBean.DUMMY_AGENT_NAME_PREFIX) && resourceIdWithAgent.getAgent().getAgentToken() .startsWith(ResourceHandlerBean.DUMMY_AGENT_TOKEN_PREFIX)) { values.addAll(Collections.<MeasurementData> emptySet()); continue; } query = entityManager.createNamedQuery(MeasurementSchedule.FIND_BY_RESOURCE_IDS_AND_DEFINITION_IDS); query.setParameter("definitionIds", ArrayUtils.wrapInList(definitionIds)); query.setParameter("resourceIds", Arrays.asList(resourceIdWithAgent.getResourceId())); List<MeasurementSchedule> schedules = query.getResultList(); Map<Integer, Integer> scheduleIdToResourceIdMap = new HashMap<Integer, Integer>(schedules.size()); Set<MeasurementScheduleRequest> requests = new HashSet<MeasurementScheduleRequest>(schedules.size()); for (MeasurementSchedule schedule : schedules) { requests.add(new MeasurementScheduleRequest(schedule)); scheduleIdToResourceIdMap.put(schedule.getId(), resourceIdWithAgent.getResourceId()); } AgentClient ac = agentClientManager.getAgentClient(resourceIdWithAgent.getAgent()); Set<MeasurementData> newValues = ac.getMeasurementAgentService().getRealTimeMeasurementValue( resourceIdWithAgent.getResourceId(), requests); values.addAll(newValues); // Add the resource id as a prefix of the name, because the name is not unique across different platforms for (MeasurementData value : newValues) { value.setName(String.valueOf(scheduleIdToResourceIdMap.get(value.getScheduleId())) + ":" + value.getName()); } } } if (values != null && !values.isEmpty()) { //we just got data from the agent so let's push them through the alerting pushToAlertSubsystem(values); } return values; } @Override public List<MeasurementDataNumeric> findRawData(Subject subject, int scheduleId, long startTime, long endTime) { List<MeasurementDataNumeric> result = new ArrayList<MeasurementDataNumeric>(); String table = MeasurementDataManagerUtility.getCurrentRawTable(); Connection connection = null; PreparedStatement ps = null; ResultSet rs = null; try { connection = rhqDs.getConnection(); ps = connection.prepareStatement( // TODO supply real impl that spans multiple tables "SELECT time_stamp,value FROM " + table + " WHERE schedule_id= ? AND time_stamp BETWEEN ? AND ?"); ps.setLong(1, scheduleId); ps.setLong(2, startTime); ps.setLong(3, endTime); rs = ps.executeQuery(); while (rs.next()) { MeasurementDataNumeric point = new MeasurementDataNumeric(rs.getLong(1), scheduleId, rs.getDouble(2)); result.add(point); } } catch (SQLException e) { e.printStackTrace(); // TODO: Customise this generated block } finally { JDBCUtil.safeClose(connection, ps, rs); } return result; } /** * Return all known trait data for the passed schedule, defined by resourceId and definitionId * * @param resourceId PK of a {@link Resource} * @param definitionId PK of a {@link MeasurementDefinition} * * @return a List of {@link MeasurementDataTrait} objects. */ @Override @SuppressWarnings("unchecked") public List<MeasurementDataTrait> findTraits(Subject subject, int resourceId, int definitionId) { if (authorizationManager.canViewResource(subject, resourceId) == false) { throw new PermissionException("User[" + subject.getName() + "] does not have permission to view trait data for resource[id=" + resourceId + "] and definition[id=" + definitionId + "]"); } Query q = entityManager.createNamedQuery(MeasurementDataTrait.FIND_ALL_FOR_RESOURCE_AND_DEFINITION); q.setParameter("resourceId", resourceId); q.setParameter("definitionId", definitionId); List<Object[]> queryResult = q.getResultList(); List<MeasurementDataTrait> result = new ArrayList<MeasurementDataTrait>(queryResult.size()); for (Object[] objs : queryResult) { MeasurementDataTrait mdt = fillMeasurementDataTraitFromObjectArray(objs); result.add(mdt); } return result; } @Override public PageList<MeasurementDataTrait> findTraitsByCriteria(Subject subject, MeasurementDataTraitCriteria criteria) { CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria); Map<String, Object> filterFields = generator.getFilterFields(criteria); if (!this.authorizationManager.isInventoryManager(subject)) { generator.setAuthorizationResourceFragment(CriteriaQueryGenerator.AuthorizationTokenType.RESOURCE, "schedule.resource", subject.getId()); } CriteriaQueryRunner<MeasurementDataTrait> queryRunner = new CriteriaQueryRunner(criteria, generator, this.entityManager); PageList<MeasurementDataTrait> results = queryRunner.execute(); // Fetch the metric definition for each schedule, so the results include the trait names. for (MeasurementDataTrait result : results) { result.getSchedule().getDefinition().getName(); } // If the query is filtered by group id, also fetch the Resource for each schedule, so the results include the // Resource names. if (filterFields.get(MeasurementDataTraitCriteria.FILTER_FIELD_GROUP_ID) != null) { for (MeasurementDataTrait result : results) { result.getSchedule().getResource().getName(); } } return results; } private MeasurementDataManagerUtility getConnectedUtilityInstance() { return MeasurementDataManagerUtility.getInstance(rhqDs); } private void pushToAlertSubsystem(Set<MeasurementData> data) { MeasurementReport fakeReport = new MeasurementReport(); for(MeasurementData datum : data) { if (datum instanceof MeasurementDataTrait) { fakeReport.addData((MeasurementDataTrait) datum); } else if (datum instanceof MeasurementDataNumeric) { fakeReport.addData((MeasurementDataNumeric) datum); } } this.measurementDataManager.mergeMeasurementReport(fakeReport); } }