/*
* 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.core.pc.measurement;
import java.lang.management.ManagementFactory;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.rhq.core.clientapi.agent.measurement.MeasurementAgentService;
import org.rhq.core.domain.measurement.DataType;
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.MeasurementReport;
import org.rhq.core.domain.measurement.MeasurementSchedule;
import org.rhq.core.domain.measurement.MeasurementScheduleRequest;
import org.rhq.core.domain.measurement.NumericType;
import org.rhq.core.domain.measurement.ResourceMeasurementScheduleRequest;
import org.rhq.core.domain.resource.Resource;
import org.rhq.core.domain.resource.ResourceType;
import org.rhq.core.pc.ContainerService;
import org.rhq.core.pc.PluginContainer;
import org.rhq.core.pc.PluginContainerConfiguration;
import org.rhq.core.pc.agent.AgentService;
import org.rhq.core.pc.agent.AgentServiceStreamRemoter;
import org.rhq.core.pc.inventory.InventoryManager;
import org.rhq.core.pc.inventory.ResourceContainer;
import org.rhq.core.pc.util.ComponentUtil;
import org.rhq.core.pc.util.FacetLockType;
import org.rhq.core.pc.util.LoggingThreadFactory;
import org.rhq.core.pluginapi.measurement.MeasurementFacet;
/**
* Manage the scheduled process of measurement data collection, detection and sending across all plugins.
*
* <p/>
* <p>This is an agent service; its interface is made remotely accessible if this is deployed within the agent.</p>
*
* @author Greg Hinkle
* @author John Mazzitelli
*/
public class MeasurementManager extends AgentService implements MeasurementAgentService, ContainerService,
MeasurementManagerMBean {
public static final String OBJECT_NAME = "rhq.pc:type=MeasurementManager";
private static final String COLLECTOR_THREAD_POOL_NAME = "MeasurementManager.collector";
private static final String SENDER_THREAD_POOL_NAME = "MeasurementManager.sender";
private static final Random RANDOM = new Random();
static final int FACET_METHOD_TIMEOUT = 30 * 1000; // 30 seconds
static final Log LOG = LogFactory.getLog(MeasurementManager.class);
private final ScheduledThreadPoolExecutor collectorThreadPool;
private final ScheduledThreadPoolExecutor senderThreadPool;
private final MeasurementSenderRunner measurementSenderRunner;
private final MeasurementCollectorRunner measurementCollectorRunner;
private final PluginContainerConfiguration configuration;
private final PriorityQueue<ScheduledMeasurementInfo> scheduledRequests = new PriorityQueue<ScheduledMeasurementInfo>(
10000);
private final InventoryManager inventoryManager;
private final Map<Integer, String> traitCache = new HashMap<Integer, String>();
private final Map<Integer, CachedValue> perMinuteCache = new HashMap<Integer, CachedValue>();
private volatile MeasurementReport activeReport = new MeasurementReport();
private final ReentrantReadWriteLock measurementLock = new ReentrantReadWriteLock(true);
// -- monitoring information
private final AtomicLong collectedMeasurements = new AtomicLong(0);
private final AtomicLong totalTimeCollecting = new AtomicLong(0);
private final AtomicLong sinceLastCollectedMeasurements = new AtomicLong(0);
private final AtomicLong sinceLastCollectedTime = new AtomicLong(System.currentTimeMillis());
private final AtomicLong lateCollections = new AtomicLong(0);
private final AtomicLong failedCollection = new AtomicLong(0);
public MeasurementManager(PluginContainerConfiguration configuration, AgentServiceStreamRemoter streamRemoter,
InventoryManager inventoryManager) {
super(MeasurementAgentService.class, streamRemoter);
this.configuration = configuration;
this.inventoryManager = inventoryManager;
if (configuration.isInsideAgent()) {
int threadPoolSize = configuration.getMeasurementCollectionThreadPoolSize();
collectorThreadPool = new ScheduledThreadPoolExecutor(threadPoolSize, new LoggingThreadFactory(
COLLECTOR_THREAD_POOL_NAME, true));
senderThreadPool = new ScheduledThreadPoolExecutor(2, new LoggingThreadFactory(SENDER_THREAD_POOL_NAME,
true));
measurementSenderRunner = new MeasurementSenderRunner(this);
measurementCollectorRunner = new MeasurementCollectorRunner(this);
} else {
senderThreadPool = null;
collectorThreadPool = null;
measurementSenderRunner = null;
measurementCollectorRunner = null;
}
}
public void initialize() {
LOG.info("Initializing Measurement Manager...");
if (configuration.isStartManagementBean()) {
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
try {
server.registerMBean(this, new ObjectName(OBJECT_NAME));
} catch (JMException e) {
LOG.error("Unable to register MeasurementManagerMBean", e);
}
}
if (configuration.isInsideAgent()) {
long collectionInitialDelaySecs = configuration.getMeasurementCollectionInitialDelay();
// Schedule the measurement sender to send measurement reports periodically.
senderThreadPool.scheduleAtFixedRate(measurementSenderRunner, collectionInitialDelaySecs, 30,
TimeUnit.SECONDS);
// Schedule the measurement collector to collect metrics periodically, whenever there are one or more
// metrics due to be collected.
collectorThreadPool.schedule(new MeasurementCollectionRequester(), collectionInitialDelaySecs,
TimeUnit.SECONDS);
// Load persistent measurement schedules from the InventoryManager and reconstitute them.
Resource platform = inventoryManager.getPlatform();
if (platform == null) {
throw new IllegalStateException("null platform");
}
reschedule(platform);
}
LOG.info("Measurement Manager initialized.");
}
class MeasurementCollectionRequester implements Runnable {
@Override
public void run() {
try {
while (!collectorThreadPool.isShutdown()) {
long next = getNextExpectedCollectionTime();
if (next == Long.MIN_VALUE) {
Thread.sleep(10000);
} else {
long delay = next - System.currentTimeMillis();
if (delay <= 0) {
// collectorThreadPool.execute(measurementCollectorRunner);
measurementCollectorRunner.call();
} else {
if (!collectorThreadPool.isShutdown()) {
Thread.sleep(delay);
}
}
}
}
} catch (InterruptedException e) {
// Log nothing - if we got interrupted, it's probably because the PC is shutting down.
Thread.currentThread().interrupt();
} catch (Throwable e) {
LOG.error("Measurement collection shutting down due to error", e);
} finally {
LOG.info("Shutting down measurement collection...");
}
}
}
private void reschedule(Resource resource) {
if (LOG.isDebugEnabled()) {
LOG.debug("In Reschedule for: " + resource);
}
int resourceId = resource.getId();
if (resourceId != 0) {
ResourceContainer container = this.inventoryManager.getResourceContainer(resourceId);
if (container != null) {
Set<MeasurementScheduleRequest> schedules = container.getMeasurementSchedule();
if (schedules != null) {
if (LOG.isDebugEnabled()) {
LOG.debug("Rescheduling: " + resource.getId());
}
scheduleCollection(resourceId, schedules);
}
for (Resource child : this.inventoryManager.getContainerChildren(resource, container)) {
reschedule(child);
}
}
}
}
public MeasurementReport getActiveReport() {
return activeReport;
}
ReentrantReadWriteLock getLock() {
return measurementLock;
}
/**
* Check if the passed trait is new or has changed
* @param scheduleId
* @param traitValue
*
* @return true if the value is new or changed and should be included in the report
*/
public boolean checkTrait(int scheduleId, String traitValue) {
if (traitCache.containsKey(scheduleId)) {
String historic = traitCache.get(scheduleId);
if (((historic == null) && (traitValue != null)) || ((historic != null) && !historic.equals(traitValue))) {
traitCache.put(scheduleId, traitValue);
return true;
} else {
return false;
}
} else {
traitCache.put(scheduleId, traitValue);
return true;
}
}
/**
* If you want to get a cached value of a trait, pass in its schedule ID.
* This is useful if you don't care to obtain the latest-n-greated value of the trait,
* and you want to avoid making a live call to the managed resource to obtain its value.
* Note that if the trait is not yet cached, this will return null, and the caller will
* be forced to make a live call to obtain the trait value, but at least this can help
* avoid unnecessarily calling the live resource.
*
* @param scheduleId the schedule for the trait for a specific resource
* @return the trait's cached value, <code>null</code> if not available
*/
public String getCachedTraitValue(int scheduleId) {
return traitCache.get(scheduleId);
}
public void perMinuteItizeData(MeasurementReport report) {
Iterator<MeasurementDataNumeric> iter = report.getNumericData().iterator();
while (iter.hasNext()) {
MeasurementData d = iter.next();
MeasurementDataNumeric numeric = (MeasurementDataNumeric) d;
if (numeric.isPerMinuteCollection()) {
Double perMinuteValue = updatePerMinuteMetric(numeric);
if (perMinuteValue == null) {
// This is the first collection, don't return the value yet
iter.remove();
} else {
// set the value to the transformed rate value
numeric.setValue(perMinuteValue);
}
}
}
}
@Override
public void shutdown() {
if (this.collectorThreadPool != null) {
LOG.debug("Shutting down measurement collector thread pool...");
PluginContainer.shutdownExecutorService(this.collectorThreadPool, true);
}
if (this.senderThreadPool != null) {
LOG.debug("Shutting down measurement sender thread pool...");
PluginContainer.shutdownExecutorService(this.senderThreadPool, true);
}
if (configuration.isStartManagementBean()) {
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
try {
server.unregisterMBean(new ObjectName(OBJECT_NAME));
} catch (JMException e) {
LOG.warn("Unable to unregister MeasurementManagerMBean", e);
}
}
}
/**
* This remoted method allows the server to schedule a bunch of resources with one call.
*
* This method will update the set of {@link MeasurementSchedule}s in the agent.
*
* Use {@link #scheduleCollection(Set)} if you want to replace the existing ones.
*
* @param scheduleRequests
*/
@Override
public synchronized void updateCollection(Set<ResourceMeasurementScheduleRequest> scheduleRequests) {
for (ResourceMeasurementScheduleRequest resourceRequest : scheduleRequests) {
ResourceContainer resourceContainer = inventoryManager.getResourceContainer(resourceRequest.getResourceId());
if (resourceContainer != null) {
// Update (not overwrite) measurement schedule data ...
resourceContainer.updateMeasurementSchedule(resourceRequest.getMeasurementSchedules());
// ... and then reschedule collection
scheduleCollection(resourceRequest.getResourceId(), resourceRequest.getMeasurementSchedules());
if (resourceRequest.getAvailabilitySchedule() != null) {
// Set availability schedule data if present
// This method also triggers a reschedule of availability check
resourceContainer.setAvailabilitySchedule(resourceRequest.getAvailabilitySchedule());
}
} else {
// This will happen when the server sends down schedules to an agent with a cleaned inventory
// Its ok to skip these because the agent will request a reschedule once its been able to synchronize
// and add these to inventory
LOG.trace("Resource container was null - could not schedule collection for resource "
+ resourceRequest.getResourceId());
}
}
// TODO GH: Should I kick the pool or should I just go with the 30 second granularity on collections?
// If I get much more granular then the server could end up with too many small reports from many agents
// This may be another reason to have a separate sending mechanism from the collection mechanism.
clearDuplicateSchedules();
}
/**
* This remoted method allows the server to schedule a bunch of resources with one call.
*
* BE CAREFUL, as this will replace all existing schedules with the passed set.
*
* Use {@link #updateCollection(Set)} if you want to schedule additional {@link MeasurementSchedule}s
*
* @param scheduleRequests
*/
@Override
public synchronized void scheduleCollection(Set<ResourceMeasurementScheduleRequest> scheduleRequests) {
for (ResourceMeasurementScheduleRequest resourceRequest : scheduleRequests) {
ResourceContainer resourceContainer = inventoryManager.getResourceContainer(resourceRequest.getResourceId());
if (resourceContainer != null) {
// Set measurement schedule data ...
resourceContainer.setMeasurementSchedule(resourceRequest.getMeasurementSchedules());
// ... and then reschedule collection
scheduleCollection(resourceRequest.getResourceId(), resourceRequest.getMeasurementSchedules());
// Set availability schedule data
// This method also triggers a reschedule of availability check
resourceContainer.setAvailabilitySchedule(resourceRequest.getAvailabilitySchedule());
} else {
// This will happen when the server sends down schedules to an agent with a cleaned inventory
// It's ok to skip these because the agent will request a reschedule once its been able to synchronize
// and add these to inventory
LOG.debug("Resource container was null, could not schedule collection for resource "
+ resourceRequest.getResourceId());
}
}
// TODO GH: Should I kick the pool or should I just go with the 30 second granularity on collections?
// If I get much more granular then the server could end up with too many small reports from many agents
// This may be another reason to have a separate sending mechanism from the collection mechanism.
clearDuplicateSchedules();
}
/**
* Used to direct reschedule resources from the persisted to disk schedules
*
* @param resourceId The resource to collect on
* @param requests The measurements to collect
*/
public synchronized void scheduleCollection(int resourceId, Set<MeasurementScheduleRequest> requests) {
// This ensures that all the schedules for a single resource start at the same time
// This will enable them to be collected at the same time
long firstCollection = System.currentTimeMillis();
// see BZ 751231 for why we delay the first collection
if (configuration != null) {
firstCollection += (configuration.getMeasurementCollectionInitialDelay() * 1000L);
} else {
firstCollection += 30000L;
}
for (MeasurementScheduleRequest request : requests) {
ScheduledMeasurementInfo info = new ScheduledMeasurementInfo(request, resourceId);
info.setNextCollection(firstCollection);
this.scheduledRequests.remove(info);
// Don't add it if collection is disabled for this resource
if (info.isEnabled()) {
this.scheduledRequests.offer(info);
}
}
}
@Override
public synchronized void unscheduleCollection(Set<Integer> resourceIds) {
Iterator<ScheduledMeasurementInfo> itr = this.scheduledRequests.iterator();
while (itr.hasNext()) {
ScheduledMeasurementInfo info = itr.next();
if (resourceIds.contains(info.getResourceId())) {
itr.remove();
}
}
}
private void clearDuplicateSchedules() {
HashSet<Integer> set = new HashSet<Integer>(this.scheduledRequests.size());
Iterator<ScheduledMeasurementInfo> iter = this.scheduledRequests.iterator();
while (iter.hasNext()) {
ScheduledMeasurementInfo info = iter.next();
if (set.contains(info.getScheduleId())) {
LOG.debug("Found duplicate schedule - will remove it: " + info);
iter.remove();
} else {
set.add(info.getScheduleId());
}
}
}
// spinder 12/16/11. BZ 760139. Modified to return empty sets instead of 'null' even for erroneous conditions.
// Server side logging or erroneous runtime conditions still occurs, but callers to getRealTimeMeasurementValues
// won't have to additionally check for null values now. This is a safe and better pattern.
public Set<MeasurementData> getRealTimeMeasurementValue(int resourceId, Set<MeasurementScheduleRequest> requests) {
if (requests.size() == 0) {
// There's no need to even call getValues() on the ResourceComponent if the list of metric names is empty.
return Collections.emptySet();
}
MeasurementFacet measurementFacet;
ResourceContainer resourceContainer = inventoryManager.getResourceContainer(resourceId);
if (resourceContainer == null) {
LOG.warn("Can not get resource container for resource with id " + resourceId);
return Collections.emptySet();
}
Resource resource = resourceContainer.getResource();
ResourceType resourceType = resource.getResourceType();
if (resourceType.getMetricDefinitions().isEmpty())
return Collections.emptySet();
try {
measurementFacet = ComponentUtil.getComponent(resourceId, MeasurementFacet.class, FacetLockType.READ,
FACET_METHOD_TIMEOUT, true, true, true);
} catch (Exception e) {
LOG.warn("Cannot get measurement facet for Resource [" + resourceId + "]. Cause: " + e);
return Collections.emptySet();
}
MeasurementReport report = new MeasurementReport();
for (MeasurementScheduleRequest request : requests) {
request.setEnabled(true);
}
try {
measurementFacet.getValues(report, Collections.unmodifiableSet(requests));
} catch (Throwable t) {
LOG.error("Could not get measurement values", t);
return Collections.emptySet();
}
Iterator<MeasurementDataNumeric> iterator = report.getNumericData().iterator();
while (iterator.hasNext()) {
MeasurementDataNumeric numeric = iterator.next();
if (numeric.isPerMinuteCollection()) {
CachedValue currentValue = perMinuteCache.get(numeric.getScheduleId());
if (currentValue == null) {
iterator.remove();
} else {
numeric.setValue(calculatePerMinuteValue(numeric, currentValue));
}
}
}
Set<MeasurementData> values = new HashSet<MeasurementData>();
values.addAll(report.getNumericData());
values.addAll(report.getTraitData());
return values;
}
@Override
public long getNextExpectedCollectionTime() {
ScheduledMeasurementInfo nextScheduledMeasurement = this.scheduledRequests.peek();
if (nextScheduledMeasurement == null) {
return Long.MIN_VALUE;
} else {
return nextScheduledMeasurement.getNextCollection();
}
}
/**
* Returns the complete set of scheduled measurement collections.
*
* @return all measurement schedules
*/
public synchronized Set<ScheduledMeasurementInfo> getNextScheduledSet() {
ScheduledMeasurementInfo first = this.scheduledRequests.peek();
if ((first == null) || (first.getNextCollection() > System.currentTimeMillis())) {
return null;
}
Set<ScheduledMeasurementInfo> nextScheduledSet = new HashSet<ScheduledMeasurementInfo>();
ScheduledMeasurementInfo next = this.scheduledRequests.peek();
while ((next != null) && (next.getResourceId() == first.getResourceId())
&& (next.getNextCollection() == first.getNextCollection())) {
nextScheduledSet.add(this.scheduledRequests.poll());
next = this.scheduledRequests.peek();
}
return nextScheduledSet;
}
/**
* Reschedules the given measurement schedules so the next collection occurs in the future.
* The next collection will be pushed out by the number of seconds of the schedule's collection
* interval.
*
* @param scheduledMeasurementInfos the schedules to reschedule
*/
public synchronized void reschedule(Set<ScheduledMeasurementInfo> scheduledMeasurementInfos) {
for (ScheduledMeasurementInfo scheduledMeasurement : scheduledMeasurementInfos) {
long interval = scheduledMeasurement.getInterval();
scheduledMeasurement.setNextCollection(scheduledMeasurement.getNextCollection() + interval);
this.scheduledRequests.offer(scheduledMeasurement);
}
}
/**
* Reschedules the given [late] measurement schedules so the next collection occurs in the future, and with
* some randomization to the nextCollection times. Late collections are those that were not actually
* performed due to collection falling behind. The nextCollection will be set to:
* <pre>
* Now + 30s + [1..Interval]
*
* Where [1..Interval] is some random number of seconds no lower that 1 and no higher than the standard interval
* for the measurement.
* </pre>
*
* @param scheduledMeasurementInfos the late schedules to reschedule
*/
synchronized void rescheduleLateCollections(Set<ScheduledMeasurementInfo> scheduledMeasurementInfos) {
if (LOG.isDebugEnabled()) {
LOG.debug("Rescheduling [" + scheduledMeasurementInfos.size() + "] late collections: "
+ scheduledMeasurementInfos);
}
long now = System.currentTimeMillis();
for (ScheduledMeasurementInfo scheduledMeasurement : scheduledMeasurementInfos) {
// push out 30s from the current time to at least get a minimal 30s interval
long nextCollection = now + 30000L;
// then add a random number of seconds [1..interval]. This will spread out the next collection times to
// hopefully avoid the "hot-spot" that caused us to fall behind.
long interval = scheduledMeasurement.getInterval();
int maxRandomInterval = (int) (interval / 1000L); // exclusive upper bound
long randomInterval = ((RANDOM.nextInt(maxRandomInterval) + 1) * 1000L);
nextCollection += randomInterval;
if (LOG.isTraceEnabled()) {
LOG.trace("Rescheduling next collection of [" + scheduledMeasurement + "] for "
+ new Date(nextCollection));
}
scheduledMeasurement.setNextCollection(nextCollection);
this.scheduledRequests.offer(scheduledMeasurement);
}
}
/**
* Sends the given measurement report to the server, if this plugin container has server services that it can
* communicate with.
*
* @param report
*/
public void sendMeasurementReport(MeasurementReport report) {
this.collectedMeasurements.addAndGet(report.getDataCount());
this.sinceLastCollectedMeasurements.addAndGet(report.getDataCount());
this.totalTimeCollecting.addAndGet(report.getCollectionTime());
if (configuration.getServerServices() != null) {
try {
configuration.getServerServices().getMeasurementServerService().mergeMeasurementReport(report);
} catch (Exception e) {
LOG.warn("Failure to report measurements to server", e);
}
}
}
private Double updatePerMinuteMetric(MeasurementDataNumeric numeric) {
CachedValue previousValue = this.perMinuteCache.get(numeric.getScheduleId());
this.perMinuteCache.put(numeric.getScheduleId(), new CachedValue(numeric.getTimestamp(), numeric.getValue()));
return calculatePerMinuteValue(numeric, previousValue);
}
private Double calculatePerMinuteValue(MeasurementDataNumeric numeric, CachedValue currentValue) {
Double perMinuteValue = null;
if (currentValue != null) {
long timeDifference = numeric.getTimestamp() - currentValue.timestamp;
perMinuteValue = (60000D / timeDifference) * (numeric.getValue() - currentValue.value);
if (numeric.getRawNumericType() == NumericType.TRENDSDOWN)
perMinuteValue *= -1D; // Multiply by -1, so per-minute value is positive.
if (perMinuteValue < 0)
// A negative value means the raw metric must have been reset, which means we can't accurately
// calculate a per-minute value this time around; return null to indicate this.
perMinuteValue = null;
}
return perMinuteValue;
}
@Override
public Map<String, Object> getMeasurementScheduleInfoForResource(int resourceId) {
Map<String, Object> results = null;
for (ScheduledMeasurementInfo info : new PriorityQueue<ScheduledMeasurementInfo>(scheduledRequests)) {
if (info.getResourceId() == resourceId) {
if (results == null) {
results = new HashMap<String, Object>();
}
String scheduleId = String.valueOf(info.getScheduleId());
String interval = String.valueOf(info.getInterval()) + "ms";
results.put(scheduleId, interval);
}
}
return results;
}
/**
* Given the name of a trait, this will find the value of that trait for the given resource.
*
* @param container the container of the resource whose trait value is to be obtained
* @param traitName the name of the trait whose value is to be obtained
*
* @return the value of the trait, or <code>null</code> if unknown
*/
public String getTraitValue(ResourceContainer container, String traitName) {
Integer traitScheduleId = null;
Set<MeasurementScheduleRequest> schedules = container.getMeasurementSchedule();
for (MeasurementScheduleRequest schedule : schedules) {
if (schedule.getName().equals(traitName)) {
if (schedule.getDataType() != DataType.TRAIT) {
throw new IllegalArgumentException("Measurement named [" + traitName + "] for resource ["
+ container.getResource().getName() + "] is not a trait, it is of type ["
+ schedule.getDataType() + "]");
}
traitScheduleId = schedule.getScheduleId();
}
}
if (traitScheduleId == null) {
throw new IllegalArgumentException("There is no trait [" + traitName + "] for resource ["
+ container.getResource().getName() + "]");
}
String traitValue = getCachedTraitValue(traitScheduleId);
if (traitValue == null) {
// the trait hasn't been collected yet, so it isn't cached. We need to get its live value
Set<MeasurementScheduleRequest> requests = new HashSet<MeasurementScheduleRequest>();
requests.add(new MeasurementScheduleRequest(MeasurementScheduleRequest.NO_SCHEDULE_ID, traitName, 0, true,
DataType.TRAIT));
Set<MeasurementData> dataset = getRealTimeMeasurementValue(container.getResource().getId(), requests);
if (dataset.size() == 1) {
Object value = dataset.iterator().next().getValue();
if (value != null) {
traitValue = value.toString();
}
} else if (dataset.isEmpty()) {
// so the agent doesn't have a cached value of the trait an the resource seems to be either down
// or unable to collect the trait.
// Let's try asking the server for the last known value
MeasurementDataTrait value = configuration.getServerServices().getMeasurementServerService()
.getLastKnownTraitValue(traitScheduleId);
if (value != null) {
traitValue = value.getValue();
}
} else {
throw new IllegalStateException(
"Asked for value of trait " + traitName + " on resource " + container.getResource() +
" but got more than one value back. This is unexpected.");
}
}
return traitValue;
}
// -- MBean monitoring methods
@Override
public long getMeasurementsCollected() {
return this.collectedMeasurements.get();
}
@Override
public long getMeasurementsCollectedPerMinute() {
long now = System.currentTimeMillis();
// Make sure we track at least 60 seconds before resetting
if ((now - this.sinceLastCollectedTime.get()) > 60000) {
sinceLastCollectedTime.set(now);
sinceLastCollectedMeasurements.set(0);
}
if ((now - sinceLastCollectedTime.get()) == 0) {
return 0;
}
long collectionTimeInMinutes = (now - sinceLastCollectedTime.get()) / 1000L / 60L;
if (collectionTimeInMinutes == 0) {
return 0;
}
long ret = this.sinceLastCollectedMeasurements.get() / (collectionTimeInMinutes);
return ret;
}
@Override
public long getCurrentlyScheduleMeasurements() {
return this.scheduledRequests.size();
}
@Override
public long getTotalTimeCollectingMeasurements() {
return this.totalTimeCollecting.get();
}
@Override
public long getLateCollections() {
return lateCollections.get();
}
public MeasurementReport swapReport() {
try {
this.measurementLock.writeLock().lock();
MeasurementReport previousReport = this.activeReport;
this.activeReport = new MeasurementReport();
return previousReport;
} finally {
this.measurementLock.writeLock().unlock();
}
}
void incrementLateCollections(int count) {
this.lateCollections.addAndGet(count);
}
void incrementFailedCollections(int count) {
this.failedCollection.addAndGet(count);
}
@Override
public long getFailedCollections() {
return failedCollection.get();
}
private static class CachedValue {
CachedValue(long timestamp, double value) {
this.timestamp = timestamp;
this.value = value;
}
long timestamp;
double value;
}
public InventoryManager getInventoryManager() {
return inventoryManager;
}
}