/* * Copyright Terracotta, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.ehcache.impl.internal.statistics; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.Map; import org.ehcache.Cache; import org.ehcache.core.statistics.StoreOperationOutcomes; import org.ehcache.core.statistics.TierOperationOutcomes; import org.ehcache.core.statistics.TierStatistics; import org.ehcache.core.statistics.TypedValueStatistic; import org.terracotta.statistics.ConstantValueStatistic; import org.terracotta.statistics.OperationStatistic; import org.terracotta.statistics.ValueStatistic; import org.terracotta.statistics.extended.StatisticType; import static org.ehcache.impl.internal.statistics.StatsUtils.findStatisticOnDescendants; /** * Contains usage statistics relative to a given tier. */ class DefaultTierStatistics implements TierStatistics { private static final ValueStatistic<Long> NOT_AVAILABLE = ConstantValueStatistic.instance(-1L); private volatile CompensatingCounters compensatingCounters = CompensatingCounters.empty(); private final String tierName; private final Map<String, TypedValueStatistic> knownStatistics; private final OperationStatistic<TierOperationOutcomes.GetOutcome> get; private final OperationStatistic<StoreOperationOutcomes.PutOutcome> put; private final OperationStatistic<StoreOperationOutcomes.PutIfAbsentOutcome> putIfAbsent; private final OperationStatistic<StoreOperationOutcomes.ReplaceOutcome> replace; private final OperationStatistic<StoreOperationOutcomes.ConditionalReplaceOutcome> conditionalReplace; private final OperationStatistic<StoreOperationOutcomes.RemoveOutcome> remove; private final OperationStatistic<StoreOperationOutcomes.ConditionalRemoveOutcome> conditionalRemove; private final OperationStatistic<TierOperationOutcomes.EvictionOutcome> eviction; private final OperationStatistic<StoreOperationOutcomes.ExpirationOutcome> expiration; private final OperationStatistic<StoreOperationOutcomes.ComputeOutcome> compute; private final OperationStatistic<StoreOperationOutcomes.ComputeIfAbsentOutcome> computeIfAbsent; private final ValueStatistic<Long> mapping; private final ValueStatistic<Long> maxMapping; private final ValueStatistic<Long> allocatedMemory; private final ValueStatistic<Long> occupiedMemory; public DefaultTierStatistics(Cache<?, ?> cache, String tierName) { this.tierName = tierName; get = findOperationStatistic(cache, tierName, "tier", "get"); put = findOperationStatistic(cache, tierName, "put"); putIfAbsent = findOperationStatistic(cache, tierName, "putIfAbsent"); replace = findOperationStatistic(cache, tierName, "replace"); conditionalReplace = findOperationStatistic(cache, tierName, "conditionalReplace"); remove = findOperationStatistic(cache, tierName, "remove"); conditionalRemove = findOperationStatistic(cache, tierName, "conditionalRemove"); eviction = findOperationStatistic(cache, tierName, "tier", "eviction"); expiration = findOperationStatistic(cache, tierName, "expiration"); compute = findOperationStatistic(cache, tierName, "compute"); computeIfAbsent = findOperationStatistic(cache, tierName, "computeIfAbsent"); mapping = findValueStatistics(cache, tierName, "mappings"); maxMapping = findValueStatistics(cache, tierName, "maxMappings"); allocatedMemory = findValueStatistics(cache, tierName, "allocatedMemory"); occupiedMemory = findValueStatistics(cache, tierName, "occupiedMemory"); Map<String, TypedValueStatistic> knownStatistics = createKnownStatistics(tierName); this.knownStatistics = Collections.unmodifiableMap(knownStatistics); } private Map<String, TypedValueStatistic> createKnownStatistics(String tierName) { Map<String, TypedValueStatistic> knownStatistics = new HashMap<String, TypedValueStatistic>(7); addKnownStatistic(knownStatistics, tierName, "HitCount", get, new TypedValueStatistic(StatisticType.COUNTER) { @Override public Number value() { return getHits(); } }); addKnownStatistic(knownStatistics, tierName, "MissCount", get, new TypedValueStatistic(StatisticType.COUNTER) { @Override public Number value() { return getMisses(); } }); addKnownStatistic(knownStatistics, tierName, "PutCount", get, new TypedValueStatistic(StatisticType.COUNTER) { @Override public Number value() { return getPuts(); } }); addKnownStatistic(knownStatistics, tierName, "UpdateCount", get, new TypedValueStatistic(StatisticType.COUNTER) { @Override public Number value() { return getUpdates(); } }); addKnownStatistic(knownStatistics, tierName, "RemovalCount", get, new TypedValueStatistic(StatisticType.COUNTER) { @Override public Number value() { return getRemovals(); } }); addKnownStatistic(knownStatistics, tierName, "EvictionCount", get, new TypedValueStatistic(StatisticType.COUNTER) { @Override public Number value() { return getEvictions(); } }); addKnownStatistic(knownStatistics, tierName, "ExpirationCount", get, new TypedValueStatistic(StatisticType.COUNTER) { @Override public Number value() { return getExpirations(); } }); addKnownStatistic(knownStatistics, tierName, "MappingCount", mapping, new TypedValueStatistic(StatisticType.COUNTER) { @Override public Number value() { return getMappings(); } }); addKnownStatistic(knownStatistics, tierName, "MaxMappingCount", maxMapping, new TypedValueStatistic(StatisticType.COUNTER) { @Override public Number value() { return getMaxMappings(); } }); addKnownStatistic(knownStatistics, tierName, "AllocatedByteSize", allocatedMemory, new TypedValueStatistic(StatisticType.SIZE) { @Override public Number value() { return getAllocatedByteSize(); } }); addKnownStatistic(knownStatistics, tierName, "OccupiedByteSize", occupiedMemory, new TypedValueStatistic(StatisticType.SIZE) { @Override public Number value() { return getOccupiedByteSize(); } }); return knownStatistics; } public Map<String, TypedValueStatistic> getKnownStatistics() { return knownStatistics; } private static void addKnownStatistic(Map<String, TypedValueStatistic> knownStatistics, String tierName, String name, Object stat, TypedValueStatistic statistic) { if (stat != NOT_AVAILABLE) { knownStatistics.put(tierName + ":" + name, statistic); } } private <T extends Enum<T>> OperationStatistic<T> findOperationStatistic(Cache<?, ?> cache, String tierName, String tag, String stat) { OperationStatistic<T> s = findStatisticOnDescendants(cache, tierName, tag, stat); if(s == null) { return ZeroOperationStatistic.get(); } return s; } private <T extends Enum<T>> OperationStatistic<T> findOperationStatistic(Cache<?, ?> cache, String tierName, String stat) { OperationStatistic<T> s = findStatisticOnDescendants(cache, tierName, stat); if(s == null) { return ZeroOperationStatistic.get(); } return s; } private ValueStatistic<Long> findValueStatistics(Cache<?, ?> cache, String tierName, String statName) { ValueStatistic<Long> stat = findStatisticOnDescendants(cache, tierName, statName); if (stat == null) { return NOT_AVAILABLE; } return stat; } /** * Reset the values for this tier. However, note that {@code mapping, maxMappings, allocatedMemory, occupiedMemory} * but be reset since it doesn't make sense. */ public void clear() { compensatingCounters = compensatingCounters.snapshot(this); } public long getHits() { return get.sum(EnumSet.of(TierOperationOutcomes.GetOutcome.HIT)) + putIfAbsent.sum(EnumSet.of(StoreOperationOutcomes.PutIfAbsentOutcome.HIT)) + replace.sum(EnumSet.of(StoreOperationOutcomes.ReplaceOutcome.REPLACED)) + compute.sum(EnumSet.of(StoreOperationOutcomes.ComputeOutcome.HIT)) + computeIfAbsent.sum(EnumSet.of(StoreOperationOutcomes.ComputeIfAbsentOutcome.HIT)) + conditionalReplace.sum(EnumSet.of(StoreOperationOutcomes.ConditionalReplaceOutcome.REPLACED)) + conditionalRemove.sum(EnumSet.of(StoreOperationOutcomes.ConditionalRemoveOutcome.REMOVED)) - compensatingCounters.hits; } public long getMisses() { return get.sum(EnumSet.of(TierOperationOutcomes.GetOutcome.MISS)) + putIfAbsent.sum(EnumSet.of(StoreOperationOutcomes.PutIfAbsentOutcome.PUT)) + replace.sum(EnumSet.of(StoreOperationOutcomes.ReplaceOutcome.MISS)) + computeIfAbsent.sum(EnumSet.of(StoreOperationOutcomes.ComputeIfAbsentOutcome.NOOP)) + conditionalReplace.sum(EnumSet.of(StoreOperationOutcomes.ConditionalReplaceOutcome.MISS)) + conditionalRemove.sum(EnumSet.of(StoreOperationOutcomes.ConditionalRemoveOutcome.MISS)) - compensatingCounters.misses; } public long getPuts() { return put.sum(EnumSet.of(StoreOperationOutcomes.PutOutcome.PUT)) + putIfAbsent.sum(EnumSet.of(StoreOperationOutcomes.PutIfAbsentOutcome.PUT)) + put.sum(EnumSet.of(StoreOperationOutcomes.PutOutcome.REPLACED)) + compute.sum(EnumSet.of(StoreOperationOutcomes.ComputeOutcome.PUT)) + computeIfAbsent.sum(EnumSet.of(StoreOperationOutcomes.ComputeIfAbsentOutcome.PUT)) + replace.sum(EnumSet.of(StoreOperationOutcomes.ReplaceOutcome.REPLACED)) + conditionalReplace.sum(EnumSet.of(StoreOperationOutcomes.ConditionalReplaceOutcome.REPLACED)) - compensatingCounters.puts; } public long getUpdates() { return put.sum(EnumSet.of(StoreOperationOutcomes.PutOutcome.REPLACED)) + replace.sum(EnumSet.of(StoreOperationOutcomes.ReplaceOutcome.REPLACED)) + conditionalReplace.sum(EnumSet.of(StoreOperationOutcomes.ConditionalReplaceOutcome.REPLACED)) - compensatingCounters.updates; } public long getRemovals() { return remove.sum(EnumSet.of(StoreOperationOutcomes.RemoveOutcome.REMOVED)) + compute.sum(EnumSet.of(StoreOperationOutcomes.ComputeOutcome.REMOVED)) + conditionalRemove.sum(EnumSet.of(StoreOperationOutcomes.ConditionalRemoveOutcome.REMOVED)) - compensatingCounters.removals; } public long getEvictions() { return eviction.sum(EnumSet.of(TierOperationOutcomes.EvictionOutcome.SUCCESS)) - compensatingCounters.evictions; } public long getExpirations() { return expiration.sum() - compensatingCounters.expirations; } public long getMappings() { return mapping.value(); } public long getMaxMappings() { return maxMapping.value(); } public long getAllocatedByteSize() { return allocatedMemory.value(); } public long getOccupiedByteSize() { return occupiedMemory.value(); } private static class CompensatingCounters { final long hits; final long misses; final long puts; final long updates; final long removals; final long evictions; final long expirations; private CompensatingCounters(long hits, long misses, long puts, long updates, long removals, long evictions, long expirations) { this.hits = hits; this.misses = misses; this.puts = puts; this.updates = updates; this.removals = removals; this.evictions = evictions; this.expirations = expirations; } static CompensatingCounters empty() { return new CompensatingCounters(0, 0, 0, 0, 0, 0, 0); } CompensatingCounters snapshot(DefaultTierStatistics statistics) { return new CompensatingCounters( statistics.getHits() + hits, statistics.getMisses() + misses, statistics.getPuts() + puts, statistics.getUpdates() + updates, statistics.getRemovals() + removals, statistics.getEvictions() + evictions, statistics.getExpirations() + expirations ); } } }