/* * INESC-ID, Instituto de Engenharia de Sistemas e Computadores Investigação e Desevolvimento em Lisboa * Copyright 2013 INESC-ID and/or its affiliates and other * contributors as indicated by the @author tags. All rights reserved. * See the copyright.txt in the distribution for a full listing of * individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 3.0 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.infinispan.dataplacement; import org.infinispan.distribution.DistributionManager; import org.infinispan.distribution.ch.ConsistentHash; import org.infinispan.distribution.ch.DataPlacementConsistentHash; import org.infinispan.remoting.transport.Address; import org.infinispan.stats.topK.StreamLibContainer; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import static org.infinispan.stats.topK.StreamLibContainer.Stat.*; /** * Manages all the remote access and creates the request list to send to each other member * * @author Zhongmiao Li * @author João Paiva * @author Pedro Ruivo * @since 5.2 */ public class AccessesManager { private static final Log log = LogFactory.getLog(AccessesManager.class); private final DistributionManager distributionManager; private ClusterSnapshot clusterSnapshot; private Accesses[] accessesByPrimaryOwner; private final StreamLibContainer streamLibContainer; private boolean hasAccessesCalculated; private int maxNumberOfKeysToRequest; public AccessesManager(DistributionManager distributionManager, int maxNumberOfKeysToRequest) { this.distributionManager = distributionManager; this.maxNumberOfKeysToRequest = maxNumberOfKeysToRequest; streamLibContainer = StreamLibContainer.getInstance(); } /** * reset the state (before each round) * * @param clusterSnapshot the current cluster snapshot */ public final synchronized void resetState(ClusterSnapshot clusterSnapshot) { this.clusterSnapshot = clusterSnapshot; accessesByPrimaryOwner = new Accesses[clusterSnapshot.size()]; for (int i = 0; i < accessesByPrimaryOwner.length; ++i) { accessesByPrimaryOwner[i] = new Accesses(); } hasAccessesCalculated = false; } /** * returns the request object list for the {@code member} * * @param member the destination member * @return the request object list. It can be empty if no requests are necessary */ public synchronized final ObjectRequest getObjectRequestForAddress(Address member) { int addressIndex = clusterSnapshot.indexOf(member); if (addressIndex == -1) { log.warnf("Trying to get Object Requests to send to %s but it does not exists in cluster snapshot %s", member, clusterSnapshot); return new ObjectRequest(null, null); } ObjectRequest request = accessesByPrimaryOwner[addressIndex].toObjectRequest(); if (log.isInfoEnabled()) { log.debugf("Getting request list for %s. Request is %s", member, request.toString(log.isDebugEnabled())); } return request; } /** * sets the max number of keys to request to a new value, only if is higher than zero * * @param maxNumberOfKeysToRequest the new value */ public synchronized final void setMaxNumberOfKeysToRequest(int maxNumberOfKeysToRequest) { if (maxNumberOfKeysToRequest > 0) { this.maxNumberOfKeysToRequest = maxNumberOfKeysToRequest; } } /** * returns the max number of keys to request * * @return returns the max number of keys to request */ public int getMaxNumberOfKeysToRequest() { return maxNumberOfKeysToRequest; } /** * calculates the object request list to request to each member */ public synchronized final void calculateAccesses(){ if (hasAccessesCalculated) { return; } hasAccessesCalculated = true; if (log.isTraceEnabled()) { log.trace("Calculating accessed keys for data placement optimization"); } RemoteTopKeyRequest request = new RemoteTopKeyRequest(streamLibContainer.getCapacity() * 2); request.merge(streamLibContainer.getTopKFrom(REMOTE_PUT, maxNumberOfKeysToRequest), 2); request.merge(streamLibContainer.getTopKFrom(REMOTE_GET, maxNumberOfKeysToRequest), 1); sortObjectsByPrimaryOwner(request.toRequestMap(maxNumberOfKeysToRequest), true); request.clear(); LocalTopKeyRequest localTopKeyRequest = new LocalTopKeyRequest(); localTopKeyRequest.merge(streamLibContainer.getTopKFrom(LOCAL_PUT), 2); localTopKeyRequest.merge(streamLibContainer.getTopKFrom(LOCAL_GET), 1); sortObjectsByPrimaryOwner(localTopKeyRequest.toRequestMap(), false); request.clear(); if (log.isTraceEnabled()) { StringBuilder stringBuilder = new StringBuilder("Accesses:\n"); for (int i = 0; i < accessesByPrimaryOwner.length; ++i) { stringBuilder.append(clusterSnapshot.get(i)).append(" ==> ").append(accessesByPrimaryOwner[i]).append("\n"); } log.debug(stringBuilder); } streamLibContainer.resetStat(REMOTE_GET); streamLibContainer.resetStat(LOCAL_GET); streamLibContainer.resetStat(REMOTE_PUT); streamLibContainer.resetStat(LOCAL_PUT); } public final ObjectRequest[] getAccesses() { ObjectRequest[] objectRequests = new ObjectRequest[accessesByPrimaryOwner.length]; for (int i = 0; i < objectRequests.length; ++i) { objectRequests[i] = accessesByPrimaryOwner[i].toObjectRequest(); } return objectRequests; } /** * sort the keys and number of access by primary owner * * * @param accesses the remote accesses * @param remote true if the accesses to process are from remote access, false otherwise */ @SuppressWarnings("unchecked") private void sortObjectsByPrimaryOwner(Map<Object, Long> accesses, boolean remote) { Map<Object, List<Address>> primaryOwners = getDefaultConsistentHash().locateAll(accesses.keySet(), 1); for (Entry<Object, Long> entry : accesses.entrySet()) { Object key = entry.getKey(); Address primaryOwner = primaryOwners.remove(key).get(0); int addressIndex = clusterSnapshot.indexOf(primaryOwner); if (addressIndex == -1) { log.warnf("Primary owner [%s] does not exists in cluster snapshot %s", primaryOwner, clusterSnapshot); continue; } accessesByPrimaryOwner[addressIndex].add(entry.getKey(), entry.getValue(), remote); } } /** * returns the actual consistent hashing * * @return the actual consistent hashing */ public final ConsistentHash getDefaultConsistentHash() { ConsistentHash hash = this.distributionManager.getConsistentHash(); return hash instanceof DataPlacementConsistentHash ? ((DataPlacementConsistentHash) hash).getDefaultHash() : hash; } private class Accesses { private final Map<Object, Long> localAccesses; private final Map<Object, Long> remoteAccesses; private Accesses() { localAccesses = new HashMap<Object, Long>(); remoteAccesses = new HashMap<Object, Long>(); } private void add(Object key, long accesses, boolean remote) { Map<Object, Long> toPut = remote ? remoteAccesses : localAccesses; toPut.put(key, accesses); } private ObjectRequest toObjectRequest() { return new ObjectRequest(remoteAccesses.size() == 0 ? null : remoteAccesses, localAccesses.size() == 0 ? null : localAccesses); } @Override public String toString() { return "Accesses{" + "localAccesses=" + localAccesses.size() + ", remoteAccesses=" + remoteAccesses.size() + '}'; } } public static class RemoteTopKeyRequest { private final Map<Object, Integer> keyAccessIndexMap; private final ArrayList<KeyAccess> sortedKeyAccess; public RemoteTopKeyRequest(int expectedSize) { keyAccessIndexMap = new HashMap<Object, Integer>(); sortedKeyAccess = new ArrayList<KeyAccess>(expectedSize * 2); } private void add(Object key, long accesses) { if (accesses <= 0) { return; } Integer index = keyAccessIndexMap.get(key); if (index == null) { KeyAccess keyAccess = new KeyAccess(key, accesses); add(keyAccess); } else { KeyAccess keyAccess = sortedKeyAccess.get(index); keyAccess.accesses += accesses; update(index); } } private void add(KeyAccess keyAccess) { int indexToInsert = 0; while (indexToInsert < sortedKeyAccess.size()) { KeyAccess current = sortedKeyAccess.get(indexToInsert); if (keyAccess.accesses >= current.accesses) { addAndUpdateIndexes(keyAccess, indexToInsert); return; } indexToInsert++; } addAndUpdateIndexes(keyAccess, indexToInsert); } private void update(int index) { KeyAccess toUpdate = sortedKeyAccess.remove(index); if (index == sortedKeyAccess.size()) { index--; } while (index >= 0) { KeyAccess current = sortedKeyAccess.get(index); if (toUpdate.accesses <= current.accesses) { addAndUpdateIndexes(toUpdate, index + 1); return; } index--; } addAndUpdateIndexes(toUpdate, index + 1); } private void addAndUpdateIndexes(KeyAccess keyAccess, int index) { sortedKeyAccess.add(index, keyAccess); keyAccessIndexMap.put(keyAccess.key, index); for (int i = index + 1; i < sortedKeyAccess.size(); ++i) { keyAccessIndexMap.put(sortedKeyAccess.get(i).key, i); } } public final boolean contains(Object key) { return keyAccessIndexMap.containsKey(key); } public final KeyAccess get(Object key) { Integer index = keyAccessIndexMap.get(key); return index == null ? null : sortedKeyAccess.get(index); } public final ArrayList<KeyAccess> getSortedKeyAccess() { return sortedKeyAccess; } public final void merge(Map<Object, Long> toMerge, double multiplierFactor) { for (Entry<Object, Long> entry : toMerge.entrySet()) { add(entry.getKey(), (long) (entry.getValue() * multiplierFactor)); } } public final Map<Object, Long> toRequestMap(int maxSize) { Map<Object, Long> map = new HashMap<Object, Long>(); int size = 0; for (KeyAccess keyAccess : sortedKeyAccess) { if (size >= maxSize) { return map; } map.put(keyAccess.key, keyAccess.accesses); size++; } return map; } public final void clear() { keyAccessIndexMap.clear(); sortedKeyAccess.clear(); } @Override public String toString() { return "RemoteTopKeyRequest{" + "keyAccessIndexMap=" + keyAccessIndexMap + ", sortedKeyAccess=" + sortedKeyAccess + '}'; } } public static class LocalTopKeyRequest { private final Map<Object, KeyAccess> keyAccessMap; public LocalTopKeyRequest() { keyAccessMap = new HashMap<Object, KeyAccess>(); } private void add(Object key, long accesses) { if (accesses <= 0) { return; } KeyAccess access = keyAccessMap.get(key); if (access == null) { access = new KeyAccess(key, accesses); keyAccessMap.put(key, access); } else { access.accesses += accesses; } } public final boolean contains(Object key) { return keyAccessMap.containsKey(key); } public final KeyAccess get(Object key) { return keyAccessMap.get(key); } public final void merge(Map<Object, Long> toMerge, double multiplierFactor) { for (Entry<Object, Long> entry : toMerge.entrySet()) { add(entry.getKey(), (long) (entry.getValue() * multiplierFactor)); } } public final Map<Object, Long> toRequestMap() { Map<Object, Long> map = new HashMap<Object, Long>(); for (KeyAccess keyAccess : keyAccessMap.values()) { map.put(keyAccess.key, keyAccess.accesses); } return map; } public final void clear() { keyAccessMap.clear(); } @Override public String toString() { return "LocalTopKeyRequest{" + "keyAccessMap=" + keyAccessMap + '}'; } } public static class KeyAccess { private final Object key; private long accesses; public KeyAccess(Object key, long accesses) { this.key = key; this.accesses = accesses; } public final Object getKey() { return key; } public final long getAccesses() { return accesses; } @Override public String toString() { return "KeyAccess{" + "key=" + key + ", accesses=" + accesses + '}'; } } }