Skip to content

Commit ec6e3ef

Browse files
committed
Backup: publish the backup usage metric only when it changes
The backup sync published a BACKUP.USAGE.METRIC event for every VM on every run (every 5 minutes by default) even when nothing changed, which made up most of the usage_event rows. Remember the last value published per VM and offering and skip unchanged ones. The usage server ignores unchanged sizes, so republishing after a restart or on another management server is harmless.
1 parent 28b6c07 commit ec6e3ef

2 files changed

Lines changed: 67 additions & 1 deletion

File tree

‎server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.util.TimeZone;
3333
import java.util.Timer;
3434
import java.util.TimerTask;
35+
import java.util.concurrent.ConcurrentHashMap;
3536
import java.util.stream.Collectors;
3637
import java.util.stream.Stream;
3738

@@ -249,6 +250,12 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
249250
private static Map<String, BackupProvider> backupProvidersMap = new HashMap<>();
250251
private List<BackupProvider> backupProviders;
251252

253+
// Per zone, the backup usage metric last published for each (VM, backup offering) pair, as (size,
254+
// protected size). The backup sync runs every few minutes but the usage server only needs the
255+
// metric when it changes. After a restart or on another management server each value is
256+
// published once more, which the usage server ignores as the size is unchanged.
257+
private final Map<Long, Map<Pair<Long, Long>, Pair<Long, Long>>> lastPublishedBackupUsageMetrics = new ConcurrentHashMap<>();
258+
252259
public AsyncJobDispatcher getAsyncJobDispatcher() {
253260
return asyncJobDispatcher;
254261
}
@@ -2014,7 +2021,7 @@ private void syncOutOfBandBackups(final BackupProvider backupProvider, DataCente
20142021
}
20152022
}
20162023

2017-
private void updateBackupUsageRecords(final BackupProvider backupProvider, DataCenter dataCenter) {
2024+
protected void updateBackupUsageRecords(final BackupProvider backupProvider, DataCenter dataCenter) {
20182025
List<Long> vmIdsWithBackups = backupDao.listVmIdsWithBackupsInZone(dataCenter.getId());
20192026
List<VMInstanceVO> vmsWithBackups;
20202027
if (vmIdsWithBackups.size() == 0) {
@@ -2025,6 +2032,8 @@ private void updateBackupUsageRecords(final BackupProvider backupProvider, DataC
20252032
List<VMInstanceVO> vmsWithBackupOffering = vmInstanceDao.listByZoneAndBackupOffering(dataCenter.getId(), null); //should return including removed
20262033
Set<VMInstanceVO> vms = Stream.concat(vmsWithBackups.stream(), vmsWithBackupOffering.stream()) .collect(Collectors.toSet());
20272034

2035+
Map<Pair<Long, Long>, Pair<Long, Long>> lastPublished = lastPublishedBackupUsageMetrics.getOrDefault(dataCenter.getId(), Collections.emptyMap());
2036+
Map<Pair<Long, Long>, Pair<Long, Long>> published = new HashMap<>();
20282037
for (final VirtualMachine vm : vms) {
20292038

20302039
Map<Long, Pair<Long, Long>> backupOfferingToSizeMap = new HashMap<>();
@@ -2055,12 +2064,20 @@ private void updateBackupUsageRecords(final BackupProvider backupProvider, DataC
20552064
Pair<Long, Long> sizes = entry.getValue();
20562065
Long backupSize = sizes.first();
20572066
Long protectedSize = sizes.second();
2067+
Pair<Long, Long> key = new Pair<>(vm.getId(), offeringId);
2068+
Pair<Long, Long> metric = new Pair<>(backupSize, protectedSize);
2069+
published.put(key, metric);
2070+
if (metric.equals(lastPublished.get(key))) {
2071+
continue;
2072+
}
20582073
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_USAGE_METRIC, vm.getAccountId(),
20592074
vm.getDataCenterId(), vm.getId(), "Backup-" + vm.getHostName() + "-" + vm.getUuid(),
20602075
offeringId, null, backupSize, protectedSize,
20612076
Backup.class.getSimpleName(), vm.getUuid());
20622077
}
20632078
}
2079+
// Replacing the map also drops pairs that are no longer reported, e.g. expunged VMs.
2080+
lastPublishedBackupUsageMetrics.put(dataCenter.getId(), published);
20642081
}
20652082

20662083
private Backup checkAndUpdateIfBackupEntryExistsForRestorePoint(Backup.RestorePoint restorePoint, List<Backup> backupsInDb, VirtualMachine vm) {

‎server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -898,6 +898,55 @@ public void testBackupSyncTask() {
898898
}
899899
}
900900

901+
private void verifyBackupUsageMetricPublished(MockedStatic<UsageEventUtils> usageEventUtilsMocked, int times, Long size, Long protectedSize) {
902+
usageEventUtilsMocked.verify(() -> UsageEventUtils.publishUsageEvent(Mockito.eq(EventTypes.EVENT_VM_BACKUP_USAGE_METRIC), Mockito.anyLong(),
903+
Mockito.anyLong(), Mockito.anyLong(), Mockito.any(), Mockito.anyLong(), Mockito.any(), Mockito.eq(size), Mockito.eq(protectedSize),
904+
Mockito.any(), Mockito.any()), times(times));
905+
}
906+
907+
@Test
908+
public void updateBackupUsageRecordsPublishesMetricOnlyWhenItChanges() {
909+
Long dataCenterId = 1L;
910+
Long vmId = 2L;
911+
Long offeringId = 3L;
912+
913+
DataCenterVO dataCenter = mock(DataCenterVO.class);
914+
when(dataCenter.getId()).thenReturn(dataCenterId);
915+
VMInstanceVO vm = mock(VMInstanceVO.class);
916+
when(vm.getId()).thenReturn(vmId);
917+
when(backupDao.listVmIdsWithBackupsInZone(dataCenterId)).thenReturn(List.of(vmId));
918+
when(vmInstanceDao.listByIdsIncludingRemoved(List.of(vmId))).thenReturn(List.of(vm));
919+
when(vmInstanceDao.listByZoneAndBackupOffering(dataCenterId, null)).thenReturn(List.of(vm));
920+
921+
BackupVO backup = new BackupVO();
922+
backup.setBackupOfferingId(offeringId);
923+
backup.setSize(100L);
924+
backup.setProtectedSize(1000L);
925+
when(backupDao.listByVmId(null, vmId)).thenReturn(List.of(backup));
926+
927+
BackupManagerImpl.BackupSyncTask backupSyncTask = backupManager.new BackupSyncTask(backupManager);
928+
BackupProvider backupProvider = mock(BackupProvider.class);
929+
try (MockedStatic<UsageEventUtils> usageEventUtilsMocked = Mockito.mockStatic(UsageEventUtils.class)) {
930+
backupSyncTask.updateBackupUsageRecords(backupProvider, dataCenter);
931+
backupSyncTask.updateBackupUsageRecords(backupProvider, dataCenter);
932+
verifyBackupUsageMetricPublished(usageEventUtilsMocked, 1, 100L, 1000L);
933+
934+
backup.setSize(200L);
935+
backupSyncTask.updateBackupUsageRecords(backupProvider, dataCenter);
936+
backupSyncTask.updateBackupUsageRecords(backupProvider, dataCenter);
937+
verifyBackupUsageMetricPublished(usageEventUtilsMocked, 1, 200L, 1000L);
938+
939+
// A VM that is no longer reported is forgotten, so it is published again if it comes back.
940+
when(backupDao.listVmIdsWithBackupsInZone(dataCenterId)).thenReturn(List.of());
941+
when(vmInstanceDao.listByZoneAndBackupOffering(dataCenterId, null)).thenReturn(List.of());
942+
backupSyncTask.updateBackupUsageRecords(backupProvider, dataCenter);
943+
when(backupDao.listVmIdsWithBackupsInZone(dataCenterId)).thenReturn(List.of(vmId));
944+
when(vmInstanceDao.listByZoneAndBackupOffering(dataCenterId, null)).thenReturn(List.of(vm));
945+
backupSyncTask.updateBackupUsageRecords(backupProvider, dataCenter);
946+
verifyBackupUsageMetricPublished(usageEventUtilsMocked, 2, 200L, 1000L);
947+
}
948+
}
949+
901950
@Test
902951
public void checkCallerAccessToBackupScheduleVmTestExecuteAccessCheckMethods() {
903952
long vmId = 1L;

0 commit comments

Comments
 (0)