<commit_before>/*
 * Copyright (c) 2008-2018, 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.eviction;

import com.hazelcast.config.MapConfig;
import com.hazelcast.config.MaxSizeConfig;
import com.hazelcast.internal.nearcache.NearCache;
import com.hazelcast.logging.ILogger;
import com.hazelcast.map.impl.MapContainer;
import com.hazelcast.map.impl.MapServiceContext;
import com.hazelcast.map.impl.PartitionContainer;
import com.hazelcast.map.impl.nearcache.MapNearCacheManager;
import com.hazelcast.map.impl.recordstore.RecordStore;
import com.hazelcast.monitor.NearCacheStats;
import com.hazelcast.nio.Address;
import com.hazelcast.spi.NodeEngine;
import com.hazelcast.spi.partition.IPartition;
import com.hazelcast.spi.partition.IPartitionService;
import com.hazelcast.util.MemoryInfoAccessor;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

import static com.hazelcast.cluster.memberselector.MemberSelectors.DATA_MEMBER_SELECTOR;
import static com.hazelcast.memory.MemorySize.toPrettyString;
import static com.hazelcast.memory.MemoryUnit.MEGABYTES;
import static com.hazelcast.util.Preconditions.checkNotNull;
import static java.lang.String.format;

/**
 * Checks whether a specific threshold is exceeded or not
 * according to configured {@link com.hazelcast.config.MaxSizeConfig.MaxSizePolicy}
 * to start eviction process.
 *
 * @see EvictorImpl#evictionChecker
 */
public class EvictionChecker {

    protected static final double ONE_HUNDRED_PERCENT = 100D;

    protected final ILogger logger;
    protected final MapServiceContext mapServiceContext;
    protected final MemoryInfoAccessor memoryInfoAccessor;
    protected final AtomicBoolean misconfiguredPerNodeMaxSizeWarningLogged;

    public EvictionChecker(MemoryInfoAccessor givenMemoryInfoAccessor, MapServiceContext mapServiceContext) {
        checkNotNull(givenMemoryInfoAccessor, "givenMemoryInfoAccessor cannot be null");
        checkNotNull(mapServiceContext, "mapServiceContext cannot be null");

        this.logger = mapServiceContext.getNodeEngine().getLogger(getClass());
        this.mapServiceContext = mapServiceContext;
        this.memoryInfoAccessor = givenMemoryInfoAccessor;

        if (logger.isFinestEnabled()) {
            logger.finest("Used memoryInfoAccessor=" + this.memoryInfoAccessor.getClass().getCanonicalName());
        }

        this.misconfiguredPerNodeMaxSizeWarningLogged = new AtomicBoolean();
    }


    public boolean checkEvictable(RecordStore recordStore) {
        if (recordStore.size() == 0) {
            return false;
        }

        String mapName = recordStore.getName();

        MapContainer mapContainer = recordStore.getMapContainer();
        MaxSizeConfig maxSizeConfig = mapContainer.getMapConfig().getMaxSizeConfig();
        MaxSizeConfig.MaxSizePolicy maxSizePolicy = maxSizeConfig.getMaxSizePolicy();

        switch (maxSizePolicy) {
            case PER_NODE:
                return checkPerNodeEviction(recordStore);
            case PER_PARTITION:
                int partitionId = recordStore.getPartitionId();
                return checkPerPartitionEviction(mapName, maxSizeConfig, partitionId);
            case USED_HEAP_PERCENTAGE:
                return checkHeapPercentageEviction(mapName, maxSizeConfig);
            case USED_HEAP_SIZE:
                return checkHeapSizeEviction(mapName, maxSizeConfig);
            case FREE_HEAP_PERCENTAGE:
                return checkFreeHeapPercentageEviction(maxSizeConfig);
            case FREE_HEAP_SIZE:
                return checkFreeHeapSizeEviction(maxSizeConfig);
            default:
                throw new IllegalArgumentException("Not an appropriate max size policy [" + maxSizePolicy + ']');
        }
    }

    protected boolean checkPerNodeEviction(RecordStore recordStore) {
        double maxExpectedRecordStoreSize = calculatePerNodeMaxRecordStoreSize(recordStore);
        return recordStore.size() > maxExpectedRecordStoreSize;
    }

    /**
     * Calculates and returns the expected maximum size of an evicted record-store
     * when {@link com.hazelcast.config.MaxSizeConfig.MaxSizePolicy#PER_NODE PER_NODE} max-size-policy is used.
     */
    public double calculatePerNodeMaxRecordStoreSize(RecordStore recordStore) {
        MapConfig mapConfig = recordStore.getMapContainer().getMapConfig();
        MaxSizeConfig maxSizeConfig = mapConfig.getMaxSizeConfig();
        NodeEngine nodeEngine = mapServiceContext.getNodeEngine();

        int configuredMaxSize = maxSizeConfig.getSize();
        int memberCount = nodeEngine.getClusterService().getSize(DATA_MEMBER_SELECTOR);
        int partitionCount = nodeEngine.getPartitionService().getPartitionCount();

        final double perNodeMaxRecordStoreSize = (1D * configuredMaxSize * memberCount / partitionCount);
        if (perNodeMaxRecordStoreSize < 1 && misconfiguredPerNodeMaxSizeWarningLogged.compareAndSet(false, true)) {
            int minMaxSize = (int) Math.ceil((1D * partitionCount / memberCount));
            logger.warning(format("The max size configuration for map \"%s\" does not allow any data in the map. "
                    + "Given the current cluster size of %d members with %d partitions, max size should be at "
                            + "least %d.", mapConfig.getName(), memberCount, partitionCount, minMaxSize));
        }
        return perNodeMaxRecordStoreSize;
    }

    protected boolean checkPerPartitionEviction(String mapName, MaxSizeConfig maxSizeConfig, int partitionId) {
        final double maxSize = maxSizeConfig.getSize();
        final PartitionContainer container = mapServiceContext.getPartitionContainer(partitionId);
        if (container == null) {
            return false;<commit_msg>Fixed backward compatibility issue for PER_NODE capacity calculation algorithm<commit_after>/*
 * Copyright (c) 2008-2018, 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.eviction;

import com.hazelcast.config.MapConfig;
import com.hazelcast.config.MaxSizeConfig;
import com.hazelcast.internal.nearcache.NearCache;
import com.hazelcast.logging.ILogger;
import com.hazelcast.map.impl.MapContainer;
import com.hazelcast.map.impl.MapServiceContext;
import com.hazelcast.map.impl.PartitionContainer;
import com.hazelcast.map.impl.nearcache.MapNearCacheManager;
import com.hazelcast.map.impl.recordstore.RecordStore;
import com.hazelcast.monitor.NearCacheStats;
import com.hazelcast.nio.Address;
import com.hazelcast.spi.NodeEngine;
import com.hazelcast.spi.partition.IPartition;
import com.hazelcast.spi.partition.IPartitionService;
import com.hazelcast.util.MemoryInfoAccessor;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

import static com.hazelcast.cluster.memberselector.MemberSelectors.DATA_MEMBER_SELECTOR;
import static com.hazelcast.memory.MemorySize.toPrettyString;
import static com.hazelcast.memory.MemoryUnit.MEGABYTES;
import static com.hazelcast.util.Preconditions.checkNotNull;
import static java.lang.String.format;

/**
 * Checks whether a specific threshold is exceeded or not
 * according to configured {@link com.hazelcast.config.MaxSizeConfig.MaxSizePolicy}
 * to start eviction process.
 *
 * @see EvictorImpl#evictionChecker
 */
public class EvictionChecker {

    private static final int MIN_SANE_PER_PARTITION_SIZE = 2;
    protected static final double ONE_HUNDRED_PERCENT = 100D;

    protected final ILogger logger;
    protected final MapServiceContext mapServiceContext;
    protected final MemoryInfoAccessor memoryInfoAccessor;
    protected final AtomicBoolean misconfiguredPerNodeMaxSizeWarningLogged;

    public EvictionChecker(MemoryInfoAccessor givenMemoryInfoAccessor, MapServiceContext mapServiceContext) {
        checkNotNull(givenMemoryInfoAccessor, "givenMemoryInfoAccessor cannot be null");
        checkNotNull(mapServiceContext, "mapServiceContext cannot be null");

        this.logger = mapServiceContext.getNodeEngine().getLogger(getClass());
        this.mapServiceContext = mapServiceContext;
        this.memoryInfoAccessor = givenMemoryInfoAccessor;

        if (logger.isFinestEnabled()) {
            logger.finest("Used memoryInfoAccessor=" + this.memoryInfoAccessor.getClass().getCanonicalName());
        }

        this.misconfiguredPerNodeMaxSizeWarningLogged = new AtomicBoolean();
    }


    public boolean checkEvictable(RecordStore recordStore) {
        if (recordStore.size() == 0) {
            return false;
        }

        String mapName = recordStore.getName();

        MapContainer mapContainer = recordStore.getMapContainer();
        MaxSizeConfig maxSizeConfig = mapContainer.getMapConfig().getMaxSizeConfig();
        MaxSizeConfig.MaxSizePolicy maxSizePolicy = maxSizeConfig.getMaxSizePolicy();

        switch (maxSizePolicy) {
            case PER_NODE:
                return checkPerNodeEviction(recordStore);
            case PER_PARTITION:
                int partitionId = recordStore.getPartitionId();
                return checkPerPartitionEviction(mapName, maxSizeConfig, partitionId);
            case USED_HEAP_PERCENTAGE:
                return checkHeapPercentageEviction(mapName, maxSizeConfig);
            case USED_HEAP_SIZE:
                return checkHeapSizeEviction(mapName, maxSizeConfig);
            case FREE_HEAP_PERCENTAGE:
                return checkFreeHeapPercentageEviction(maxSizeConfig);
            case FREE_HEAP_SIZE:
                return checkFreeHeapSizeEviction(maxSizeConfig);
            default:
                throw new IllegalArgumentException("Not an appropriate max size policy [" + maxSizePolicy + ']');
        }
    }

    protected boolean checkPerNodeEviction(RecordStore recordStore) {
        double maxExpectedRecordStoreSize = calculatePerNodeMaxRecordStoreSize(recordStore);
        return recordStore.size() > maxExpectedRecordStoreSize;
    }

    /**
     * Calculates and returns the expected maximum size of an evicted record-store
     * when {@link com.hazelcast.config.MaxSizeConfig.MaxSizePolicy#PER_NODE PER_NODE} max-size-policy is used.
     */
    public double calculatePerNodeMaxRecordStoreSize(RecordStore recordStore) {
        MapConfig mapConfig = recordStore.getMapContainer().getMapConfig();
        MaxSizeConfig maxSizeConfig = mapConfig.getMaxSizeConfig();
        NodeEngine nodeEngine = mapServiceContext.getNodeEngine();

        int configuredMaxSize = maxSizeConfig.getSize();
        int memberCount = nodeEngine.getClusterService().getSize(DATA_MEMBER_SELECTOR);
        int partitionCount = nodeEngine.getPartitionService().getPartitionCount();

        double perNodeMaxRecordStoreSize = (1D * configuredMaxSize * memberCount / partitionCount);
        if(perNodeMaxRecordStoreSize < 1) {
            perNodeMaxRecordStoreSize = MIN_SANE_PER_PARTITION_SIZE;
            if(misconfiguredPerNodeMaxSizeWarningLogged.compareAndSet(false, true)) {
                int minMaxSize = (int) Math.ceil((1D * partitionCount / memberCount));
                int newSize = MIN_SANE_PER_PARTITION_SIZE * partitionCount / memberCount;
                logger.warning(format("The max size configuration for map \"%s\" does not allow any data in the map. "
                        + "Given the current cluster size of %d members with %d partitions, max size should be at "
                        + "least %d. Map size is forced set to %d for backward compatibility", mapConfig.getName(),
                        memberCount, partitionCount, minMaxSize, newSize));
            }
        }
        return perNodeMaxRecordStoreSize;
    }

    protected boolean checkPerPartitionEviction(String mapName, MaxSizeConfig maxSizeConfig, int partitionId) {
        final double maxSize = maxSizeConfig.getSize();
        final PartitionContainer container = mapServiceContext.getPartitionContainer(partitionId);
        if (container == null) {
            return false;
{'fraction_non_alphanumeric': 0.07653177435274466, 'fraction_numerical': 0.002961050793409764, 'mean_word_length': 3.697574893009986, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5109597', 'n_tokens_mistral': 3529, 'n_tokens_neox': 3439, 'n_words': 849}