/**
* 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.ambari.server.controller.metrics.timeline.cache;
import java.io.IOException;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.hadoop.metrics2.sink.timeline.TimelineMetrics;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.sf.ehcache.CacheException;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import net.sf.ehcache.constructs.blocking.LockTimeoutException;
import net.sf.ehcache.constructs.blocking.UpdatingCacheEntryFactory;
import net.sf.ehcache.constructs.blocking.UpdatingSelfPopulatingCache;
import net.sf.ehcache.statistics.StatisticsGateway;
public class TimelineMetricCache extends UpdatingSelfPopulatingCache {
private final static Logger LOG = LoggerFactory.getLogger(TimelineMetricCache.class);
private static AtomicInteger printCacheStatsCounter = new AtomicInteger(0);
/**
* Creates a SelfPopulatingCache.
*
* @param cache @Cache
* @param factory @CacheEntryFactory
*/
public TimelineMetricCache(Ehcache cache, UpdatingCacheEntryFactory factory) throws CacheException {
super(cache, factory);
}
/**
* Get metrics for an app grouped by the requested @TemporalInfo which is a
* part of the @TimelineAppMetricCacheKey
* @param key @TimelineAppMetricCacheKey
* @return @org.apache.hadoop.metrics2.sink.timeline.TimelineMetrics
*/
public TimelineMetrics getAppTimelineMetricsFromCache(TimelineAppMetricCacheKey key) throws IllegalArgumentException, IOException {
if (LOG.isDebugEnabled()) {
LOG.debug("Fetching metrics with key: " + key);
}
// Make sure key is valid
validateKey(key);
Element element = null;
try {
element = get(key);
} catch (LockTimeoutException le) {
// Ehcache masks the Socket Timeout to look as a LockTimeout
Throwable t = le.getCause();
if (t instanceof CacheException) {
t = t.getCause();
if (t instanceof SocketTimeoutException) {
throw new SocketTimeoutException(t.getMessage());
}
if (t instanceof ConnectException) {
throw new ConnectException(t.getMessage());
}
}
}
TimelineMetrics timelineMetrics = new TimelineMetrics();
if (element != null && element.getObjectValue() != null) {
TimelineMetricsCacheValue value = (TimelineMetricsCacheValue) element.getObjectValue();
if (LOG.isDebugEnabled()) {
LOG.debug("Returning value from cache: " + value);
}
timelineMetrics = value.getTimelineMetrics();
}
if (LOG.isDebugEnabled()) {
// Print stats every 100 calls - Note: Supported in debug mode only
if (printCacheStatsCounter.getAndIncrement() == 0) {
StatisticsGateway statistics = this.getStatistics();
LOG.debug("Metrics cache stats => \n" +
", Evictions = " + statistics.cacheEvictedCount() +
", Expired = " + statistics.cacheExpiredCount() +
", Hits = " + statistics.cacheHitCount() +
", Misses = " + statistics.cacheMissCount() +
", Hit ratio = " + statistics.cacheHitRatio() +
", Puts = " + statistics.cachePutCount() +
", Size in MB = " + (statistics.getLocalHeapSizeInBytes() / 1048576));
} else {
printCacheStatsCounter.compareAndSet(100, 0);
}
}
return timelineMetrics;
}
/**
* Set new time bounds on the cache key so that update can use the new
* query window. We do this quietly which means regular get/update logic is
* not invoked.
*/
@Override
public Element get(Object key) throws LockTimeoutException {
Element element = this.getQuiet(key);
if (element != null) {
if (LOG.isTraceEnabled()) {
LOG.trace("key : " + element.getObjectKey());
LOG.trace("value : " + element.getObjectValue());
}
// Set new time boundaries on the key
TimelineAppMetricCacheKey existingKey = (TimelineAppMetricCacheKey) element.getObjectKey();
LOG.debug("Existing temporal info: " + existingKey.getTemporalInfo() +
" for : " + existingKey.getMetricNames());
TimelineAppMetricCacheKey newKey = (TimelineAppMetricCacheKey) key;
existingKey.setTemporalInfo(newKey.getTemporalInfo());
LOG.debug("New temporal info: " + newKey.getTemporalInfo() +
" for : " + existingKey.getMetricNames());
if (existingKey.getSpec() == null || !existingKey.getSpec().equals(newKey.getSpec())) {
existingKey.setSpec(newKey.getSpec());
LOG.debug("New spec: " + newKey.getSpec() +
" for : " + existingKey.getMetricNames());
}
}
return super.get(key);
}
private void validateKey(TimelineAppMetricCacheKey key) throws IllegalArgumentException {
StringBuilder msg = new StringBuilder("Invalid metric key requested.");
boolean throwException = false;
if (key.getTemporalInfo() == null) {
msg.append(" No temporal info provided.");
throwException = true;
}
if (key.getSpec() == null) {
msg.append(" Missing call spec for metric request.");
}
if (throwException) {
throw new IllegalArgumentException(msg.toString());
}
}
}