From b9b0168da4c7b8c2d36a226b2747db5dab60fa98 Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Sat, 1 Jun 2013 08:00:48 +0200 Subject: [PATCH] CLOUDSTACK-1192: Add Disk I/O Statistics --- api/src/com/cloud/vm/VmDiskStats.java | 30 +++ api/src/com/cloud/vm/VmStats.java | 8 + .../api/response/UserVmResponse.java | 28 ++ .../apache/cloudstack/usage/UsageTypes.java | 8 + .../classes/resources/messages.properties | 4 + client/tomcatconf/applicationContext.xml.in | 2 + .../cloud/agent/api/GetVmDiskStatsAnswer.java | 47 ++++ .../agent/api/GetVmDiskStatsCommand.java | 54 ++++ .../com/cloud/agent/api/VmDiskStatsEntry.java | 90 +++++++ .../src/com/cloud/agent/api/VmStatsEntry.java | 48 ++++ .../src/com/cloud/usage/UsageVmDiskVO.java | 180 +++++++++++++ .../src/com/cloud/usage/dao/UsageDao.java | 4 + .../src/com/cloud/usage/dao/UsageDaoImpl.java | 105 ++++++++ .../com/cloud/usage/dao/UsageVmDiskDao.java | 29 ++ .../cloud/usage/dao/UsageVmDiskDaoImpl.java | 139 ++++++++++ .../com/cloud/user/VmDiskStatisticsVO.java | 216 +++++++++++++++ .../cloud/user/dao/VmDiskStatisticsDao.java | 35 +++ .../user/dao/VmDiskStatisticsDaoImpl.java | 134 ++++++++++ .../src/com/cloud/vm/dao/UserVmData.java | 36 +++ .../resource/LibvirtComputingResource.java | 96 +++++++ .../xen/resource/CitrixResourceBase.java | 103 +++++++ .../src/com/cloud/api/ApiResponseHelper.java | 11 + .../api/query/dao/UserVmJoinDaoImpl.java | 12 + .../src/com/cloud/configuration/Config.java | 1 + .../src/com/cloud/server/StatsCollector.java | 252 ++++++++++++++++++ .../com/cloud/storage/VolumeManagerImpl.java | 14 + server/src/com/cloud/vm/UserVmManager.java | 5 + .../src/com/cloud/vm/UserVmManagerImpl.java | 190 +++++++++++++ server/test/async-job-component.xml | 1 + .../com/cloud/vm/MockUserVmManagerImpl.java | 12 + setup/db/db/schema-410to420.sql | 76 ++++++ ui/dictionary.jsp | 4 + ui/scripts/instances.js | 12 +- .../src/com/cloud/usage/UsageManagerImpl.java | 157 ++++++++++- .../cloud/usage/parser/VmDiskUsageParser.java | 208 +++++++++++++++ .../com/cloud/usage/UsageManagerTest.java | 3 + .../usage/UsageManagerTestConfiguration.java | 2 + 37 files changed, 2353 insertions(+), 3 deletions(-) create mode 100644 api/src/com/cloud/vm/VmDiskStats.java create mode 100644 core/src/com/cloud/agent/api/GetVmDiskStatsAnswer.java create mode 100644 core/src/com/cloud/agent/api/GetVmDiskStatsCommand.java create mode 100644 core/src/com/cloud/agent/api/VmDiskStatsEntry.java create mode 100644 engine/schema/src/com/cloud/usage/UsageVmDiskVO.java create mode 100644 engine/schema/src/com/cloud/usage/dao/UsageVmDiskDao.java create mode 100644 engine/schema/src/com/cloud/usage/dao/UsageVmDiskDaoImpl.java create mode 100644 engine/schema/src/com/cloud/user/VmDiskStatisticsVO.java create mode 100644 engine/schema/src/com/cloud/user/dao/VmDiskStatisticsDao.java create mode 100644 engine/schema/src/com/cloud/user/dao/VmDiskStatisticsDaoImpl.java create mode 100644 usage/src/com/cloud/usage/parser/VmDiskUsageParser.java diff --git a/api/src/com/cloud/vm/VmDiskStats.java b/api/src/com/cloud/vm/VmDiskStats.java new file mode 100644 index 00000000000..0cf82d0047d --- /dev/null +++ b/api/src/com/cloud/vm/VmDiskStats.java @@ -0,0 +1,30 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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.cloud.vm; + +public interface VmDiskStats { + // vm related disk stats + + public Long getIORead(); + + public Long getIOWrite(); + + public Long getBytesRead(); + + public Long getBytesWrite(); + +} diff --git a/api/src/com/cloud/vm/VmStats.java b/api/src/com/cloud/vm/VmStats.java index 7d0bd61b9d8..d284db0f64a 100644 --- a/api/src/com/cloud/vm/VmStats.java +++ b/api/src/com/cloud/vm/VmStats.java @@ -23,5 +23,13 @@ public interface VmStats { public double getNetworkReadKBs(); public double getNetworkWriteKBs(); + + public double getDiskReadIOs(); + + public double getDiskWriteIOs(); + + public double getDiskReadKBs(); + + public double getDiskWriteKBs(); } diff --git a/api/src/org/apache/cloudstack/api/response/UserVmResponse.java b/api/src/org/apache/cloudstack/api/response/UserVmResponse.java index c3bbf8db382..1f9eb1ac63f 100644 --- a/api/src/org/apache/cloudstack/api/response/UserVmResponse.java +++ b/api/src/org/apache/cloudstack/api/response/UserVmResponse.java @@ -137,6 +137,18 @@ public class UserVmResponse extends BaseResponse implements ControlledEntityResp @SerializedName("networkkbswrite") @Param(description="the outgoing network traffic on the host") private Long networkKbsWrite; + @SerializedName("diskkbsread") @Param(description="the read (bytes) of disk on the vm") + private Long diskKbsRead; + + @SerializedName("diskkbswrite") @Param(description="the write (bytes) of disk on the vm") + private Long diskKbsWrite; + + @SerializedName("diskioread") @Param(description="the read (io) of disk on the vm") + private Long diskIORead; + + @SerializedName("diskiowrite") @Param(description="the write (io) of disk on the vm") + private Long diskIOWrite; + @SerializedName("guestosid") @Param(description="Os type ID of the virtual machine") private String guestOsId; @@ -300,6 +312,22 @@ public class UserVmResponse extends BaseResponse implements ControlledEntityResp public void setIsoDisplayText(String isoDisplayText) { this.isoDisplayText = isoDisplayText; } + + public void setDiskKbsRead(Long diskKbsRead) { + this.diskKbsRead = diskKbsRead; + } + + public void setDiskKbsWrite(Long diskKbsWrite) { + this.diskKbsWrite = diskKbsWrite; + } + + public void setDiskIORead(Long diskIORead) { + this.diskIORead = diskIORead; + } + + public void setDiskIOWrite(Long diskIOWrite) { + this.diskIOWrite = diskIOWrite; + } public void setServiceOfferingId(String serviceOfferingId) { this.serviceOfferingId = serviceOfferingId; diff --git a/api/src/org/apache/cloudstack/usage/UsageTypes.java b/api/src/org/apache/cloudstack/usage/UsageTypes.java index 2baa1d20057..ddf10979cb7 100644 --- a/api/src/org/apache/cloudstack/usage/UsageTypes.java +++ b/api/src/org/apache/cloudstack/usage/UsageTypes.java @@ -36,6 +36,10 @@ public class UsageTypes { public static final int PORT_FORWARDING_RULE = 12; public static final int NETWORK_OFFERING = 13; public static final int VPN_USERS = 14; + public static final int VM_DISK_IO_READ = 21; + public static final int VM_DISK_IO_WRITE = 22; + public static final int VM_DISK_BYTES_READ = 23; + public static final int VM_DISK_BYTES_WRITE = 24; public static List listUsageTypes(){ List responseList = new ArrayList(); @@ -53,6 +57,10 @@ public class UsageTypes { responseList.add(new UsageTypeResponse(PORT_FORWARDING_RULE, "Port Forwarding Usage")); responseList.add(new UsageTypeResponse(NETWORK_OFFERING, "Network Offering Usage")); responseList.add(new UsageTypeResponse(VPN_USERS, "VPN users usage")); + responseList.add(new UsageTypeResponse(VM_DISK_IO_READ, "VM Disk usage(I/O Read)")); + responseList.add(new UsageTypeResponse(VM_DISK_IO_WRITE, "VM Disk usage(I/O Write)")); + responseList.add(new UsageTypeResponse(VM_DISK_BYTES_READ, "VM Disk usage(Bytes Read)")); + responseList.add(new UsageTypeResponse(VM_DISK_BYTES_WRITE, "VM Disk usage(Bytes Write)")); return responseList; } } diff --git a/client/WEB-INF/classes/resources/messages.properties b/client/WEB-INF/classes/resources/messages.properties index 1638be19e49..ce20fa49f1c 100644 --- a/client/WEB-INF/classes/resources/messages.properties +++ b/client/WEB-INF/classes/resources/messages.properties @@ -464,10 +464,14 @@ label.disabled=Disabled label.disabling.vpn.access=Disabling VPN Access label.disk.allocated=Disk Allocated label.disk.offering=Disk Offering +label.disk.read.bytes=Disk Read (Bytes) +label.disk.read.io=Disk Read (IO) label.disk.size.gb=Disk Size (in GB) label.disk.size=Disk Size label.disk.total=Disk Total label.disk.volume=Disk Volume +label.disk.write.bytes=Disk Write (Bytes) +label.disk.write.io=Disk Write (IO) label.display.name=Display name label.display.text=Display Text label.dns.1=DNS 1 diff --git a/client/tomcatconf/applicationContext.xml.in b/client/tomcatconf/applicationContext.xml.in index 2fe51414e3e..11ed42b0800 100644 --- a/client/tomcatconf/applicationContext.xml.in +++ b/client/tomcatconf/applicationContext.xml.in @@ -341,11 +341,13 @@ + + diff --git a/core/src/com/cloud/agent/api/GetVmDiskStatsAnswer.java b/core/src/com/cloud/agent/api/GetVmDiskStatsAnswer.java new file mode 100644 index 00000000000..18cb7948a38 --- /dev/null +++ b/core/src/com/cloud/agent/api/GetVmDiskStatsAnswer.java @@ -0,0 +1,47 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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.cloud.agent.api; + +import java.util.HashMap; +import java.util.List; + +import com.cloud.agent.api.LogLevel.Log4jLevel; + +@LogLevel(Log4jLevel.Trace) +public class GetVmDiskStatsAnswer extends Answer { + + String hostName; + HashMap> vmDiskStatsMap; + + public GetVmDiskStatsAnswer(GetVmDiskStatsCommand cmd, String details, String hostName, HashMap> vmDiskStatsMap) { + super(cmd, true, details); + this.hostName = hostName; + this.vmDiskStatsMap = vmDiskStatsMap; + } + + public String getHostName() { + return hostName; + } + + public HashMap> getVmDiskStatsMap() { + return vmDiskStatsMap; + } + + protected GetVmDiskStatsAnswer() { + //no-args constructor for json serialization-deserialization + } +} diff --git a/core/src/com/cloud/agent/api/GetVmDiskStatsCommand.java b/core/src/com/cloud/agent/api/GetVmDiskStatsCommand.java new file mode 100644 index 00000000000..2b690020cb2 --- /dev/null +++ b/core/src/com/cloud/agent/api/GetVmDiskStatsCommand.java @@ -0,0 +1,54 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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.cloud.agent.api; + +import java.util.List; + +import com.cloud.agent.api.LogLevel.Log4jLevel; + +@LogLevel(Log4jLevel.Trace) +public class GetVmDiskStatsCommand extends Command { + List vmNames; + String hostGuid; + String hostName; + + protected GetVmDiskStatsCommand() { + } + + public GetVmDiskStatsCommand(List vmNames, String hostGuid, String hostName) { + this.vmNames = vmNames; + this.hostGuid = hostGuid; + this.hostName = hostName; + } + + public List getVmNames() { + return vmNames; + } + + public String getHostGuid(){ + return this.hostGuid; + } + + public String getHostName(){ + return this.hostName; + } + + @Override + public boolean executeInSequence() { + return false; + } +} diff --git a/core/src/com/cloud/agent/api/VmDiskStatsEntry.java b/core/src/com/cloud/agent/api/VmDiskStatsEntry.java new file mode 100644 index 00000000000..9bec031c50d --- /dev/null +++ b/core/src/com/cloud/agent/api/VmDiskStatsEntry.java @@ -0,0 +1,90 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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.cloud.agent.api; + +import com.cloud.vm.VmDiskStats; + +public class VmDiskStatsEntry implements VmDiskStats { + + String vmName; + String path; + Long ioRead = 0L; + Long ioWrite = 0L; + Long bytesWrite = 0L; + Long bytesRead = 0L; + + public VmDiskStatsEntry() { + } + + public VmDiskStatsEntry(String vmName, String path, Long ioWrite, Long ioRead, Long bytesWrite, Long bytesRead) { + this.ioRead = ioRead; + this.ioWrite = ioWrite; + this.bytesRead = bytesRead; + this.bytesWrite = bytesWrite; + this.vmName = vmName; + this.path = path; + } + + public void setVmName(String vmName) { + this.vmName = vmName; + } + + public String getVmName() { + return vmName; + } + + public void setPath(String path) { + this.path = path; + } + + public String getPath() { + return path; + } + + public void setBytesRead(Long bytesRead) { + this.bytesRead = bytesRead; + } + + public Long getBytesRead() { + return bytesRead; + } + + public void setBytesWrite(Long bytesWrite) { + this.bytesWrite = bytesWrite; + } + + public Long getBytesWrite() { + return bytesWrite; + } + + public void setIORead(Long ioRead) { + this.ioRead = ioRead; + } + + public Long getIORead() { + return ioRead; + } + + public void setIOWrite(Long ioWrite) { + this.ioWrite = ioWrite; + } + + public Long getIOWrite() { + return ioWrite; + } + +} diff --git a/core/src/com/cloud/agent/api/VmStatsEntry.java b/core/src/com/cloud/agent/api/VmStatsEntry.java index 8828e9114f4..9c6df1a09f8 100755 --- a/core/src/com/cloud/agent/api/VmStatsEntry.java +++ b/core/src/com/cloud/agent/api/VmStatsEntry.java @@ -23,6 +23,10 @@ public class VmStatsEntry implements VmStats { double cpuUtilization; double networkReadKBs; double networkWriteKBs; + double diskReadIOs; + double diskWriteIOs; + double diskReadKBs; + double diskWriteKBs; int numCPUs; String entityType; @@ -37,6 +41,18 @@ public class VmStatsEntry implements VmStats { this.numCPUs = numCPUs; this.entityType = entityType; } + + public VmStatsEntry(double cpuUtilization, double networkReadKBs, double networkWriteKBs, + double diskReadKBs, double diskWriteKBs, int numCPUs, String entityType) + { + this.cpuUtilization = cpuUtilization; + this.networkReadKBs = networkReadKBs; + this.networkWriteKBs = networkWriteKBs; + this.diskReadKBs = diskReadKBs; + this.diskWriteKBs = diskWriteKBs; + this.numCPUs = numCPUs; + this.entityType = entityType; + } public double getCPUUtilization() { return cpuUtilization; @@ -62,6 +78,38 @@ public class VmStatsEntry implements VmStats { this.networkWriteKBs = networkWriteKBs; } + public double getDiskReadIOs() { + return diskReadIOs; + } + + public void setDiskReadIOs(double diskReadIOs) { + this.diskReadIOs = diskReadIOs; + } + + public double getDiskWriteIOs() { + return diskWriteIOs; + } + + public void setDiskWriteIOs(double diskWriteIOs) { + this.diskWriteIOs = diskWriteIOs; + } + + public double getDiskReadKBs() { + return diskReadKBs; + } + + public void setDiskReadKBs(double diskReadKBs) { + this.diskReadKBs = diskReadKBs; + } + + public double getDiskWriteKBs() { + return diskWriteKBs; + } + + public void setDiskWriteKBs(double diskWriteKBs) { + this.diskWriteKBs = diskWriteKBs; + } + public int getNumCPUs() { return numCPUs; } diff --git a/engine/schema/src/com/cloud/usage/UsageVmDiskVO.java b/engine/schema/src/com/cloud/usage/UsageVmDiskVO.java new file mode 100644 index 00000000000..6c3ca6940b1 --- /dev/null +++ b/engine/schema/src/com/cloud/usage/UsageVmDiskVO.java @@ -0,0 +1,180 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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.cloud.usage; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name="usage_vm_disk") +public class UsageVmDiskVO { + @Id + @Column(name="account_id") + private long accountId; + + @Column(name="zone_id") + private long zoneId; + + @Column(name="vm_id") + private Long vmId; + + @Column(name="volume_id") + private Long volumeId; + + @Column(name="io_read") + private long ioRead; + + @Column(name="io_write") + private long ioWrite; + + @Column(name="agg_io_write") + private long aggIOWrite; + + @Column(name="agg_io_read") + private long aggIORead; + + @Column(name="bytes_read") + private long bytesRead; + + @Column(name="bytes_write") + private long bytesWrite; + + @Column(name="agg_bytes_write") + private long aggBytesWrite; + + @Column(name="agg_bytes_read") + private long aggBytesRead; + + @Column(name="event_time_millis") + private long eventTimeMillis = 0; + + protected UsageVmDiskVO() { + } + + public UsageVmDiskVO(Long accountId, long zoneId, Long vmId, Long volumeId, long ioRead, long ioWrite, long aggIORead, long aggIOWrite, + long bytesRead, long bytesWrite, long aggBytesRead, long aggBytesWrite, long eventTimeMillis) { + this.accountId = accountId; + this.zoneId = zoneId; + this.vmId = vmId; + this.volumeId = volumeId; + this.ioRead = ioRead; + this.ioWrite = ioWrite; + this.aggIOWrite = aggIOWrite; + this.aggIORead = aggIORead; + this.bytesRead = bytesRead; + this.bytesWrite = bytesWrite; + this.aggBytesWrite = aggBytesWrite; + this.aggBytesRead = aggBytesRead; + this.eventTimeMillis = eventTimeMillis; + } + + public long getAccountId() { + return accountId; + } + + public void setAccountId(long accountId) { + this.accountId = accountId; + } + + public long getZoneId() { + return zoneId; + } + public void setZoneId(long zoneId) { + this.zoneId = zoneId; + } + + public Long getIORead() { + return ioRead; + } + + public void setIORead(Long ioRead) { + this.ioRead = ioRead; + } + + public Long getIOWrite() { + return ioWrite; + } + + public void setIOWrite(Long ioWrite) { + this.ioWrite = ioWrite; + } + + public Long getBytesRead() { + return bytesRead; + } + + public void setBytesRead(Long bytesRead) { + this.bytesRead = bytesRead; + } + + public Long getBytesWrite() { + return bytesWrite; + } + + public void setBytesWrite(Long bytesWrite) { + this.bytesWrite = bytesWrite; + } + + public long getEventTimeMillis() { + return eventTimeMillis; + } + public void setEventTimeMillis(long eventTimeMillis) { + this.eventTimeMillis = eventTimeMillis; + } + + public Long getVmId() { + return vmId; + } + + public Long getVolumeId() { + return volumeId; + } + + public long getAggIOWrite() { + return aggIOWrite; + } + + public void setAggIOWrite(long aggIOWrite) { + this.aggIOWrite = aggIOWrite; + } + + public long getAggIORead() { + return aggIORead; + } + + public void setAggIORead(long aggIORead) { + this.aggIORead = aggIORead; + } + + public long getAggBytesWrite() { + return aggBytesWrite; + } + + public void setAggBytesWrite(long aggBytesWrite) { + this.aggBytesWrite = aggBytesWrite; + } + + public long getAggBytesRead() { + return aggBytesRead; + } + + public void setAggBytesRead(long aggBytesRead) { + this.aggBytesRead = aggBytesRead; + } +} diff --git a/engine/schema/src/com/cloud/usage/dao/UsageDao.java b/engine/schema/src/com/cloud/usage/dao/UsageDao.java index 6d0c162b52b..8a806553112 100644 --- a/engine/schema/src/com/cloud/usage/dao/UsageDao.java +++ b/engine/schema/src/com/cloud/usage/dao/UsageDao.java @@ -21,6 +21,7 @@ import java.util.List; import com.cloud.usage.UsageVO; import com.cloud.user.AccountVO; import com.cloud.user.UserStatisticsVO; +import com.cloud.user.VmDiskStatisticsVO; import com.cloud.utils.db.Filter; import com.cloud.utils.db.GenericDao; import com.cloud.utils.db.SearchCriteria; @@ -36,4 +37,7 @@ public interface UsageDao extends GenericDao { Long getLastAccountId(); Long getLastUserStatsId(); List listPublicTemplatesByAccount(long accountId); + Long getLastVmDiskStatsId(); + void updateVmDiskStats(List vmNetStats); + void saveVmDiskStats(List vmNetStats); } diff --git a/engine/schema/src/com/cloud/usage/dao/UsageDaoImpl.java b/engine/schema/src/com/cloud/usage/dao/UsageDaoImpl.java index a5867f0656e..f7d5069eef9 100644 --- a/engine/schema/src/com/cloud/usage/dao/UsageDaoImpl.java +++ b/engine/schema/src/com/cloud/usage/dao/UsageDaoImpl.java @@ -32,6 +32,7 @@ import org.springframework.stereotype.Component; import com.cloud.usage.UsageVO; import com.cloud.user.AccountVO; import com.cloud.user.UserStatisticsVO; +import com.cloud.user.VmDiskStatisticsVO; import com.cloud.utils.DateUtil; import com.cloud.utils.db.Filter; import com.cloud.utils.db.GenericDaoBase; @@ -56,6 +57,13 @@ public class UsageDaoImpl extends GenericDaoBase implements Usage private static final String GET_LAST_USER_STATS = "SELECT id FROM cloud_usage.user_statistics ORDER BY id DESC LIMIT 1"; private static final String GET_PUBLIC_TEMPLATES_BY_ACCOUNTID = "SELECT id FROM cloud.vm_template WHERE account_id = ? AND public = '1' AND removed IS NULL"; + private static final String GET_LAST_VM_DISK_STATS = "SELECT id FROM cloud_usage.vm_disk_statistics ORDER BY id DESC LIMIT 1"; + private static final String INSERT_VM_DISK_STATS = "INSERT INTO cloud_usage.vm_disk_statistics (id, data_center_id, account_id, vm_id, volume_id, net_io_read, net_io_write, current_io_read, " + + "current_io_write, agg_io_read, agg_io_write, net_bytes_read, net_bytes_write, current_bytes_read, current_bytes_write, agg_bytes_read, agg_bytes_write) " + + " VALUES (?,?,?,?,?,?,?,?,?,?, ?, ?, ?, ?,?, ?, ?)"; + private static final String UPDATE_VM_DISK_STATS = "UPDATE cloud_usage.vm_disk_statistics SET net_io_read=?, net_io_write=?, current_io_read=?, current_io_write=?, agg_io_read=?, agg_io_write=?, " + + "net_bytes_read=?, net_bytes_write=?, current_bytes_read=?, current_bytes_write=?, agg_bytes_read=?, agg_bytes_write=? WHERE id=?"; + protected final static TimeZone s_gmtTimeZone = TimeZone.getTimeZone("GMT"); public UsageDaoImpl () {} @@ -270,4 +278,101 @@ public class UsageDaoImpl extends GenericDaoBase implements Usage } return templateList; } + + @Override + public Long getLastVmDiskStatsId() { + Transaction txn = Transaction.currentTxn(); + PreparedStatement pstmt = null; + String sql = GET_LAST_VM_DISK_STATS; + try { + pstmt = txn.prepareAutoCloseStatement(sql); + ResultSet rs = pstmt.executeQuery(); + if (rs.next()) { + return Long.valueOf(rs.getLong(1)); + } + } catch (Exception ex) { + s_logger.error("error getting last vm disk stats id", ex); + } + return null; + } + + @Override + public void updateVmDiskStats(List vmDiskStats) { + Transaction txn = Transaction.currentTxn(); + try { + txn.start(); + String sql = UPDATE_VM_DISK_STATS; + PreparedStatement pstmt = null; + pstmt = txn.prepareAutoCloseStatement(sql); // in reality I just want CLOUD_USAGE dataSource connection + for (VmDiskStatisticsVO vmDiskStat : vmDiskStats) { + pstmt.setLong(1, vmDiskStat.getNetIORead()); + pstmt.setLong(2, vmDiskStat.getNetIOWrite()); + pstmt.setLong(3, vmDiskStat.getCurrentIORead()); + pstmt.setLong(4, vmDiskStat.getCurrentIOWrite()); + pstmt.setLong(5, vmDiskStat.getAggIORead()); + pstmt.setLong(6, vmDiskStat.getAggIOWrite()); + pstmt.setLong(7, vmDiskStat.getNetBytesRead()); + pstmt.setLong(8, vmDiskStat.getNetBytesWrite()); + pstmt.setLong(9, vmDiskStat.getCurrentBytesRead()); + pstmt.setLong(10, vmDiskStat.getCurrentBytesWrite()); + pstmt.setLong(11, vmDiskStat.getAggBytesRead()); + pstmt.setLong(12, vmDiskStat.getAggBytesWrite()); + pstmt.setLong(13, vmDiskStat.getId()); + pstmt.addBatch(); + } + pstmt.executeBatch(); + txn.commit(); + } catch (Exception ex) { + txn.rollback(); + s_logger.error("error saving vm disk stats to cloud_usage db", ex); + throw new CloudRuntimeException(ex.getMessage()); + } + + } + + @Override + public void saveVmDiskStats(List vmDiskStats) { + Transaction txn = Transaction.currentTxn(); + try { + txn.start(); + String sql = INSERT_VM_DISK_STATS; + PreparedStatement pstmt = null; + pstmt = txn.prepareAutoCloseStatement(sql); // in reality I just want CLOUD_USAGE dataSource connection + for (VmDiskStatisticsVO vmDiskStat : vmDiskStats) { + pstmt.setLong(1, vmDiskStat.getId()); + pstmt.setLong(2, vmDiskStat.getDataCenterId()); + pstmt.setLong(3, vmDiskStat.getAccountId()); + if(vmDiskStat.getVmId() != null){ + pstmt.setLong(4, vmDiskStat.getVmId()); + } else { + pstmt.setNull(4, Types.BIGINT); + } + if(vmDiskStat.getVolumeId() != null){ + pstmt.setLong(5, vmDiskStat.getVolumeId()); + } else { + pstmt.setNull(5, Types.BIGINT); + } + pstmt.setLong(6, vmDiskStat.getNetIORead()); + pstmt.setLong(7, vmDiskStat.getNetIOWrite()); + pstmt.setLong(8, vmDiskStat.getCurrentIORead()); + pstmt.setLong(9, vmDiskStat.getCurrentIOWrite()); + pstmt.setLong(10, vmDiskStat.getAggIORead()); + pstmt.setLong(11, vmDiskStat.getAggIOWrite()); + pstmt.setLong(12, vmDiskStat.getNetBytesRead()); + pstmt.setLong(13, vmDiskStat.getNetBytesWrite()); + pstmt.setLong(14, vmDiskStat.getCurrentBytesRead()); + pstmt.setLong(15, vmDiskStat.getCurrentBytesWrite()); + pstmt.setLong(16, vmDiskStat.getAggBytesRead()); + pstmt.setLong(17, vmDiskStat.getAggBytesWrite()); + pstmt.addBatch(); + } + pstmt.executeBatch(); + txn.commit(); + } catch (Exception ex) { + txn.rollback(); + s_logger.error("error saving vm disk stats to cloud_usage db", ex); + throw new CloudRuntimeException(ex.getMessage()); + } + + } } diff --git a/engine/schema/src/com/cloud/usage/dao/UsageVmDiskDao.java b/engine/schema/src/com/cloud/usage/dao/UsageVmDiskDao.java new file mode 100644 index 00000000000..b72a8d4120f --- /dev/null +++ b/engine/schema/src/com/cloud/usage/dao/UsageVmDiskDao.java @@ -0,0 +1,29 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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.cloud.usage.dao; + +import java.util.List; +import java.util.Map; + +import com.cloud.usage.UsageVmDiskVO; +import com.cloud.utils.db.GenericDao; + +public interface UsageVmDiskDao extends GenericDao { + Map getRecentVmDiskStats(); + void deleteOldStats(long maxEventTime); + void saveUsageVmDisks(List usageVmDisks); +} diff --git a/engine/schema/src/com/cloud/usage/dao/UsageVmDiskDaoImpl.java b/engine/schema/src/com/cloud/usage/dao/UsageVmDiskDaoImpl.java new file mode 100644 index 00000000000..8436c5955c8 --- /dev/null +++ b/engine/schema/src/com/cloud/usage/dao/UsageVmDiskDaoImpl.java @@ -0,0 +1,139 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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.cloud.usage.dao; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.ejb.Local; + +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.usage.UsageVmDiskVO; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.exception.CloudRuntimeException; + +@Component +@Local(value={UsageVmDiskDao.class}) +public class UsageVmDiskDaoImpl extends GenericDaoBase implements UsageVmDiskDao { + private static final Logger s_logger = Logger.getLogger(UsageVMInstanceDaoImpl.class.getName()); + private static final String SELECT_LATEST_STATS = "SELECT uvd.account_id, uvd.zone_id, uvd.vm_id, uvd.volume_id, uvd.io_read, uvd.io_write, uvd.agg_io_read, uvd.agg_io_write, " + + "uvd.bytes_read, uvd.bytes_write, uvd.agg_bytes_read, uvd.agg_bytes_write, uvd.event_time_millis " + + "FROM cloud_usage.usage_vm_disk uvd INNER JOIN (SELECT vmdiskusage.account_id as acct_id, vmdiskusage.zone_id as z_id, max(vmdiskusage.event_time_millis) as max_date " + + "FROM cloud_usage.usage_vm_disk vmdiskusage " + + "GROUP BY vmdiskusage.account_id, vmdiskusage.zone_id " + + ") joinnet on uvd.account_id = joinnet.acct_id and uvd.zone_id = joinnet.z_id and uvd.event_time_millis = joinnet.max_date"; + private static final String DELETE_OLD_STATS = "DELETE FROM cloud_usage.usage_vm_disk WHERE event_time_millis < ?"; + + private static final String INSERT_USAGE_VM_DISK = "INSERT INTO cloud_usage.usage_vm_disk (account_id, zone_id, vm_id, volume_id, io_read, io_write, agg_io_read, agg_io_write, bytes_read, bytes_write, agg_bytes_read, agg_bytes_write, event_time_millis) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)"; + + public UsageVmDiskDaoImpl() { + } + + @Override + public Map getRecentVmDiskStats() { + Transaction txn = Transaction.open(Transaction.USAGE_DB); + String sql = SELECT_LATEST_STATS; + PreparedStatement pstmt = null; + try { + pstmt = txn.prepareAutoCloseStatement(sql); + ResultSet rs = pstmt.executeQuery(); + Map returnMap = new HashMap(); + while (rs.next()) { + long accountId = rs.getLong(1); + long zoneId = rs.getLong(2); + long vmId = rs.getLong(3); + Long volumeId = rs.getLong(4); + long ioRead = rs.getLong(5); + long ioWrite = rs.getLong(6); + long aggIORead = rs.getLong(7); + long aggIOWrite = rs.getLong(8); + long bytesRead = rs.getLong(9); + long bytesWrite = rs.getLong(10); + long aggBytesRead = rs.getLong(11); + long aggBytesWrite = rs.getLong(12); + long eventTimeMillis = rs.getLong(13); + if(vmId != 0){ + returnMap.put(zoneId + "-" + accountId+ "-Vm-" + vmId+ "-Disk-" + volumeId, new UsageVmDiskVO(accountId, zoneId, vmId, volumeId, ioRead, ioWrite, aggIORead, aggIOWrite, bytesRead, bytesWrite, aggBytesRead, aggBytesWrite, eventTimeMillis)); + } else { + returnMap.put(zoneId + "-" + accountId, new UsageVmDiskVO(accountId, zoneId, vmId, volumeId, ioRead, ioWrite, aggIORead, aggIOWrite, bytesRead, bytesWrite, aggBytesRead, aggBytesWrite, eventTimeMillis)); + } + } + return returnMap; + } catch (Exception ex) { + s_logger.error("error getting recent usage disk stats", ex); + } finally { + txn.close(); + } + return null; + } + + @Override + public void deleteOldStats(long maxEventTime) { + Transaction txn = Transaction.currentTxn(); + String sql = DELETE_OLD_STATS; + PreparedStatement pstmt = null; + try { + txn.start(); + pstmt = txn.prepareAutoCloseStatement(sql); + pstmt.setLong(1, maxEventTime); + pstmt.executeUpdate(); + txn.commit(); + } catch (Exception ex) { + txn.rollback(); + s_logger.error("error deleting old usage disk stats", ex); + } + } + + @Override + public void saveUsageVmDisks(List usageVmDisks) { + Transaction txn = Transaction.currentTxn(); + try { + txn.start(); + String sql = INSERT_USAGE_VM_DISK; + PreparedStatement pstmt = null; + pstmt = txn.prepareAutoCloseStatement(sql); // in reality I just want CLOUD_USAGE dataSource connection + for (UsageVmDiskVO usageVmDisk : usageVmDisks) { + pstmt.setLong(1, usageVmDisk.getAccountId()); + pstmt.setLong(2, usageVmDisk.getZoneId()); + pstmt.setLong(3, usageVmDisk.getVmId()); + pstmt.setLong(4, usageVmDisk.getVolumeId()); + pstmt.setLong(5, usageVmDisk.getIORead()); + pstmt.setLong(6, usageVmDisk.getIOWrite()); + pstmt.setLong(7, usageVmDisk.getAggIORead()); + pstmt.setLong(8, usageVmDisk.getAggIOWrite()); + pstmt.setLong(9, usageVmDisk.getBytesRead()); + pstmt.setLong(10, usageVmDisk.getBytesWrite()); + pstmt.setLong(11, usageVmDisk.getAggBytesRead()); + pstmt.setLong(12, usageVmDisk.getAggBytesWrite()); + pstmt.setLong(13, usageVmDisk.getEventTimeMillis()); + pstmt.addBatch(); + } + pstmt.executeBatch(); + txn.commit(); + } catch (Exception ex) { + txn.rollback(); + s_logger.error("error saving usage_vm_disk to cloud_usage db", ex); + throw new CloudRuntimeException(ex.getMessage()); + } + } +} diff --git a/engine/schema/src/com/cloud/user/VmDiskStatisticsVO.java b/engine/schema/src/com/cloud/user/VmDiskStatisticsVO.java new file mode 100644 index 00000000000..d1842c3042c --- /dev/null +++ b/engine/schema/src/com/cloud/user/VmDiskStatisticsVO.java @@ -0,0 +1,216 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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.cloud.user; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name="vm_disk_statistics") +public class VmDiskStatisticsVO { + @Id + @GeneratedValue(strategy=GenerationType.IDENTITY) + @Column(name="id") + private Long id; + + @Column(name="data_center_id", updatable=false) + private long dataCenterId; + + @Column(name="account_id", updatable=false) + private long accountId; + + @Column(name="vm_id") + private Long vmId; + + @Column(name="volume_id") + private Long volumeId; + + @Column(name="net_io_read") + private long netIORead; + + @Column(name="net_io_write") + private long netIOWrite; + + @Column(name="current_io_read") + private long currentIORead; + + @Column(name="current_io_write") + private long currentIOWrite; + + @Column(name="agg_io_read") + private long aggIORead; + + @Column(name="agg_io_write") + private long aggIOWrite; + + @Column(name="net_bytes_read") + private long netBytesRead; + + @Column(name="net_bytes_write") + private long netBytesWrite; + + @Column(name="current_bytes_read") + private long currentBytesRead; + + @Column(name="current_bytes_write") + private long currentBytesWrite; + + @Column(name="agg_bytes_read") + private long aggBytesRead; + + @Column(name="agg_bytes_write") + private long aggBytesWrite; + + protected VmDiskStatisticsVO() { + } + + public VmDiskStatisticsVO(long accountId, long dcId, Long vmId, Long volumeId) { + this.accountId = accountId; + this.dataCenterId = dcId; + this.vmId = vmId; + this.volumeId = volumeId; + this.netBytesRead = 0; + this.netBytesWrite = 0; + this.currentBytesRead = 0; + this.currentBytesWrite = 0; + this.netBytesRead = 0; + this.netBytesWrite = 0; + this.currentBytesRead = 0; + this.currentBytesWrite = 0; + } + + public long getAccountId() { + return accountId; + } + + public Long getId() { + return id; + } + + public long getDataCenterId() { + return dataCenterId; + } + + public Long getVmId() { + return vmId; + } + + public Long getVolumeId() { + return volumeId; + } + + public long getCurrentIORead() { + return currentIORead; + } + + public void setCurrentIORead(long currentIORead) { + this.currentIORead = currentIORead; + } + + public long getCurrentIOWrite() { + return currentIOWrite; + } + + public void setCurrentIOWrite(long currentIOWrite) { + this.currentIOWrite = currentIOWrite; + } + + public long getNetIORead() { + return netIORead; + } + + public long getNetIOWrite() { + return netIOWrite; + } + + public void setNetIORead(long netIORead) { + this.netIORead = netIORead; + } + + public void setNetIOWrite(long netIOWrite) { + this.netIOWrite = netIOWrite; + } + + public long getAggIORead() { + return aggIORead; + } + + public void setAggIORead(long aggIORead) { + this.aggIORead = aggIORead; + } + + public long getAggIOWrite() { + return aggIOWrite; + } + + public void setAggIOWrite(long aggIOWrite) { + this.aggIOWrite = aggIOWrite; + } + + public long getCurrentBytesRead() { + return currentBytesRead; + } + + public void setCurrentBytesRead(long currentBytesRead) { + this.currentBytesRead = currentBytesRead; + } + + public long getCurrentBytesWrite() { + return currentBytesWrite; + } + + public void setCurrentBytesWrite(long currentBytesWrite) { + this.currentBytesWrite = currentBytesWrite; + } + + public long getNetBytesRead() { + return netBytesRead; + } + + public long getNetBytesWrite() { + return netBytesWrite; + } + + public void setNetBytesRead(long netBytesRead) { + this.netBytesRead = netBytesRead; + } + + public void setNetBytesWrite(long netBytesWrite) { + this.netBytesWrite = netBytesWrite; + } + + public long getAggBytesRead() { + return aggBytesRead; + } + + public void setAggBytesRead(long aggBytesRead) { + this.aggBytesRead = aggBytesRead; + } + + public long getAggBytesWrite() { + return aggBytesWrite; + } + + public void setAggBytesWrite(long aggBytesWrite) { + this.aggBytesWrite = aggBytesWrite; + } + +} diff --git a/engine/schema/src/com/cloud/user/dao/VmDiskStatisticsDao.java b/engine/schema/src/com/cloud/user/dao/VmDiskStatisticsDao.java new file mode 100644 index 00000000000..55206a61935 --- /dev/null +++ b/engine/schema/src/com/cloud/user/dao/VmDiskStatisticsDao.java @@ -0,0 +1,35 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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.cloud.user.dao; + +import java.util.Date; +import java.util.List; + +import com.cloud.user.VmDiskStatisticsVO; +import com.cloud.utils.db.GenericDao; + +public interface VmDiskStatisticsDao extends GenericDao { + VmDiskStatisticsVO findBy(long accountId, long dcId, long vmId, long volumeId); + + VmDiskStatisticsVO lock(long accountId, long dcId, long vmId, long volumeId); + + List listBy(long accountId); + + List listActiveAndRecentlyDeleted(Date minRemovedDate, int startIndex, int limit); + + List listUpdatedStats(); +} diff --git a/engine/schema/src/com/cloud/user/dao/VmDiskStatisticsDaoImpl.java b/engine/schema/src/com/cloud/user/dao/VmDiskStatisticsDaoImpl.java new file mode 100644 index 00000000000..02f3406c497 --- /dev/null +++ b/engine/schema/src/com/cloud/user/dao/VmDiskStatisticsDaoImpl.java @@ -0,0 +1,134 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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.cloud.user.dao; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.TimeZone; + +import javax.ejb.Local; + +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.user.VmDiskStatisticsVO; +import com.cloud.utils.DateUtil; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.Transaction; + +@Component +@Local(value={VmDiskStatisticsDao.class}) +public class VmDiskStatisticsDaoImpl extends GenericDaoBase implements VmDiskStatisticsDao { + private static final Logger s_logger = Logger.getLogger(VmDiskStatisticsDaoImpl.class); + private static final String ACTIVE_AND_RECENTLY_DELETED_SEARCH = "SELECT vns.id, vns.data_center_id, vns.account_id, vns.vm_id, vns.volume_id, vns.agg_io_read, vns.agg_io_write, vns.agg_bytes_read, vns.agg_bytes_write " + + "FROM vm_disk_statistics vns, account a " + + "WHERE vns.account_id = a.id AND (a.removed IS NULL OR a.removed >= ?) " + + "ORDER BY vns.id"; + private static final String UPDATED_VM_NETWORK_STATS_SEARCH = "SELECT id, current_io_read, current_io_write, net_io_read, net_io_write, agg_io_read, agg_io_write, " + + "current_bytes_read, current_bytes_write, net_bytes_read, net_bytes_write, agg_bytes_read, agg_bytes_write " + + "from vm_disk_statistics " + + "where (agg_io_read < net_io_read + current_io_read) OR (agg_io_write < net_io_write + current_io_write) OR " + + "(agg_bytes_read < net_bytes_read + current_bytes_read) OR (agg_bytes_write < net_bytes_write + current_bytes_write)"; + private final SearchBuilder AllFieldsSearch; + private final SearchBuilder AccountSearch; + + + public VmDiskStatisticsDaoImpl() { + AccountSearch = createSearchBuilder(); + AccountSearch.and("account", AccountSearch.entity().getAccountId(), SearchCriteria.Op.EQ); + AccountSearch.done(); + + AllFieldsSearch = createSearchBuilder(); + AllFieldsSearch.and("account", AllFieldsSearch.entity().getAccountId(), SearchCriteria.Op.EQ); + AllFieldsSearch.and("dc", AllFieldsSearch.entity().getDataCenterId(), SearchCriteria.Op.EQ); + AllFieldsSearch.and("volume", AllFieldsSearch.entity().getVolumeId(), SearchCriteria.Op.EQ); + AllFieldsSearch.and("vm", AllFieldsSearch.entity().getVmId(), SearchCriteria.Op.EQ); + AllFieldsSearch.done(); + } + + @Override + public VmDiskStatisticsVO findBy(long accountId, long dcId, long vmId, long volumeId) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("account", accountId); + sc.setParameters("dc", dcId); + sc.setParameters("volume", volumeId); + sc.setParameters("vm", vmId); + return findOneBy(sc); + } + + @Override + public VmDiskStatisticsVO lock(long accountId, long dcId, long vmId, long volumeId) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("account", accountId); + sc.setParameters("dc", dcId); + sc.setParameters("volume", volumeId); + sc.setParameters("vm", vmId); + return lockOneRandomRow(sc, true); + } + + @Override + public List listBy(long accountId) { + SearchCriteria sc = AccountSearch.create(); + sc.setParameters("account", accountId); + return search(sc, null); + } + + @Override + public List listActiveAndRecentlyDeleted(Date minRemovedDate, int startIndex, int limit) { + List vmDiskStats = new ArrayList(); + if (minRemovedDate == null) return vmDiskStats; + + Transaction txn = Transaction.currentTxn(); + try { + String sql = ACTIVE_AND_RECENTLY_DELETED_SEARCH + " LIMIT " + startIndex + "," + limit; + PreparedStatement pstmt = null; + pstmt = txn.prepareAutoCloseStatement(sql); + pstmt.setString(1, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), minRemovedDate)); + ResultSet rs = pstmt.executeQuery(); + while (rs.next()) { + vmDiskStats.add(toEntityBean(rs, false)); + } + } catch (Exception ex) { + s_logger.error("error saving vm disk stats to cloud_usage db", ex); + } + return vmDiskStats; + } + + @Override + public List listUpdatedStats() { + List vmDiskStats = new ArrayList(); + + Transaction txn = Transaction.currentTxn(); + try { + PreparedStatement pstmt = null; + pstmt = txn.prepareAutoCloseStatement(UPDATED_VM_NETWORK_STATS_SEARCH); + ResultSet rs = pstmt.executeQuery(); + while (rs.next()) { + vmDiskStats.add(toEntityBean(rs, false)); + } + } catch (Exception ex) { + s_logger.error("error lisitng updated vm disk stats", ex); + } + return vmDiskStats; + } + +} diff --git a/engine/schema/src/com/cloud/vm/dao/UserVmData.java b/engine/schema/src/com/cloud/vm/dao/UserVmData.java index 674fc005a85..6622a7dc8e6 100644 --- a/engine/schema/src/com/cloud/vm/dao/UserVmData.java +++ b/engine/schema/src/com/cloud/vm/dao/UserVmData.java @@ -57,6 +57,10 @@ public class UserVmData { private String cpuUsed; private Long networkKbsRead; private Long networkKbsWrite; + private Long diskKbsRead; + private Long diskKbsWrite; + private Long diskIORead; + private Long diskIOWrite; private Long guestOsId; private Long rootDeviceId; private String rootDeviceType; @@ -364,6 +368,38 @@ public class UserVmData { this.networkKbsWrite = networkKbsWrite; } + public Long getDiskKbsRead() { + return diskKbsRead; + } + + public void setDiskKbsRead(Long diskKbsRead) { + this.diskKbsRead = diskKbsRead; + } + + public Long getDiskKbsWrite() { + return diskKbsWrite; + } + + public void setDiskKbsWrite(Long diskKbsWrite) { + this.diskKbsWrite = diskKbsWrite; + } + + public Long getDiskIORead() { + return diskIORead; + } + + public void setDiskIORead(Long diskIORead) { + this.diskIORead = diskIORead; + } + + public Long getDiskIOWrite() { + return diskIOWrite; + } + + public void setDiskIOWrite(Long diskIOWrite) { + this.diskIOWrite = diskIOWrite; + } + public Long getGuestOsId() { return guestOsId; } diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index f979cfe00e4..46fce2487b6 100755 --- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -63,6 +63,7 @@ import org.apache.cloudstack.utils.qemu.QemuImgFile; import org.apache.cloudstack.utils.qemu.QemuImgException; import org.libvirt.Connect; import org.libvirt.Domain; +import org.libvirt.DomainBlockStats; import org.libvirt.DomainInfo; import org.libvirt.DomainInterfaceStats; import org.libvirt.DomainSnapshot; @@ -99,6 +100,8 @@ import com.cloud.agent.api.GetHostStatsAnswer; import com.cloud.agent.api.GetHostStatsCommand; import com.cloud.agent.api.GetStorageStatsAnswer; import com.cloud.agent.api.GetStorageStatsCommand; +import com.cloud.agent.api.GetVmDiskStatsAnswer; +import com.cloud.agent.api.GetVmDiskStatsCommand; import com.cloud.agent.api.GetVmStatsAnswer; import com.cloud.agent.api.GetVmStatsCommand; import com.cloud.agent.api.GetVncPortAnswer; @@ -145,6 +148,7 @@ import com.cloud.agent.api.StopCommand; import com.cloud.agent.api.UnPlugNicAnswer; import com.cloud.agent.api.UnPlugNicCommand; import com.cloud.agent.api.UpgradeSnapshotCommand; +import com.cloud.agent.api.VmDiskStatsEntry; import com.cloud.agent.api.VmStatsEntry; import com.cloud.agent.api.check.CheckSshAnswer; import com.cloud.agent.api.check.CheckSshCommand; @@ -1117,6 +1121,8 @@ ServerResource { return execute((StopCommand) cmd); } else if (cmd instanceof GetVmStatsCommand) { return execute((GetVmStatsCommand) cmd); + } else if (cmd instanceof GetVmDiskStatsCommand) { + return execute((GetVmDiskStatsCommand) cmd); } else if (cmd instanceof RebootRouterCommand) { return execute((RebootRouterCommand) cmd); } else if (cmd instanceof RebootCommand) { @@ -3007,6 +3013,26 @@ ServerResource { } } + protected GetVmDiskStatsAnswer execute(GetVmDiskStatsCommand cmd) { + List vmNames = cmd.getVmNames(); + try { + HashMap> vmDiskStatsNameMap = new HashMap>(); + Connect conn = LibvirtConnection.getConnection(); + for (String vmName : vmNames) { + List statEntry = getVmDiskStat(conn, vmName); + if (statEntry == null) { + continue; + } + + vmDiskStatsNameMap.put(vmName, statEntry); + } + return new GetVmDiskStatsAnswer(cmd, "", cmd.getHostName(), vmDiskStatsNameMap); + } catch (LibvirtException e) { + s_logger.debug("Can't get vm disk stats: " + e.toString()); + return new GetVmDiskStatsAnswer(cmd, null, null, null); + } + } + protected GetVmStatsAnswer execute(GetVmStatsCommand cmd) { List vmNames = cmd.getVmNames(); try { @@ -4512,10 +4538,46 @@ ServerResource { } } + private List getVmDiskStat(Connect conn, String vmName) + throws LibvirtException { + Domain dm = null; + try { + dm = getDomain(conn, vmName); + + List stats = new ArrayList(); + + List disks = getDisks(conn, vmName); + + for (DiskDef disk : disks) { + DomainBlockStats blockStats = dm.blockStats(disk.getDiskLabel()); + String path = disk.getDiskPath(); // for example, path = /mnt/pool_uuid/disk_path/ + String diskPath = null; + if (path != null) { + String[] token = path.split("/"); + if (token.length > 3) { + diskPath = token[3]; + VmDiskStatsEntry stat = new VmDiskStatsEntry(vmName, diskPath, blockStats.wr_req, blockStats.rd_req, blockStats.wr_bytes, blockStats.rd_bytes); + stats.add(stat); + } + } + } + + return stats; + } finally { + if (dm != null) { + dm.free(); + } + } + } + private class vmStats { long _usedTime; long _tx; long _rx; + long _io_rd; + long _io_wr; + long _bytes_rd; + long _bytes_wr; Calendar _timestamp; } @@ -4572,10 +4634,44 @@ ServerResource { stats.setNetworkWriteKBs(deltatx / 1024); } + /* get disk stats */ + List disks = getDisks(conn, vmName); + long io_rd = 0; + long io_wr = 0; + long bytes_rd = 0; + long bytes_wr = 0; + for (DiskDef disk : disks) { + DomainBlockStats blockStats = dm.blockStats(disk.getDiskLabel()); + io_rd += blockStats.rd_req; + io_wr += blockStats.wr_req; + bytes_rd += blockStats.rd_bytes; + bytes_wr += blockStats.wr_bytes; + } + + if (oldStats != null) { + long deltaiord = io_rd - oldStats._io_rd; + if (deltaiord > 0) + stats.setDiskReadIOs(deltaiord); + long deltaiowr = io_wr - oldStats._io_wr; + if (deltaiowr > 0) + stats.setDiskWriteIOs(deltaiowr); + long deltabytesrd = bytes_rd - oldStats._bytes_rd; + if (deltabytesrd > 0) + stats.setDiskReadKBs(deltabytesrd / 1024); + long deltabyteswr = bytes_wr - oldStats._bytes_wr; + if (deltabyteswr > 0) + stats.setDiskWriteKBs(deltabyteswr / 1024); + } + + /* save to Hashmap */ vmStats newStat = new vmStats(); newStat._usedTime = info.cpuTime; newStat._rx = rx; newStat._tx = tx; + newStat._io_rd = io_rd; + newStat._io_wr = io_wr; + newStat._bytes_rd = bytes_rd; + newStat._bytes_wr = bytes_wr; newStat._timestamp = now; _vmStats.put(vmName, newStat); return stats; diff --git a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java index 7626d1205c7..a2cceb14d90 100644 --- a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java +++ b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java @@ -57,6 +57,8 @@ import com.cloud.agent.api.GetHostStatsAnswer; import com.cloud.agent.api.GetHostStatsCommand; import com.cloud.agent.api.GetStorageStatsAnswer; import com.cloud.agent.api.GetStorageStatsCommand; +import com.cloud.agent.api.GetVmDiskStatsAnswer; +import com.cloud.agent.api.GetVmDiskStatsCommand; import com.cloud.agent.api.GetVmStatsAnswer; import com.cloud.agent.api.GetVmStatsCommand; import com.cloud.agent.api.GetVncPortAnswer; @@ -111,6 +113,7 @@ import com.cloud.agent.api.UnPlugNicAnswer; import com.cloud.agent.api.UnPlugNicCommand; import com.cloud.agent.api.UpdateHostPasswordCommand; import com.cloud.agent.api.UpgradeSnapshotCommand; +import com.cloud.agent.api.VmDiskStatsEntry; import com.cloud.agent.api.VmStatsEntry; import com.cloud.agent.api.check.CheckSshAnswer; import com.cloud.agent.api.check.CheckSshCommand; @@ -236,6 +239,7 @@ import com.xensource.xenapi.Types.VmBadPowerState; import com.xensource.xenapi.Types.VmPowerState; import com.xensource.xenapi.Types.XenAPIException; import com.xensource.xenapi.VBD; +import com.xensource.xenapi.VBDMetrics; import com.xensource.xenapi.VDI; import com.xensource.xenapi.VIF; import com.xensource.xenapi.VLAN; @@ -482,6 +486,8 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe return execute((GetHostStatsCommand) cmd); } else if (clazz == GetVmStatsCommand.class) { return execute((GetVmStatsCommand) cmd); + } else if (cmd instanceof GetVmDiskStatsCommand) { + return execute((GetVmDiskStatsCommand) cmd); } else if (clazz == CheckHealthCommand.class) { return execute((CheckHealthCommand) cmd); } else if (clazz == StopCommand.class) { @@ -2584,6 +2590,80 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe return hostStats; } + protected GetVmDiskStatsAnswer execute( GetVmDiskStatsCommand cmd) { + Connection conn = getConnection(); + List vmNames = cmd.getVmNames(); + HashMap> vmDiskStatsNameMap = new HashMap>(); + if( vmNames.size() == 0 ) { + return new GetVmDiskStatsAnswer(cmd, "", cmd.getHostName(),vmDiskStatsNameMap); + } + try { + + // Determine the UUIDs of the requested VMs + List vmUUIDs = new ArrayList(); + + for (String vmName : vmNames) { + VM vm = getVM(conn, vmName); + vmUUIDs.add(vm.getUuid(conn)); + } + + HashMap> vmDiskStatsUUIDMap = getVmDiskStats(conn, cmd, vmUUIDs, cmd.getHostGuid()); + if( vmDiskStatsUUIDMap == null ) { + return new GetVmDiskStatsAnswer(cmd, "", cmd.getHostName(), vmDiskStatsNameMap); + } + + for (String vmUUID : vmDiskStatsUUIDMap.keySet()) { + List vmDiskStatsUUID = vmDiskStatsUUIDMap.get(vmUUID); + String vmName = vmNames.get(vmUUIDs.indexOf(vmUUID)); + for (VmDiskStatsEntry vmDiskStat : vmDiskStatsUUID) { + vmDiskStat.setVmName(vmName); + } + vmDiskStatsNameMap.put(vmName, vmDiskStatsUUID); + } + + return new GetVmDiskStatsAnswer(cmd, "", cmd.getHostName(),vmDiskStatsNameMap); + } catch (XenAPIException e) { + String msg = "Unable to get VM disk stats" + e.toString(); + s_logger.warn(msg, e); + return new GetVmDiskStatsAnswer(cmd, "", cmd.getHostName(),vmDiskStatsNameMap); + } catch (XmlRpcException e) { + String msg = "Unable to get VM disk stats" + e.getMessage(); + s_logger.warn(msg, e); + return new GetVmDiskStatsAnswer(cmd, "", cmd.getHostName(),vmDiskStatsNameMap); + } + } + + private HashMap> getVmDiskStats(Connection conn, GetVmDiskStatsCommand cmd, List vmUUIDs, String hostGuid) { + HashMap> vmResponseMap = new HashMap>(); + + for (String vmUUID : vmUUIDs) { + vmResponseMap.put(vmUUID, new ArrayList()); + } + + try { + for (String vmUUID : vmUUIDs) { + VM vm = VM.getByUuid(conn, vmUUID); + List vmDiskStats = new ArrayList(); + for (VBD vbd : vm.getVBDs(conn)) { + if (!vbd.getType(conn).equals(Types.VbdType.CD)) { + VmDiskStatsEntry stats = new VmDiskStatsEntry(); + VBDMetrics record = vbd.getMetrics(conn); + stats.setPath(vbd.getVDI(conn).getUuid(conn)); + stats.setBytesRead((long)(record.getIoReadKbs(conn) * 1024)); + stats.setBytesWrite((long)(record.getIoWriteKbs(conn) * 1024)); + vmDiskStats.add(stats); + } + } + vmResponseMap.put(vmUUID, vmDiskStats); + } + } catch (Exception e) { + s_logger.warn("Error while collecting disk stats from : ", e); + return null; + } + + return vmResponseMap; + } + protected GetVmStatsAnswer execute( GetVmStatsCommand cmd) { Connection conn = getConnection(); List vmNames = cmd.getVmNames(); @@ -2693,6 +2773,29 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe } } + try { + for (String vmUUID : vmUUIDs) { + VM vm = VM.getByUuid(conn, vmUUID); + VmStatsEntry stats = vmResponseMap.get(vmUUID); + double diskReadKBs = 0; + double diskWriteKBs = 0; + for (VBD vbd : vm.getVBDs(conn)) { + VBDMetrics record = vbd.getMetrics(conn); + diskReadKBs += record.getIoReadKbs(conn); + diskWriteKBs += record.getIoWriteKbs(conn); + } + if (stats == null) { + stats = new VmStatsEntry(); + } + stats.setDiskReadKBs(diskReadKBs); + stats.setDiskWriteKBs(diskWriteKBs); + vmResponseMap.put(vmUUID, stats); + } + } catch (Exception e) { + s_logger.warn("Error while collecting disk stats from : ", e); + return null; + } + return vmResponseMap; } diff --git a/server/src/com/cloud/api/ApiResponseHelper.java b/server/src/com/cloud/api/ApiResponseHelper.java index c9ca08935f8..029b14c5e0f 100755 --- a/server/src/com/cloud/api/ApiResponseHelper.java +++ b/server/src/com/cloud/api/ApiResponseHelper.java @@ -3397,6 +3397,17 @@ public class ApiResponseHelper implements ResponseGenerator { NetworkVO network = _entityMgr.findByIdIncludingRemoved(NetworkVO.class, usageRecord.getNetworkId().toString()); usageRecResponse.setNetworkId(network.getUuid()); + } else if(usageRecord.getUsageType() == UsageTypes.VM_DISK_IO_READ || usageRecord.getUsageType() == UsageTypes.VM_DISK_IO_WRITE || + usageRecord.getUsageType() == UsageTypes.VM_DISK_BYTES_READ || usageRecord.getUsageType() == UsageTypes.VM_DISK_BYTES_WRITE){ + //Device Type + usageRecResponse.setType(usageRecord.getType()); + //VM Instance Id + VMInstanceVO vm = _entityMgr.findByIdIncludingRemoved(VMInstanceVO.class, usageRecord.getUsageId().toString()); + usageRecResponse.setUsageId(vm.getUuid()); + //Volume ID + VolumeVO volume = _entityMgr.findByIdIncludingRemoved(VolumeVO.class, usageRecord.getUsageId().toString()); + usageRecResponse.setUsageId(volume.getUuid()); + } else if(usageRecord.getUsageType() == UsageTypes.VOLUME){ //Volume ID VolumeVO volume = _entityMgr.findByIdIncludingRemoved(VolumeVO.class, usageRecord.getUsageId().toString()); diff --git a/server/src/com/cloud/api/query/dao/UserVmJoinDaoImpl.java b/server/src/com/cloud/api/query/dao/UserVmJoinDaoImpl.java index 77c3c443541..dbfe94dad26 100644 --- a/server/src/com/cloud/api/query/dao/UserVmJoinDaoImpl.java +++ b/server/src/com/cloud/api/query/dao/UserVmJoinDaoImpl.java @@ -160,6 +160,18 @@ public class UserVmJoinDaoImpl extends GenericDaoBase implem Double networkKbWrite = Double.valueOf(vmStats.getNetworkWriteKBs()); userVmResponse.setNetworkKbsWrite(networkKbWrite.longValue()); + + Double diskKbsRead = Double.valueOf(vmStats.getDiskReadKBs()); + userVmResponse.setDiskKbsRead(diskKbsRead.longValue()); + + Double diskKbsWrite = Double.valueOf(vmStats.getDiskWriteKBs()); + userVmResponse.setDiskKbsWrite(diskKbsWrite.longValue()); + + Double diskIORead = Double.valueOf(vmStats.getDiskReadIOs()); + userVmResponse.setDiskIORead(diskIORead.longValue()); + + Double diskIOWrite = Double.valueOf(vmStats.getDiskWriteIOs()); + userVmResponse.setDiskIOWrite(diskIOWrite.longValue()); } } diff --git a/server/src/com/cloud/configuration/Config.java b/server/src/com/cloud/configuration/Config.java index 929d56bf10c..5ee0fad8643 100755 --- a/server/src/com/cloud/configuration/Config.java +++ b/server/src/com/cloud/configuration/Config.java @@ -229,6 +229,7 @@ public enum Config { NetworkGcInterval("Advanced", ManagementServer.class, Integer.class, "network.gc.interval", "600", "Seconds to wait before checking for networks to shutdown", null), CapacitySkipcountingHours("Advanced", ManagementServer.class, Integer.class, "capacity.skipcounting.hours", "3600", "Time (in seconds) to wait before release VM's cpu and memory when VM in stopped state", null), VmStatsInterval("Advanced", ManagementServer.class, Integer.class, "vm.stats.interval", "60000", "The interval (in milliseconds) when vm stats are retrieved from agents.", null), + VmDiskStatsInterval("Advanced", ManagementServer.class, Integer.class, "vm.disk.stats.interval", "0", "Interval (in seconds) to report vm disk statistics.", null), VmTransitionWaitInterval("Advanced", ManagementServer.class, Integer.class, "vm.tranisition.wait.interval", "3600", "Time (in seconds) to wait before taking over a VM in transition state", null), VmDestroyForcestop("Advanced", ManagementServer.class, Boolean.class, "vm.destroy.forcestop", "false", "On destroy, force-stop takes this value ", null), diff --git a/server/src/com/cloud/server/StatsCollector.java b/server/src/com/cloud/server/StatsCollector.java index 05be0e2e3af..39b743976b3 100755 --- a/server/src/com/cloud/server/StatsCollector.java +++ b/server/src/com/cloud/server/StatsCollector.java @@ -17,11 +17,14 @@ package com.cloud.server; import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TimeZone; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -45,8 +48,11 @@ import com.cloud.agent.api.Answer; import com.cloud.agent.api.GetFileStatsCommand; import com.cloud.agent.api.GetStorageStatsCommand; import com.cloud.agent.api.HostStatsEntry; +import com.cloud.agent.api.VmDiskStatsEntry; import com.cloud.agent.api.VmStatsEntry; import com.cloud.agent.manager.Commands; +import com.cloud.cluster.ManagementServerHostVO; +import com.cloud.cluster.dao.ManagementServerHostDao; import com.cloud.exception.AgentUnavailableException; import com.cloud.exception.StorageUnavailableException; import com.cloud.host.Host; @@ -59,19 +65,31 @@ import com.cloud.resource.ResourceState; import com.cloud.storage.StorageManager; import com.cloud.storage.StoragePoolHostVO; import com.cloud.storage.StorageStats; +import com.cloud.storage.Volume; import com.cloud.storage.VolumeStats; import com.cloud.storage.VolumeVO; import com.cloud.storage.dao.StoragePoolHostDao; import com.cloud.storage.dao.VolumeDao; import com.cloud.storage.secondary.SecondaryStorageVmManager; +import com.cloud.user.UserStatisticsVO; +import com.cloud.user.UserStatsLogVO; +import com.cloud.user.VmDiskStatisticsVO; +import com.cloud.user.dao.VmDiskStatisticsDao; import com.cloud.utils.NumbersUtil; import com.cloud.utils.component.ComponentMethodInterceptable; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.concurrency.NamedThreadFactory; +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GlobalLock; import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.net.MacAddress; +import com.cloud.vm.NicVO; import com.cloud.vm.UserVmManager; import com.cloud.vm.UserVmVO; import com.cloud.vm.VmStats; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachine.State; import com.cloud.vm.dao.UserVmDao; /** @@ -96,6 +114,8 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc @Inject private SecondaryStorageVmManager _ssvmMgr; @Inject private ResourceManager _resourceMgr; @Inject private ConfigurationDao _configDao; + @Inject private VmDiskStatisticsDao _vmDiskStatsDao; + @Inject private ManagementServerHostDao _msHostDao; private ConcurrentHashMap _hostStats = new ConcurrentHashMap(); private final ConcurrentHashMap _VmStats = new ConcurrentHashMap(); @@ -107,6 +127,15 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc long hostAndVmStatsInterval = -1L; long storageStatsInterval = -1L; long volumeStatsInterval = -1L; + int vmDiskStatsInterval = 0; + + private ScheduledExecutorService _diskStatsUpdateExecutor; + private int _usageAggregationRange = 1440; + private String _usageTimeZone = "GMT"; + private final long mgmtSrvrId = MacAddress.getMacAddress().toLong(); + private static final int ACQUIRE_GLOBAL_LOCK_TIMEOUT_FOR_COOPERATION = 5; // 5 seconds + private static final int USAGE_AGGREGATION_RANGE_MIN = 10; // 10 minutes, same to com.cloud.usage.UsageManagerImpl.USAGE_AGGREGATION_RANGE_MIN + private boolean _dailyOrHourly = false; //private final GlobalLock m_capacityCheckLock = GlobalLock.getInternLock("capacity.check"); @@ -136,6 +165,7 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc hostAndVmStatsInterval = NumbersUtil.parseLong(configs.get("vm.stats.interval"), 60000L); storageStatsInterval = NumbersUtil.parseLong(configs.get("storage.stats.interval"), 60000L); volumeStatsInterval = NumbersUtil.parseLong(configs.get("volume.stats.interval"), -1L); + vmDiskStatsInterval = NumbersUtil.parseInt(configs.get("vm.disk.stats.interval"), 0); if (hostStatsInterval > 0) { _executor.scheduleWithFixedDelay(new HostCollector(), 15000L, hostStatsInterval, TimeUnit.MILLISECONDS); @@ -148,6 +178,12 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc if (storageStatsInterval > 0) { _executor.scheduleWithFixedDelay(new StorageCollector(), 15000L, storageStatsInterval, TimeUnit.MILLISECONDS); } + + if (vmDiskStatsInterval > 0) { + if (vmDiskStatsInterval < 300) + vmDiskStatsInterval = 300; + _executor.scheduleAtFixedRate(new VmDiskStatsTask(), vmDiskStatsInterval, vmDiskStatsInterval, TimeUnit.SECONDS); + } // -1 means we don't even start this thread to pick up any data. if (volumeStatsInterval > 0) { @@ -155,6 +191,49 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc } else { s_logger.info("Disabling volume stats collector"); } + + //Schedule disk stats update task + _diskStatsUpdateExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("DiskStatsUpdater")); + String aggregationRange = configs.get("usage.stats.job.aggregation.range"); + _usageAggregationRange = NumbersUtil.parseInt(aggregationRange, 1440); + _usageTimeZone = configs.get("usage.aggregation.timezone"); + if(_usageTimeZone == null){ + _usageTimeZone = "GMT"; + } + TimeZone usageTimezone = TimeZone.getTimeZone(_usageTimeZone); + Calendar cal = Calendar.getInstance(usageTimezone); + cal.setTime(new Date()); + long endDate = 0; + int HOURLY_TIME = 60; + final int DAILY_TIME = 60 * 24; + if (_usageAggregationRange == DAILY_TIME) { + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + cal.roll(Calendar.DAY_OF_YEAR, true); + cal.add(Calendar.MILLISECOND, -1); + endDate = cal.getTime().getTime(); + _dailyOrHourly = true; + } else if (_usageAggregationRange == HOURLY_TIME) { + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + cal.roll(Calendar.HOUR_OF_DAY, true); + cal.add(Calendar.MILLISECOND, -1); + endDate = cal.getTime().getTime(); + _dailyOrHourly = true; + } else { + endDate = cal.getTime().getTime(); + _dailyOrHourly = false; + } + if (_usageAggregationRange < USAGE_AGGREGATION_RANGE_MIN) { + s_logger.warn("Usage stats job aggregation range is to small, using the minimum value of " + USAGE_AGGREGATION_RANGE_MIN); + _usageAggregationRange = USAGE_AGGREGATION_RANGE_MIN; + } + _diskStatsUpdateExecutor.scheduleAtFixedRate(new VmDiskStatsUpdaterTask(), (endDate - System.currentTimeMillis()), + (_usageAggregationRange * 60 * 1000), TimeUnit.MILLISECONDS); + } class HostCollector implements Runnable { @@ -249,6 +328,10 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc statsInMemory.setNumCPUs(statsForCurrentIteration.getNumCPUs()); statsInMemory.setNetworkReadKBs(statsInMemory.getNetworkReadKBs() + statsForCurrentIteration.getNetworkReadKBs()); statsInMemory.setNetworkWriteKBs(statsInMemory.getNetworkWriteKBs() + statsForCurrentIteration.getNetworkWriteKBs()); + statsInMemory.setDiskWriteKBs(statsInMemory.getDiskWriteKBs() + statsForCurrentIteration.getDiskWriteKBs()); + statsInMemory.setDiskReadIOs(statsInMemory.getDiskReadIOs() + statsForCurrentIteration.getDiskReadIOs()); + statsInMemory.setDiskWriteIOs(statsInMemory.getDiskWriteIOs() + statsForCurrentIteration.getDiskWriteIOs()); + statsInMemory.setDiskReadKBs(statsInMemory.getDiskReadKBs() + statsForCurrentIteration.getDiskReadKBs()); _VmStats.put(vmId, statsInMemory); } @@ -270,6 +353,175 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc public VmStats getVmStats(long id) { return _VmStats.get(id); } + + class VmDiskStatsUpdaterTask implements Runnable { + @Override + public void run() { + GlobalLock scanLock = GlobalLock.getInternLock("vm.disk.stats"); + try { + if(scanLock.lock(ACQUIRE_GLOBAL_LOCK_TIMEOUT_FOR_COOPERATION)) { + //Check for ownership + //msHost in UP state with min id should run the job + ManagementServerHostVO msHost = _msHostDao.findOneInUpState(new Filter(ManagementServerHostVO.class, "id", true, 0L, 1L)); + if(msHost == null || (msHost.getMsid() != mgmtSrvrId)){ + s_logger.debug("Skipping aggregate disk stats update"); + scanLock.unlock(); + return; + } + Transaction txn = Transaction.open(Transaction.CLOUD_DB); + try { + txn.start(); + //get all stats with delta > 0 + List updatedVmNetStats = _vmDiskStatsDao.listUpdatedStats(); + for(VmDiskStatisticsVO stat : updatedVmNetStats){ + if (_dailyOrHourly) { + //update agg bytes + stat.setAggBytesRead(stat.getCurrentBytesRead() + stat.getNetBytesRead()); + stat.setAggBytesWrite(stat.getCurrentBytesWrite() + stat.getNetBytesWrite()); + stat.setAggIORead(stat.getCurrentIORead() + stat.getNetIORead()); + stat.setAggIOWrite(stat.getCurrentIOWrite() + stat.getNetIOWrite()); + _vmDiskStatsDao.update(stat.getId(), stat); + } + } + s_logger.debug("Successfully updated aggregate vm disk stats"); + txn.commit(); + } catch (Exception e){ + txn.rollback(); + s_logger.debug("Failed to update aggregate disk stats", e); + } finally { + scanLock.unlock(); + txn.close(); + } + } + } catch (Exception e){ + s_logger.debug("Exception while trying to acquire disk stats lock", e); + } finally { + scanLock.releaseRef(); + } + } + } + + class VmDiskStatsTask implements Runnable { + @Override + public void run() { + // collect the vm disk statistics(total) from hypervisor. added by weizhou, 2013.03. + Transaction txn = Transaction.open(Transaction.CLOUD_DB); + try { + txn.start(); + SearchCriteria sc = _hostDao.createSearchCriteria(); + sc.addAnd("status", SearchCriteria.Op.EQ, Status.Up.toString()); + sc.addAnd("resourceState", SearchCriteria.Op.NIN, ResourceState.Maintenance, ResourceState.PrepareForMaintenance, ResourceState.ErrorInMaintenance); + sc.addAnd("type", SearchCriteria.Op.EQ, Host.Type.Routing.toString()); + List hosts = _hostDao.search(sc, null); + + for (HostVO host : hosts) { + List vms = _userVmDao.listRunningByHostId(host.getId()); + List vmIds = new ArrayList(); + + for (UserVmVO vm : vms) { + if (vm.getType() == VirtualMachine.Type.User) // user vm + vmIds.add(vm.getId()); + } + + HashMap> vmDiskStatsById = _userVmMgr.getVmDiskStatistics(host.getId(), host.getName(), vmIds); + if (vmDiskStatsById == null) + continue; + + Set vmIdSet = vmDiskStatsById.keySet(); + for(Long vmId : vmIdSet) + { + List vmDiskStats = vmDiskStatsById.get(vmId); + if (vmDiskStats == null) + continue; + UserVmVO userVm = _userVmDao.findById(vmId); + for (VmDiskStatsEntry vmDiskStat:vmDiskStats) { + SearchCriteria sc_volume = _volsDao.createSearchCriteria(); + sc_volume.addAnd("path", SearchCriteria.Op.EQ, vmDiskStat.getPath()); + VolumeVO volume = _volsDao.search(sc_volume, null).get(0); + VmDiskStatisticsVO previousVmDiskStats = _vmDiskStatsDao.findBy(userVm.getAccountId(), userVm.getDataCenterId(), vmId, volume.getId()); + VmDiskStatisticsVO vmDiskStat_lock = _vmDiskStatsDao.lock(userVm.getAccountId(), userVm.getDataCenterId(), vmId, volume.getId()); + + if ((vmDiskStat.getBytesRead() == 0) && (vmDiskStat.getBytesWrite() == 0) + && (vmDiskStat.getIORead() == 0) && (vmDiskStat.getIOWrite() == 0)) { + s_logger.debug("IO/bytes read and write are all 0. Not updating vm_disk_statistics"); + continue; + } + + if (vmDiskStat_lock == null) { + s_logger.warn("unable to find vm disk stats from host for account: " + userVm.getAccountId() + " with vmId: " + userVm.getId()+ " and volumeId:" + volume.getId()); + continue; + } + + if (previousVmDiskStats != null + && ((previousVmDiskStats.getCurrentBytesRead() != vmDiskStat_lock.getCurrentBytesRead()) + || (previousVmDiskStats.getCurrentBytesWrite() != vmDiskStat_lock.getCurrentBytesWrite()) + || (previousVmDiskStats.getCurrentIORead() != vmDiskStat_lock.getCurrentIORead()) + || (previousVmDiskStats.getCurrentIOWrite() != vmDiskStat_lock.getCurrentIOWrite()))) { + s_logger.debug("vm disk stats changed from the time GetVmDiskStatsCommand was sent. " + + "Ignoring current answer. Host: " + host.getName() + " . VM: " + vmDiskStat.getVmName() + + " Read(Bytes): " + vmDiskStat.getBytesRead() + " write(Bytes): " + vmDiskStat.getBytesWrite() + + " Read(IO): " + vmDiskStat.getIORead() + " write(IO): " + vmDiskStat.getIOWrite()); + continue; + } + + if (vmDiskStat_lock.getCurrentBytesRead() > vmDiskStat.getBytesRead()) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Read # of bytes that's less than the last one. " + + "Assuming something went wrong and persisting it. Host: " + host.getName() + " . VM: " + vmDiskStat.getVmName() + + " Reported: " + vmDiskStat.getBytesRead() + " Stored: " + vmDiskStat_lock.getCurrentBytesRead()); + } + vmDiskStat_lock.setNetBytesRead(vmDiskStat_lock.getNetBytesRead() + vmDiskStat_lock.getCurrentBytesRead()); + } + vmDiskStat_lock.setCurrentBytesRead(vmDiskStat.getBytesRead()); + if (vmDiskStat_lock.getCurrentBytesWrite() > vmDiskStat.getBytesWrite()) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Write # of bytes that's less than the last one. " + + "Assuming something went wrong and persisting it. Host: " + host.getName() + " . VM: " + vmDiskStat.getVmName() + + " Reported: " + vmDiskStat.getBytesWrite() + " Stored: " + vmDiskStat_lock.getCurrentBytesWrite()); + } + vmDiskStat_lock.setNetBytesWrite(vmDiskStat_lock.getNetBytesWrite() + vmDiskStat_lock.getCurrentBytesWrite()); + } + vmDiskStat_lock.setCurrentBytesWrite(vmDiskStat.getBytesWrite()); + if (vmDiskStat_lock.getCurrentIORead() > vmDiskStat.getIORead()) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Read # of IO that's less than the last one. " + + "Assuming something went wrong and persisting it. Host: " + host.getName() + " . VM: " + vmDiskStat.getVmName() + + " Reported: " + vmDiskStat.getIORead() + " Stored: " + vmDiskStat_lock.getCurrentIORead()); + } + vmDiskStat_lock.setNetIORead(vmDiskStat_lock.getNetIORead() + vmDiskStat_lock.getCurrentIORead()); + } + vmDiskStat_lock.setCurrentIORead(vmDiskStat.getIORead()); + if (vmDiskStat_lock.getCurrentIOWrite() > vmDiskStat.getIOWrite()) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Write # of IO that's less than the last one. " + + "Assuming something went wrong and persisting it. Host: " + host.getName() + " . VM: " + vmDiskStat.getVmName() + + " Reported: " + vmDiskStat.getIOWrite() + " Stored: " + vmDiskStat_lock.getCurrentIOWrite()); + } + vmDiskStat_lock.setNetIOWrite(vmDiskStat_lock.getNetIOWrite() + vmDiskStat_lock.getCurrentIOWrite()); + } + vmDiskStat_lock.setCurrentIOWrite(vmDiskStat.getIOWrite()); + + if (! _dailyOrHourly) { + //update agg bytes + vmDiskStat_lock.setAggBytesWrite(vmDiskStat_lock.getNetBytesWrite() + vmDiskStat_lock.getCurrentBytesWrite()); + vmDiskStat_lock.setAggBytesRead(vmDiskStat_lock.getNetBytesRead() + vmDiskStat_lock.getCurrentBytesRead()); + vmDiskStat_lock.setAggIOWrite(vmDiskStat_lock.getNetIOWrite() + vmDiskStat_lock.getCurrentIOWrite()); + vmDiskStat_lock.setAggIORead(vmDiskStat_lock.getNetIORead() + vmDiskStat_lock.getCurrentIORead()); + } + + _vmDiskStatsDao.update(vmDiskStat_lock.getId(), vmDiskStat_lock); + } + } + } + txn.commit(); + } catch (Exception e) { + s_logger.warn("Error while collecting vm disk stats from hosts", e); + } finally { + txn.close(); + } + + } + } class StorageCollector implements Runnable { @Override diff --git a/server/src/com/cloud/storage/VolumeManagerImpl.java b/server/src/com/cloud/storage/VolumeManagerImpl.java index d064f3becfa..43f36817d75 100644 --- a/server/src/com/cloud/storage/VolumeManagerImpl.java +++ b/server/src/com/cloud/storage/VolumeManagerImpl.java @@ -128,9 +128,11 @@ import com.cloud.template.TemplateManager; import com.cloud.user.Account; import com.cloud.user.AccountManager; import com.cloud.user.ResourceLimitService; +import com.cloud.user.VmDiskStatisticsVO; import com.cloud.user.UserContext; import com.cloud.user.dao.AccountDao; import com.cloud.user.dao.UserDao; +import com.cloud.user.dao.VmDiskStatisticsDao; import com.cloud.uservm.UserVm; import com.cloud.utils.EnumUtils; import com.cloud.utils.NumbersUtil; @@ -280,6 +282,8 @@ public class VolumeManagerImpl extends ManagerBase implements VolumeManager { @Inject protected ResourceTagDao _resourceTagDao; @Inject + protected VmDiskStatisticsDao _vmDiskStatsDao; + @Inject protected VMSnapshotDao _vmSnapshotDao; @Inject protected List _storagePoolAllocators; @@ -1558,6 +1562,13 @@ public class VolumeManagerImpl extends ManagerBase implements VolumeManager { } else { _volsDao.attachVolume(volume.getId(), vm.getId(), deviceId); } + // insert record for disk I/O statistics + VmDiskStatisticsVO diskstats = _vmDiskStatsDao.findBy(vm.getAccountId(), vm.getDataCenterId(),vm.getId(), volume.getId()); + if (diskstats == null) { + diskstats = new VmDiskStatisticsVO(vm.getAccountId(), vm.getDataCenterId(),vm.getId(), volume.getId()); + _vmDiskStatsDao.persist(diskstats); + } + return _volsDao.findById(volume.getId()); } else { if (answer != null) { @@ -1895,6 +1906,9 @@ public class VolumeManagerImpl extends ManagerBase implements VolumeManager { .getPoolId()); cmd.setPoolUuid(volumePool.getUuid()); + // Collect vm disk statistics from host before stopping Vm + _userVmMgr.collectVmDiskStatistics(vm); + try { answer = _agentMgr.send(vm.getHostId(), cmd); } catch (Exception e) { diff --git a/server/src/com/cloud/vm/UserVmManager.java b/server/src/com/cloud/vm/UserVmManager.java index 4dcfb73e2a1..348017a0a44 100755 --- a/server/src/com/cloud/vm/UserVmManager.java +++ b/server/src/com/cloud/vm/UserVmManager.java @@ -20,6 +20,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import com.cloud.agent.api.VmDiskStatsEntry; import com.cloud.agent.api.VmStatsEntry; import com.cloud.api.query.vo.UserVmJoinVO; import com.cloud.exception.*; @@ -65,6 +66,8 @@ public interface UserVmManager extends VirtualMachineGuru, UserVmServi */ HashMap getVirtualMachineStatistics(long hostId, String hostName, List vmIds); + HashMap> getVmDiskStatistics(long hostId, String hostName, List vmIds); + boolean deleteVmGroup(long groupId); boolean addInstanceToGroup(long userVmId, String group); @@ -95,4 +98,6 @@ public interface UserVmManager extends VirtualMachineGuru, UserVmServi boolean upgradeVirtualMachine(Long id, Long serviceOfferingId) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException, VirtualMachineMigrationException; boolean setupVmForPvlan(boolean add, Long hostId, NicProfile nic); + + void collectVmDiskStatistics (UserVmVO userVm); } diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java index 86bdb14ff1a..8cf05aa7761 100755 --- a/server/src/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/com/cloud/vm/UserVmManagerImpl.java @@ -66,6 +66,8 @@ import org.apache.log4j.Logger; import com.cloud.agent.AgentManager; import com.cloud.agent.AgentManager.OnError; import com.cloud.agent.api.Answer; +import com.cloud.agent.api.GetVmDiskStatsAnswer; +import com.cloud.agent.api.GetVmDiskStatsCommand; import com.cloud.agent.api.GetVmStatsAnswer; import com.cloud.agent.api.GetVmStatsCommand; import com.cloud.agent.api.PlugNicAnswer; @@ -75,6 +77,7 @@ import com.cloud.agent.api.StartAnswer; import com.cloud.agent.api.StopAnswer; import com.cloud.agent.api.UnPlugNicAnswer; import com.cloud.agent.api.UnPlugNicCommand; +import com.cloud.agent.api.VmDiskStatsEntry; import com.cloud.agent.api.VmStatsEntry; import com.cloud.agent.api.to.NicTO; import com.cloud.agent.api.to.VirtualMachineTO; @@ -213,9 +216,11 @@ import com.cloud.user.SSHKeyPairVO; import com.cloud.user.User; import com.cloud.user.UserContext; import com.cloud.user.UserVO; +import com.cloud.user.VmDiskStatisticsVO; import com.cloud.user.dao.AccountDao; import com.cloud.user.dao.SSHKeyPairDao; import com.cloud.user.dao.UserDao; +import com.cloud.user.dao.VmDiskStatisticsDao; import com.cloud.uservm.UserVm; import com.cloud.utils.Journal; import com.cloud.utils.NumbersUtil; @@ -239,6 +244,7 @@ import com.cloud.vm.VirtualMachine.State; import com.cloud.vm.dao.InstanceGroupDao; import com.cloud.vm.dao.InstanceGroupVMMapDao; import com.cloud.vm.dao.NicDao; +import com.cloud.vm.dao.SecondaryStorageVmDao; import com.cloud.vm.dao.UserVmCloneSettingDao; import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.UserVmDetailsDao; @@ -394,6 +400,12 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use protected GuestOSCategoryDao _guestOSCategoryDao; @Inject UsageEventDao _usageEventDao; + + @Inject + SecondaryStorageVmDao _secondaryDao; + @Inject + VmDiskStatisticsDao _vmDiskStatsDao; + @Inject protected VMSnapshotDao _vmSnapshotDao; @Inject @@ -411,6 +423,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use protected ScheduledExecutorService _executor = null; protected int _expungeInterval; protected int _expungeDelay; + protected boolean _dailyOrHourly = false; protected String _name; protected String _instance; @@ -1098,6 +1111,41 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use } } + + @Override + public HashMap> getVmDiskStatistics(long hostId, String hostName, List vmIds) throws CloudRuntimeException { + HashMap> vmDiskStatsById = new HashMap>(); + + if (vmIds.isEmpty()) { + return vmDiskStatsById; + } + + List vmNames = new ArrayList(); + + for (Long vmId : vmIds) { + UserVmVO vm = _vmDao.findById(vmId); + vmNames.add(vm.getInstanceName()); + } + + Answer answer = _agentMgr.easySend(hostId, new GetVmDiskStatsCommand(vmNames, _hostDao.findById(hostId).getGuid(), hostName)); + if (answer == null || !answer.getResult()) { + s_logger.warn("Unable to obtain VM disk statistics."); + return null; + } else { + HashMap> vmDiskStatsByName = ((GetVmDiskStatsAnswer)answer).getVmDiskStatsMap(); + + if (vmDiskStatsByName == null) { + s_logger.warn("Unable to obtain VM disk statistics."); + return null; + } + + for (String vmName : vmDiskStatsByName.keySet()) { + vmDiskStatsById.put(vmIds.get(vmNames.indexOf(vmName)), vmDiskStatsByName.get(vmName)); + } + } + + return vmDiskStatsById; + } @Override public boolean upgradeVirtualMachine(Long vmId, Long newServiceOfferingId) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException, VirtualMachineMigrationException{ @@ -1397,6 +1445,18 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use _executor = Executors.newScheduledThreadPool(wrks, new NamedThreadFactory("UserVm-Scavenger")); + String aggregationRange = configs.get("usage.stats.job.aggregation.range"); + int _usageAggregationRange = NumbersUtil.parseInt(aggregationRange, 1440); + int HOURLY_TIME = 60; + final int DAILY_TIME = 60 * 24; + if (_usageAggregationRange == DAILY_TIME) { + _dailyOrHourly = true; + } else if (_usageAggregationRange == HOURLY_TIME) { + _dailyOrHourly = true; + } else { + _dailyOrHourly = false; + } + _itMgr.registerGuru(VirtualMachine.Type.User, this); VirtualMachine.State.getStateMachine().registerListener( @@ -2929,6 +2989,17 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use userVm.setPrivateMacAddress(nic.getMacAddress()); } } + + List volumes = _volsDao.findByInstance(userVm.getId()); + VmDiskStatisticsVO diskstats = null; + for (VolumeVO volume : volumes) { + diskstats = _vmDiskStatsDao.findBy(userVm.getAccountId(), userVm.getDataCenterId(),userVm.getId(), volume.getId()); + if (diskstats == null) { + diskstats = new VmDiskStatisticsVO(userVm.getAccountId(), userVm.getDataCenterId(),userVm.getId(), volume.getId()); + _vmDiskStatsDao.persist(diskstats); + } + } + return true; } @@ -3308,6 +3379,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use boolean status; State vmState = vm.getState(); + // Collect vm disk statistics from host before stopping Vm + collectVmDiskStatistics(vm); + try { VirtualMachineEntity vmEntity = _orchSrvc.getVirtualMachine(vm.getUuid()); status = vmEntity.destroy(new Long(userId).toString()); @@ -3344,6 +3418,122 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use ex.addProxyObject(vm.getUuid(), "vmId"); throw ex; } + + } + + @Override + public void collectVmDiskStatistics (UserVmVO userVm) { + // Collect vm disk statistics from host before stopping Vm + long hostId = userVm.getHostId(); + List vmNames = new ArrayList(); + vmNames.add(userVm.getInstanceName()); + HostVO host = _hostDao.findById(hostId); + + GetVmDiskStatsAnswer diskStatsAnswer = null; + try { + diskStatsAnswer = (GetVmDiskStatsAnswer) _agentMgr.easySend(hostId, new GetVmDiskStatsCommand(vmNames, host.getGuid(), host.getName())); + } catch (Exception e) { + s_logger.warn("Error while collecting disk stats for vm: " + userVm.getHostName() + " from host: " + host.getName(), e); + return; + } + if (diskStatsAnswer != null) { + if (!diskStatsAnswer.getResult()) { + s_logger.warn("Error while collecting disk stats vm: " + userVm.getHostName() + " from host: " + host.getName() + "; details: " + diskStatsAnswer.getDetails()); + return; + } + Transaction txn = Transaction.open(Transaction.CLOUD_DB); + try { + txn.start(); + HashMap> vmDiskStatsByName = diskStatsAnswer.getVmDiskStatsMap(); + List vmDiskStats = vmDiskStatsByName.get(userVm.getInstanceName()); + + if (vmDiskStats == null) + return; + + for (VmDiskStatsEntry vmDiskStat:vmDiskStats) { + SearchCriteria sc_volume = _volsDao.createSearchCriteria(); + sc_volume.addAnd("path", SearchCriteria.Op.EQ, vmDiskStat.getPath()); + VolumeVO volume = _volsDao.search(sc_volume, null).get(0); + VmDiskStatisticsVO previousVmDiskStats = _vmDiskStatsDao.findBy(userVm.getAccountId(), userVm.getDataCenterId(), userVm.getId(), volume.getId()); + VmDiskStatisticsVO vmDiskStat_lock = _vmDiskStatsDao.lock(userVm.getAccountId(), userVm.getDataCenterId(), userVm.getId(), volume.getId()); + + if ((vmDiskStat.getIORead() == 0) && (vmDiskStat.getIOWrite() == 0) && (vmDiskStat.getBytesRead() == 0) && (vmDiskStat.getBytesWrite() == 0)) { + s_logger.debug("Read/Write of IO and Bytes are both 0. Not updating vm_disk_statistics"); + continue; + } + + if (vmDiskStat_lock == null) { + s_logger.warn("unable to find vm disk stats from host for account: " + userVm.getAccountId() + " with vmId: " + userVm.getId()+ " and volumeId:" + volume.getId()); + continue; + } + + if (previousVmDiskStats != null + && ((previousVmDiskStats.getCurrentIORead() != vmDiskStat_lock.getCurrentIORead()) + || ((previousVmDiskStats.getCurrentIOWrite() != vmDiskStat_lock.getCurrentIOWrite()) + || (previousVmDiskStats.getCurrentBytesRead() != vmDiskStat_lock.getCurrentBytesRead()) + || (previousVmDiskStats.getCurrentBytesWrite() != vmDiskStat_lock.getCurrentBytesWrite())))) { + s_logger.debug("vm disk stats changed from the time GetVmDiskStatsCommand was sent. " + + "Ignoring current answer. Host: " + host.getName() + " . VM: " + vmDiskStat.getVmName() + + " IO Read: " + vmDiskStat.getIORead() + " IO Write: " + vmDiskStat.getIOWrite() + + " Bytes Read: " + vmDiskStat.getBytesRead() + " Bytes Write: " + vmDiskStat.getBytesWrite()); + continue; + } + + if (vmDiskStat_lock.getCurrentIORead() > vmDiskStat.getIORead()) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Read # of IO that's less than the last one. " + + "Assuming something went wrong and persisting it. Host: " + host.getName() + " . VM: " + vmDiskStat.getVmName() + + " Reported: " + vmDiskStat.getIORead() + " Stored: " + vmDiskStat_lock.getCurrentIORead()); + } + vmDiskStat_lock.setNetIORead(vmDiskStat_lock.getNetIORead() + vmDiskStat_lock.getCurrentIORead()); + } + vmDiskStat_lock.setCurrentIORead(vmDiskStat.getIORead()); + if (vmDiskStat_lock.getCurrentIOWrite() > vmDiskStat.getIOWrite()) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Write # of IO that's less than the last one. " + + "Assuming something went wrong and persisting it. Host: " + host.getName() + " . VM: " + vmDiskStat.getVmName() + + " Reported: " + vmDiskStat.getIOWrite() + " Stored: " + vmDiskStat_lock.getCurrentIOWrite()); + } + vmDiskStat_lock.setNetIOWrite(vmDiskStat_lock.getNetIOWrite() + vmDiskStat_lock.getCurrentIOWrite()); + } + vmDiskStat_lock.setCurrentIOWrite(vmDiskStat.getIOWrite()); + if (vmDiskStat_lock.getCurrentBytesRead() > vmDiskStat.getBytesRead()) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Read # of Bytes that's less than the last one. " + + "Assuming something went wrong and persisting it. Host: " + host.getName() + " . VM: " + vmDiskStat.getVmName() + + " Reported: " + vmDiskStat.getBytesRead() + " Stored: " + vmDiskStat_lock.getCurrentBytesRead()); + } + vmDiskStat_lock.setNetBytesRead(vmDiskStat_lock.getNetBytesRead() + vmDiskStat_lock.getCurrentBytesRead()); + } + vmDiskStat_lock.setCurrentBytesRead(vmDiskStat.getBytesRead()); + if (vmDiskStat_lock.getCurrentBytesWrite() > vmDiskStat.getBytesWrite()) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Write # of Bytes that's less than the last one. " + + "Assuming something went wrong and persisting it. Host: " + host.getName() + " . VM: " + vmDiskStat.getVmName() + + " Reported: " + vmDiskStat.getBytesWrite() + " Stored: " + vmDiskStat_lock.getCurrentBytesWrite()); + } + vmDiskStat_lock.setNetBytesWrite(vmDiskStat_lock.getNetBytesWrite() + vmDiskStat_lock.getCurrentBytesWrite()); + } + vmDiskStat_lock.setCurrentBytesWrite(vmDiskStat.getBytesWrite()); + + if (! _dailyOrHourly) { + //update agg bytes + vmDiskStat_lock.setAggIORead(vmDiskStat_lock.getNetIORead() + vmDiskStat_lock.getCurrentIORead()); + vmDiskStat_lock.setAggIOWrite(vmDiskStat_lock.getNetIOWrite() + vmDiskStat_lock.getCurrentIOWrite()); + vmDiskStat_lock.setAggBytesRead(vmDiskStat_lock.getNetBytesRead() + vmDiskStat_lock.getCurrentBytesRead()); + vmDiskStat_lock.setAggBytesWrite(vmDiskStat_lock.getNetBytesWrite() + vmDiskStat_lock.getCurrentBytesWrite()); + } + + _vmDiskStatsDao.update(vmDiskStat_lock.getId(), vmDiskStat_lock); + } + txn.commit(); + } catch (Exception e) { + txn.rollback(); + s_logger.warn("Unable to update vm disk statistics for vm: " + userVm.getId() + " from host: " + hostId, e); + } finally { + txn.close(); + } + } } diff --git a/server/test/async-job-component.xml b/server/test/async-job-component.xml index 46982523a23..55f47cc5b50 100644 --- a/server/test/async-job-component.xml +++ b/server/test/async-job-component.xml @@ -74,6 +74,7 @@ under the License. 300 + 50 -1 diff --git a/server/test/com/cloud/vm/MockUserVmManagerImpl.java b/server/test/com/cloud/vm/MockUserVmManagerImpl.java index 448a5dd9a21..40c49d4430a 100644 --- a/server/test/com/cloud/vm/MockUserVmManagerImpl.java +++ b/server/test/com/cloud/vm/MockUserVmManagerImpl.java @@ -47,6 +47,7 @@ import org.apache.cloudstack.api.command.user.vmgroup.DeleteVMGroupCmd; import org.springframework.stereotype.Component; import com.cloud.agent.api.StopAnswer; +import com.cloud.agent.api.VmDiskStatsEntry; import com.cloud.agent.api.VmStatsEntry; import com.cloud.agent.api.to.NicTO; import com.cloud.agent.api.to.VirtualMachineTO; @@ -168,6 +169,12 @@ public class MockUserVmManagerImpl extends ManagerBase implements UserVmManager, return null; } + @Override + public HashMap> getVmDiskStatistics(long hostId, String hostName, List vmIds) { + // TODO Auto-generated method stub + return null; + } + @Override public boolean deleteVmGroup(long groupId) { // TODO Auto-generated method stub @@ -461,4 +468,9 @@ public class MockUserVmManagerImpl extends ManagerBase implements UserVmManager, // TODO Auto-generated method stub return false; } + + @Override + public void collectVmDiskStatistics (UserVmVO userVm) { + // TODO Auto-generated method stub + } } diff --git a/setup/db/db/schema-410to420.sql b/setup/db/db/schema-410to420.sql index 03e14a9ddbb..bcfbcc931fe 100644 --- a/setup/db/db/schema-410to420.sql +++ b/setup/db/db/schema-410to420.sql @@ -1771,6 +1771,82 @@ INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'manag UPDATE `cloud`.`snapshots` set swift_id=null where swift_id=0; +DROP TABLE IF EXISTS `cloud`.`vm_disk_statistics`; +CREATE TABLE `cloud`.`vm_disk_statistics` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `data_center_id` bigint(20) unsigned NOT NULL, + `account_id` bigint(20) unsigned NOT NULL, + `vm_id` bigint(20) unsigned NOT NULL, + `volume_id` bigint(20) unsigned NOT NULL DEFAULT '0', + `net_io_read` bigint(20) unsigned NOT NULL DEFAULT '0', + `net_io_write` bigint(20) unsigned NOT NULL DEFAULT '0', + `current_io_read` bigint(20) unsigned NOT NULL DEFAULT '0', + `current_io_write` bigint(20) unsigned NOT NULL DEFAULT '0', + `agg_io_read` bigint(20) unsigned NOT NULL DEFAULT '0', + `agg_io_write` bigint(20) unsigned NOT NULL DEFAULT '0', + `net_bytes_read` bigint(20) unsigned NOT NULL DEFAULT '0', + `net_bytes_write` bigint(20) unsigned NOT NULL DEFAULT '0', + `current_bytes_read` bigint(20) unsigned NOT NULL DEFAULT '0', + `current_bytes_write` bigint(20) unsigned NOT NULL DEFAULT '0', + `agg_bytes_read` bigint(20) unsigned NOT NULL DEFAULT '0', + `agg_bytes_write` bigint(20) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + UNIQUE KEY `id` (`id`), + UNIQUE KEY `account_id` (`account_id`,`data_center_id`,`vm_id`,`volume_id`), + KEY `i_vm_disk_statistics__account_id` (`account_id`), + KEY `i_vm_disk_statistics__account_id_data_center_id` (`account_id`,`data_center_id`), + CONSTRAINT `fk_vm_disk_statistics__account_id` FOREIGN KEY (`account_id`) REFERENCES `account` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8; + +insert into `cloud`.`vm_disk_statistics`(data_center_id,account_id,vm_id,volume_id) +select volumes.data_center_id, volumes.account_id, vm_instance.id, volumes.id from volumes,vm_instance where vm_instance.vm_type="User" and vm_instance.state<>"Expunging" and volumes.instance_id=vm_instance.id order by vm_instance.id; + +DROP TABLE IF EXISTS `cloud_usage`.`vm_disk_statistics`; +CREATE TABLE `cloud_usage`.`vm_disk_statistics` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `data_center_id` bigint(20) unsigned NOT NULL, + `account_id` bigint(20) unsigned NOT NULL, + `vm_id` bigint(20) unsigned NOT NULL, + `volume_id` bigint(20) unsigned NOT NULL DEFAULT '0', + `net_io_read` bigint(20) unsigned NOT NULL DEFAULT '0', + `net_io_write` bigint(20) unsigned NOT NULL DEFAULT '0', + `current_io_read` bigint(20) unsigned NOT NULL DEFAULT '0', + `current_io_write` bigint(20) unsigned NOT NULL DEFAULT '0', + `agg_io_read` bigint(20) unsigned NOT NULL DEFAULT '0', + `agg_io_write` bigint(20) unsigned NOT NULL DEFAULT '0', + `net_bytes_read` bigint(20) unsigned NOT NULL DEFAULT '0', + `net_bytes_write` bigint(20) unsigned NOT NULL DEFAULT '0', + `current_bytes_read` bigint(20) unsigned NOT NULL DEFAULT '0', + `current_bytes_write` bigint(20) unsigned NOT NULL DEFAULT '0', + `agg_bytes_read` bigint(20) unsigned NOT NULL DEFAULT '0', + `agg_bytes_write` bigint(20) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + UNIQUE KEY `id` (`id`), + UNIQUE KEY `account_id` (`account_id`,`data_center_id`,`vm_id`,`volume_id`) +) ENGINE=InnoDB CHARSET=utf8; + +insert into `cloud_usage`.`vm_disk_statistics` select * from `cloud`.`vm_disk_statistics`; + +DROP TABLE IF EXISTS `cloud_usage`.`usage_vm_disk`; +CREATE TABLE `cloud_usage`.`usage_vm_disk` ( + `account_id` bigint(20) unsigned NOT NULL, + `zone_id` bigint(20) unsigned NOT NULL, + `vm_id` bigint(20) unsigned NOT NULL, + `volume_id` bigint(20) unsigned NOT NULL DEFAULT '0', + `io_read` bigint(20) unsigned NOT NULL DEFAULT '0', + `io_write` bigint(20) unsigned NOT NULL DEFAULT '0', + `agg_io_read` bigint(20) unsigned NOT NULL DEFAULT '0', + `agg_io_write` bigint(20) unsigned NOT NULL DEFAULT '0', + `bytes_read` bigint(20) unsigned NOT NULL DEFAULT '0', + `bytes_write` bigint(20) unsigned NOT NULL DEFAULT '0', + `agg_bytes_read` bigint(20) unsigned NOT NULL DEFAULT '0', + `agg_bytes_write` bigint(20) unsigned NOT NULL DEFAULT '0', + `event_time_millis` bigint(20) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`account_id`,`zone_id`,`vm_id`,`volume_id`,`event_time_millis`) +) ENGINE=InnoDB CHARSET=utf8; + +INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'vm.disk.stats.interval', 0, 'Interval (in seconds) to report vm disk statistics.'); + -- Re-enable foreign key checking, at the end of the upgrade path SET foreign_key_checks = 1; diff --git a/ui/dictionary.jsp b/ui/dictionary.jsp index ded9ea063d4..d7f7dd58d63 100644 --- a/ui/dictionary.jsp +++ b/ui/dictionary.jsp @@ -469,11 +469,15 @@ dictionary = { 'label.disable.vpn': '', 'label.disabling.vpn.access': '', 'label.disk.allocated': '', +'label.disk.read.bytes': '', +'label.disk.read.io': '', 'label.disk.offering': '', 'label.disk.size': '', 'label.disk.size.gb': '', 'label.disk.total': '', 'label.disk.volume': '', +'label.disk.write.bytes': '', +'label.disk.write.io': '', 'label.display.name': '', 'label.display.text': '', 'label.dns.1': '', diff --git a/ui/scripts/instances.js b/ui/scripts/instances.js index 31237a8855b..6a589baf83d 100644 --- a/ui/scripts/instances.js +++ b/ui/scripts/instances.js @@ -1648,7 +1648,11 @@ totalCPU: { label: 'label.total.cpu' }, cpuused: { label: 'label.cpu.utilized' }, networkkbsread: { label: 'label.network.read' }, - networkkbswrite: { label: 'label.network.write' } + networkkbswrite: { label: 'label.network.write' }, + diskkbsread: { label: 'label.disk.read.bytes' }, + diskkbswrite: { label: 'label.disk.write.bytes' }, + diskioread: { label: 'label.disk.read.io' }, + diskiowrite: { label: 'label.disk.write.io' } }, dataProvider: function(args) { $.ajax({ @@ -1662,7 +1666,11 @@ totalCPU: jsonObj.cpunumber + " x " + cloudStack.converters.convertHz(jsonObj.cpuspeed), cpuused: jsonObj.cpuused, networkkbsread: (jsonObj.networkkbsread == null)? "N/A": cloudStack.converters.convertBytes(jsonObj.networkkbsread * 1024), - networkkbswrite: (jsonObj.networkkbswrite == null)? "N/A": cloudStack.converters.convertBytes(jsonObj.networkkbswrite * 1024) + networkkbswrite: (jsonObj.networkkbswrite == null)? "N/A": cloudStack.converters.convertBytes(jsonObj.networkkbswrite * 1024), + diskkbsread: (jsonObj.diskkbsread == null)? "N/A": cloudStack.converters.convertBytes(jsonObj.diskkbsread * 1024), + diskkbswrite: (jsonObj.diskkbswrite == null)? "N/A": cloudStack.converters.convertBytes(jsonObj.diskkbswrite * 1024), + diskioread: (jsonObj.diskioread == null)? "N/A": cloudStack.converters.convertBytes(jsonObj.diskioread * 1024), + diskiowrite: (jsonObj.diskiowrite == null)? "N/A": cloudStack.converters.convertBytes(jsonObj.diskiowrite * 1024) } }); } diff --git a/usage/src/com/cloud/usage/UsageManagerImpl.java b/usage/src/com/cloud/usage/UsageManagerImpl.java index 0c2ad6ef339..65f354c1ccc 100644 --- a/usage/src/com/cloud/usage/UsageManagerImpl.java +++ b/usage/src/com/cloud/usage/UsageManagerImpl.java @@ -54,6 +54,7 @@ import com.cloud.usage.dao.UsageSecurityGroupDao; import com.cloud.usage.dao.UsageStorageDao; import com.cloud.usage.dao.UsageVMInstanceDao; import com.cloud.usage.dao.UsageVPNUserDao; +import com.cloud.usage.dao.UsageVmDiskDao; import com.cloud.usage.dao.UsageVolumeDao; import com.cloud.usage.parser.IPAddressUsageParser; import com.cloud.usage.parser.LoadBalancerUsageParser; @@ -64,13 +65,15 @@ import com.cloud.usage.parser.SecurityGroupUsageParser; import com.cloud.usage.parser.StorageUsageParser; import com.cloud.usage.parser.VMInstanceUsageParser; import com.cloud.usage.parser.VPNUserUsageParser; +import com.cloud.usage.parser.VmDiskUsageParser; import com.cloud.usage.parser.VolumeUsageParser; import com.cloud.user.Account; import com.cloud.user.AccountVO; import com.cloud.user.UserStatisticsVO; +import com.cloud.user.VmDiskStatisticsVO; import com.cloud.user.dao.AccountDao; import com.cloud.user.dao.UserStatisticsDao; - +import com.cloud.user.dao.VmDiskStatisticsDao; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.concurrency.NamedThreadFactory; @@ -108,6 +111,8 @@ public class UsageManagerImpl extends ManagerBase implements UsageManager, Runna @Inject private UsageVPNUserDao m_usageVPNUserDao; @Inject private UsageSecurityGroupDao m_usageSecurityGroupDao; @Inject private UsageJobDao m_usageJobDao; + @Inject private VmDiskStatisticsDao m_vmDiskStatsDao; + @Inject private UsageVmDiskDao m_usageVmDiskDao; @Inject protected AlertManager _alertMgr; @Inject protected UsageEventDao _usageEventDao; @Inject ConfigurationDao _configDao; @@ -121,6 +126,7 @@ public class UsageManagerImpl extends ManagerBase implements UsageManager, Runna TimeZone m_usageTimezone = TimeZone.getTimeZone("GMT");; private final GlobalLock m_heartbeatLock = GlobalLock.getInternLock("usage.job.heartbeat.check"); private List usageNetworks = new ArrayList(); + private List usageVmDisks = new ArrayList(); private final ScheduledExecutorService m_executor = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("Usage-Job")); private final ScheduledExecutorService m_heartbeatExecutor = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("Usage-HB")); @@ -389,6 +395,8 @@ public class UsageManagerImpl extends ManagerBase implements UsageManager, Runna List accounts = null; List userStats = null; Map networkStats = null; + List vmDiskStats = null; + Map vmDiskUsages = null; Transaction userTxn = Transaction.open(Transaction.CLOUD_DB); try { Long limit = Long.valueOf(500); @@ -479,6 +487,46 @@ public class UsageManagerImpl extends ManagerBase implements UsageManager, Runna } offset = new Long(offset.longValue() + limit.longValue()); } while ((userStats != null) && !userStats.isEmpty()); + + // reset offset + offset = Long.valueOf(0); + + // get all the vm network stats to create usage_vm_network records for the vm network usage + Long lastVmDiskStatsId = m_usageDao.getLastVmDiskStatsId(); + if (lastVmDiskStatsId == null) { + lastVmDiskStatsId = Long.valueOf(0); + } + SearchCriteria sc4 = m_vmDiskStatsDao.createSearchCriteria(); + sc4.addAnd("id", SearchCriteria.Op.LTEQ, lastVmDiskStatsId); + do { + Filter filter = new Filter(VmDiskStatisticsVO.class, "id", true, offset, limit); + + vmDiskStats = m_vmDiskStatsDao.search(sc4, filter); + + if ((vmDiskStats != null) && !vmDiskStats.isEmpty()) { + // now copy the accounts to cloud_usage db + m_usageDao.updateVmDiskStats(vmDiskStats); + } + offset = new Long(offset.longValue() + limit.longValue()); + } while ((vmDiskStats != null) && !vmDiskStats.isEmpty()); + + // reset offset + offset = Long.valueOf(0); + + sc4 = m_vmDiskStatsDao.createSearchCriteria(); + sc4.addAnd("id", SearchCriteria.Op.GT, lastVmDiskStatsId); + do { + Filter filter = new Filter(VmDiskStatisticsVO.class, "id", true, offset, limit); + + vmDiskStats = m_vmDiskStatsDao.search(sc4, filter); + + if ((vmDiskStats != null) && !vmDiskStats.isEmpty()) { + // now copy the accounts to cloud_usage db + m_usageDao.saveVmDiskStats(vmDiskStats); + } + offset = new Long(offset.longValue() + limit.longValue()); + } while ((vmDiskStats != null) && !vmDiskStats.isEmpty()); + } finally { userTxn.close(); } @@ -565,6 +613,53 @@ public class UsageManagerImpl extends ManagerBase implements UsageManager, Runna s_logger.debug("created network stats helper entries for " + numAcctsProcessed + " accts"); } + // get vm disk stats in order to compute vm disk usage + vmDiskUsages = m_usageVmDiskDao.getRecentVmDiskStats(); + + // Keep track of user stats for an account, across all of its public IPs + Map aggregatedDiskStats = new HashMap(); + startIndex = 0; + do { + vmDiskStats = m_vmDiskStatsDao.listActiveAndRecentlyDeleted(recentlyDeletedDate, startIndex, 500); + + if (vmDiskUsages != null) { + for (VmDiskStatisticsVO vmDiskStat : vmDiskStats) { + if(vmDiskStat.getVmId() != null){ + String hostKey = vmDiskStat.getDataCenterId() + "-" + vmDiskStat.getAccountId()+"-Vm-" + vmDiskStat.getVmId()+"-Disk-" + vmDiskStat.getVolumeId(); + VmDiskStatisticsVO hostAggregatedStat = aggregatedDiskStats.get(hostKey); + if (hostAggregatedStat == null) { + hostAggregatedStat = new VmDiskStatisticsVO(vmDiskStat.getAccountId(), vmDiskStat.getDataCenterId(), vmDiskStat.getVmId(),vmDiskStat.getVolumeId()); + } + + hostAggregatedStat.setAggIORead(hostAggregatedStat.getAggIORead() + vmDiskStat.getAggIORead()); + hostAggregatedStat.setAggIOWrite(hostAggregatedStat.getAggIOWrite() + vmDiskStat.getAggIOWrite()); + hostAggregatedStat.setAggBytesRead(hostAggregatedStat.getAggBytesRead() + vmDiskStat.getAggBytesRead()); + hostAggregatedStat.setAggBytesWrite(hostAggregatedStat.getAggBytesWrite() + vmDiskStat.getAggBytesWrite()); + aggregatedDiskStats.put(hostKey, hostAggregatedStat); + } + } + } + startIndex += 500; + } while ((userStats != null) && !userStats.isEmpty()); + + // loop over the user stats, create delta entries in the usage_disk helper table + numAcctsProcessed = 0; + usageVmDisks.clear(); + for (String key : aggregatedDiskStats.keySet()) { + UsageVmDiskVO currentVmDiskStats = null; + if (vmDiskStats != null) { + currentVmDiskStats = vmDiskUsages.get(key); + } + + createVmDiskHelperEntry(aggregatedDiskStats.get(key), currentVmDiskStats, endDateMillis); + numAcctsProcessed++; + } + m_usageVmDiskDao.saveUsageVmDisks(usageVmDisks); + + if (s_logger.isDebugEnabled()) { + s_logger.debug("created vm disk stats helper entries for " + numAcctsProcessed + " accts"); + } + // commit the helper records, then start a new transaction usageTxn.commit(); usageTxn.start(); @@ -701,6 +796,13 @@ public class UsageManagerImpl extends ManagerBase implements UsageManager, Runna } } + parsed = VmDiskUsageParser.parse(account, currentStartDate, currentEndDate); + if (s_logger.isDebugEnabled()) { + if (!parsed) { + s_logger.debug("vm disk usage successfully parsed? " + parsed + " (for account: " + account.getAccountName() + ", id: " + account.getId() + ")"); + } + } + parsed = VolumeUsageParser.parse(account, currentStartDate, currentEndDate); if (s_logger.isDebugEnabled()) { if (!parsed) { @@ -1006,6 +1108,59 @@ public class UsageManagerImpl extends ManagerBase implements UsageManager, Runna usageNetworks.add(usageNetworkVO); } + private void createVmDiskHelperEntry(VmDiskStatisticsVO vmDiskStat, UsageVmDiskVO usageVmDiskStat, long timestamp) { + long currentAccountedIORead = 0L; + long currentAccountedIOWrite = 0L; + long currentAccountedBytesRead = 0L; + long currentAccountedBytesWrite = 0L; + if (usageVmDiskStat != null) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("getting current accounted bytes for... accountId: " + usageVmDiskStat.getAccountId() + " in zone: " + vmDiskStat.getDataCenterId() + "; aiw: " + vmDiskStat.getAggIOWrite() + + "; air: " + usageVmDiskStat.getAggIORead() + "; abw: " + vmDiskStat.getAggBytesWrite() + "; abr: " + usageVmDiskStat.getAggBytesRead()); + } + currentAccountedIORead = usageVmDiskStat.getAggIORead(); + currentAccountedIOWrite = usageVmDiskStat.getAggIOWrite(); + currentAccountedBytesRead = usageVmDiskStat.getAggBytesRead(); + currentAccountedBytesWrite = usageVmDiskStat.getAggBytesWrite(); + } + long ioRead = vmDiskStat.getAggIORead() - currentAccountedIORead; + long ioWrite = vmDiskStat.getAggIOWrite() - currentAccountedIOWrite; + long bytesRead = vmDiskStat.getAggBytesRead() - currentAccountedBytesRead; + long bytesWrite = vmDiskStat.getAggBytesWrite() - currentAccountedBytesWrite; + + if (ioRead < 0) { + s_logger.warn("Calculated negative value for io read: " + ioRead + ", vm disk stats say: " + vmDiskStat.getAggIORead() + ", previous vm disk usage was: " + currentAccountedIORead); + ioRead = 0; + } + if (ioWrite < 0) { + s_logger.warn("Calculated negative value for io write: " + ioWrite + ", vm disk stats say: " + vmDiskStat.getAggIOWrite() + ", previous vm disk usage was: " + currentAccountedIOWrite); + ioWrite = 0; + } + if (bytesRead < 0) { + s_logger.warn("Calculated negative value for bytes read: " + bytesRead + ", vm disk stats say: " + vmDiskStat.getAggBytesRead() + ", previous vm disk usage was: " + currentAccountedBytesRead); + bytesRead = 0; + } + if (bytesWrite < 0) { + s_logger.warn("Calculated negative value for bytes write: " + bytesWrite + ", vm disk stats say: " + vmDiskStat.getAggBytesWrite() + ", previous vm disk usage was: " + currentAccountedBytesWrite); + bytesWrite = 0; + } + + long vmId = 0; + + if(vmDiskStat.getVmId() != null){ + vmId = vmDiskStat.getVmId(); + } + + UsageVmDiskVO usageVmDiskVO = new UsageVmDiskVO(vmDiskStat.getAccountId(), vmDiskStat.getDataCenterId(), vmId, vmDiskStat.getVolumeId(), ioRead, ioWrite, + vmDiskStat.getAggIORead(), vmDiskStat.getAggIOWrite(), bytesRead, bytesWrite, vmDiskStat.getAggBytesRead(), vmDiskStat.getAggBytesWrite(), timestamp); + if (s_logger.isDebugEnabled()) { + s_logger.debug("creating vmDiskHelperEntry... accountId: " + vmDiskStat.getAccountId() + " in zone: " + vmDiskStat.getDataCenterId() + "; aiw: " + vmDiskStat.getAggIOWrite() + "; air: " + vmDiskStat.getAggIORead() + + "; curAIR: " + currentAccountedIORead + "; curAIW: " + currentAccountedIOWrite + "; uir: " + ioRead + "; uiw: " + ioWrite + "; abw: " + vmDiskStat.getAggBytesWrite() + "; abr: " + vmDiskStat.getAggBytesRead() + + "; curABR: " + currentAccountedBytesRead + "; curABW: " + currentAccountedBytesWrite + "; ubr: " + bytesRead + "; ubw: " + bytesWrite); + } + usageVmDisks.add(usageVmDiskVO); + } + private void createIPHelperEvent(UsageEventVO event) { String ipAddress = event.getResourceName(); diff --git a/usage/src/com/cloud/usage/parser/VmDiskUsageParser.java b/usage/src/com/cloud/usage/parser/VmDiskUsageParser.java new file mode 100644 index 00000000000..b8a5f98c99b --- /dev/null +++ b/usage/src/com/cloud/usage/parser/VmDiskUsageParser.java @@ -0,0 +1,208 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// 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.cloud.usage.parser; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; + +import org.apache.log4j.Logger; +import org.apache.cloudstack.usage.UsageTypes; + +import com.cloud.usage.UsageVO; +import com.cloud.usage.UsageVmDiskVO; +import com.cloud.usage.dao.UsageDao; +import com.cloud.usage.dao.UsageVmDiskDao; +import com.cloud.user.AccountVO; + +import com.cloud.utils.db.SearchCriteria; +import org.springframework.stereotype.Component; + +@Component +public class VmDiskUsageParser { +public static final Logger s_logger = Logger.getLogger(VmDiskUsageParser.class.getName()); + + private static UsageDao m_usageDao; + private static UsageVmDiskDao m_usageVmDiskDao; + + @Inject private UsageDao _usageDao; + @Inject private UsageVmDiskDao _usageVmDiskDao; + + @PostConstruct + void init() { + m_usageDao = _usageDao; + m_usageVmDiskDao = _usageVmDiskDao; + } + + public static boolean parse(AccountVO account, Date startDate, Date endDate) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Parsing all Vm Disk usage events for account: " + account.getId()); + } + + if ((endDate == null) || endDate.after(new Date())) { + endDate = new Date(); + } + + // - query usage_disk table for all entries for userId with + // event_date in the given range + SearchCriteria sc = m_usageVmDiskDao.createSearchCriteria(); + sc.addAnd("accountId", SearchCriteria.Op.EQ, account.getId()); + sc.addAnd("eventTimeMillis", SearchCriteria.Op.BETWEEN, startDate.getTime(), endDate.getTime()); + List usageVmDiskVOs = m_usageVmDiskDao.search(sc, null); + + Map vmDiskUsageByZone = new HashMap(); + + // Calculate the bytes since last parsing + for (UsageVmDiskVO usageVmDisk : usageVmDiskVOs) { + long zoneId = usageVmDisk.getZoneId(); + String key = ""+zoneId; + if(usageVmDisk.getVmId() != 0){ + key += "-Vm-" + usageVmDisk.getVmId()+"-Disk-" + usageVmDisk.getVolumeId(); + } + VmDiskInfo vmDiskInfo = vmDiskUsageByZone.get(key); + + long ioRead = usageVmDisk.getIORead(); + long ioWrite = usageVmDisk.getIOWrite(); + long bytesRead = usageVmDisk.getBytesRead(); + long bytesWrite = usageVmDisk.getBytesWrite(); + if (vmDiskInfo != null) { + ioRead += vmDiskInfo.getIORead(); + ioWrite += vmDiskInfo.getIOWrite(); + bytesRead += vmDiskInfo.getBytesRead(); + bytesWrite += vmDiskInfo.getBytesWrite(); + } + + vmDiskUsageByZone.put(key, new VmDiskInfo(zoneId, usageVmDisk.getVmId(), usageVmDisk.getVolumeId(), ioRead, ioWrite, bytesRead, bytesWrite)); + } + + for (String key : vmDiskUsageByZone.keySet()) { + VmDiskInfo vmDiskInfo = vmDiskUsageByZone.get(key); + long ioRead = vmDiskInfo.getIORead(); + long ioWrite = vmDiskInfo.getIOWrite(); + long bytesRead = vmDiskInfo.getBytesRead(); + long bytesWrite = vmDiskInfo.getBytesWrite(); + + if ((ioRead > 0L) || (ioWrite > 0L) || (bytesRead > 0L) || (bytesWrite > 0L)) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Creating vm disk usage record, io read:" + ioRead + ", io write: " + ioWrite + "bytes read:" + bytesRead + ", bytes write: " + bytesWrite + "for account: " + + account.getId() + " in availability zone " + vmDiskInfo.getZoneId() + ", start: " + startDate + ", end: " + endDate); + } + + Long vmId = null; + + // Create the usage record for bytes read + String usageDesc = "disk bytes read"; + if(vmDiskInfo.getVmId() != 0){ + vmId = vmDiskInfo.getVmId(); + usageDesc += " for Vm: "+vmDiskInfo.getVmId()+" and Volume: "+ vmDiskInfo.getVolumeId(); + } + UsageVO usageRecord = new UsageVO(vmDiskInfo.getZoneId(), account.getId(), account.getDomainId(), usageDesc, ioRead + " io read", + UsageTypes.VM_DISK_IO_READ, new Double(ioRead), vmId, "VirtualMachine", vmDiskInfo.getVolumeId(), startDate, endDate); + m_usageDao.persist(usageRecord); + + // Create the usage record for bytes write + usageDesc = "disk bytes write"; + if(vmDiskInfo.getVmId() != 0){ + usageDesc += " for Vm: "+vmDiskInfo.getVmId()+" and Volume: "+ vmDiskInfo.getVolumeId(); + } + usageRecord = new UsageVO(vmDiskInfo.getZoneId(), account.getId(), account.getDomainId(), usageDesc, ioWrite + " io write", + UsageTypes.VM_DISK_BYTES_WRITE, new Double(ioWrite), vmId, "VirtualMachine", vmDiskInfo.getVolumeId(), startDate, endDate); + m_usageDao.persist(usageRecord); + + // Create the usage record for bytes read + usageDesc = "disk bytes read"; + if(vmDiskInfo.getVmId() != 0){ + vmId = vmDiskInfo.getVmId(); + usageDesc += " for Vm: "+vmDiskInfo.getVmId()+" and Volume: "+ vmDiskInfo.getVolumeId(); + } + usageRecord = new UsageVO(vmDiskInfo.getZoneId(), account.getId(), account.getDomainId(), usageDesc, bytesRead + " bytes read", + UsageTypes.VM_DISK_BYTES_READ, new Double(bytesRead), vmId, "VirtualMachine", vmDiskInfo.getVolumeId(), startDate, endDate); + m_usageDao.persist(usageRecord); + + // Create the usage record for bytes write + usageDesc = "disk bytes write"; + if(vmDiskInfo.getVmId() != 0){ + usageDesc += " for Vm: "+vmDiskInfo.getVmId()+" and Volume: "+ vmDiskInfo.getVolumeId(); + } + usageRecord = new UsageVO(vmDiskInfo.getZoneId(), account.getId(), account.getDomainId(), usageDesc, bytesWrite + " bytes write", + UsageTypes.VM_DISK_BYTES_WRITE, new Double(bytesWrite), vmId, "VirtualMachine", vmDiskInfo.getVolumeId(), startDate, endDate); + m_usageDao.persist(usageRecord); + + } else { + // Don't charge anything if there were zero bytes processed + if (s_logger.isDebugEnabled()) { + s_logger.debug("No vm disk usage record (0 bytes used) generated for account: " + account.getId()); + } + } + } + + return true; + } + + private static class VmDiskInfo { + private long zoneId; + private long vmId; + private Long volumeId; + private long ioRead; + private long ioWrite; + private long bytesRead; + private long bytesWrite; + + public VmDiskInfo(long zoneId, long vmId, Long volumeId, long ioRead, long ioWrite, long bytesRead, long bytesWrite) { + this.zoneId = zoneId; + this.vmId = vmId; + this.volumeId = volumeId; + this.ioRead = ioRead; + this.ioWrite = ioWrite; + this.bytesRead = bytesRead; + this.bytesWrite = bytesWrite; + } + + public long getZoneId() { + return zoneId; + } + + public long getVmId() { + return vmId; + } + + public Long getVolumeId() { + return volumeId; + } + + public long getIORead() { + return ioRead; + } + + public long getIOWrite() { + return ioWrite; + } + + public long getBytesRead() { + return bytesRead; + } + + public long getBytesWrite() { + return bytesWrite; + } + + } +} diff --git a/usage/test/com/cloud/usage/UsageManagerTest.java b/usage/test/com/cloud/usage/UsageManagerTest.java index eac3fcb69b6..520ab265504 100644 --- a/usage/test/com/cloud/usage/UsageManagerTest.java +++ b/usage/test/com/cloud/usage/UsageManagerTest.java @@ -46,6 +46,8 @@ public class UsageManagerTest extends TestCase { @Inject NetworkUsageParser netParser = null; @Inject + VmDiskUsageParser vmdiskParser = null; + @Inject PortForwardingUsageParser pfParser = null; @Inject SecurityGroupUsageParser sgParser = null; @@ -87,6 +89,7 @@ public class UsageManagerTest extends TestCase { lbParser.parse(account, startDate, endDate); noParser.parse(account, startDate, endDate); netParser.parse(account, startDate, endDate); + vmdiskParser.parse(account, startDate, endDate); pfParser.parse(account, startDate, endDate); sgParser.parse(account, startDate, endDate); stParser.parse(account, startDate, endDate); diff --git a/usage/test/com/cloud/usage/UsageManagerTestConfiguration.java b/usage/test/com/cloud/usage/UsageManagerTestConfiguration.java index 1d3ed7b245d..1a342b59ff2 100644 --- a/usage/test/com/cloud/usage/UsageManagerTestConfiguration.java +++ b/usage/test/com/cloud/usage/UsageManagerTestConfiguration.java @@ -53,6 +53,7 @@ import java.io.IOException; UsagePortForwardingRuleDaoImpl.class, UsageNetworkOfferingDaoImpl.class, UsageVPNUserDaoImpl.class, + UsageVmDiskDaoImpl.class, UsageSecurityGroupDaoImpl.class, ConfigurationDaoImpl.class, UsageManagerImpl.class, @@ -64,6 +65,7 @@ import java.io.IOException; PortForwardingUsageParser.class, SecurityGroupUsageParser.class, StorageUsageParser.class, + VmDiskUsageParser.class, VolumeUsageParser.class, VPNUserUsageParser.class, UserStatisticsDaoImpl.class},