一、Cloudsim 示例源码阅读笔记 以NonPowerAware示例


一、Cloudsim 示例源码阅读笔记 以NonPowerAware示例

image-20250913123502146

导师布置了阅读上面论文,关于面向云数据中心的虚拟机整合策略,其中用到了cloudsim作为仿真环境,在PlanetLab跟踪和Google集群跟踪进行了广泛的模拟实验。为了复现论文呢实验结果,需要对cloudsim代码进行详细研究,故发此文作笔记。

前言:仅聚焦主要和重点过程,并非完整流程,比如不含CloudSim初始化和broker对于云任务和虚机的提交等

1.1 基本内容

image-20250913124524041

位置:org.cloudbus.cloudsim.examples.power.random中的NonPowerAware.java
功能:对异构非功耗感知数据中心的仿真,返回所有主机仿真过程中的最大功率
返回:所有主机仿真过程中的最大功率(最大能耗)

1.2 云任务/虚机/主机/功耗模型参数设置

位置:org.cloudbus.cloudsim.examples.power中的Constants.java
用户数量(numUser) = 1
数据中心数量 = 1

云任务时长:2500天

public final static double SCHEDULING_INTERVAL = 300;
public final static double SIMULATION_LIMIT = 24 * 60 * 60;

public final static int CLOUDLET_LENGTH	= 2500 * (int) SIMULATION_LIMIT;
public final static int CLOUDLET_PES	= 1;

4种类型虚拟机参数设置

image-20250913124850200

2种异构类型物理机参数设置

/*
 * Host types:
 *   HP ProLiant ML110 G4 (1 x [Xeon 3040 1860 MHz, 2 cores], 4GB)
 *   HP ProLiant ML110 G5 (1 x [Xeon 3075 2660 MHz, 2 cores], 4GB)
 *   We increase the memory size to enable over-subscription (x4)
 */
public final static int HOST_TYPES	 = 2;
public final static int[] HOST_MIPS	 = { 1860, 2660 };
public final static int[] HOST_PES	 = { 2, 2 };
public final static int[] HOST_RAM	 = { 4096, 4096 };
public final static int HOST_BW		 = 1000000; // 1 Gbit/s
public final static int HOST_STORAGE = 1000000; // 1 GB

2种异构物理机(惠普G4和惠普G5)的功耗模型

public final static PowerModel[] HOST_POWER = {
    new PowerModelSpecPowerHpProLiantMl110G4Xeon3040(),
    new PowerModelSpecPowerHpProLiantMl110G5Xeon3075()
};

在CloudSim中的仿真模型,实际编程是传入CPU利用率∈[0,1],归一化到[0,10]整数以表征[0%, 10%, 20%,…,90%,100%],通过整数索引来获取对应的能耗值(单位W),如该型号主机10%资源利用率对应能耗为89.4w

public class PowerModelSpecPowerHpProLiantMl110G4Xeon3040 extends PowerModelSpecPower {
	/** 
         * The power consumption according to the utilization percentage. 
         * @see #getPowerData(int) 
         */
	private final double[] power = { 86, 89.4, 92.6, 96, 99.5, 102, 106, 108, 112, 114, 117 };
	@Override
	protected double getPowerData(int index) {
		return power[index];
	}
}

不同主机厂商功耗模型网址:http://www.spec.org/power_ssj2008/results/power_ssj2008.html

image-20250913125516034

  • HP ProLiant ML110 G4型号主机为例,以说明空载状态下的主机都有一定能耗损失

    image-20250913125803556

  • HP ProLiant ML110 G5型号主机为例

    image-20250913125922022

因此,对于空载状态下高能耗的物理机,较好的虚机的放置策略/整合策略可节能更多。

1.3 初始化云任务(CloudletList)—如何载入自定义真实数据集中的CPU利用率?

image-20250913130339097

进入到PlanetLabHelper.createCloudletListPlanetLab() 方法中,可见其对每个云任务中的{CPU利用率,RAM, Bw}设置方式

image-20250913131043125

具体实现过程如下

  • 定义1个data数组用来存储每个时刻的CPU利用率,1天按5分钟分割,则总长需存储288个时刻的CPU利用率
  • 从Trace文件中逐个读取每个时刻的CPU利用率并存入data数组中
public UtilizationModelPlanetLabInMemory(String inputPath, double schedulingInterval)
        throws NumberFormatException,
        IOException {
    //288*5 = 60*24 即表示一天的CPU利用率,每5min一个样本
    data = new double[289]; 
    setSchedulingInterval(schedulingInterval); 
    BufferedReader input = new BufferedReader(new FileReader(inputPath));
    int n = data.length;
    for (int i = 0; i < n - 1; i++) {
        data[i] = Integer.parseInt(input.readLine()) / 100.0;
    }
    data[n - 1] = data[n - 2];
    input.close();
}

根据不同时刻,作为索引可直接从data中获取对应CPU利用率,此处仅需简单处理下时间不能整除时的情况

@Override
public double getUtilization(double time) {
    if (time % getSchedulingInterval() == 0) {
        return data[(int) time / (int) getSchedulingInterval()];
    }
    int time1 = (int) Math.floor(time / getSchedulingInterval());
    int time2 = (int) Math.ceil(time / getSchedulingInterval());
    double utilization1 = data[time1];
    double utilization2 = data[time2];
    double delta = (utilization2 - utilization1) / ((time2 - time1) * getSchedulingInterval());
    return utilization1 + delta * (time - time1 * getSchedulingInterval());
}

1.4 初始化虚拟机(vmList)

回到NonPowerAware.java

本例共创建1052个虚拟机

List<Vm> vmList = Helper.createVmList(brokerId, cloudletList.size());

虚机中对于云任务所需CPU资源的调度策略默认为时间共享,从该方法继承自CloudletSchedulerTimeShared可见

public static List<Vm> createVmList(int brokerId, int vmsNumber) {
    List<Vm> vms = new ArrayList<>();
    for (int i = 0; i < vmsNumber; i++) {
        int vmType = i / (int) Math.ceil((double) vmsNumber / Constants.VM_TYPES);
        vms.add(new PowerVm(
                i,
                brokerId,
                Constants.VM_MIPS[vmType],
                Constants.VM_PES[vmType],
                Constants.VM_RAM[vmType],
                Constants.VM_BW,
                Constants.VM_SIZE,
                1,
                "Xen",
                new CloudletSchedulerDynamicWorkload(Constants.VM_MIPS[vmType], Constants.VM_PES[vmType]),
                Constants.SCHEDULING_INTERVAL));
    }
    return vms;
}
public class CloudletSchedulerDynamicWorkload extends CloudletSchedulerTimeShared

1.5 初始化物理机(hostList)——如何自定义不同型号物理机配置?

同上,此处物理机的CPU调度策略依然默认时间共享

List<PowerHost> hostList = Helper.createHostList(PlanetLabConstants.NUMBER_OF_HOSTS);

进入到Helper.createHostList()方法中,可以发现此处for循环中的hostList.add方法可以通过自定义参数和函数以自定义不同型号物理机配置

比如案例中的不同主机硬盘存储均为同一定值,可以依照RamProvisionerSimple自定义StorageProvisionerSimple实现不同主机拥有不同硬盘存储

/**
 * Creates the host list.
 *
 * @param hostsNumber the hosts number
 *
 * @return the list< power host>
 */
public static List<PowerHost> createHostList(int hostsNumber) {
    List<PowerHost> hostList = new ArrayList<>();
    for (int i = 0; i < hostsNumber; i++) {
        int hostType = i % Constants.HOST_TYPES;

        List<Pe> peList = new ArrayList<>();
        for (int j = 0; j < Constants.HOST_PES[hostType]; j++) { //每个host上有2核CPU处理器
            peList.add(new Pe(j, new PeProvisionerSimple(Constants.HOST_MIPS[hostType])));
        }

        hostList.add(new PowerHost(
                i,
                new RamProvisionerSimple(Constants.HOST_RAM[hostType]), //为每台物理机设置RAM
                new BwProvisionerSimple(Constants.HOST_BW), //为每台物理机设置bandwidth
                Constants.HOST_STORAGE, //为每台物理机设置硬盘存储
                peList, //物理机多核设置
                new VmSchedulerTimeSharedOverSubscription(peList), //时间共享
                Constants.HOST_POWER[hostType])); //设置物理机功耗模型
    }
    return hostList;
}

1.6 初始化数据中心(datacenter)——在哪修改虚拟机分配策略?(默认First Fit)

PowerDatacenterNonPowerAware datacenter = (PowerDatacenterNonPowerAware) Helper.createDatacenter(
					"Datacenter", //数据中心name
					PowerDatacenterNonPowerAware.class, //数据中心类:此处为非能耗感知数据中心
					hostList, 
					new VmAllocationPolicySimple(hostList)); //虚拟机分配策略

接下来,我们看看传入的虚拟机分配策略是什么?这里能耗虚机分配策略继承原始PowerVmAllocationPolicyAbstract

public class PowerVmAllocationPolicySimple extends PowerVmAllocationPolicyAbstract {...}

继续进入PowerVmAllocationPolicyAbstract,可见其用First Fit策略进行虚拟机到主机的映射,因此想要修改或对比其他如Best Fit策略,则可以 继承 PowerVmAllocationPolicyAbstract类, 并修改findHostForVm()中的算法即可

/**
 * Finds the first host that has enough resources to host a given VM.
 * 
 * @param vm the vm to find a host for it
 * @return the first host found that can host the VM
 */
public PowerHost findHostForVm(Vm vm) {
    for (PowerHost host : this.<PowerHost> getHostList()) {
        if (host.isSuitableForVm(vm)) {
            return host;
        }
    }
    return null;
}

1.7 仿真结果

image-20250913135232050

进入printResults可发现,结束后从当前数据中心状态获取以下等指标

  • **能耗(kw/h)**——依据数据中心不同异构主机的功耗模型累计得到
  • 虚机迁移数
  • SLAV服务违反率
  • 开机激活态主机数

1.8 非功耗感知和功耗感知有啥区别?

功耗计算方式 数据中心类别
非功耗感知 使用该物理机CPU利用率为100%下的功耗作为实时功耗 PowerDatacenterNonPowerAware
功耗感知 使用该物理机当前CPU利用率下的功耗作为实时功耗 PowerDatacenter

1.8.1 非功耗感知数据中心(PowerDatacenterNonPowerAware)

我们进入PowerDatacenterNonPowerAware.java类中,这里它继承了功耗感知数据中心并重写了相关方法

public class PowerDatacenterNonPowerAware extends PowerDatacenter {...}
/*省略了部分代码*/
//当前数据中心最大功耗
double timeframePower = 0.0;
// 统计每台物理机最大功耗
for (PowerHost host : this.<PowerHost> getHostList()) {
    double hostPower = 0.0;
    //用主机最大功耗代替主机功耗
    hostPower = host.getMaxPower() * timeDiff;
	timeframePower += hostPower;
setPower(getPower() + timeframePower);

这里的具体实现,即传入100%的CPU利用率,获取最大功耗

default double getMaxPower() {
    double power = 0;
    try {
        power = getPowerModel().getPower(1);
    } catch (Exception e) {
        e.printStackTrace();
        System.exit(0);
    }
    return power;
}

完整实现

public abstract class PowerModelSpecPower implements PowerModel {
	@Override
	public double getPower(double utilization) throws IllegalArgumentException {
		if (utilization < 0 || utilization > 1) {
			throw new IllegalArgumentException("Utilization value must be between 0 and 1");
		}
		// if CPU利用率能整除10%,则直接代入索引获取对应功耗
		if (utilization % 0.1 == 0) {
			return getPowerData((int) (utilization * 10));
		}
		// then CPU利用率不能整除10%,则按比例插值计算
		int utilization1 = (int) Math.floor(utilization * 10);
		int utilization2 = (int) Math.ceil(utilization * 10);
		double power1 = getPowerData(utilization1);
		double power2 = getPowerData(utilization2);
		double delta = (power2 - power1) / 10;
        return power1 + delta * (utilization - (double) utilization1 / 10) * 100;
	}
    protected abstract double getPowerData(int index);
}

1.8.2 功耗感知数据中心(PowerDatacenter)

我们进入PowerDatacenter.java类中,在updateCloudetProcessingWithoutSchedulingFutureEventsForce()我们最终找到,获取数据中心能耗的流程。

protected double updateCloudetProcessingWithoutSchedulingFutureEventsForce() {
    //省略了部分日志记录和虚拟机进程更新代码
    double currentTime = CloudSim.clock();
    double minTime = Double.MAX_VALUE;
    double timeDiff = currentTime - getLastProcessTime();
    // 初始化数据中心能耗=0
    double timeFrameDatacenterEnergy = 0.0;
    // 距离上次处理已经过去了一段时间
    if (timeDiff > 0) {
        //统计每个物理机的能耗
        for (PowerHost host : this.<PowerHost> getHostList()) {
            // 5min前的物理机cpu利用率
            double previousUtilizationOfCpu = host.getPreviousUtilizationOfCpu();
            // 当前时刻物理机的CPU利用率
            double utilizationOfCpu = host.getUtilizationOfCpu();
            // 物理机能耗(通过线性插值的方式获取:两点中间取中间点即可)
            double timeFrameHostEnergy = host.getEnergyLinearInterpolation(
                    previousUtilizationOfCpu,
                    utilizationOfCpu,
                    timeDiff); //timeDiff = currentTime = getLastProcessTime()
            timeFrameDatacenterEnergy += timeFrameHostEnergy;
        }
    }
    //更新能耗值
    setPower(getPower() + timeFrameDatacenterEnergy);
    return minTime;
}

线性插值的方式很简单,两点中取中间点即可

public double getEnergyLinearInterpolation(double fromUtilization, double toUtilization, double time) {
    if (fromUtilization == 0) {
        return 0;
    }
    double fromPower = getPower(fromUtilization);
    double toPower = getPower(toUtilization);
    return (fromPower + (toPower - fromPower) / 2) * time;
}

此处的getPower(利用率)即是使用该物理机功耗模型,输入利用率,获取对应的能耗值得到

image-20250913143631709

1.9 载入PlanetLab 2011/3/3一天的CPU利用率数据进行实验

1.9.1 数据特征

2011/3/3当天有约1052个不同虚拟机CPU利用率数据文件,每个数据文件表示对应型号物理机当天的CPU利用率变换,采样频率为5min一次,1天共288个样本

image-20250913143908582

1.9.2 具体修改

String inputFolder = NonPowerAware.class.getClassLoader().getResource("workload/planetlab/20110303").getPath();

关于载入真实数据的方法,在1.3 初始化云任务(CloudletList)—如何载入自定义真实数据集中的CPU利用率?中介绍过,此处不予赘述。

只需修改数据中心类别即可切换以上两种数据中心的不同功耗结果

image-20250913144826454

以下给出基于功耗感知和非功耗感知在PlanetLab数据集同一天同样参数设置下的结果,在没有任何策略优化的情况下,功耗感知对功耗的实际计算更为贴切

image-20250913145419425

image-20250913145609418

PlanetLab数据 主机数 VM数 云任务数 VM放置策略 虚机调度策略 主机调度策略 关机主机数 耗能(kw/h)
功耗感知 800 1052 1052 First Fit 时间共享 时间共享 342 1086.59
非功耗感知 800 1052 1052 First Fit 时间共享 时间共享 457 2410.8

Author: qwq小小舒
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source qwq小小舒 !
  TOC