/**
* Copyright 2013 the original author or authors.
*
* 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 io.neba.core.resourcemodels.caching;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import static java.util.Collections.sort;
/**
* @author Olaf Otto
*/
public class CacheKeyStatistics {
/**
* @author Olaf Otto
*/
public static class KeyReport {
private final Object key;
private final int misses;
private final int hits;
private final int writes;
public KeyReport(Object key, int misses, int hits, int writes) {
this.key = key;
this.misses = misses;
this.hits = hits;
this.writes = writes;
}
@Override
public String toString() {
return key +
": misses=" + misses +
", hits=" + hits +
", writes=" + writes;
}
}
/**
* @author Olaf Otto
*/
public static class ReportSummary {
private final int totalNumberOfHits;
private final int totalNumberOfMisses;
private final int totalNumberOfWrites;
public ReportSummary(int totalNumberOfHits, int totalNumberOfMisses, int totalNumberOfWrites) {
this.totalNumberOfHits = totalNumberOfHits;
this.totalNumberOfMisses = totalNumberOfMisses;
this.totalNumberOfWrites = totalNumberOfWrites;
}
public int getTotalNumberOfHits() {
return totalNumberOfHits;
}
public int getTotalNumberOfMisses() {
return totalNumberOfMisses;
}
public int getTotalNumberOfWrites() {
return totalNumberOfWrites;
}
}
private final Set<Object> keys = new HashSet<>(1024);
private final Map<Object, AtomicInteger> misses = new HashMap<>(1024);
private final Map<Object, AtomicInteger> hits = new HashMap<>(1024);
private final Map<Object, AtomicInteger> writes = new HashMap<>(1024);
public void reportMiss(Object key) {
if (key == null) {
throw new IllegalArgumentException("Method argument key must not be null.");
}
this.keys.add(key);
getMisses(key).incrementAndGet();
}
public void reportHit(Object key) {
if (key == null) {
throw new IllegalArgumentException("Method argument key must not be null.");
}
this.keys.add(key);
getHits(key).incrementAndGet();
}
public void reportWrite(Object key) {
if (key == null) {
throw new IllegalArgumentException("Method argument key must not be null.");
}
this.keys.add(key);
getWrites(key).incrementAndGet();
}
private AtomicInteger getOrCreate(Object key, Map<Object, AtomicInteger> map) {
AtomicInteger i = map.get(key);
if (i == null) {
i = new AtomicInteger(0);
map.put(key, i);
}
return i;
}
private AtomicInteger getMisses(Object key) {
return getOrCreate(key, this.misses);
}
private AtomicInteger getHits(Object key) {
return getOrCreate(key, this.hits);
}
private AtomicInteger getWrites(Object key) {
return getOrCreate(key, this.writes);
}
public List<KeyReport> getKeyReports() {
List<KeyReport> keyReports = new ArrayList<>(this.keys.size());
for (Object key: this.keys) {
KeyReport keyReport = new KeyReport(key,
getMisses(key).intValue(),
getHits(key).intValue(),
getWrites(key).intValue());
keyReports.add(keyReport);
}
sort(keyReports, (o1, o2) -> o2.hits - o1.hits);
return keyReports;
}
public ReportSummary getReportSummary() {
int totalHits = 0, totalMisses = 0, totalWrites = 0;
for (Object key: this.keys) {
totalHits += getHits(key).intValue();
totalMisses += getMisses(key).intValue();
totalWrites += getWrites(key).intValue();
}
return new ReportSummary(totalHits, totalMisses, totalWrites);
}
}