From 88ff08e3b0b110b30dd3ae8fbb85daa80be38d55 Mon Sep 17 00:00:00 2001 From: "Chen, Taiyue" Date: Mon, 25 May 2026 10:21:43 +0800 Subject: [PATCH 1/2] [vm]: optimize the statistics method of OS running time on virtual machines DBImpact Resolves: ZSV-12297 Change-Id: I6a746a746b6b6a6172646470627a64636173656c --- .../zstack/compute/vm/VmBootTimeUtils.java | 83 +++++++++++++++++++ .../org/zstack/compute/vm/VmInstanceBase.java | 35 ++++---- conf/db/zsv/V5.1.0__schema.sql | 34 ++++++++ .../org/zstack/header/vm/VmInstanceAO.java | 12 +++ .../org/zstack/header/vm/VmInstanceAO_.java | 1 + .../java/org/zstack/kvm/KVMHostFactory.java | 3 + 6 files changed, 150 insertions(+), 18 deletions(-) create mode 100644 compute/src/main/java/org/zstack/compute/vm/VmBootTimeUtils.java diff --git a/compute/src/main/java/org/zstack/compute/vm/VmBootTimeUtils.java b/compute/src/main/java/org/zstack/compute/vm/VmBootTimeUtils.java new file mode 100644 index 00000000000..7d4c430ce04 --- /dev/null +++ b/compute/src/main/java/org/zstack/compute/vm/VmBootTimeUtils.java @@ -0,0 +1,83 @@ +package org.zstack.compute.vm; + +import org.apache.commons.lang.StringUtils; +import org.zstack.core.db.Q; +import org.zstack.core.db.SQL; +import org.zstack.header.vm.VmInstanceState; +import org.zstack.header.vm.VmInstanceVO; +import org.zstack.header.vm.VmInstanceVO_; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +public class VmBootTimeUtils { + private static final String STOPPED_UPTIME = "0"; + private static final DateTimeFormatter BOOT_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + public String getUptime(String vmUuid, VmInstanceState state) { + if (StringUtils.isBlank(vmUuid) || state != VmInstanceState.Running) { + return STOPPED_UPTIME; + } + + String bootTime = getBootTime(vmUuid); + if (StringUtils.isNotEmpty(bootTime)) { + return bootTime; + } + + return resetBootTime(vmUuid); + } + + public String getBootTime(String vmUuid) { + if (StringUtils.isBlank(vmUuid)) { + return null; + } + + Timestamp bootTime = Q.New(VmInstanceVO.class) + .select(VmInstanceVO_.bootTime) + .eq(VmInstanceVO_.uuid, vmUuid) + .findValue(); + return formatBootTime(bootTime); + } + + public String resetBootTime(String vmUuid) { + if (!vmExists(vmUuid)) { + return STOPPED_UPTIME; + } + + Timestamp bootTime = Timestamp.valueOf(LocalDateTime.now()); + setBootTime(vmUuid, bootTime); + return formatBootTime(bootTime); + } + + public void clearBootTime(String vmUuid) { + if (StringUtils.isBlank(vmUuid)) { + return; + } + + SQL.New(VmInstanceVO.class) + .eq(VmInstanceVO_.uuid, vmUuid) + .set(VmInstanceVO_.bootTime, (Timestamp) null) + .update(); + } + + private static boolean vmExists(String vmUuid) { + return StringUtils.isNotBlank(vmUuid) + && Q.New(VmInstanceVO.class).eq(VmInstanceVO_.uuid, vmUuid).isExists(); + } + + private static void setBootTime(String vmUuid, Timestamp bootTime) { + SQL.New(VmInstanceVO.class) + .eq(VmInstanceVO_.uuid, vmUuid) + .set(VmInstanceVO_.bootTime, bootTime) + .update(); + } + + private static String formatBootTime(Timestamp bootTime) { + if (bootTime == null) { + return null; + } + + return bootTime.toLocalDateTime().format(BOOT_TIME_FORMATTER); + } +} \ No newline at end of file diff --git a/compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java b/compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java index c4efaee8303..ba9a5eca945 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java @@ -151,6 +151,7 @@ public class VmInstanceBase extends AbstractVmInstance { protected String syncThreadName; private final static StaticIpOperator ipOperator = new StaticIpOperator(); private final static VmConfigSyncHelper vmConfigSyncHelper = new VmConfigSyncHelper(); + private final static VmBootTimeUtils vmBootTimeUtils = new VmBootTimeUtils(); private void detachTpmKeyProviderBestEffort(String tpmUuid) { if (tpmUuid == null) { @@ -396,6 +397,8 @@ protected void scripts() { if (bs != state) { logger.debug(String.format("vm[uuid:%s] changed state from %s to %s in db", self.getUuid(), bs, state)); + updateBootTimeAfterStateChanged(bs, state); + VmCanonicalEvents.VmStateChangedData data = new VmCanonicalEvents.VmStateChangedData(); data.setVmUuid(self.getUuid()); data.setOldState(bs.toString()); @@ -419,6 +422,18 @@ public void run(VmStateChangedExtensionPoint ext) { return self; } + private void updateBootTimeAfterStateChanged(VmInstanceState oldState, VmInstanceState newState) { + if (newState == VmInstanceState.Stopped || newState == VmInstanceState.Destroyed) { + vmBootTimeUtils.clearBootTime(self.getUuid()); + return; + } + + if (newState == VmInstanceState.Running + && (oldState == VmInstanceState.Starting || oldState == VmInstanceState.Rebooting || oldState == VmInstanceState.Stopped)) { + vmBootTimeUtils.resetBootTime(self.getUuid()); + } + } + @Override @MessageSafe public void handleMessage(final Message msg) { @@ -3724,24 +3739,8 @@ public void done(ErrorCodeList errorCodeList) { private void handle(APIGetVmUptimeMsg msg) { APIGetVmUptimeReply reply = new APIGetVmUptimeReply(); - GetVmUptimeMsg gmsg = new GetVmUptimeMsg(); - gmsg.setVmInstanceUuid(self.getUuid()); - gmsg.setHostUuid(self.getHostUuid()); - bus.makeTargetServiceIdByResourceUuid(gmsg, HostConstant.SERVICE_ID, self.getHostUuid()); - - bus.send(gmsg, new CloudBusCallBack(msg) { - @Override - public void run(MessageReply r) { - if (!r.isSuccess()) { - reply.setSuccess(false); - reply.setError(r.getError()); - } else { - GetVmUptimeReply re = (GetVmUptimeReply) r; - reply.setUptime(re.getUptime()); - } - bus.reply(msg, reply); - } - }); + reply.setUptime(vmBootTimeUtils.getUptime(self.getUuid(), self.getState())); + bus.reply(msg, reply); } diff --git a/conf/db/zsv/V5.1.0__schema.sql b/conf/db/zsv/V5.1.0__schema.sql index f2302d4e9d4..cf7ca2b8934 100644 --- a/conf/db/zsv/V5.1.0__schema.sql +++ b/conf/db/zsv/V5.1.0__schema.sql @@ -36,3 +36,37 @@ SET src.`type` = IF(ldap.serverType IN ('OpenLdap', 'WindowsAD'), ldap.serverTyp WHERE src.`type` = 'ldap'; CALL INSERT_COLUMN('ThirdPartyAccountSourceVO', 'updateAccountStrategies', 'varchar(255)', 0, '', 'createAccountStrategy'); + +-- Feature: VM boot time | ZSV-12297 + +CALL INSERT_COLUMN('VmInstanceEO', 'bootTime', 'timestamp', 1, NULL, 'state'); + +CREATE OR REPLACE VIEW `zstack`.`VmInstanceVO` AS +SELECT uuid, + name, + description, + zoneUuid, + clusterUuid, + imageUuid, + hostUuid, + internalId, + lastHostUuid, + instanceOfferingUuid, + rootVolumeUuid, + defaultL3NetworkUuid, + type, + hypervisorType, + cpuNum, + cpuSpeed, + memorySize, + reservedMemorySize, + platform, + guestOsType, + allocatorStrategy, + createDate, + lastOpDate, + state, + architecture, + bootTime +FROM `zstack`.`VmInstanceEO` +WHERE deleted IS NULL; \ No newline at end of file diff --git a/header/src/main/java/org/zstack/header/vm/VmInstanceAO.java b/header/src/main/java/org/zstack/header/vm/VmInstanceAO.java index 001dbe2298b..780d86fc48c 100755 --- a/header/src/main/java/org/zstack/header/vm/VmInstanceAO.java +++ b/header/src/main/java/org/zstack/header/vm/VmInstanceAO.java @@ -99,6 +99,9 @@ public class VmInstanceAO extends ResourceVO { @Enumerated(EnumType.STRING) private VmInstanceState state; + @Column + private Timestamp bootTime; + public VmInstanceAO() { } @@ -125,6 +128,7 @@ public VmInstanceAO(VmInstanceAO other) { this.createDate = other.createDate; this.lastOpDate = other.lastOpDate; this.state = other.state; + this.bootTime = other.bootTime; this.platform = other.platform; this.guestOsType = other.guestOsType; this.architecture = other.architecture; @@ -279,6 +283,14 @@ public void setState(VmInstanceState state) { this.state = state; } + public Timestamp getBootTime() { + return bootTime; + } + + public void setBootTime(Timestamp bootTime) { + this.bootTime = bootTime; + } + public String getRootVolumeUuid() { return rootVolumeUuid; } diff --git a/header/src/main/java/org/zstack/header/vm/VmInstanceAO_.java b/header/src/main/java/org/zstack/header/vm/VmInstanceAO_.java index 90dee2c2ef6..49d3a7079fc 100755 --- a/header/src/main/java/org/zstack/header/vm/VmInstanceAO_.java +++ b/header/src/main/java/org/zstack/header/vm/VmInstanceAO_.java @@ -31,5 +31,6 @@ public class VmInstanceAO_ extends ResourceVO_ { public static volatile SingularAttribute cpuSpeed; public static volatile SingularAttribute createDate; public static volatile SingularAttribute lastOpDate; + public static volatile SingularAttribute bootTime; public static volatile SingularAttribute state; } diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostFactory.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostFactory.java index 7d0b2a7d342..4be7abd072e 100644 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostFactory.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostFactory.java @@ -7,6 +7,7 @@ import org.zstack.compute.vm.CrashStrategy; import org.zstack.compute.vm.VmGlobalConfig; import org.zstack.compute.vm.VmNicManager; +import org.zstack.compute.vm.VmBootTimeUtils; import org.zstack.core.CoreGlobalProperty; import org.zstack.core.Platform; import org.zstack.core.ansible.AnsibleFacade; @@ -96,6 +97,7 @@ public class KVMHostFactory extends AbstractService implements HypervisorFactory, Component, ManagementNodeReadyExtensionPoint, MaxDataVolumeNumberExtensionPoint, HypervisorMessageFactory, ProxyHardwareFactory { private static final CLogger logger = Utils.getLogger(KVMHostFactory.class); + private static final VmBootTimeUtils vmBootTimeUtils = new VmBootTimeUtils(); public static final HypervisorType hypervisorType = new HypervisorType(KVMConstant.KVM_HYPERVISOR_TYPE); public static final VolumeFormat QCOW2_FORMAT = new VolumeFormat(VolumeConstant.VOLUME_FORMAT_QCOW2, hypervisorType); @@ -539,6 +541,7 @@ public String handleSyncHttpCall(TransmitVmOperationToMnCmd cmd) { }); restf.registerSyncHttpCallHandler(KVMConstant.KVM_REPORT_VM_REBOOT_EVENT, ReportVmRebootEventCmd.class, cmd -> { + vmBootTimeUtils.resetBootTime(cmd.vmUuid); evf.fire(VmCanonicalEvents.VM_LIBVIRT_REPORT_REBOOT, cmd.vmUuid); return null; From 53ad7b65221a5a355aacdbc186993708770c26b9 Mon Sep 17 00:00:00 2001 From: "Chen, Taiyue" Date: Tue, 26 May 2026 11:32:50 +0800 Subject: [PATCH 2/2] [compute]: fix all issues mentioned by ai Resolves: ZSV-12297 Change-Id: I617664617a6f6c66736f6b717575636f676f7961 --- .../zstack/compute/vm/VmBootTimeUtils.java | 53 +++++++++------- .../org/zstack/compute/vm/VmInstanceBase.java | 61 +++++++++++++++---- conf/db/zsv/V5.1.0__schema.sql | 2 +- 3 files changed, 81 insertions(+), 35 deletions(-) diff --git a/compute/src/main/java/org/zstack/compute/vm/VmBootTimeUtils.java b/compute/src/main/java/org/zstack/compute/vm/VmBootTimeUtils.java index 7d4c430ce04..06ae1417b98 100644 --- a/compute/src/main/java/org/zstack/compute/vm/VmBootTimeUtils.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmBootTimeUtils.java @@ -10,22 +10,15 @@ import java.sql.Timestamp; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; public class VmBootTimeUtils { - private static final String STOPPED_UPTIME = "0"; + public static final String STOPPED_UPTIME = "0"; + public static final String UNKNOWN_UPTIME = ""; private static final DateTimeFormatter BOOT_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - public String getUptime(String vmUuid, VmInstanceState state) { - if (StringUtils.isBlank(vmUuid) || state != VmInstanceState.Running) { - return STOPPED_UPTIME; - } - - String bootTime = getBootTime(vmUuid); - if (StringUtils.isNotEmpty(bootTime)) { - return bootTime; - } - - return resetBootTime(vmUuid); + public static boolean isBootTimeValidState(VmInstanceState state) { + return state != null && state != VmInstanceState.Stopped && state != VmInstanceState.Destroyed; } public String getBootTime(String vmUuid) { @@ -40,14 +33,15 @@ public String getBootTime(String vmUuid) { return formatBootTime(bootTime); } - public String resetBootTime(String vmUuid) { - if (!vmExists(vmUuid)) { - return STOPPED_UPTIME; + public void resetBootTime(String vmUuid) { + if (StringUtils.isBlank(vmUuid)) { + return; } - Timestamp bootTime = Timestamp.valueOf(LocalDateTime.now()); - setBootTime(vmUuid, bootTime); - return formatBootTime(bootTime); + SQL.New(VmInstanceVO.class) + .eq(VmInstanceVO_.uuid, vmUuid) + .set(VmInstanceVO_.bootTime, Timestamp.valueOf(LocalDateTime.now())) + .update(); } public void clearBootTime(String vmUuid) { @@ -61,14 +55,15 @@ public void clearBootTime(String vmUuid) { .update(); } - private static boolean vmExists(String vmUuid) { - return StringUtils.isNotBlank(vmUuid) - && Q.New(VmInstanceVO.class).eq(VmInstanceVO_.uuid, vmUuid).isExists(); - } + public void backfillBootTimeIfMissing(String vmUuid, String hostUptime) { + Timestamp bootTime = parseBootTime(hostUptime); + if (StringUtils.isBlank(vmUuid) || bootTime == null) { + return; + } - private static void setBootTime(String vmUuid, Timestamp bootTime) { SQL.New(VmInstanceVO.class) .eq(VmInstanceVO_.uuid, vmUuid) + .isNull(VmInstanceVO_.bootTime) .set(VmInstanceVO_.bootTime, bootTime) .update(); } @@ -80,4 +75,16 @@ private static String formatBootTime(Timestamp bootTime) { return bootTime.toLocalDateTime().format(BOOT_TIME_FORMATTER); } + + private static Timestamp parseBootTime(String bootTime) { + if (StringUtils.isBlank(bootTime)) { + return null; + } + + try { + return Timestamp.valueOf(LocalDateTime.parse(bootTime, BOOT_TIME_FORMATTER)); + } catch (DateTimeParseException ignored) { + return null; + } + } } \ No newline at end of file diff --git a/compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java b/compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java index ba9a5eca945..09d66646ddd 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java @@ -382,6 +382,7 @@ protected void scripts() { } self.setState(state); + self.setBootTime(getBootTimeAfterStateChanged(bs, state, getCurrentBootTimeForStateChange())); self = merge(self); } }; @@ -397,8 +398,6 @@ protected void scripts() { if (bs != state) { logger.debug(String.format("vm[uuid:%s] changed state from %s to %s in db", self.getUuid(), bs, state)); - updateBootTimeAfterStateChanged(bs, state); - VmCanonicalEvents.VmStateChangedData data = new VmCanonicalEvents.VmStateChangedData(); data.setVmUuid(self.getUuid()); data.setOldState(bs.toString()); @@ -422,16 +421,33 @@ public void run(VmStateChangedExtensionPoint ext) { return self; } - private void updateBootTimeAfterStateChanged(VmInstanceState oldState, VmInstanceState newState) { - if (newState == VmInstanceState.Stopped || newState == VmInstanceState.Destroyed) { - vmBootTimeUtils.clearBootTime(self.getUuid()); - return; + private Timestamp getCurrentBootTimeForStateChange() { + if (self.getBootTime() != null) { + return self.getBootTime(); + } + + return Q.New(VmInstanceVO.class) + .select(VmInstanceVO_.bootTime) + .eq(VmInstanceVO_.uuid, self.getUuid()) + .findValue(); + } + + private Timestamp getBootTimeAfterStateChanged(VmInstanceState oldState, VmInstanceState newState, Timestamp currentBootTime) { + if (!VmBootTimeUtils.isBootTimeValidState(newState)) { + return null; + } + + if (newState != VmInstanceState.Running) { + return currentBootTime; } - if (newState == VmInstanceState.Running - && (oldState == VmInstanceState.Starting || oldState == VmInstanceState.Rebooting || oldState == VmInstanceState.Stopped)) { - vmBootTimeUtils.resetBootTime(self.getUuid()); + if (oldState == VmInstanceState.Starting + || oldState == VmInstanceState.Rebooting + || (oldState == VmInstanceState.Resuming && currentBootTime == null)) { + return Timestamp.valueOf(LocalDateTime.now()); } + + return currentBootTime; } @Override @@ -3739,8 +3755,31 @@ public void done(ErrorCodeList errorCodeList) { private void handle(APIGetVmUptimeMsg msg) { APIGetVmUptimeReply reply = new APIGetVmUptimeReply(); - reply.setUptime(vmBootTimeUtils.getUptime(self.getUuid(), self.getState())); - bus.reply(msg, reply); + String uptime = StringUtils.defaultString(vmBootTimeUtils.getBootTime(self.getUuid()), VmBootTimeUtils.UNKNOWN_UPTIME); + if (StringUtils.isNotEmpty(uptime)) { + reply.setUptime(uptime); + bus.reply(msg, reply); + return; + } + + GetVmUptimeMsg gmsg = new GetVmUptimeMsg(); + gmsg.setVmInstanceUuid(self.getUuid()); + gmsg.setHostUuid(self.getHostUuid()); + bus.makeTargetServiceIdByResourceUuid(gmsg, HostConstant.SERVICE_ID, self.getHostUuid()); + + bus.send(gmsg, new CloudBusCallBack(msg) { + @Override + public void run(MessageReply r) { + if (r.isSuccess()) { + GetVmUptimeReply re = (GetVmUptimeReply) r; + vmBootTimeUtils.backfillBootTimeIfMissing(self.getUuid(), re.getUptime()); + reply.setUptime(StringUtils.defaultString(vmBootTimeUtils.getBootTime(self.getUuid()), VmBootTimeUtils.UNKNOWN_UPTIME)); + } else { + reply.setUptime(VmBootTimeUtils.UNKNOWN_UPTIME); + } + bus.reply(msg, reply); + } + }); } diff --git a/conf/db/zsv/V5.1.0__schema.sql b/conf/db/zsv/V5.1.0__schema.sql index cf7ca2b8934..9afa129e064 100644 --- a/conf/db/zsv/V5.1.0__schema.sql +++ b/conf/db/zsv/V5.1.0__schema.sql @@ -39,7 +39,7 @@ CALL INSERT_COLUMN('ThirdPartyAccountSourceVO', 'updateAccountStrategies', 'varc -- Feature: VM boot time | ZSV-12297 -CALL INSERT_COLUMN('VmInstanceEO', 'bootTime', 'timestamp', 1, NULL, 'state'); +CALL INSERT_COLUMN('VmInstanceEO', 'bootTime', 'timestamp NULL', 1, NULL, 'state'); CREATE OR REPLACE VIEW `zstack`.`VmInstanceVO` AS SELECT uuid,