/*
* Copyright (C) 2014 Indeed 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 com.indeed.imhotep.service;
import com.google.common.base.Function;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.indeed.util.core.Either;
import com.indeed.util.core.io.Closeables2;
import com.indeed.util.core.reference.ReloadableSharedReference;
import com.indeed.util.core.reference.SharedReference;
import com.indeed.flamdex.api.FlamdexOutOfMemoryException;
import com.indeed.flamdex.api.IntValueLookup;
import com.indeed.imhotep.ImhotepStatusDump;
import org.apache.log4j.Logger;
import javax.annotation.Nullable;
import java.io.Closeable;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author jplaisance
*/
public final class MetricCacheImpl implements MetricCache {
private static final Logger log = Logger.getLogger(MetricCacheImpl.class);
private final LoadingCache<String, ReloadableSharedReference<IntValueLookup, FlamdexOutOfMemoryException>> reloaders;
private final Map<String, IntValueLookup> loadedMetrics;
private final ReloadableSharedReference.Closer<Map.Entry<String, IntValueLookup>> closeMetric;
private boolean closed = false;
public MetricCacheImpl(final Function<String, Either<FlamdexOutOfMemoryException, IntValueLookup>> loadMetric, final ReloadableSharedReference.Closer<Map.Entry<String, IntValueLookup>> closeMetric) {
this.closeMetric = closeMetric;
loadedMetrics = Maps.newHashMap();
this.reloaders = CacheBuilder.newBuilder().build(
new CacheLoader<String, ReloadableSharedReference<IntValueLookup, FlamdexOutOfMemoryException>>() {
@Override
public ReloadableSharedReference<IntValueLookup, FlamdexOutOfMemoryException> load(@Nullable final String metric) {
return ReloadableSharedReference.create(
new ReloadableSharedReference.Loader<IntValueLookup, FlamdexOutOfMemoryException>() {
@Override
public IntValueLookup load() throws FlamdexOutOfMemoryException {
final IntValueLookup lookup = loadMetric.apply(metric).get();
synchronized (loadedMetrics) {
if (closed) {
closeMetric.close(Maps.immutableEntry(metric, lookup));
throw new IllegalStateException("already closed");
}
if (loadedMetrics.containsKey(metric)) {
closeMetric.close(Maps.immutableEntry(metric, lookup));
log.error("metric "+metric+" is already loaded");
return loadedMetrics.get(metric);
}
loadedMetrics.put(metric, lookup);
}
return lookup;
}
},
new ReloadableSharedReference.Closer<IntValueLookup>() {
@Override
public void close(final IntValueLookup intValueLookup) {
synchronized (loadedMetrics) {
if (!closed) {
final IntValueLookup removed = loadedMetrics.remove(metric);
if (intValueLookup != removed) {
log.error("something is wrong with ReloadableSharedReference, load and close should be called in pairs");
}
closeMetric.close(Maps.immutableEntry(metric, intValueLookup));
}
}
}
}
);
}
}
);
}
@Override
public IntValueLookup getMetric(final String metric) throws FlamdexOutOfMemoryException {
return new CachedIntValueLookup(reloaders.getUnchecked(metric).copy());
}
@Override
public List<ImhotepStatusDump.MetricDump> getMetricDump() {
final List<ImhotepStatusDump.MetricDump> ret = Lists.newArrayList();
synchronized (loadedMetrics) {
if (closed) {
throw new IllegalStateException("already closed");
}
for (final Map.Entry<String, IntValueLookup> entry : loadedMetrics.entrySet()) {
ret.add(new ImhotepStatusDump.MetricDump(entry.getKey(), entry.getValue().memoryUsed()));
}
}
return ret;
}
@Override
public Set<String> getLoadedMetrics() {
synchronized (loadedMetrics) {
if (closed) {
throw new IllegalStateException("already closed");
}
return Sets.newHashSet(loadedMetrics.keySet());
}
}
private static final class CachedIntValueLookup implements IntValueLookup {
private final SharedReference<IntValueLookup> reference;
private final IntValueLookup metric;
private CachedIntValueLookup(final SharedReference<IntValueLookup> reference) {
this.reference = reference;
metric = reference.get();
}
@Override
public long getMin() {
return metric.getMin();
}
@Override
public long getMax() {
return metric.getMax();
}
@Override
public void lookup(final int[] docIds, final long[] values, final int n) {
metric.lookup(docIds, values, n);
}
@Override
public long memoryUsed() {
return metric.memoryUsed();
}
@Override
public void close() {
Closeables2.closeQuietly(reference, log);
}
}
@Override
public void close() {
synchronized (loadedMetrics) {
if (!closed) {
closed = true;
try {
Closeables2.closeAll(log, Collections2.transform(loadedMetrics.entrySet(), new Function<Map.Entry<String, IntValueLookup>, Closeable>() {
public Closeable apply(final Map.Entry<String, IntValueLookup> metric) {
return closeMetric.asCloseable(metric);
}
}));
} finally {
loadedMetrics.clear();
}
}
}
}
}