(self, resource_request: SignalResourceRequest)
return new_target_capacity
Compare signal to the resources allocated and compute appropriate capacity change.

        :param resource_request: a resource_request object from the signal evaluation
        :returns: the new target capacity we should scale to
Compare signal to the resources allocated and compute appropriate capacity change.
Compare signal to the resources allocated and compute appropriate capacity change .
def _compute_target_capacity(self, resource_request: SignalResourceRequest) -> float:
        """ Compare signal to the resources allocated and compute appropriate capacity change.

        :param resource_request: a resource_request object from the signal evaluation
        :returns: the new target capacity we should scale to
        """
        current_target_capacity = self.pool_manager.target_capacity
        cluster_total_resources = self.pool_manager.cluster_connector.get_cluster_total_resources()
        cluster_allocated_resources = self.pool_manager.cluster_connector.get_cluster_allocated_resources()
        non_orphan_fulfilled_capacity = self.pool_manager.non_orphan_fulfilled_capacity
        logger.info(f"Currently at target_capacity of {current_target_capacity}")
        logger.info(f"Currently non-orphan fulfilled capacity is {non_orphan_fulfilled_capacity}")
        logger.info(f"Current cluster total resources: {cluster_total_resources}")
        logger.info(f"Current cluster allocated resources: {cluster_allocated_resources}")
        logger.info(f"Current setpoint: {self.autoscaling_config.setpoint}")
        # This block of code is kinda complicated logic for figuring out what happens if the cluster
        # or the resource request is empty.  There are essentially four checks, as follows:
        #
        # 1. If the resource request is all 'None', this is shorthand for "don't change the cluster"
        #
        # 2. Otherwise if the resource request contains 0s and Nones, this is a "real" zero request,
        #    so set the target capacity to zero
        #
        # 3. If we have a non-zero resource request but the cluster is empty, we need to figure out
        #    how much to scale up by:
        #
        #    a. First we try to get some historical data to translate resources into weighted capacity;
        #       for each resource type, we divide by the capacity present in the cluster at that time
        #       and then take a max over all the resource types to see which one to use to fulfill the
        #       request
        #    b. If we can't find any historical data, we instead just bump the cluster by 1 so that
        #       on the next autoscaling cycle we can figure out the resource-to-weight value; note that
        #       this adds an extra autoscaling cycle before you can get all your resources.
        #
        # 4. If the resource request and the target capacity are non-zero, but the nodes haven't joined
        #    the cluster yet, we just need to wait until they join before doing anything else.

        if all(requested_quantity is None for requested_quantity in resource_request):
            logger.info("No data from signal, not changing capacity")
            return current_target_capacity
        elif all(requested_quantity in {0, None} for requested_quantity in resource_request):
            return 0
        elif current_target_capacity == 0:
            try:
                logger.info("Current target capacity is 0 and we received a non-zero resource request")
                logger.info("Trying to use historical data to determine weighted resource values...")
                historical_weighted_resources = self._get_historical_weighted_resource_value()
                max_weighted_capacity_request = max(
                    [
                        (request or 0) / history
                        for request, history in zip(resource_request, historical_weighted_resources)
                        if history != 0
                    ]
                )
                logger.info(f"Success!  Historical data is {historical_weighted_resources}")
                logger.info(f"max_weighted_capacity_request = {max_weighted_capacity_request}")
                return max_weighted_capacity_request / self.autoscaling_config.setpoint
            except ValueError:
                logger.info("No historical data found; scaling up by 1 to get some data")
                return 1
        elif non_orphan_fulfilled_capacity == 0:
            # Entering the main body of this method with non_orphan_fulfilled_capacity = 0 guarantees that
            # new_target_capacity will be 0, which we do not want (since the resource request is non-zero)
            logger.info(
                "Non-orphan fulfilled capacity is 0 and current target capacity > 0, not changing target to let the "
                "new instances join"
            )
            return current_target_capacity

        # If we get here, everything is non-zero and we can use the "normal" logic to determine scaling
        (most_constrained_resource, usage_pct,) = self._get_most_constrained_resource_for_request(
            resource_request, cluster_total_resources,
        )
        logger.info(
            f"Fulfilling resource request will cause {most_constrained_resource} to be the most constrained resource "
            f"at {usage_pct} usage"
        )

        # We want to scale the cluster so that requested / (total * scale_factor) = setpoint.
        # We already have requested/total in the form of usage_pct, so we can solve for scale_factor:
        scale_factor = usage_pct / self.autoscaling_config.setpoint

        # Because we scale by the percentage of the "most fulfilled resource" we want to make sure that the
        # target capacity change is based on what's currently present.  A simple example illustrates the point:
        #
        #   * Suppose we have target_capacity = 50, fulfilled_capacity = 10, and setpoint = 0.5
        #   * The signal requests 100 CPUs, and Mesos says there are 200 CPUs in the cluster (this is the
        #       non_orphan_fulfilled_capacity)
        #   * The new target capacity in this case should be 10, not 100 (as it would be if we scaled off the
        #       current target_capacity)
        #
        # This also ensures that the right behavior happens when rolling a resource group.  To see this, let
        # X be the target_capacity of the original resource group; if we create the new resource group with target
        # capacity X, then our non_orphan_fulfilled_capacity will (eventually) be 2X and our scale_factor will be
        # (setpoint / 2) / setpoint (assuming the utilization doesn't change), so our new target_capacity will be X.
        # Since stale resource groups have a target_capacity of 0 and aren't included in modify_target_capacity
        # calculations, this ensures the correct behaviour.  The math here continues to work out as the old resource
        # group scales down, because as the fulfilled_capacity decreases, the scale_factor increases by the same
        # amount.  Tada!
        new_target_capacity = non_orphan_fulfilled_capacity * scale_factor

        # If the percentage change between current target capacity and the new target capacity is more than the
        # allowable margin we scale up/down to reach the setpoint. We want to use target_capacity here instead of
        # get_resource_total to protect against short-term fluctuations in the cluster.
        target_capacity_percentage_change = abs(new_target_capacity - current_target_capacity) / current_target_capacity
        logger.info(
            f"Percentage change between current target capacity {current_target_capacity}, and new target capacity "
            f"{new_target_capacity}, is {target_capacity_percentage_change}"
        )
        margin = self.autoscaling_config.target_capacity_margin
        if target_capacity_percentage_change >= margin:
            logger.info(
                f"Percentage change between current and new target capacities is greater than margin ({margin}). "
                f"Scaling to {new_target_capacity}."
            )
        else:
            logger.info(f"We are within our target capacity margin ({margin}). Not changing target capacity.")
            new_target_capacity = current_target_capacity

        return new_target_capacity
def _compute_target_capacity ( self , resource_request : SignalResourceRequest ) -> float : current_target_capacity = self . pool_manager . target_capacity cluster_total_resources = self . pool_manager . cluster_connector . get_cluster_total_resources ( ) cluster_allocated_resources = self . pool_manager . cluster_connector . get_cluster_allocated_resources ( ) non_orphan_fulfilled_capacity = self . pool_manager . non_orphan_fulfilled_capacity logger . info ( f"Currently at target_capacity of {current_target_capacity}" ) logger . info ( f"Currently non-orphan fulfilled capacity is {non_orphan_fulfilled_capacity}" ) logger . info ( f"Current cluster total resources: {cluster_total_resources}" ) logger . info ( f"Current cluster allocated resources: {cluster_allocated_resources}" ) logger . info ( f"Current setpoint: {self.autoscaling_config.setpoint}" ) # This block of code is kinda complicated logic for figuring out what happens if the cluster # or the resource request is empty.  There are essentially four checks, as follows: # # 1. If the resource request is all 'None', this is shorthand for "don't change the cluster" # # 2. Otherwise if the resource request contains 0s and Nones, this is a "real" zero request, #    so set the target capacity to zero # # 3. If we have a non-zero resource request but the cluster is empty, we need to figure out #    how much to scale up by: # #    a. First we try to get some historical data to translate resources into weighted capacity; #       for each resource type, we divide by the capacity present in the cluster at that time #       and then take a max over all the resource types to see which one to use to fulfill the #       request #    b. If we can't find any historical data, we instead just bump the cluster by 1 so that #       on the next autoscaling cycle we can figure out the resource-to-weight value; note that #       this adds an extra autoscaling cycle before you can get all your resources. # # 4. If the resource request and the target capacity are non-zero, but the nodes haven't joined #    the cluster yet, we just need to wait until they join before doing anything else. if all ( requested_quantity is None for requested_quantity in resource_request ) : logger . info ( "No data from signal, not changing capacity" ) return current_target_capacity elif all ( requested_quantity in { 0 , None } for requested_quantity in resource_request ) : return 0 elif current_target_capacity == 0 : try : logger . info ( "Current target capacity is 0 and we received a non-zero resource request" ) logger . info ( "Trying to use historical data to determine weighted resource values..." ) historical_weighted_resources = self . _get_historical_weighted_resource_value ( ) max_weighted_capacity_request = max ( [ ( request or 0 ) / history for request , history in zip ( resource_request , historical_weighted_resources ) if history != 0 ] ) logger . info ( f"Success!  Historical data is {historical_weighted_resources}" ) logger . info ( f"max_weighted_capacity_request = {max_weighted_capacity_request}" ) return max_weighted_capacity_request / self . autoscaling_config . setpoint except ValueError : logger . info ( "No historical data found; scaling up by 1 to get some data" ) return 1 elif non_orphan_fulfilled_capacity == 0 : # Entering the main body of this method with non_orphan_fulfilled_capacity = 0 guarantees that # new_target_capacity will be 0, which we do not want (since the resource request is non-zero) logger . info ( "Non-orphan fulfilled capacity is 0 and current target capacity > 0, not changing target to let the " "new instances join" ) return current_target_capacity # If we get here, everything is non-zero and we can use the "normal" logic to determine scaling ( most_constrained_resource , usage_pct , ) = self . _get_most_constrained_resource_for_request ( resource_request , cluster_total_resources , ) logger . info ( f"Fulfilling resource request will cause {most_constrained_resource} to be the most constrained resource " f"at {usage_pct} usage" ) # We want to scale the cluster so that requested / (total * scale_factor) = setpoint. # We already have requested/total in the form of usage_pct, so we can solve for scale_factor: scale_factor = usage_pct / self . autoscaling_config . setpoint # Because we scale by the percentage of the "most fulfilled resource" we want to make sure that the # target capacity change is based on what's currently present.  A simple example illustrates the point: # #   * Suppose we have target_capacity = 50, fulfilled_capacity = 10, and setpoint = 0.5 #   * The signal requests 100 CPUs, and Mesos says there are 200 CPUs in the cluster (this is the #       non_orphan_fulfilled_capacity) #   * The new target capacity in this case should be 10, not 100 (as it would be if we scaled off the #       current target_capacity) # # This also ensures that the right behavior happens when rolling a resource group.  To see this, let # X be the target_capacity of the original resource group; if we create the new resource group with target # capacity X, then our non_orphan_fulfilled_capacity will (eventually) be 2X and our scale_factor will be # (setpoint / 2) / setpoint (assuming the utilization doesn't change), so our new target_capacity will be X. # Since stale resource groups have a target_capacity of 0 and aren't included in modify_target_capacity # calculations, this ensures the correct behaviour.  The math here continues to work out as the old resource # group scales down, because as the fulfilled_capacity decreases, the scale_factor increases by the same # amount.  Tada! new_target_capacity = non_orphan_fulfilled_capacity * scale_factor # If the percentage change between current target capacity and the new target capacity is more than the # allowable margin we scale up/down to reach the setpoint. We want to use target_capacity here instead of # get_resource_total to protect against short-term fluctuations in the cluster. target_capacity_percentage_change = abs ( new_target_capacity - current_target_capacity ) / current_target_capacity logger . info ( f"Percentage change between current target capacity {current_target_capacity}, and new target capacity " f"{new_target_capacity}, is {target_capacity_percentage_change}" ) margin = self . autoscaling_config . target_capacity_margin if target_capacity_percentage_change >= margin : logger . info ( f"Percentage change between current and new target capacities is greater than margin ({margin}). " f"Scaling to {new_target_capacity}." ) else : logger . info ( f"We are within our target capacity margin ({margin}). Not changing target capacity." ) new_target_capacity = current_target_capacity return new_target_capacity