blob: 4d3a42a9fcd443651d91d99be58a8838bd088b7f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.jackrabbit.oak.plugins.document.persistentCache;
import com.google.common.base.Objects;
import org.apache.jackrabbit.api.stats.TimeSeries;
import org.apache.jackrabbit.oak.api.jmx.PersistentCacheStatsMBean;
import org.apache.jackrabbit.oak.commons.IOUtils;
import org.apache.jackrabbit.oak.commons.jmx.AnnotatedStandardMBean;
import org.apache.jackrabbit.oak.stats.CounterStats;
import org.apache.jackrabbit.oak.stats.Counting;
import org.apache.jackrabbit.oak.stats.MeterStats;
import org.apache.jackrabbit.oak.stats.StatisticsProvider;
import org.apache.jackrabbit.oak.stats.StatsOptions;
import org.apache.jackrabbit.oak.stats.TimerStats;
import org.apache.jackrabbit.stats.TimeSeriesStatsUtil;
import javax.management.openmbean.CompositeData;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
/**
* Persistence Cache Statistics.
*/
public class PersistentCacheStats extends AnnotatedStandardMBean implements PersistentCacheStatsMBean {
private static final Boolean ENABLE_READ_TIMER;
private static final Boolean ENABLE_LOAD_TIMER;
private static final Boolean ENABLE_REJECTED_PUT;
static {
String enableReadTimer = System.getProperty("PersistentCacheStats.readTimer", "false");
String enableLoadTimer = System.getProperty("PersistentCacheStats.loadTimer", "false");
String enableRejectedPut = System.getProperty("PersistentCacheStats.rejectedPut", "false");
ENABLE_READ_TIMER = Boolean.parseBoolean(enableReadTimer);
ENABLE_LOAD_TIMER = Boolean.parseBoolean(enableLoadTimer);
ENABLE_REJECTED_PUT = Boolean.parseBoolean(enableRejectedPut);
}
private static final String HITS = "HITS";
private static final String REQUESTS = "REQUESTS";
private static final String LOAD_TIMER = "LOAD_TIMER";
private static final String LOAD_EXCEPTIONS = "LOAD_EXCEPTIONS";
private static final String PUT_ONE = "CACHE_PUT";
private static final String BROADCAST_RECV = "BROADCAST_RECV";
private static final String INVALIDATE_ONE = "INVALIDATE_ONE";
private static final String INVALIDATE_ALL = "INVALIDATE_ALL";
private static final String READ_TIMER = "READ_TIMER";
private static final String USED_DISK_SPACE = "USED_SPACE_BYTES";
private static final String PUT_REJECTED_ALREADY_PERSISTED = "PUT_REJECTED_ALREADY_PERSISTED";
private static final String PUT_REJECTED_ENTRY_NOT_USED = "PUT_REJECTED_ENTRY_NOT_USED";
private static final String PUT_REJECTED_FULL_QUEUE = "PUT_REJECTED_FULL_QUEUE";
private static final String PUT_REJECTED_SECONDARY_CACHE = "PUT_REJECTED_SECONDARY_CACHE";
private final StatisticsProvider statisticsProvider;
private final String cacheName;
private final MeterStats hitMeter;
private final TimeSeries hitRateHistory;
private final MeterStats requestMeter;
private final TimeSeries requestRateHistory;
private final MeterStats loadExceptionMeter;
private final TimeSeries loadExceptionRateHistory;
private final TimerStats loadTimer;
private final TimeSeries loadRateHistory;
private final MeterStats putMeter;
private final TimeSeries putRateHistory;
private final MeterStats broadcastRecvMeter;
private final TimeSeries broadcastRecvRateHistory;
private final MeterStats invalidateOneMeter;
private final TimeSeries invalidateOneRateHistory;
private final MeterStats invalidateAllMeter;
private final TimeSeries invalidateAllRateHistory;
private final MeterStats putRejectedAlreadyPersistedMeter;
private final TimeSeries putRejectedAlreadyPersistedHistory;
private final MeterStats putRejectedEntryNotUsedMeter;
private final TimeSeries putRejectedEntryNotUseHistory;
private final MeterStats putRejectedByFullQueueMeter;
private final TimeSeries putRejectedByFullQueueHistory;
private final MeterStats putRejectedAsCachedInSecMeter;
private final TimeSeries putRejectedAsCachedInSecHistory;
private final TimerStats readTimer;
private final CounterStats usedSpaceByteCounter;
private final TimeSeries usedSpaceByteCounterHistory;
private final UsedSpaceTracker diskStats;
private final TimeSeries hitPercentageHistory;
public PersistentCacheStats(CacheType cacheType, StatisticsProvider provider) {
super(PersistentCacheStatsMBean.class);
if (provider == null) {
statisticsProvider = StatisticsProvider.NOOP;
} else {
statisticsProvider = provider;
}
// Configure cache name
cacheName = "PersistentCache.NodeCache." + cacheType.name().toLowerCase();
// Fetch stats and time series
String statName;
statName = getStatName(HITS, cacheName);
hitMeter = statisticsProvider.getMeter(statName, StatsOptions.DEFAULT);
hitRateHistory = getTimeSeries(statName);
statName = getStatName(REQUESTS, cacheName);
requestMeter = statisticsProvider.getMeter(statName, StatsOptions.DEFAULT);
requestRateHistory = getTimeSeries(statName);
hitPercentageHistory = new PercentageTimeSeries(hitRateHistory, requestRateHistory);
statName = getStatName(LOAD_TIMER, cacheName);
loadRateHistory = new DifferenceTimeSeries(requestRateHistory, hitRateHistory);
if (ENABLE_LOAD_TIMER) {
loadTimer = statisticsProvider.getTimer(statName, StatsOptions.METRICS_ONLY);
}
else {
loadTimer = StatisticsProvider.NOOP.getTimer(statName, StatsOptions.METRICS_ONLY);
}
statName = getStatName(LOAD_EXCEPTIONS, cacheName);
loadExceptionMeter = statisticsProvider.getMeter(statName, StatsOptions.DEFAULT);
loadExceptionRateHistory = getTimeSeries(statName);
statName = getStatName(PUT_ONE, cacheName);
putMeter = statisticsProvider.getMeter(statName, StatsOptions.DEFAULT);
putRateHistory = getTimeSeries(statName);
statName = getStatName(BROADCAST_RECV, cacheName);
broadcastRecvMeter = statisticsProvider.getMeter(statName, StatsOptions.DEFAULT);
broadcastRecvRateHistory = getTimeSeries(statName);
statName = getStatName(INVALIDATE_ONE, cacheName);
invalidateOneMeter = statisticsProvider.getMeter(statName, StatsOptions.DEFAULT);
invalidateOneRateHistory = getTimeSeries(statName);
statName = getStatName(INVALIDATE_ALL, cacheName);
invalidateAllMeter = statisticsProvider.getMeter(statName, StatsOptions.DEFAULT);
invalidateAllRateHistory = getTimeSeries(statName);
statName = getStatName(USED_DISK_SPACE, cacheName);
usedSpaceByteCounter = statisticsProvider.getCounterStats(statName, StatsOptions.DEFAULT);
usedSpaceByteCounterHistory = getTimeSeries(statName, false);
statName = getStatName(READ_TIMER, cacheName);
if (ENABLE_READ_TIMER) {
readTimer = statisticsProvider.getTimer(statName, StatsOptions.METRICS_ONLY);
}
else {
readTimer = StatisticsProvider.NOOP.getTimer(statName, StatsOptions.METRICS_ONLY);
}
statName = getStatName(PUT_REJECTED_ALREADY_PERSISTED, cacheName);
if (ENABLE_REJECTED_PUT) {
putRejectedAlreadyPersistedMeter = statisticsProvider.getMeter(statName, StatsOptions.DEFAULT);
putRejectedAlreadyPersistedHistory = getTimeSeries(statName);
} else {
putRejectedAlreadyPersistedMeter = StatisticsProvider.NOOP.getMeter(statName, StatsOptions.DEFAULT);
putRejectedAlreadyPersistedHistory = StatisticsProvider.NOOP.getStats().getTimeSeries(statName, false);
}
statName = getStatName(PUT_REJECTED_ENTRY_NOT_USED, cacheName);
if (ENABLE_REJECTED_PUT) {
putRejectedEntryNotUsedMeter = statisticsProvider.getMeter(statName, StatsOptions.DEFAULT);
putRejectedEntryNotUseHistory = getTimeSeries(statName);
} else {
putRejectedEntryNotUsedMeter = StatisticsProvider.NOOP.getMeter(statName, StatsOptions.DEFAULT);
putRejectedEntryNotUseHistory = StatisticsProvider.NOOP.getStats().getTimeSeries(statName, false);
}
statName = getStatName(PUT_REJECTED_FULL_QUEUE, cacheName);
if (ENABLE_REJECTED_PUT) {
putRejectedByFullQueueMeter = statisticsProvider.getMeter(statName, StatsOptions.DEFAULT);
putRejectedByFullQueueHistory = getTimeSeries(statName);
} else {
putRejectedByFullQueueMeter = StatisticsProvider.NOOP.getMeter(statName, StatsOptions.DEFAULT);
putRejectedByFullQueueHistory = StatisticsProvider.NOOP.getStats().getTimeSeries(statName, false);
}
statName = getStatName(PUT_REJECTED_SECONDARY_CACHE, cacheName);
putRejectedAsCachedInSecMeter = statisticsProvider.getMeter(statName, StatsOptions.DEFAULT);
putRejectedAsCachedInSecHistory = getTimeSeries(statName);
diskStats = new UsedSpaceTracker(usedSpaceByteCounter);
}
//~--------------------------------------< stats update methods
public void markHit() {
hitMeter.mark();
}
public void markRequest() {
requestMeter.mark();
}
public void markException() {
loadExceptionMeter.mark();
}
public void markPut() {
putMeter.mark();
}
public void markRecvBroadcast() {
broadcastRecvMeter.mark();
}
public void markInvalidateOne() {
invalidateOneMeter.mark();
}
public void markInvalidateAll() {
invalidateAllMeter.mark();
}
public void markPutRejectedAlreadyPersisted() {
putRejectedAlreadyPersistedMeter.mark();
}
public void markPutRejectedEntryNotUsed() {
putRejectedEntryNotUsedMeter.mark();
}
public void markPutRejectedAsCachedInSecondary() {
putRejectedAsCachedInSecMeter.mark();
}
public void markPutRejectedQueueFull() {
putRejectedByFullQueueMeter.mark();
}
public TimerStats.Context startReadTimer() {
return this.readTimer.time();
}
public TimerStats.Context startLoaderTimer() {
return this.loadTimer.time();
}
// Update disk space
public void addWriteGeneration(int generation) {
diskStats.addWriteGeneration(generation);
}
public void removeReadGeneration(int generation) {
diskStats.removeReadGeneration(generation);
}
public void markBytesWritten(long numBytes) {
diskStats.markBytesWritten(numBytes);
}
//~--------------------------------------< diskspace usage helper
static class UsedSpaceTracker {
private final CounterStats byteCounter;
private final Map<Integer, AtomicLong> generationByteCounters;
private AtomicLong currentGenCounter;
UsedSpaceTracker(CounterStats usageCounter) {
this.byteCounter = usageCounter;
this.generationByteCounters = new ConcurrentHashMap<Integer, AtomicLong>();
this.currentGenCounter = new AtomicLong();
}
void addWriteGeneration(int generation) {
currentGenCounter = new AtomicLong(0L);
generationByteCounters.put(generation, currentGenCounter);
}
void removeReadGeneration(int generation) {
AtomicLong genCounter = generationByteCounters.remove(generation);
byteCounter.dec(genCounter == null ? 0L : genCounter.get());
}
void markBytesWritten(long bytes) {
currentGenCounter.addAndGet(bytes);
byteCounter.inc(bytes);
}
}
//~--------------------------------------< CacheStatsMbean
@Override
public String getName() {
return cacheName;
}
@Override
public long getRequestCount() {
return requestMeter.getCount();
}
@Override
public long getHitCount() {
return hitMeter.getCount();
}
@Override
public double getHitRate() {
long hitCount = hitMeter.getCount();
long requestCount = requestMeter.getCount();
return (requestCount == 0L ? 0L : (double)hitCount/requestCount);
}
@Override
public long getMissCount() {
return requestMeter.getCount() - hitMeter.getCount();
}
@Override
public double getMissRate() {
long missCount = getMissCount();
long requestCount = requestMeter.getCount();
return (requestCount == 0L ? 0L : (double)missCount/requestCount);
}
@Override
public long getLoadCount() {
return getMissCount();
}
@Override
public long getLoadSuccessCount() {
return getLoadCount() - getLoadExceptionCount();
}
@Override
public long getLoadExceptionCount() {
return loadExceptionMeter.getCount();
}
@Override
public double getLoadExceptionRate() {
long exceptionCount = loadExceptionMeter.getCount();
long loadCount = loadTimer.getCount();
return (loadCount == 0L ? 0L : (double)exceptionCount/loadCount);
}
@Override
public long estimateCurrentWeight() {
return usedSpaceByteCounter.getCount();
}
@Override
public CompositeData getRequestRateHistory() {
return TimeSeriesStatsUtil.asCompositeData(requestRateHistory, "Persistent cache requests");
}
@Override
public CompositeData getHitRateHistory() {
return TimeSeriesStatsUtil.asCompositeData(hitRateHistory, "Persistent cache hits");
}
@Override
public CompositeData getLoadRateHistory() {
return TimeSeriesStatsUtil.asCompositeData(loadRateHistory, "Persistent cache loads/misses");
}
@Override
public CompositeData getLoadExceptionRateHistory() {
return TimeSeriesStatsUtil.asCompositeData(loadExceptionRateHistory, "Persistent cache load exceptions");
}
@Override
public CompositeData getHitPercentageHistory() {
return TimeSeriesStatsUtil.asCompositeData(hitPercentageHistory, "Persistent cache hit percentage");
}
@Override
public CompositeData getPutRateHistory() {
return TimeSeriesStatsUtil.asCompositeData(putRateHistory, "Persistent cache manual put entry");
}
@Override
public CompositeData getPutRejectedAlreadyPersistedRateHistory() {
return TimeSeriesStatsUtil.asCompositeData(putRejectedAlreadyPersistedHistory, "Persistent cache put rejected (already persisted)");
}
@Override
public CompositeData getPutRejectedEntryNotUsedRateHistory() {
return TimeSeriesStatsUtil.asCompositeData(putRejectedEntryNotUseHistory, "Persistent cache put rejected (entry not used)");
}
@Override
public CompositeData getPutRejectedQueueFullRateHistory() {
return TimeSeriesStatsUtil.asCompositeData(putRejectedByFullQueueHistory, "Persistent cache put rejected (queue is full)");
}
@Override
public CompositeData getPutRejectedAsCachedInSecRateHistory() {
return TimeSeriesStatsUtil.asCompositeData(putRejectedAsCachedInSecHistory, "Persistent cache put rejected " +
"(entry is covered by secondary)");
}
@Override
public CompositeData getInvalidateOneRateHistory() {
return TimeSeriesStatsUtil.asCompositeData(invalidateOneRateHistory, "Persistent cache invalidate one entry");
}
@Override
public CompositeData getInvalidateAllRateHistory() {
return TimeSeriesStatsUtil.asCompositeData(invalidateAllRateHistory, "Persistent cache invalidate all entries");
}
@Override
public CompositeData getBroadcastRecvRateHistory() {
return TimeSeriesStatsUtil.asCompositeData(broadcastRecvRateHistory, "Persistent cache entries received from broadcast");
}
@Override
public CompositeData getUsedSpaceHistory() {
return TimeSeriesStatsUtil.asCompositeData(usedSpaceByteCounterHistory, "Persistent cache estimated size (bytes)");
}
@Override
public String cacheInfoAsString() {
return Objects.toStringHelper("PersistentCacheStats")
.add("requestCount", getRequestCount())
.add("hitCount", getHitCount())
.add("hitRate", String.format("%1.2f", getHitRate()))
.add("missCount", getMissCount())
.add("missRate", String.format("%1.2f", getMissRate()))
.add("loadCount", getLoadCount())
.add("loadSuccessCount", getLoadSuccessCount())
.add("loadExceptionCount", getLoadExceptionCount())
.add("totalWeight", IOUtils.humanReadableByteCount(estimateCurrentWeight()))
.toString();
}
//~--------------------------------------< CacheStatsMbean - stats that are not (yet) available
@Override
public long getTotalLoadTime() {
return 0;
}
@Override
public double getAverageLoadPenalty() {
return 0;
}
@Override
public long getEvictionCount() {
return 0;
}
@Override
public long getElementCount() {
return 0;
}
@Override
public long getMaxTotalWeight() {
return Long.MAX_VALUE;
}
@Override
public void resetStats() {
// ignored
}
Counting getPutRejectedAsCachedInSecCounter() {
return putRejectedAsCachedInSecMeter;
}
//~--------------------------------------< private helpers
private static String getStatName(String meter, String cacheName) {
return cacheName + "." + meter;
}
private TimeSeries getTimeSeries(String name) {
return statisticsProvider.getStats().getTimeSeries(name, true);
}
private TimeSeries getTimeSeries(String name, boolean resetValues) {
return statisticsProvider.getStats().getTimeSeries(name, resetValues);
}
/**
* TimeSeries that computes the hit ratio in percentages. ( hit/total * 100 )
*/
private static class PercentageTimeSeries implements TimeSeries {
private TimeSeries hit, total;
PercentageTimeSeries(TimeSeries hit, TimeSeries total) {
this.hit = hit;
this.total = total;
}
@Override
public long[] getValuePerSecond() {
return percentage(hit.getValuePerSecond(), total.getValuePerSecond());
}
@Override
public long[] getValuePerMinute() {
return percentage(hit.getValuePerMinute(), total.getValuePerMinute());
}
@Override
public long[] getValuePerHour() {
return percentage(hit.getValuePerHour(), total.getValuePerHour());
}
@Override
public long[] getValuePerWeek() {
return percentage(hit.getValuePerWeek(), total.getValuePerWeek());
}
@Override
public long getMissingValue() {
return 0;
}
private static long[] percentage(long[] a, long[] b) {
long[] result = new long[a.length];
for (int i=0; i<a.length; ++i) {
if (b[i] == 0) {
result[i] = 0;
}
else {
result[i] = (a[i] * 100) / b[i];
}
}
return result;
}
}
/**
* TimeSeries as a difference between two other TimeSeries
*/
private static class DifferenceTimeSeries implements TimeSeries {
private TimeSeries tsA, tsB;
DifferenceTimeSeries(TimeSeries tsA, TimeSeries tsB) {
this.tsA = tsA;
this.tsB = tsB;
}
@Override
public long[] getValuePerSecond() {
return difference(tsA.getValuePerSecond(), tsB.getValuePerSecond());
}
@Override
public long[] getValuePerMinute() {
return difference(tsA.getValuePerMinute(), tsB.getValuePerMinute());
}
@Override
public long[] getValuePerHour() {
return difference(tsA.getValuePerHour(), tsB.getValuePerHour());
}
@Override
public long[] getValuePerWeek() {
return difference(tsA.getValuePerWeek(), tsB.getValuePerWeek());
}
@Override
public long getMissingValue() {
return 0;
}
private static long[] difference(long[] a, long[] b) {
long[] result = new long[a.length];
for (int i=0; i<a.length; ++i) {
result[i] = a[i] - b[i];
}
return result;
}
}
}