/*
* 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 java.util.Set;
import org.ehcache.core.InternalCache;
import org.ehcache.core.statistics.BulkOps;
import org.ehcache.core.statistics.CacheOperationOutcomes;
import org.ehcache.core.statistics.CacheStatistics;
import org.ehcache.core.statistics.TierStatistics;
import org.ehcache.core.statistics.TypedValueStatistic;
import org.terracotta.statistics.OperationStatistic;
import org.terracotta.statistics.derived.LatencySampling;
import org.terracotta.statistics.derived.MinMaxAverage;
import org.terracotta.statistics.extended.StatisticType;
import org.terracotta.statistics.jsr166e.LongAdder;
import org.terracotta.statistics.observer.ChainedOperationObserver;
import static java.util.EnumSet.allOf;
import static org.ehcache.impl.internal.statistics.StatsUtils.findLowestTier;
import static org.ehcache.impl.internal.statistics.StatsUtils.findOperationStatisticOnChildren;
import static org.ehcache.impl.internal.statistics.StatsUtils.findTiers;
/**
* Contains usage statistics relative to a given cache.
*/
class DefaultCacheStatistics implements CacheStatistics {
private volatile CompensatingCounters compensatingCounters = CompensatingCounters.empty();
private final OperationStatistic<CacheOperationOutcomes.GetOutcome> get;
private final OperationStatistic<CacheOperationOutcomes.PutOutcome> put;
private final OperationStatistic<CacheOperationOutcomes.RemoveOutcome> remove;
private final OperationStatistic<CacheOperationOutcomes.PutIfAbsentOutcome> putIfAbsent;
private final OperationStatistic<CacheOperationOutcomes.ReplaceOutcome> replace;
private final OperationStatistic<CacheOperationOutcomes.ConditionalRemoveOutcome> conditionalRemove;
private final Map<BulkOps, LongAdder> bulkMethodEntries;
private final LatencyMonitor<CacheOperationOutcomes.GetOutcome> averageGetTime;
private final LatencyMonitor<CacheOperationOutcomes.PutOutcome> averagePutTime;
private final LatencyMonitor<CacheOperationOutcomes.RemoveOutcome> averageRemoveTime;
private final Map<String, TierStatistics> tierStatistics;
private final TierStatistics lowestTier;
private final Map<String, TypedValueStatistic> knownStatistics;
public DefaultCacheStatistics(InternalCache<?, ?> cache) {
bulkMethodEntries = cache.getBulkMethodEntries();
get = findOperationStatisticOnChildren(cache, CacheOperationOutcomes.GetOutcome.class, "get");
put = findOperationStatisticOnChildren(cache, CacheOperationOutcomes.PutOutcome.class, "put");
remove = findOperationStatisticOnChildren(cache, CacheOperationOutcomes.RemoveOutcome.class, "remove");
putIfAbsent = findOperationStatisticOnChildren(cache, CacheOperationOutcomes.PutIfAbsentOutcome.class, "putIfAbsent");
replace = findOperationStatisticOnChildren(cache, CacheOperationOutcomes.ReplaceOutcome.class, "replace");
conditionalRemove = findOperationStatisticOnChildren(cache, CacheOperationOutcomes.ConditionalRemoveOutcome.class, "conditionalRemove");
averageGetTime = new LatencyMonitor<CacheOperationOutcomes.GetOutcome>(allOf(CacheOperationOutcomes.GetOutcome.class));
get.addDerivedStatistic(averageGetTime);
averagePutTime = new LatencyMonitor<CacheOperationOutcomes.PutOutcome>(allOf(CacheOperationOutcomes.PutOutcome.class));
put.addDerivedStatistic(averagePutTime);
averageRemoveTime = new LatencyMonitor<CacheOperationOutcomes.RemoveOutcome>(allOf(CacheOperationOutcomes.RemoveOutcome.class));
remove.addDerivedStatistic(averageRemoveTime);
String[] tierNames = findTiers(cache);
String lowestTierName = findLowestTier(tierNames);
TierStatistics lowestTier = null;
tierStatistics = new HashMap<String, TierStatistics>(tierNames.length);
for (String tierName : tierNames) {
TierStatistics tierStatistics = new DefaultTierStatistics(cache, tierName);
this.tierStatistics.put(tierName, tierStatistics);
if (lowestTierName.equals(tierName)) {
lowestTier = tierStatistics;
}
}
this.lowestTier = lowestTier;
knownStatistics = createKnownStatistics();
}
private Map<String, TypedValueStatistic> createKnownStatistics() {
Map<String, TypedValueStatistic> knownStatistics = new HashMap<String, TypedValueStatistic>(30);
knownStatistics.put("Cache:HitCount", new TypedValueStatistic(StatisticType.COUNTER) {
@Override
public Number value() {
return getCacheHits();
}
});
knownStatistics.put("Cache:MissCount", new TypedValueStatistic(StatisticType.COUNTER) {
@Override
public Number value() {
return getCacheMisses();
}
});
knownStatistics.put("Cache:PutCount", new TypedValueStatistic(StatisticType.COUNTER) {
@Override
public Number value() {
return getCachePuts();
}
});
knownStatistics.put("Cache:UpdateCount", new TypedValueStatistic(StatisticType.COUNTER) {
@Override
public Number value() {
return getCacheUpdates();
}
});
knownStatistics.put("Cache:RemovalCount", new TypedValueStatistic(StatisticType.COUNTER) {
@Override
public Number value() {
return getCacheRemovals();
}
});
knownStatistics.put("Cache:EvictionCount", new TypedValueStatistic(StatisticType.COUNTER) {
@Override
public Number value() {
return getCacheEvictions();
}
});
knownStatistics.put("Cache:ExpirationCount", new TypedValueStatistic(StatisticType.COUNTER) {
@Override
public Number value() {
return getCacheExpirations();
}
});
for (TierStatistics tier : tierStatistics.values()) {
knownStatistics.putAll(tier.getKnownStatistics());
}
return Collections.unmodifiableMap(knownStatistics);
}
public Map<String, TypedValueStatistic> getKnownStatistics() {
return knownStatistics;
}
public Map<String, TierStatistics> getTierStatistics() {
return Collections.unmodifiableMap(tierStatistics);
}
public void clear() {
compensatingCounters = compensatingCounters.snapshot(this);
averageGetTime.clear();
averagePutTime.clear();
averageRemoveTime.clear();
for (TierStatistics t : tierStatistics.values()) {
t.clear();
}
}
public long getCacheHits() {
return normalize(getHits() - compensatingCounters.cacheHits);
}
public float getCacheHitPercentage() {
long cacheHits = getCacheHits();
return normalize((float) cacheHits / (cacheHits + getCacheMisses())) * 100.0f;
}
public long getCacheMisses() {
return normalize(getMisses() - compensatingCounters.cacheMisses);
}
public float getCacheMissPercentage() {
long cacheMisses = getCacheMisses();
return normalize((float) cacheMisses / (getCacheHits() + cacheMisses)) * 100.0f;
}
public long getCacheGets() {
return normalize(getHits() + getMisses()
- compensatingCounters.cacheGets);
}
public long getCachePuts() {
return normalize(getBulkCount(BulkOps.PUT_ALL) +
put.sum(EnumSet.of(CacheOperationOutcomes.PutOutcome.PUT)) +
put.sum(EnumSet.of(CacheOperationOutcomes.PutOutcome.UPDATED)) +
putIfAbsent.sum(EnumSet.of(CacheOperationOutcomes.PutIfAbsentOutcome.PUT)) +
replace.sum(EnumSet.of(CacheOperationOutcomes.ReplaceOutcome.HIT)) -
compensatingCounters.cachePuts);
}
public long getCacheUpdates() {
return normalize(getBulkCount(BulkOps.UPDATE_ALL) +
put.sum(EnumSet.of(CacheOperationOutcomes.PutOutcome.UPDATED)) +
replace.sum(EnumSet.of(CacheOperationOutcomes.ReplaceOutcome.HIT)) -
compensatingCounters.cacheUpdates);
}
public long getCacheRemovals() {
return normalize(getBulkCount(BulkOps.REMOVE_ALL) +
remove.sum(EnumSet.of(CacheOperationOutcomes.RemoveOutcome.SUCCESS)) +
conditionalRemove.sum(EnumSet.of(CacheOperationOutcomes.ConditionalRemoveOutcome.SUCCESS)) -
compensatingCounters.cacheRemovals);
}
public long getCacheEvictions() {
return normalize(lowestTier.getEvictions());
}
public long getCacheExpirations() {
return normalize(lowestTier.getExpirations());
}
public float getCacheAverageGetTime() {
return (float) averageGetTime.value();
}
public float getCacheAveragePutTime() {
return (float) averagePutTime.value();
}
public float getCacheAverageRemoveTime() {
return (float) averageRemoveTime.value();
}
private long getMisses() {
return getBulkCount(BulkOps.GET_ALL_MISS) +
get.sum(EnumSet.of(CacheOperationOutcomes.GetOutcome.MISS)) +
putIfAbsent.sum(EnumSet.of(CacheOperationOutcomes.PutIfAbsentOutcome.PUT)) +
replace.sum(EnumSet.of(CacheOperationOutcomes.ReplaceOutcome.MISS_NOT_PRESENT)) +
conditionalRemove.sum(EnumSet.of(CacheOperationOutcomes.ConditionalRemoveOutcome.FAILURE_KEY_MISSING));
}
private long getHits() {
return getBulkCount(BulkOps.GET_ALL_HITS) +
get.sum(EnumSet.of(CacheOperationOutcomes.GetOutcome.HIT)) +
putIfAbsent.sum(EnumSet.of(CacheOperationOutcomes.PutIfAbsentOutcome.HIT)) +
replace.sum(EnumSet.of(CacheOperationOutcomes.ReplaceOutcome.HIT, CacheOperationOutcomes.ReplaceOutcome.MISS_PRESENT)) +
conditionalRemove.sum(EnumSet.of(CacheOperationOutcomes.ConditionalRemoveOutcome.SUCCESS, CacheOperationOutcomes.ConditionalRemoveOutcome.FAILURE_KEY_PRESENT));
}
private long getBulkCount(BulkOps bulkOps) {
return bulkMethodEntries.get(bulkOps).longValue();
}
private static long normalize(long value) {
return Math.max(0, value);
}
private static float normalize(float value) {
if (Float.isNaN(value)) {
return 0.0f;
}
return Math.min(1.0f, Math.max(0.0f, value));
}
private static class CompensatingCounters {
final long cacheHits;
final long cacheMisses;
final long cacheGets;
final long cachePuts;
final long cacheRemovals;
final long cacheUpdates;
private CompensatingCounters(long cacheHits, long cacheMisses, long cacheGets, long cachePuts, long cacheRemovals, long cacheUpdates) {
this.cacheHits = cacheHits;
this.cacheMisses = cacheMisses;
this.cacheGets = cacheGets;
this.cachePuts = cachePuts;
this.cacheRemovals = cacheRemovals;
this.cacheUpdates = cacheUpdates;
}
static CompensatingCounters empty() {
return new CompensatingCounters(0, 0, 0, 0, 0, 0);
}
CompensatingCounters snapshot(DefaultCacheStatistics statistics) {
return new CompensatingCounters(
cacheHits + statistics.getHits(),
cacheMisses + statistics.getMisses(),
cacheGets + statistics.getCacheGets(),
cachePuts + statistics.getCachePuts(),
cacheRemovals + statistics.getCacheRemovals(),
cacheUpdates + statistics.getCacheUpdates());
}
}
private static class LatencyMonitor<T extends Enum<T>> implements ChainedOperationObserver<T> {
private final LatencySampling<T> sampling;
private volatile MinMaxAverage average;
public LatencyMonitor(Set<T> targets) {
this.sampling = new LatencySampling<T>(targets, 1.0);
this.average = new MinMaxAverage();
sampling.addDerivedStatistic(average);
}
@Override
public void begin(long time) {
sampling.begin(time);
}
@Override
public void end(long time, T result) {
sampling.end(time, result);
}
@Override
public void end(long time, T result, long... parameters) {
sampling.end(time, result, parameters);
}
public double value() {
Double value = average.mean();
if (value == null) {
//Someone involved with 107 can't do math
return 0;
} else {
//We use nanoseconds, 107 uses microseconds
return value / 1000f;
}
}
public synchronized void clear() {
sampling.removeDerivedStatistic(average);
average = new MinMaxAverage();
sampling.addDerivedStatistic(average);
}
}
}