/*
* 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.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import org.hyperic.hq.product.MetricValue;
import org.springframework.stereotype.Repository;
/**
* The MetricDataCache caches the last measurement keyed on the derived
* measurement id. The purpose of this cache is to avoid needing to go to
* the database when looking up the last value for a metric.
*
* If this area of the code becomes a bottleneck, we may want to consider
* ditching ehcache and just using a straight HashMap -- the code is
* simpler, and locking straightforward. However, it is currently nice to
* keep ehcache, as it allows us to configure sizes and get stats.
*/
@Repository
public class EhCacheMetricDataCache implements MetricDataCache {
// The cache name, must match what is in ehcache.xml
private static final String CACHENAME = "MetricDataCache";
private final Object cacheLock = new Object();
private Cache cache;
public EhCacheMetricDataCache() {
cache = CacheManager.getInstance().getCache(CACHENAME);
}
public Collection<DataPoint> bulkAdd(List<DataPoint> data) {
HashMap<Integer,DataPoint> cachedData = new HashMap<Integer,DataPoint>(data.size());
synchronized (cacheLock) {
for (DataPoint dp : data) {
if (add(dp.getMeasurementId(), dp.getMetricValue())) {
cachedData.put(dp.getMeasurementId(), dp);
}
}
}
return cachedData.values();
}
/**
* Add a MetricValue to the cache. This method checks the timestamp of
* the MetricValue to be added to ensure it's not an older data point than
* what is already cached.
*
* Each invocation of this method is synchronized internally, so consider
* using the {@link #bulkAdd(List) bulk add} for batch updates to the cache.
*
* @param mid The measurement id.
* @param mval The MetricValue to store.
* @return true if the MetricValue was added to the cache, false otherwise.
*/
public boolean add(Integer mid, MetricValue mval) {
// Need to synchronize on cache update since the back filler
// may be updating the cache concurrently with the data inserter.
// Without synchronization, a race condition could cause the latest
// metric to be overwritten in the cache by an older metric.
synchronized (cacheLock) {
Element el = cache.get(mid);
if (el != null) {
// Check if existing cached data is newer
MetricValue val = (MetricValue)el.getObjectValue();
if (val.getTimestamp() > mval.getTimestamp()) {
return false;
}
}
cache.put(new Element(mid, mval));
}
return true;
}
public Map<Integer,MetricValue> getAll(List<Integer> mids, long timestamp) {
final Map<Integer,MetricValue> rtn = new HashMap<Integer,MetricValue>(mids.size());
synchronized (cacheLock) {
for (final Integer mid : mids ) {
final Element elem = cache.get(mid);
if (elem == null) {
continue;
}
final MetricValue val = (MetricValue) elem.getObjectValue();
if (val != null && val.getTimestamp() >= timestamp) {
rtn.put(mid, val);
}
}
}
return rtn;
}
public MetricValue get(Integer mid, long timestamp) {
Element el;
synchronized (cacheLock) {
el = cache.get(mid);
}
if (el != null) {
MetricValue val = (MetricValue)el.getObjectValue();
if (val.getTimestamp() >= timestamp) {
return val;
}
}
return null;
}
public void remove(Integer mid) {
synchronized (cacheLock) {
cache.remove(mid);
}
}
}