/* * INESC-ID, Instituto de Engenharia de Sistemas e Computadores Investigação e Desevolvimento em Lisboa * Copyright 2013 INESC-ID and/or its affiliates and other * contributors as indicated by the @author tags. All rights reserved. * See the copyright.txt in the distribution for a full listing of * individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 3.0 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.infinispan.container.gmu; import org.infinispan.configuration.cache.Configuration; import org.infinispan.container.entries.InternalCacheEntry; import org.infinispan.container.entries.gmu.InternalGMUCacheEntry; import org.infinispan.container.versioning.EntryVersion; import org.infinispan.container.versioning.VersionGenerator; import org.infinispan.container.versioning.gmu.GMUVersion; import org.infinispan.container.versioning.gmu.GMUVersionGenerator; import org.infinispan.distribution.DistributionManager; import org.infinispan.factories.annotations.Inject; import org.infinispan.factories.annotations.Start; import org.infinispan.jmx.annotations.ManagedAttribute; import org.infinispan.jmx.annotations.ManagedOperation; import org.infinispan.remoting.transport.Address; import org.infinispan.util.concurrent.IsolationLevel; import org.rhq.helpers.pluginAnnotations.agent.DataType; import org.rhq.helpers.pluginAnnotations.agent.DisplayType; import org.rhq.helpers.pluginAnnotations.agent.MeasurementType; import org.rhq.helpers.pluginAnnotations.agent.Metric; import org.rhq.helpers.pluginAnnotations.agent.Operation; import org.rhq.helpers.pluginAnnotations.agent.Parameter; import java.io.BufferedWriter; import java.io.IOException; import java.util.Collection; import java.util.EnumMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; import static org.infinispan.container.gmu.GMUEntryFactoryImpl.wrap; import static org.infinispan.transaction.gmu.GMUHelper.toGMUVersion; import static org.infinispan.transaction.gmu.GMUHelper.toGMUVersionGenerator; /** * // TODO: Document this * * @author Hugo Pimentel * @author Pedro Ruivo * @since 5.2 */ public class L1GMUContainer { private final ConcurrentHashMap<Object, L1VersionChain> l1Container; private final EnumMap<Stat, AtomicLong> stats; private Configuration configuration; private DistributionManager distributionManager; private GMUVersionGenerator gmuVersionGenerator; private volatile boolean statisticsEnabled; private boolean enabled; public L1GMUContainer() { this.l1Container = new ConcurrentHashMap<Object, L1VersionChain>(); this.stats = new EnumMap<Stat, AtomicLong>(Stat.class); } @Inject public void injectConfiguration(Configuration configuration, DistributionManager distributionManager, VersionGenerator versionGenerator) { this.enabled = configuration.locking().isolationLevel() == IsolationLevel.SERIALIZABLE && configuration.clustering().l1().enabled(); if (!enabled) { return; } this.configuration = configuration; this.distributionManager = distributionManager; this.gmuVersionGenerator = toGMUVersionGenerator(versionGenerator); } @Start public void setStatisticEnabled() { if (!enabled) { return; } this.statisticsEnabled = configuration.jmxStatistics().enabled(); } public final InternalGMUCacheEntry getValidVersion(Object key, EntryVersion txVersion, Collection<Address> readFrom) { if (!enabled) { return null; } L1VersionChain versionChain = l1Container.get(key); if (versionChain != null) { VersionEntry<L1Entry> versionEntry = findMaxVersion(versionChain, txVersion, readFrom); if (versionEntry.isFound()) { L1Entry l1Entry = versionEntry.getEntry(); Address owner = distributionManager.getPrimaryLocation(key); if (isValid(l1Entry, txVersion, owner)) { updateStats(Stat.CACHE_HIT); EntryVersion updatedTxVersion = gmuVersionGenerator.mergeAndMax(txVersion, l1Entry.getCreationVersion()); return wrap(key, l1Entry.getValue(), versionEntry.isMostRecent(), updatedTxVersion, null, null); } else { updateStats(Stat.CACHE_MISS_OLD_VERSION); } } else { updateStats(Stat.CACHE_MISS_NO_VERSION); } } else { updateStats(Stat.CACHE_MISS_NO_KEY); } return null; } public final void insertOrUpdate(Object key, InternalGMUCacheEntry value) { if (!enabled) { return; } L1VersionChain versionChain = l1Container.get(key); if (versionChain == null) { versionChain = new L1VersionChain(); L1VersionChain old = l1Container.putIfAbsent(key, versionChain); versionChain = old == null ? versionChain : old; } versionChain.add(new L1Entry(value)); } public final void handleNewEntries() { //TODO } public final boolean contains(Object key) { return l1Container.containsKey(key); } public final void clear() { l1Container.clear(); } public final String chainToString() { StringBuilder stringBuilder = new StringBuilder(); for (Map.Entry<Object, L1VersionChain> entry : l1Container.entrySet()) { stringBuilder.append(entry.getKey()) .append("=>"); entry.getValue().chainToString(stringBuilder); stringBuilder.append("\n"); } return stringBuilder.toString(); } public final VersionChain<?> getVersionChain(Object key) { return l1Container.get(key); } @ManagedOperation(description = "Resets statistics gathered by this component") @Operation(displayName = "Reset Statistics") public void resetStatistics() { for (AtomicLong atomicLong : stats.values()) { atomicLong.set(0); } } @ManagedAttribute(description = "Statistic enabled") @Metric(displayName = "Statistics enabled", dataType = DataType.TRAIT) public boolean isStatisticsEnabled() { return this.statisticsEnabled; } @ManagedOperation(description = "Enable/disable statistics") @Operation(displayName = "Enable/disable statistics") public void setStatisticsEnabled(@Parameter(name = "enabled", description = "Whether statistics should be enabled or disabled (true/false)") boolean enabled) { this.statisticsEnabled = enabled; } @ManagedAttribute(description = "Number of cache misses. Reason: only old versions") @Metric(displayName = "cacheMissOldVersion", measurementType = MeasurementType.TRENDSUP, displayType = DisplayType.SUMMARY) public long getCacheMissOldVersionsOnly() { return stats.get(Stat.CACHE_MISS_OLD_VERSION).get(); } @ManagedAttribute(description = "Number of cache misses. Reason: only new versions") @Metric(displayName = "cacheMissNoVersionThatFits", measurementType = MeasurementType.TRENDSUP, displayType = DisplayType.SUMMARY) public long getCacheMissNoVersionThatFits() { return stats.get(Stat.CACHE_MISS_NO_VERSION).get(); } @ManagedAttribute(description = "Number of cache misses. Reason: key not found") @Metric(displayName = "cacheMissKeyNotFound", measurementType = MeasurementType.TRENDSUP, displayType = DisplayType.SUMMARY) public long getCacheMissKeyNotFound() { return stats.get(Stat.CACHE_MISS_NO_KEY).get(); } @ManagedAttribute(description = "Number of Caches hits") @Metric(displayName = "cacheHits", description = "Number of caches hit") public long getCacheHits() { return stats.get(Stat.CACHE_HIT).get(); } public final void gc(GMUVersion version) { for (Map.Entry<Object, L1VersionChain> entry : l1Container.entrySet()) { entry.getValue().gc(version); } } private boolean isValid(L1Entry entry, EntryVersion txVersion, Address owner) { long entryVersionValue = toGMUVersion(entry.getReadVersion()).getVersionValue(owner); long txEntryVersionValue = toGMUVersion(txVersion).getVersionValue(owner); return entryVersionValue == GMUVersion.NON_EXISTING || txEntryVersionValue == GMUVersion.NON_EXISTING || entryVersionValue >= txEntryVersionValue; } private VersionEntry<L1Entry> findMaxVersion(L1VersionChain versionChain, EntryVersion txVersion, Collection<Address> readFrom) { EntryVersion maxVersion = gmuVersionGenerator.calculateMaxVersionToRead(txVersion, readFrom); return versionChain.get(maxVersion); } private void updateStats(Stat stat) { if (statisticsEnabled) { stats.get(stat).incrementAndGet(); } } private static enum Stat { CACHE_HIT, CACHE_MISS_OLD_VERSION, CACHE_MISS_NO_VERSION, CACHE_MISS_NO_KEY } private static class L1VersionBody extends VersionBody<L1Entry> { private L1VersionBody(L1Entry value) { super(value); } @Override public EntryVersion getVersion() { return getValue().getCreationVersion(); } @Override public boolean isOlder(VersionBody<L1Entry> otherBody) { L1Entry thisEntry = getValue(); L1Entry otherEntry = otherBody.getValue(); if (thisEntry.getValue() == null) { return true; } else if (otherEntry.getValue() == null) { return false; } return isOlder(thisEntry.getValue().getVersion(), otherEntry.getValue().getVersion()); } @Override public boolean isOlderOrEquals(EntryVersion entryVersion) { return isOlderOrEquals(getValue().getCreationVersion(), entryVersion); } @Override public boolean isEqual(VersionBody<L1Entry> otherBody) { L1Entry thisEntry = getValue(); L1Entry otherEntry = otherBody.getValue(); if (thisEntry.getValue() == null) { return otherEntry.getValue() == null; } else if (otherEntry.getValue() == null) { return false; } return isEqual(thisEntry.getValue().getVersion(), otherEntry.getValue().getVersion()); } @Override public boolean isRemove() { return false; } @Override public void reincarnate(VersionBody<L1Entry> other) { getValue().setReadVersion(other.getValue().getReadVersion()); } @Override public VersionBody<L1Entry> gc(EntryVersion minVersion) { if (minVersion == null || isOlderOrEquals(getValue().getCreationVersion(), minVersion)) { VersionBody<L1Entry> previous = getPrevious(); setPrevious(null); return previous; } else { return getPrevious(); } } @Override protected boolean isExpired(long now) { return false; } } private class L1VersionChain extends VersionChain<L1Entry> { @Override protected VersionBody<L1Entry> newValue(L1Entry value) { return new L1VersionBody(value); } @Override protected void writeValue(BufferedWriter writer, L1Entry value) throws IOException { writer.write(String.valueOf(value.getValue().getValue())); writer.write("=["); writer.write(String.valueOf(value.getCreationVersion())); writer.write(","); writer.write(String.valueOf(value.getReadVersion())); writer.write("]"); } } private class L1Entry { private final InternalCacheEntry value; private final EntryVersion creationVersion; private EntryVersion readVersion; private boolean invalid; public L1Entry(InternalGMUCacheEntry gmuCacheEntry) { this.value = gmuCacheEntry.getInternalCacheEntry(); this.creationVersion = gmuCacheEntry.getCreationVersion(); this.readVersion = gmuCacheEntry.getMaximumValidVersion(); this.invalid = false; } public final InternalCacheEntry getValue() { return value; } public EntryVersion getCreationVersion() { return creationVersion; } public EntryVersion getReadVersion() { return readVersion; } public void setReadVersion(EntryVersion readVersion) { if (readVersion == null) { return; } else if (this.readVersion == null) { this.readVersion = readVersion; return; } this.readVersion = gmuVersionGenerator.mergeAndMax(readVersion, this.readVersion); } public boolean isInvalid() { return invalid; } public void setInvalid(boolean invalid) { this.invalid = invalid; } @Override public String toString() { return "L1Entry{" + "value=" + value + ", creationVersion=" + creationVersion + ", readVersion=" + readVersion + ", invalid=" + invalid + '}'; } } }