/* * Copyright (C) 2012-2015 DataStax 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.datastax.driver.core; import com.google.common.base.Throwables; import com.google.common.cache.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.net.InetSocketAddress; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicInteger; public class CCMCache { private static final Logger LOGGER = LoggerFactory.getLogger(CCMCache.class); private static class CachedCCMAccess implements CCMAccess { private final CCMAccess ccm; private final AtomicInteger refCount = new AtomicInteger(1); private volatile boolean evicted = false; private CachedCCMAccess(CCMAccess ccm) { this.ccm = ccm; } @Override public String getClusterName() { return ccm.getClusterName(); } @Override public VersionNumber getCassandraVersion() { return ccm.getCassandraVersion(); } @Override public VersionNumber getDSEVersion() { return ccm.getDSEVersion(); } @Override public File getCcmDir() { return ccm.getCcmDir(); } @Override public File getClusterDir() { return ccm.getClusterDir(); } @Override public File getNodeDir(int n) { return ccm.getNodeDir(n); } @Override public File getNodeConfDir(int n) { return ccm.getNodeConfDir(n); } @Override public int getStoragePort() { return ccm.getStoragePort(); } @Override public int getThriftPort() { return ccm.getThriftPort(); } @Override public int getBinaryPort() { return ccm.getBinaryPort(); } @Override public void setKeepLogs(boolean keepLogs) { ccm.setKeepLogs(keepLogs); } @Override public InetSocketAddress addressOfNode(int n) { return ccm.addressOfNode(n); } @Override public void start() { ccm.start(); } @Override public void stop() { ccm.stop(); } @Override public void forceStop() { ccm.forceStop(); } @Override public void close() { refCount.decrementAndGet(); maybeClose(); } private void maybeClose() { if (refCount.get() <= 0 && evicted) { ccm.close(); } } @Override public void remove() { ccm.remove(); } @Override public void updateConfig(Map<String, Object> configs) { ccm.updateConfig(configs); } @Override public void updateDSEConfig(Map<String, Object> configs) { ccm.updateDSEConfig(configs); } @Override public String checkForErrors() { return ccm.checkForErrors(); } @Override public void start(int n) { ccm.start(n); } @Override public void stop(int n) { ccm.stop(n); } @Override public void forceStop(int n) { ccm.forceStop(n); } @Override public void pause(int n) { ccm.pause(n); } @Override public void resume(int n) { ccm.resume(n); } @Override public void remove(int n) { ccm.remove(n); } @Override public void add(int n) { ccm.add(n); } @Override public void add(int dc, int n) { ccm.add(dc, n); } @Override public void decommission(int n) { ccm.decommission(n); } @Override public void updateNodeConfig(int n, String key, Object value) { ccm.updateNodeConfig(n, key, value); } @Override public void updateNodeConfig(int n, Map<String, Object> configs) { ccm.updateNodeConfig(n, configs); } @Override public void updateDSENodeConfig(int n, String key, Object value) { ccm.updateDSENodeConfig(n, key, value); } @Override public void updateDSENodeConfig(int n, Map<String, Object> configs) { ccm.updateDSENodeConfig(n, configs); } @Override public void setWorkload(int n, Workload... workload) { ccm.setWorkload(n, workload); } @Override public void waitForUp(int node) { ccm.waitForUp(node); } @Override public void waitForDown(int node) { ccm.waitForDown(node); } @Override public ProtocolVersion getProtocolVersion() { return ccm.getProtocolVersion(); } @Override public ProtocolVersion getProtocolVersion(ProtocolVersion maximumAllowed) { return ccm.getProtocolVersion(maximumAllowed); } @Override public String toString() { return ccm.toString(); } } private static class CCMAccessLoader extends CacheLoader<CCMBridge.Builder, CachedCCMAccess> { @Override public CachedCCMAccess load(CCMBridge.Builder key) { return new CachedCCMAccess(key.build()); } } private static class CCMAccessWeigher implements Weigher<CCMBridge.Builder, CachedCCMAccess> { @Override public int weigh(CCMBridge.Builder key, CachedCCMAccess value) { return key.weight(); } } private static class CCMAccessRemovalListener implements RemovalListener<CCMBridge.Builder, CachedCCMAccess> { @Override public void onRemoval(RemovalNotification<CCMBridge.Builder, CachedCCMAccess> notification) { CachedCCMAccess cached = notification.getValue(); if (cached != null && cached.ccm != null) { LOGGER.debug("Evicting: {}, reason: {}", cached.ccm, notification.getCause()); cached.evicted = true; cached.maybeClose(); } } } /** * A LoadingCache that stores running CCM clusters. */ private static final LoadingCache<CCMBridge.Builder, CachedCCMAccess> CACHE; // The amount of memory one CCM node takes in MB. private static final int ONE_CCM_NODE_MB = 800; static { long maximumWeight; String numberOfNodes = System.getProperty("ccm.maxNumberOfNodes"); if (numberOfNodes == null) { long freeMemoryMB = TestUtils.getFreeMemoryMB(); if (freeMemoryMB < ONE_CCM_NODE_MB) LOGGER.warn("Not enough available memory: {} MB, CCM clusters might fail to start", freeMemoryMB); // CCM nodes are started with -Xms500M -Xmx500M // and allocate up to 100MB non-heap memory in the general case, // to be conservative we treat 1 "slot" as 800Mb. // We leave 3 slots out to avoid starving system memory, // and we pick a value with a minimum of 1 slot and a maximum of 8 slots. // For example, an 8GB VM with ~6.5GB currently available heap will yield 5 slots ((6500/800) - 3 = 5). long slotsAvailable = (freeMemoryMB / ONE_CCM_NODE_MB) - 3; maximumWeight = Math.min(8, Math.max(1, slotsAvailable)); } else { maximumWeight = Integer.parseInt(numberOfNodes); } LOGGER.info("Maximum number of running CCM nodes: {}", maximumWeight); CACHE = CacheBuilder.newBuilder() .initialCapacity(3) .softValues() .maximumWeight(maximumWeight) .weigher(new CCMAccessWeigher()) .removalListener(new CCMAccessRemovalListener()) .recordStats() .build(new CCMAccessLoader()); } /** * Creates or recycles a {@link CCMAccess} instance and returns it. * <p/> * Caller MUST call {@link CCMAccess#close()} when done with the cluster, * to ensure that resources will be properly freed. */ public static CCMAccess get(CCMBridge.Builder key) { CachedCCMAccess ccm = CACHE.getIfPresent(key); if (ccm != null) { ccm.refCount.incrementAndGet(); } else { try { ccm = CACHE.get(key); } catch (ExecutionException e) { throw Throwables.propagate(e); } } logCache(); return ccm; } /** * Removes the given key from the cache. */ public static void remove(CCMBridge.Builder key) { CACHE.invalidate(key); } private static void logCache() { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Free memory: {} MB", TestUtils.getFreeMemoryMB()); StringBuilder sb = new StringBuilder(); Iterator<Map.Entry<CCMBridge.Builder, CachedCCMAccess>> iterator = CACHE.asMap().entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<CCMBridge.Builder, CachedCCMAccess> entry = iterator.next(); sb.append(entry.getValue().getClusterName()) .append(" (") .append(entry.getKey().weight()) .append(")"); if (iterator.hasNext()) sb.append(", "); } LOGGER.debug("Cache contents: {{}}", sb.toString()); LOGGER.debug("Cache stats: {}", CACHE.stats()); } } }