Beispiel #1
0
def get_optimal_rackspace(cpu_usage, mem_usage, optimal=True, debug=False):
    """ 
    Calculates the price of optimal resources allocation over a certain time span (with monthly granularity). 
    Formulates the problem of satisfying user demand (in CPU and RAM) as an LP problem with a monetary objective function. 
    """    
    assert(len(cpu_usage) == len(mem_usage))
    
    prob = LpProblem("Rackspace cost optimization", LpMinimize)
    # variables
    ## 1h instances
    per_h_ondems = []
    for p in range(len(cpu_usage)):
        per_h_ondems += ["p %s ondem %s" %(p, i) for i in vms.keys()]
    
    category = LpInteger if optimal else LpContinuous
    vars = LpVariable.dicts("rackspace", per_h_ondems, 0, None, cat=category)
    
    # objective function
    prob += lpSum([vars[vm] * vms[vm.split(" ")[3]][0] for vm in per_h_ondems]), "Total cost of running the infra (wrt to CPU/RAM)" 
    
    # constraints    
    ## demand constraints
    for p in range(len(cpu_usage)):
        prob += lpSum([vars[vm] * vms[vm.split(" ")[3]][2] for vm in per_h_ondems if int(vm.split(" ")[1]) == p]) >= cpu_usage[p], "CU demand period %s" %p
        prob += lpSum([vars[vm] * vms[vm.split(" ")[3]][3] for vm in per_h_ondems if int(vm.split(" ")[1]) == p]) >= mem_usage[p], "RAM demand period %s" %p

    prob.solve()
    
    if debug:        
        for v in prob.variables():
            if v.varValue != 0.0:
                print v.name, "=", v.varValue

        print "Total Cost of the solution = ", value(prob.objective)
    
    return value(prob.objective)
Beispiel #2
0
def fg_secp_ilp(
    cg: ComputationsFactorGraph,
    agents: List[AgentDef],
    already_assigned: Distribution,
    computation_memory: Callable[[ComputationNode], float],
    communication_load: Callable[[ComputationNode, str], float],
) -> Distribution:

    variables = [n for n in cg.nodes if n.type == "VariableComputation"]
    factors = [n for n in cg.nodes if n.type == "FactorComputation"]

    agents = list(agents)
    agents_names = [a.name for a in agents]

    # Only keep computations for which we actually need to find an agent.
    vars_to_host = [
        v.name for v in variables
        if not already_assigned.has_computation(v.name)
    ]
    facs_to_host = [
        f.name for f in factors if not already_assigned.has_computation(f.name)
    ]

    # x_i^k : binary variable indicating if var x_i is hosted on agent a_k.
    xs = _build_xs_binvar(vars_to_host, agents_names)
    # f_j^k : binary variable indicating if factor f_j is hosted on agent a_k.
    fs = _build_fs_binvar(facs_to_host, agents_names)
    # alpha_ijk : binary variable indicating if  x_i and f_j are both on a_k.
    alphas = _build_alphaijk_binvars(cg, agents_names)
    logger.debug(f"alpha_ijk {alphas}")

    # LP problem with objective function (total communication cost).
    pb = LpProblem("distribution", LpMinimize)
    pb += (
        secp_dist_objective_function(cg, communication_load, alphas,
                                     agents_names),
        "Communication costs",
    )

    # Constraints.
    # All variable computations must be hosted:
    for i in vars_to_host:
        pb += (
            lpSum([xs[(i, k)] for k in agents_names]) == 1,
            "var {} is hosted".format(i),
        )

    # All factor computations must be hosted:
    for j in facs_to_host:
        pb += (
            lpSum([fs[(j, k)] for k in agents_names]) == 1,
            "factor {} is hosted".format(j),
        )

    # Each agent must host at least one computation:
    # We only need this constraints for agents that do not already host a
    # computation:
    empty_agents = [
        a for a in agents_names if not already_assigned.computations_hosted(a)
    ]
    for k in empty_agents:
        pb += (
            lpSum([xs[(i, k)] for i in vars_to_host]) +
            lpSum([fs[(j, k)] for j in facs_to_host]) >= 1,
            "atleastone {}".format(k),
        )

    # Memory capacity constraint for agents
    for a in agents:
        # Decrease capacity for already hosted computations
        capacity = a.capacity - sum([
            secp_computation_memory_in_cg(c, cg, computation_memory)
            for c in already_assigned.computations_hosted(a.name)
        ])

        pb += (
            lpSum([
                secp_computation_memory_in_cg(i, cg, computation_memory) * xs[
                    (i, a.name)] for i in vars_to_host
            ]) + lpSum([
                secp_computation_memory_in_cg(j, cg, computation_memory) * fs[
                    (j, a.name)] for j in facs_to_host
            ]) <= capacity,
            "memory {}".format(a.name),
        )

    # Linearization constraints for alpha_ijk.
    for link in cg.links:
        i, j = link.variable_node, link.factor_node
        for k in agents_names:

            if i in vars_to_host and j in facs_to_host:
                pb += alphas[((i, j), k)] <= xs[(i, k)], "lin1 {}{}{}".format(
                    i, j, k)
                pb += alphas[((i, j), k)] <= fs[(j, k)], "lin2 {}{}{}".format(
                    i, j, k)
                pb += (
                    alphas[((i, j), k)] >= xs[(i, k)] + fs[(j, k)] - 1,
                    "lin3 {}{}{}".format(i, j, k),
                )

            elif i in vars_to_host and j not in facs_to_host:
                # Var is free, factor is already hosted
                if already_assigned.agent_for(j) == k:
                    pb += alphas[((i, j), k)] == xs[(i, k)]
                else:
                    pb += alphas[((i, j), k)] == 0

            elif i not in vars_to_host and j in facs_to_host:
                # if i is not in vars_vars_to_host, it means that it's a
                # computation that is already hosted (from  hints)
                if already_assigned.agent_for(i) == k:
                    pb += alphas[((i, j), k)] == fs[(j, k)]
                else:
                    pb += alphas[((i, j), k)] == 0

            else:
                # i and j are both alredy hosted
                if (already_assigned.agent_for(i) == k
                        and already_assigned.agent_for(j) == k):
                    pb += alphas[((i, j), k)] == 1
                else:
                    pb += alphas[((i, j), k)] == 0

    # Now solve our LP
    # status = pb.solve(GLPK_CMD())
    # status = pb.solve(GLPK_CMD(mip=1))
    # status = pb.solve(GLPK_CMD(mip=0, keepFiles=1,
    #                                options=['--simplex', '--interior']))
    status = pb.solve(GLPK_CMD(keepFiles=0, msg=False, options=["--pcost"]))

    if status != LpStatusOptimal:
        raise ImpossibleDistributionException("No possible optimal"
                                              " distribution ")
    else:
        logger.debug("GLPK cost : %s", pulp.value(pb.objective))

        comp_dist = already_assigned
        for k in agents_names:

            agt_vars = [
                i for i, ka in xs if ka == k and pulp.value(xs[(i, ka)]) == 1
            ]
            comp_dist.host_on_agent(k, agt_vars)

            agt_rels = [
                j for j, ka in fs if ka == k and pulp.value(fs[(j, ka)]) == 1
            ]
            comp_dist.host_on_agent(k, agt_rels)
        return comp_dist
Beispiel #3
0
def lp_model(cg: ComputationGraph,
             agentsdef: Iterable[AgentDef],
             footprint: Callable[[str], float],
             capacity: Callable[[str], float],
             route: Callable[[str, str], float],
             msg_load: Callable[[str, str], float],
             hosting_cost: Callable[[str, str], float]):

    comp_names = [n.name for n in cg.nodes]
    agt_names = [a.name for a in agentsdef]
    pb = LpProblem('ilp_compref', LpMinimize)

    # One binary variable xij for each (variable, agent) couple
    xs = LpVariable.dict('x', (comp_names, agt_names), cat=LpBinary)

    # One binary variable for computations c1 and c2, and agent a1 and a2
    betas = {}
    count = 0
    for a1, a2 in combinations(agt_names, 2):
        # Only create variables for couple c1, c2 if there is an edge in the
        # graph between these two computations.
        for l in cg.links:
            # As we support hypergraph, we may have more than 2 ends to a link
            for c1, c2 in combinations(l.nodes, 2):
                count += 2
                b = LpVariable('b_{}_{}_{}_{}'.format(c1, a1, c2, a2),
                               cat=LpBinary)
                betas[(c1, a1, c2, a2)] = b
                pb += b <= xs[(c1, a1)]
                pb += b <= xs[(c2, a2)]
                pb += b >= xs[(c2, a2)] + xs[(c1, a1)] - 1

                b = LpVariable('b_{}_{}_{}_{}'.format(c1, a2, c2, a1),
                               cat=LpBinary)
                betas[(c1, a2, c2, a1)] = b
                pb += b <= xs[(c2, a1)]
                pb += b <= xs[(c1, a2)]
                pb += b >= xs[(c1, a2)] + xs[(c2, a1)] - 1

    # Set objective: communication + hosting_cost
    pb += _objective(xs, betas, route, msg_load, hosting_cost), \
        'Communication costs and prefs'

    # Adding constraints:
    # Constraints: Memory capacity for all agents.
    for a in agt_names:
        pb += lpSum([footprint(i) * xs[i, a] for i in comp_names])\
              <= capacity(a), \
              'Agent {} capacity'.format(a)

    # Constraints: all computations must be hosted.
    for c in comp_names:
        pb += lpSum([xs[c, a] for a in agt_names]) == 1, \
            'Computation {} hosted'.format(c)

    # solve using GLPK
    status = pb.solve(solver=GLPK_CMD(keepFiles=1, msg=False,
                                      options=['--pcost']))

    if status != LpStatusOptimal:
        raise ImpossibleDistributionException("No possible optimal"
                                              " distribution ")
    logger.debug('GLPK cost : %s', value(pb.objective))

    # print('BETAS:')
    # for c1, a1, c2, a2 in betas:
    #     print('  ', c1, a1, c2, a2, value(betas[(c1, a1, c2, a2)]))
    #
    # print('XS:')
    # for c, a in xs:
    #     print('  ', c, a, value(xs[(c, a)]))

    mapping = {}
    for k in agt_names:
        agt_computations = [i for i, ka in xs
                            if ka == k and value(xs[(i, ka)]) == 1]
        # print(k, ' -> ', agt_computations)
        mapping[k] = agt_computations
    return mapping
Beispiel #4
0
def distribute_factors(
    agents: Dict[str, AgentDef],
    cg: ComputationGraph,
    footprints: Dict[str, float],
    mapping: Dict[str, List[str]],
    msg_load: Callable[[str, str], float],
) -> Dict[str, List[str]]:
    """
    Optimal distribution of factors on agents.

    Parameters
    ----------
    cg: computations graph

    agents: dict
        a dict {agent_name : AgentDef} containing all available agents

    Returns
    -------
    a dict { agent_name: list of factor names}
    """
    pb = LpProblem("ilp_factors", LpMinimize)

    # build the inverse mapping var -> agt
    inverse_mapping = {}  # type: Dict[str, str]
    for a in mapping:
        inverse_mapping[mapping[a][0]] = a

    # One binary variable xij for each (variable, agent) couple
    factor_names = [n.name for n in cg.nodes if isinstance(n, FactorComputationNode)]
    xs = LpVariable.dict("x", (factor_names, agents), cat=LpBinary)
    logger.debug("Binary variables for factor distribution : %s", xs)

    # Hard constraints: respect agent's capacity
    for a in agents:
        # Footprint of the variable this agent is already hosting:
        v_footprint = footprints[mapping[a][0]]
        pb += (
            lpSum([footprints[fn] * xs[fn, a] for fn in factor_names])
            <= (agents[a].capacity - v_footprint),
            "Agent {} capacity".format(a),
        )

    # Hard constraints: all computations must be hosted.
    for c in factor_names:
        pb += lpSum([xs[c, a] for a in agents]) == 1, "Factor {} hosted".format(c)

    # 1st objective : minimize communication costs:
    comm = LpAffineExpression()
    for (fn, an_f) in xs:
        for vn in cg.neighbors(fn):
            an_v = inverse_mapping[vn]  # agt hosting neighbor var vn
            comm += agents[an_f].route(an_v) * msg_load(vn, fn) * xs[(fn, an_f)]

    # 2st objective : minimize hosting costs
    hosting = lpSum([agents[a].hosting_cost(c) * xs[(c, a)] for c, a in xs])

    # agregate the two objectives using RATIO_HOST_COMM
    pb += lpSum([RATIO_HOST_COMM * comm, (1 - RATIO_HOST_COMM) * hosting])

    # solve using GLPK and convert to mapping { agt_name : [factors names]}
    status = pb.solve(solver=GLPK_CMD(keepFiles=1, msg=False, options=["--pcost"]))
    if status != LpStatusOptimal:
        raise ImpossibleDistributionException(
            "No possible optimal distribution for factors"
        )
    logger.debug("GLPK cost : %s", value(pb.objective))
    mapping = {}  # type: Dict[str, List[str]]
    for k in agents:
        agt_computations = [i for i, ka in xs if ka == k and value(xs[(i, ka)]) == 1]
        # print(k, ' -> ', agt_computations)
        mapping[k] = agt_computations
    logger.debug("Factors distribution : %s ", mapping)
    return mapping
Beispiel #5
0
def cg_secp_ilp(
        cg: ComputationConstraintsHyperGraph,
        agents: List[AgentDef],
        already_assigned: Distribution,
        computation_memory: Callable[[ComputationNode], float],
        communication_load: Callable[[ComputationNode, str], float],
        timeout=600,  # Max 10 min
) -> Distribution:
    start_t = time.time()

    agents = list(agents)
    agents_names = [a.name for a in agents]

    # Only keep computations for which we actually need to find an agent.
    comps_to_host = [
        c for c in cg.node_names() if not already_assigned.has_computation(c)
    ]

    # x_i^k : binary variable indicating if var x_i is hosted on agent a_k.
    xs = _build_cs_binvar(comps_to_host, agents_names)
    # alpha_ijk : binary variable indicating if  x_i and f_j are both on a_k.
    alphas = _build_alphaijk_binvars(cg, agents_names)
    logger.debug(f"alpha_ijk {alphas}")

    # LP problem with objective function (total communication cost).
    pb = LpProblem("distribution", LpMinimize)
    pb += (
        _objective_function(cg, communication_load, alphas, agents_names),
        "Communication costs",
    )

    # Constraints.
    # All variable computations must be hosted:
    for i in comps_to_host:
        pb += (
            lpSum([xs[(i, k)] for k in agents_names]) == 1,
            "var {} is hosted".format(i),
        )
    # Each agent must host at least one computation:
    # We only need this constraints for agents that do not already host a
    # computation:
    empty_agents = [
        a for a in agents_names if not already_assigned.computations_hosted(a)
    ]
    for k in empty_agents:
        pb += (
            lpSum([xs[(i, k)] for i in comps_to_host]) >= 1,
            "atleastone {}".format(k),
        )

    # Memory capacity constraint for agents
    for a in agents:
        # Decrease capacity for already hosted computations
        capacity = a.capacity - sum([
            secp_computation_memory_in_cg(c, cg, computation_memory)
            for c in already_assigned.computations_hosted(a.name)
        ])

        pb += (
            lpSum([
                secp_computation_memory_in_cg(i, cg, computation_memory) *
                xs[(i, a.name)] for i in comps_to_host
            ]) <= capacity,
            "memory {}".format(a.name),
        )

    # Linearization constraints for alpha_ijk.
    for (i, j), k in alphas:

        if i in comps_to_host and j in comps_to_host:
            pb += alphas[((i, j), k)] <= xs[(i, k)], "lin1 {}{}{}".format(
                i, j, k)
            pb += alphas[((i, j), k)] <= xs[(j, k)], "lin2 {}{}{}".format(
                i, j, k)
            pb += (
                alphas[((i, j), k)] >= xs[(i, k)] + xs[(j, k)] - 1,
                "lin3 {}{}{}".format(i, j, k),
            )

        elif i in comps_to_host and j not in comps_to_host:
            # Var is free, factor is already hosted
            if already_assigned.agent_for(j) == k:
                pb += alphas[((i, j), k)] == xs[(i, k)]
            else:
                pb += alphas[((i, j), k)] == 0

        elif i not in comps_to_host and j in comps_to_host:
            # if i is not in vars_vars_to_host, it means that it's a
            # computation that is already hosted (from  hints)
            if already_assigned.agent_for(i) == k:
                pb += alphas[((i, j), k)] == xs[(j, k)]
            else:
                pb += alphas[((i, j), k)] == 0

        else:
            # i and j are both alredy hosted
            if (already_assigned.agent_for(i) == k
                    and already_assigned.agent_for(j) == k):
                pb += alphas[((i, j), k)] == 1
            else:
                pb += alphas[((i, j), k)] == 0

    # the timeout for the solver must be monierd by the time spent to build the pb:
    remaining_time = round(timeout - (time.time() - start_t)) - 2

    # Now solve our LP
    status = pb.solve(
        GLPK_CMD(keepFiles=0,
                 msg=False,
                 options=["--pcost", "--tmlim",
                          str(remaining_time)]))

    if status != LpStatusOptimal:
        raise ImpossibleDistributionException("No possible optimal"
                                              " distribution ")
    else:
        logger.debug("GLPK cost : %s", pulp.value(pb.objective))

        comp_dist = already_assigned
        for k in agents_names:

            agt_vars = [
                i for i, ka in xs if ka == k and pulp.value(xs[(i, ka)]) == 1
            ]
            comp_dist.host_on_agent(k, agt_vars)

        return comp_dist
Beispiel #6
0
def get_optimal_ec2(cpu_usage, mem_usage, optimal=True, debug=False):
    """ 
    Calculates the optimal allocation of resources over a certain time span (with monthly granularity). 
    Formulates the problem of satisfying user demand (in CPU and RAM) as an LP problem with a monetary objective function. 
    
    Returns allocation of reserved instances and a total price for running the allocation on AWS.    
    """    
    assert(len(cpu_usage) == len(mem_usage))
    
    prob = LpProblem("The Simplified EC2 cost optimization", LpMinimize)
    # variables
    ## 1h instances (both on-demand and reserved)
    per_h_ondems = []
    per_h_reserved = []
    for p in range(len(cpu_usage)):
        # ondemand
        per_h_ondems += ["p %s ondem %s" %(p, i) for i in vms.keys()]
        # reserved
        per_h_reserved += ["p %s reserved %s" %(p, i) for i in vms.keys()]
        
    ## nr of 1-year reserved instances
    nr_of_1year_reserved = [ "res_1year %s" % i for i in vms.keys()]
    nr_of_3year_reserved = [ "res_3year %s" % i for i in vms.keys()]
    
    category = LpInteger if optimal else LpContinuous
    vars = LpVariable.dicts("aws", per_h_ondems + per_h_reserved + nr_of_1year_reserved + nr_of_3year_reserved, \
                            lowBound = 0, upBound = None, cat = category)

    # objective function    
    prob += lpSum([vars[vm] * vms[vm.split(" ")[3]][0] for vm in per_h_ondems]) \
            + lpSum([vars[vm] * vms[vm.split(" ")[3]][3] for vm in per_h_reserved]) \
            + lpSum([vars[vm] * vms[vm.split(" ")[1]][1] for vm in nr_of_1year_reserved]) \
            + lpSum([vars[vm] * vms[vm.split(" ")[1]][2] for vm in nr_of_3year_reserved]) \
            , "Total cost of running the infrastructure consuming (CPU/RAM)/h" 

    # constraints
    ## demand constraints
    for p in range(len(cpu_usage)):
        prob += lpSum([vars[vm] * vms[vm.split(" ")[3]][4] for vm in (per_h_ondems + per_h_reserved) if int(vm.split(" ")[1]) == p]) >= cpu_usage[p], "CPU demand, period %s" %p
        prob += lpSum([vars[vm] * vms[vm.split(" ")[3]][5] for vm in (per_h_ondems + per_h_reserved) if int(vm.split(" ")[1]) == p]) >= mem_usage[p], "RAM demand. period %s" %p
    
    ## constraints on the reserved instances - cannot use more than we paid for
    for i in per_h_reserved:
        t = i.split(" ")[3]
        prob += vars["res_1year %s" % t] + vars["res_3year %s" % t] >= vars[i], "Nr. of used reserved machines of type %s" %i    
    prob.solve()   
    
    
    if debug:
        print "Status:", LpStatus[prob.status]
        print "Total Cost of the solution = ", value(prob.objective)
    
    res_instance = {}
    for v in prob.variables():
        if v.name.startswith("aws_res_") and v.varValue != 0.0:
            res_instance[v.name] = v.varValue
        
        if debug and v.varValue != 0.0:
            print v.name, "=", v.varValue
    
    return (res_instance, value(prob.objective))