All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.netflix.loadbalancer.LoadBalancerStats Maven / Gradle / Ivy

There is a newer version: 2.7.18
Show newest version
/*
*
* Copyright 2013 Netflix, 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.netflix.loadbalancer;

import com.google.common.base.Preconditions;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.RemovalListener;
import com.netflix.client.IClientConfigAware;
import com.netflix.client.config.ClientConfigFactory;
import com.netflix.client.config.CommonClientConfigKey;
import com.netflix.client.config.IClientConfig;
import com.netflix.client.config.IClientConfigKey;
import com.netflix.client.config.UnboxedIntProperty;
import com.netflix.servo.annotations.DataSourceType;
import com.netflix.servo.annotations.Monitor;
import com.netflix.servo.monitor.Monitors;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * Class that acts as a repository of operational charateristics and statistics
 * of every Node/Server in the LaodBalancer.
 * 
 * This information can be used to just observe and understand the runtime
 * behavior of the loadbalancer or more importantly for the basis that
 * determines the loadbalacing strategy
 * 
 * @author stonse
 * 
 */
public class LoadBalancerStats implements IClientConfigAware {
    
    private static final String PREFIX = "LBStats_";

    public static final IClientConfigKey ACTIVE_REQUESTS_COUNT_TIMEOUT = new CommonClientConfigKey(
            "niws.loadbalancer.serverStats.activeRequestsCount.effectiveWindowSeconds", 60 * 10) {};

    public static final IClientConfigKey CONNECTION_FAILURE_COUNT_THRESHOLD = new CommonClientConfigKey(
            "niws.loadbalancer.%s.connectionFailureCountThreshold", 3) {};

    public static final IClientConfigKey CIRCUIT_TRIP_TIMEOUT_FACTOR_SECONDS = new CommonClientConfigKey(
            "niws.loadbalancer.%s.circuitTripTimeoutFactorSeconds", 10) {};

    public static final IClientConfigKey CIRCUIT_TRIP_MAX_TIMEOUT_SECONDS = new CommonClientConfigKey(
            "niws.loadbalancer.%s.circuitTripMaxTimeoutSeconds", 30) {};

    public static final IClientConfigKey DEFAULT_CONNECTION_FAILURE_COUNT_THRESHOLD = new CommonClientConfigKey(
            "niws.loadbalancer.default.connectionFailureCountThreshold", 3) {};

    public static final IClientConfigKey DEFAULT_CIRCUIT_TRIP_TIMEOUT_FACTOR_SECONDS = new CommonClientConfigKey(
            "niws.loadbalancer.default.circuitTripTimeoutFactorSeconds", 10) {};

    public static final IClientConfigKey DEFAULT_CIRCUIT_TRIP_MAX_TIMEOUT_SECONDS = new CommonClientConfigKey(
            "niws.loadbalancer.default.circuitTripMaxTimeoutSeconds", 30) {};

    private String name;
    
    volatile Map zoneStatsMap = new ConcurrentHashMap<>();
    volatile Map> upServerListZoneMap = new ConcurrentHashMap<>();
    
    private UnboxedIntProperty connectionFailureThreshold = new UnboxedIntProperty(CONNECTION_FAILURE_COUNT_THRESHOLD.defaultValue());
        
    private UnboxedIntProperty circuitTrippedTimeoutFactor = new UnboxedIntProperty(CIRCUIT_TRIP_TIMEOUT_FACTOR_SECONDS.defaultValue());

    private UnboxedIntProperty maxCircuitTrippedTimeout = new UnboxedIntProperty(CIRCUIT_TRIP_MAX_TIMEOUT_SECONDS.defaultValue());

    private UnboxedIntProperty activeRequestsCountTimeout = new UnboxedIntProperty(ACTIVE_REQUESTS_COUNT_TIMEOUT.defaultValue());

    private final LoadingCache serverStatsCache = CacheBuilder.newBuilder()
            .expireAfterAccess(30, TimeUnit.MINUTES)
            .removalListener((RemovalListener) notification -> notification.getValue().close())
            .build(new CacheLoader() {
                public ServerStats load(Server server) {
                    return createServerStats(server);
                }
            });

    protected ServerStats createServerStats(Server server) {
        ServerStats ss = new ServerStats(this);
        //configure custom settings
        ss.setBufferSize(1000);
        ss.setPublishInterval(1000);                    
        ss.initialize(server);
        return ss;        
    }

    public LoadBalancerStats() {

    }

    public LoadBalancerStats(String name) {
        this.name = name;

        Monitors.registerObject(name, this);
    }

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
        this.name = clientConfig.getClientName();
        Preconditions.checkArgument(name != null, "IClientConfig#getCLientName() must not be null");
        this.connectionFailureThreshold = new UnboxedIntProperty(
                clientConfig.getGlobalProperty(CONNECTION_FAILURE_COUNT_THRESHOLD.format(name))
                    .fallbackWith(clientConfig.getGlobalProperty(DEFAULT_CONNECTION_FAILURE_COUNT_THRESHOLD))
        );
        this.circuitTrippedTimeoutFactor = new UnboxedIntProperty(
                clientConfig.getGlobalProperty(CIRCUIT_TRIP_TIMEOUT_FACTOR_SECONDS.format(name))
                        .fallbackWith(clientConfig.getGlobalProperty(DEFAULT_CIRCUIT_TRIP_TIMEOUT_FACTOR_SECONDS))
        );
        this.maxCircuitTrippedTimeout = new UnboxedIntProperty(
                clientConfig.getGlobalProperty(CIRCUIT_TRIP_MAX_TIMEOUT_SECONDS.format(name))
                        .fallbackWith(clientConfig.getGlobalProperty(DEFAULT_CIRCUIT_TRIP_MAX_TIMEOUT_SECONDS))
        );
        this.activeRequestsCountTimeout = new UnboxedIntProperty(
                clientConfig.getGlobalProperty(ACTIVE_REQUESTS_COUNT_TIMEOUT));
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    UnboxedIntProperty getConnectionFailureCountThreshold() {
        return connectionFailureThreshold;

    }

    UnboxedIntProperty getCircuitTrippedTimeoutFactor() {
        return circuitTrippedTimeoutFactor;
    }

    UnboxedIntProperty getCircuitTripMaxTimeoutSeconds() {
        return maxCircuitTrippedTimeout;
    }

    UnboxedIntProperty getActiveRequestsCountTimeout() {
        return activeRequestsCountTimeout;
    }

    /**
     * The caller o this class is tasked to call this method every so often if
     * the servers participating in the LoadBalancer changes
     * @param servers
     */
    public void updateServerList(List servers){
        for (Server s: servers){
            addServer(s);
        }
    }
    
    
    public void addServer(Server server) {
        if (server != null) {
            try {
                serverStatsCache.get(server);
            } catch (ExecutionException e) {
                ServerStats stats = createServerStats(server);
                serverStatsCache.asMap().putIfAbsent(server, stats);
            }
        }
    } 
    
    /**
     * Method that updates the internal stats of Response times maintained on a per Server
     * basis
     * @param server
     * @param msecs
     */
    public void noteResponseTime(Server server, double msecs){
        ServerStats ss = getServerStats(server);  
        ss.noteResponseTime(msecs);
    }
    
    protected ServerStats getServerStats(Server server) {
        if (server == null) {
            return null;
        }

        try {
            return serverStatsCache.get(server);
        } catch (ExecutionException e) {
            ServerStats stats = createServerStats(server);
            serverStatsCache.asMap().putIfAbsent(server, stats);
            return serverStatsCache.asMap().get(server);
        }
    }
    
    public void incrementActiveRequestsCount(Server server) {
        ServerStats ss = getServerStats(server); 
        ss.incrementActiveRequestsCount();
    }

    public void decrementActiveRequestsCount(Server server) {
        ServerStats ss = getServerStats(server); 
        ss.decrementActiveRequestsCount();
    }

    private ZoneStats getZoneStats(String zone) {
        zone = zone.toLowerCase();
        ZoneStats zs = zoneStatsMap.get(zone);
        if (zs == null){
            zoneStatsMap.put(zone, new ZoneStats(this.getName(), zone, this));
            zs = zoneStatsMap.get(zone);
        }
        return zs;
    }

    
    public boolean isCircuitBreakerTripped(Server server) {
        ServerStats ss = getServerStats(server);
        return ss.isCircuitBreakerTripped();
    }
        
    public void incrementSuccessiveConnectionFailureCount(Server server) {
        ServerStats ss = getServerStats(server);
        ss.incrementSuccessiveConnectionFailureCount();
    }
    
    public void clearSuccessiveConnectionFailureCount(Server server) {
        ServerStats ss = getServerStats(server);
        ss.clearSuccessiveConnectionFailureCount();        
    }

    public void incrementNumRequests(Server server){
        ServerStats ss = getServerStats(server);  
        ss.incrementNumRequests();
    }

    public void incrementZoneCounter(Server server) {
        String zone = server.getZone();
        if (zone != null) {
            getZoneStats(zone).incrementCounter();
        }
    }
    
    public void updateZoneServerMapping(Map> map) {
        upServerListZoneMap = new ConcurrentHashMap>(map);
        // make sure ZoneStats object exist for available zones for monitoring purpose
        for (String zone: map.keySet()) {
            getZoneStats(zone);
        }
    }

    public int getInstanceCount(String zone) {
        if (zone == null) {
            return 0;
        }
        zone = zone.toLowerCase();
        List currentList = upServerListZoneMap.get(zone);
        if (currentList == null) {
            return 0;
        }
        return currentList.size();
    }
    
    public int getActiveRequestsCount(String zone) {
        return getZoneSnapshot(zone).getActiveRequestsCount();
    }
        
    public double getActiveRequestsPerServer(String zone) {
        return getZoneSnapshot(zone).getLoadPerServer();
    }

    public ZoneSnapshot getZoneSnapshot(String zone) {
        if (zone == null) {
            return new ZoneSnapshot();
        }
        zone = zone.toLowerCase();
        List currentList = upServerListZoneMap.get(zone);
        return getZoneSnapshot(currentList);        
    }
    
    /**
     * This is the core function to get zone stats. All stats are reported to avoid
     * going over the list again for a different stat.
     * 
     * @param servers
     */
    public ZoneSnapshot getZoneSnapshot(List servers) {
        if (servers == null || servers.size() == 0) {
            return new ZoneSnapshot();
        }
        int instanceCount = servers.size();
        int activeConnectionsCount = 0;
        int activeConnectionsCountOnAvailableServer = 0;
        int circuitBreakerTrippedCount = 0;
        double loadPerServer = 0;
        long currentTime = System.currentTimeMillis();
        for (Server server: servers) {
            ServerStats stat = getSingleServerStat(server);   
            if (stat.isCircuitBreakerTripped(currentTime)) {
                circuitBreakerTrippedCount++;
            } else {
                activeConnectionsCountOnAvailableServer += stat.getActiveRequestsCount(currentTime);
            }
            activeConnectionsCount += stat.getActiveRequestsCount(currentTime);
        }
        if (circuitBreakerTrippedCount == instanceCount) {
            if (instanceCount > 0) {
                // should be NaN, but may not be displayable on Epic
                loadPerServer = -1;
            }
        } else {
            loadPerServer = ((double) activeConnectionsCountOnAvailableServer) / (instanceCount - circuitBreakerTrippedCount);
        }
        return new ZoneSnapshot(instanceCount, circuitBreakerTrippedCount, activeConnectionsCount, loadPerServer);
    }
    
    public int getCircuitBreakerTrippedCount(String zone) {
        return getZoneSnapshot(zone).getCircuitTrippedCount();
    }

    @Monitor(name=PREFIX + "CircuitBreakerTrippedCount", type = DataSourceType.GAUGE)   
    public int getCircuitBreakerTrippedCount() {
        int count = 0;
        for (String zone: upServerListZoneMap.keySet()) {
            count += getCircuitBreakerTrippedCount(zone);
        }
        return count;
    }
    
    public long getMeasuredZoneHits(String zone) {
        if (zone == null) {
            return 0;
        }
        zone = zone.toLowerCase();
        long count = 0;
        List currentList = upServerListZoneMap.get(zone);
        if (currentList == null) {
            return 0;
        }
        for (Server server: currentList) {
            ServerStats stat = getSingleServerStat(server);
            count += stat.getMeasuredRequestsCount();
        }
        return count;
    }
        
    public int getCongestionRatePercentage(String zone) {
        if (zone == null) {
            return 0;
        }
        zone = zone.toLowerCase();
        List currentList = upServerListZoneMap.get(zone);
        if (currentList == null || currentList.size() == 0) {
            return 0;            
        }
        int serverCount = currentList.size(); 
        int activeConnectionsCount = 0;
        int circuitBreakerTrippedCount = 0;
        for (Server server: currentList) {
            ServerStats stat = getSingleServerStat(server);   
            activeConnectionsCount += stat.getActiveRequestsCount();
            if (stat.isCircuitBreakerTripped()) {
                circuitBreakerTrippedCount++;
            }
        }
        return (int) ((activeConnectionsCount + circuitBreakerTrippedCount) * 100L / serverCount); 
    }
    
    @Monitor(name=PREFIX + "AvailableZones", type = DataSourceType.INFORMATIONAL)   
    public Set getAvailableZones() {
        return upServerListZoneMap.keySet();
    }
    
    public ServerStats getSingleServerStat(Server server) {
        return getServerStats(server);
    }

    /**
     * returns map of Stats for all servers
     */
    public Map getServerStats(){
        return serverStatsCache.asMap();
    }
    
    public Map getZoneStats() {
        return zoneStatsMap;
    }
    
    @Override
    public String toString() {
        return "Zone stats: " + zoneStatsMap.toString() 
                + "," + "Server stats: " + getSortedServerStats(getServerStats().values()).toString();
    }
    
    private static Comparator serverStatsComparator = new Comparator() {
        @Override
        public int compare(ServerStats o1, ServerStats o2) {
            String zone1 = "";
            String zone2 = "";
            if (o1.server != null && o1.server.getZone() != null) {
                zone1 = o1.server.getZone();
            }
            if (o2.server != null && o2.server.getZone() != null) {
                zone2 = o2.server.getZone();
            }
            return zone1.compareTo(zone2);            
        }
    };
    
    private static Collection getSortedServerStats(Collection stats) {
        List list = new ArrayList(stats);
        Collections.sort(list, serverStatsComparator);
        return list;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy