/*
* Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved.
*
* 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.hazelcast.map.impl;
import com.hazelcast.internal.cluster.ClusterService;
import com.hazelcast.internal.nearcache.NearCache;
import com.hazelcast.logging.ILogger;
import com.hazelcast.map.impl.nearcache.MapNearCacheManager;
import com.hazelcast.map.impl.recordstore.RecordStore;
import com.hazelcast.monitor.LocalMapStats;
import com.hazelcast.monitor.NearCacheStats;
import com.hazelcast.monitor.impl.LocalMapStatsImpl;
import com.hazelcast.nio.Address;
import com.hazelcast.spi.NodeEngine;
import com.hazelcast.spi.ProxyService;
import com.hazelcast.spi.partition.IPartition;
import com.hazelcast.spi.partition.IPartitionService;
import com.hazelcast.util.ConcurrencyUtil;
import com.hazelcast.util.ConstructorFunction;
import com.hazelcast.util.ExceptionUtil;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import static com.hazelcast.config.InMemoryFormat.NATIVE;
import static com.hazelcast.map.impl.MapService.SERVICE_NAME;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
/**
* Provides node local statistics of a map via {@link #createLocalMapStats}
* and also holds all {@link com.hazelcast.monitor.impl.LocalMapStatsImpl} implementations of all maps.
*/
public class LocalMapStatsProvider {
public static final LocalMapStats EMPTY_LOCAL_MAP_STATS = new LocalMapStatsImpl();
private static final int RETRY_COUNT = 3;
private static final int WAIT_PARTITION_TABLE_UPDATE_MILLIS = 100;
private final ILogger logger;
private final Address localAddress;
private final NodeEngine nodeEngine;
private final ClusterService clusterService;
private final MapServiceContext mapServiceContext;
private final MapNearCacheManager mapNearCacheManager;
private final IPartitionService partitionService;
private final ConcurrentMap<String, LocalMapStatsImpl> statsMap = new ConcurrentHashMap<String, LocalMapStatsImpl>(1000);
private final ConstructorFunction<String, LocalMapStatsImpl> constructorFunction
= new ConstructorFunction<String, LocalMapStatsImpl>() {
public LocalMapStatsImpl createNew(String key) {
return new LocalMapStatsImpl();
}
};
public LocalMapStatsProvider(MapServiceContext mapServiceContext) {
this.mapServiceContext = mapServiceContext;
this.nodeEngine = mapServiceContext.getNodeEngine();
this.logger = nodeEngine.getLogger(getClass());
this.mapNearCacheManager = mapServiceContext.getMapNearCacheManager();
this.clusterService = nodeEngine.getClusterService();
this.partitionService = nodeEngine.getPartitionService();
this.localAddress = clusterService.getThisAddress();
}
public LocalMapStatsImpl getLocalMapStatsImpl(String name) {
return ConcurrencyUtil.getOrPutIfAbsent(statsMap, name, constructorFunction);
}
public void destroyLocalMapStatsImpl(String name) {
statsMap.remove(name);
}
public LocalMapStatsImpl createLocalMapStats(String mapName) {
LocalMapStatsImpl stats = getLocalMapStatsImpl(mapName);
LocalMapOnDemandCalculatedStats onDemandStats = new LocalMapOnDemandCalculatedStats();
addNearCacheStats(mapName, stats, onDemandStats);
updateMapOnDemandStats(mapName, onDemandStats);
return onDemandStats.updateAndGet(stats);
}
public Map<String, LocalMapStats> createAllLocalMapStats() {
Map statsPerMap = new HashMap();
PartitionContainer[] partitionContainers = mapServiceContext.getPartitionContainers();
for (PartitionContainer partitionContainer : partitionContainers) {
IPartition partition = partitionService.getPartition(partitionContainer.getPartitionId());
Collection<RecordStore> allRecordStores = partitionContainer.getAllRecordStores();
for (RecordStore recordStore : allRecordStores) {
if (!isStatsCalculationEnabledFor(recordStore)) {
continue;
}
if (partition.isLocal()) {
addPrimaryStatsOf(recordStore, getOrCreateOnDemandStats(statsPerMap, recordStore));
} else {
addReplicaStatsOf(recordStore, getOrCreateOnDemandStats(statsPerMap, recordStore));
}
}
}
// reuse same HashMap to return calculated LocalMapStats.
for (Object object : statsPerMap.entrySet()) {
Map.Entry entry = (Map.Entry) object;
String mapName = ((String) entry.getKey());
LocalMapStatsImpl existingStats = getLocalMapStatsImpl(mapName);
LocalMapOnDemandCalculatedStats onDemand = ((LocalMapOnDemandCalculatedStats) entry.getValue());
addNearCacheStats(mapName, existingStats, onDemand);
LocalMapStatsImpl updatedStats = onDemand.updateAndGet(existingStats);
entry.setValue(updatedStats);
}
addStatsOfNoDataIncludedMaps(statsPerMap);
return statsPerMap;
}
/**
* Some maps may have a proxy but no data has been put yet. Think of one created a proxy but not put any data in it.
* By calling this method we are returning an empty stats object for those maps. This is helpful to monitor those kind
* of maps.
*/
private void addStatsOfNoDataIncludedMaps(Map statsPerMap) {
ProxyService proxyService = nodeEngine.getProxyService();
Collection<String> mapNames = proxyService.getDistributedObjectNames(SERVICE_NAME);
for (String mapName : mapNames) {
if (!statsPerMap.containsKey(mapName)) {
statsPerMap.put(mapName, EMPTY_LOCAL_MAP_STATS);
}
}
}
private static boolean isStatsCalculationEnabledFor(RecordStore recordStore) {
return recordStore.getMapContainer().getMapConfig().isStatisticsEnabled();
}
private static LocalMapOnDemandCalculatedStats getOrCreateOnDemandStats(Map<String, Object> onDemandStats,
RecordStore recordStore) {
String mapName = recordStore.getName();
Object stats = onDemandStats.get(mapName);
if (stats == null) {
stats = new LocalMapOnDemandCalculatedStats();
onDemandStats.put(mapName, stats);
}
return ((LocalMapOnDemandCalculatedStats) stats);
}
private void updateMapOnDemandStats(String mapName, LocalMapOnDemandCalculatedStats onDemandStats) {
PartitionContainer[] partitionContainers = mapServiceContext.getPartitionContainers();
for (PartitionContainer partitionContainer : partitionContainers) {
IPartition partition = partitionService.getPartition(partitionContainer.getPartitionId());
if (partition.isLocal()) {
addPrimaryStatsOf(partitionContainer.getExistingRecordStore(mapName), onDemandStats);
} else {
addReplicaStatsOf(partitionContainer.getExistingRecordStore(mapName), onDemandStats);
}
}
}
private static void addPrimaryStatsOf(RecordStore recordStore, LocalMapOnDemandCalculatedStats onDemandStats) {
if (!hasRecords(recordStore)) {
return;
}
onDemandStats.incrementLockedEntryCount(recordStore.getLockedEntryCount());
onDemandStats.incrementHits(recordStore.getHits());
onDemandStats.incrementDirtyEntryCount(recordStore.getMapDataStore().notFinishedOperationsCount());
onDemandStats.incrementOwnedEntryMemoryCost(recordStore.getOwnedEntryCost());
if (NATIVE != recordStore.getMapContainer().getMapConfig().getInMemoryFormat()) {
onDemandStats.incrementHeapCost(recordStore.getOwnedEntryCost());
}
onDemandStats.incrementOwnedEntryCount(recordStore.size());
onDemandStats.setLastAccessTime(recordStore.getLastAccessTime());
onDemandStats.setLastUpdateTime(recordStore.getLastUpdateTime());
onDemandStats.setBackupCount(recordStore.getMapContainer().getMapConfig().getTotalBackupCount());
}
/**
* Calculates and adds replica partition stats.
*/
private void addReplicaStatsOf(RecordStore recordStore, LocalMapOnDemandCalculatedStats onDemandStats) {
if (!hasRecords(recordStore)) {
return;
}
long backupEntryCount = 0;
long backupEntryMemoryCost = 0;
int totalBackupCount = recordStore.getMapContainer().getTotalBackupCount();
for (int replicaNumber = 1; replicaNumber <= totalBackupCount; replicaNumber++) {
int partitionId = recordStore.getPartitionId();
Address replicaAddress = getReplicaAddress(partitionId, replicaNumber, totalBackupCount);
if (!isReplicaAvailable(replicaAddress, totalBackupCount)) {
printWarning(partitionId, replicaNumber);
continue;
}
if (isReplicaOnThisNode(replicaAddress)) {
backupEntryMemoryCost += recordStore.getOwnedEntryCost();
backupEntryCount += recordStore.size();
}
}
if (NATIVE != recordStore.getMapContainer().getMapConfig().getInMemoryFormat()) {
onDemandStats.incrementHeapCost(backupEntryMemoryCost);
}
onDemandStats.incrementBackupEntryMemoryCost(backupEntryMemoryCost);
onDemandStats.incrementBackupEntryCount(backupEntryCount);
onDemandStats.setBackupCount(recordStore.getMapContainer().getMapConfig().getTotalBackupCount());
}
private static boolean hasRecords(RecordStore recordStore) {
return recordStore != null && recordStore.size() > 0;
}
private boolean isReplicaAvailable(Address replicaAddress, int backupCount) {
return !(replicaAddress == null && partitionService.getMaxAllowedBackupCount() >= backupCount);
}
private boolean isReplicaOnThisNode(Address replicaAddress) {
return replicaAddress != null && localAddress.equals(replicaAddress);
}
private void printWarning(int partitionId, int replica) {
logger.warning("partitionId: " + partitionId + ", replica: " + replica + " has no owner!");
}
/**
* Gets replica address. Waits if necessary.
*
* @see #waitForReplicaAddress
*/
private Address getReplicaAddress(int partitionId, int replicaNumber, int backupCount) {
IPartition partition = partitionService.getPartition(partitionId);
Address replicaAddress = partition.getReplicaAddress(replicaNumber);
if (replicaAddress == null) {
replicaAddress = waitForReplicaAddress(replicaNumber, partition, backupCount);
}
return replicaAddress;
}
/**
* Waits partition table update to get replica address if current replica address is null.
*/
private Address waitForReplicaAddress(int replica, IPartition partition, int backupCount) {
int tryCount = RETRY_COUNT;
Address replicaAddress = null;
while (replicaAddress == null && partitionService.getMaxAllowedBackupCount() >= backupCount && tryCount-- > 0) {
sleep();
replicaAddress = partition.getReplicaAddress(replica);
}
return replicaAddress;
}
private static void sleep() {
try {
MILLISECONDS.sleep(WAIT_PARTITION_TABLE_UPDATE_MILLIS);
} catch (InterruptedException e) {
throw ExceptionUtil.rethrow(e);
}
}
private void addNearCacheStats(String mapName, LocalMapStatsImpl localMapStats,
LocalMapOnDemandCalculatedStats onDemandStats) {
NearCache nearCache = mapNearCacheManager.getNearCache(mapName);
if (nearCache == null) {
return;
}
NearCacheStats nearCacheStats = nearCache.getNearCacheStats();
localMapStats.setNearCacheStats(nearCacheStats);
if (NATIVE != nearCache.getInMemoryFormat()) {
onDemandStats.incrementHeapCost(nearCacheStats.getOwnedEntryMemoryCost());
}
}
private static class LocalMapOnDemandCalculatedStats {
private int backupCount;
private long hits;
private long ownedEntryCount;
private long backupEntryCount;
private long ownedEntryMemoryCost;
private long backupEntryMemoryCost;
// Holds total heap cost of map & Near Cache & backups.
private long heapCost;
private long lockedEntryCount;
private long dirtyEntryCount;
private long lastAccessTime;
private long lastUpdateTime;
public void setBackupCount(int backupCount) {
this.backupCount = backupCount;
}
public void incrementHits(long hits) {
this.hits += hits;
}
public void incrementOwnedEntryCount(long ownedEntryCount) {
this.ownedEntryCount += ownedEntryCount;
}
public void incrementBackupEntryCount(long backupEntryCount) {
this.backupEntryCount += backupEntryCount;
}
public void incrementOwnedEntryMemoryCost(long ownedEntryMemoryCost) {
this.ownedEntryMemoryCost += ownedEntryMemoryCost;
}
public void incrementBackupEntryMemoryCost(long backupEntryMemoryCost) {
this.backupEntryMemoryCost += backupEntryMemoryCost;
}
public void incrementLockedEntryCount(long lockedEntryCount) {
this.lockedEntryCount += lockedEntryCount;
}
public void incrementDirtyEntryCount(long dirtyEntryCount) {
this.dirtyEntryCount += dirtyEntryCount;
}
public void incrementHeapCost(long heapCost) {
this.heapCost += heapCost;
}
public LocalMapStatsImpl updateAndGet(LocalMapStatsImpl stats) {
stats.setBackupCount(backupCount);
stats.setHits(hits);
stats.setOwnedEntryCount(ownedEntryCount);
stats.setBackupEntryCount(backupEntryCount);
stats.setOwnedEntryMemoryCost(ownedEntryMemoryCost);
stats.setBackupEntryMemoryCost(backupEntryMemoryCost);
stats.setHeapCost(heapCost);
stats.setLockedEntryCount(lockedEntryCount);
stats.setDirtyEntryCount(dirtyEntryCount);
stats.setLastAccessTime(lastAccessTime);
stats.setLastUpdateTime(lastUpdateTime);
return stats;
}
public void setLastAccessTime(long lastAccessTime) {
if (lastAccessTime > this.lastAccessTime) {
this.lastAccessTime = lastAccessTime;
}
}
public void setLastUpdateTime(long lastUpdateTime) {
if (lastUpdateTime > this.lastUpdateTime) {
this.lastUpdateTime = lastUpdateTime;
}
}
}
}