Skip to content

Commit 141899f

Browse files
committed
swap: Add UI and cli support
1 parent c9e3701 commit 141899f

2 files changed

Lines changed: 115 additions & 3 deletions

File tree

vmm/src/console.html

Lines changed: 99 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,19 @@ <h2>Deploy a new instance</h2>
661661
</div>
662662
</div>
663663

664+
<div class="form-group">
665+
<label for="swapSize">Swap (optional)</label>
666+
<div style="display: flex; align-items: center; gap: 8px;">
667+
<input id="swapSize" v-model.number="vmForm.swapValue" type="number" min="0"
668+
step="0.1" placeholder="Swap size" style="flex: 1;">
669+
<select v-model="vmForm.swapUnit" style="width: 80px;">
670+
<option value="MB">MB</option>
671+
<option value="GB">GB</option>
672+
</select>
673+
</div>
674+
<small style="color: #666;">Leave as 0 to disable swap.</small>
675+
</div>
676+
664677
<div class="form-group">
665678
<label for="diskSize">Disk Size (GB)</label>
666679
<input id="diskSize" v-model.number="vmForm.disk_size" type="number"
@@ -941,6 +954,10 @@ <h4 style="margin: 0 0 12px 0;">VM Configuration</h4>
941954
<span class="detail-value">{{ formatMemory(vm.configuration?.memory)
942955
}}</span>
943956
</div>
957+
<div class="detail-item" v-if="vm.configuration?.swap_size">
958+
<span class="detail-label">Swap:</span>
959+
<span class="detail-value">{{ formatMemory(bytesToMB(vm.configuration.swap_size)) }}</span>
960+
</div>
944961
<div class="detail-item">
945962
<span class="detail-label">Disk Size:</span>
946963
<span class="detail-value">{{ vm.configuration?.disk_size }} GB</span>
@@ -1063,6 +1080,21 @@ <h3>Update VM Config</h3>
10631080
</div>
10641081
</div>
10651082

1083+
<div class="form-group">
1084+
<label for="upgradeSwap">Swap (optional)</label>
1085+
<div style="display: flex; align-items: center; gap: 8px;">
1086+
<input id="upgradeSwap" v-model.number="upgradeDialog.swapValue" type="number" min="0"
1087+
step="0.1" placeholder="Swap size" style="flex: 1;"
1088+
:disabled="!upgradeDialog.updateCompose">
1089+
<select v-model="upgradeDialog.swapUnit" style="width: 80px;"
1090+
:disabled="!upgradeDialog.updateCompose">
1091+
<option value="MB">MB</option>
1092+
<option value="GB">GB</option>
1093+
</select>
1094+
</div>
1095+
<small style="color: #666;">Enable "Update compose" to change swap size.</small>
1096+
</div>
1097+
10661098
<div class="form-group">
10671099
<label for="diskSize">Disk Size (GB)</label>
10681100
<input id="diskSize" v-model.number="upgradeDialog.disk_size" type="number"
@@ -1454,6 +1486,9 @@ <h3>Derive VM</h3>
14541486
memory: 2048, // This will be computed from memoryValue and memoryUnit
14551487
memoryValue: 2,
14561488
memoryUnit: 'GB',
1489+
swap_size: 0,
1490+
swapValue: 0,
1491+
swapUnit: 'GB',
14571492
disk_size: 20,
14581493
selectedGpus: [],
14591494
attachAllGpus: false,
@@ -1492,6 +1527,9 @@ <h3>Derive VM</h3>
14921527
memory: 0, // This will be computed from memoryValue and memoryUnit
14931528
memoryValue: 0,
14941529
memoryUnit: 'MB',
1530+
swap_size: 0,
1531+
swapValue: 0,
1532+
swapUnit: 'GB',
14951533
disk_size: 0,
14961534
image: '',
14971535
ports: [],
@@ -1646,6 +1684,11 @@ <h3>Derive VM</h3>
16461684
app_compose.pre_launch_script = vmForm.value.preLaunchScript;
16471685
}
16481686

1687+
const swapBytes = Math.max(0, Math.round(vmForm.value.swap_size || 0));
1688+
if (swapBytes > 0) {
1689+
app_compose.swap_size = swapBytes;
1690+
}
1691+
16491692
// If APP_LAUNCH_TOKEN is set, add it's sha256 hash to the app compose file
16501693
const launchToken = vmForm.value.encryptedEnvs.find(env => env.key === 'APP_LAUNCH_TOKEN');
16511694
if (launchToken) {
@@ -1753,15 +1796,39 @@ <h3>Derive VM</h3>
17531796
showCreateDialog.value = true;
17541797
vmForm.value.encryptedEnvs = [];
17551798
vmForm.value.app_id = null;
1799+
vmForm.value.swapValue = 0;
1800+
vmForm.value.swapUnit = 'GB';
1801+
vmForm.value.swap_size = 0;
17561802
loadGpus();
17571803
};
17581804

17591805
// Memory conversion functions
17601806
const convertMemoryToMB = (value, unit) => {
1807+
const numericValue = Number(value);
1808+
if (!Number.isFinite(numericValue) || numericValue < 0) {
1809+
return 0;
1810+
}
17611811
if (unit === 'GB') {
1762-
return value * 1024;
1812+
return numericValue * 1024;
17631813
}
1764-
return value;
1814+
return numericValue;
1815+
};
1816+
1817+
const BYTES_PER_MB = 1024 * 1024;
1818+
1819+
const convertSwapToBytes = (value, unit) => {
1820+
const mb = convertMemoryToMB(value, unit);
1821+
if (!Number.isFinite(mb) || mb <= 0) {
1822+
return 0;
1823+
}
1824+
return Math.max(0, Math.round(mb * BYTES_PER_MB));
1825+
};
1826+
1827+
const bytesToMB = (bytes) => {
1828+
if (!bytes) {
1829+
return 0;
1830+
}
1831+
return bytes / BYTES_PER_MB;
17651832
};
17661833

17671834
const formatMemory = (memoryMB) => {
@@ -1784,6 +1851,14 @@ <h3>Derive VM</h3>
17841851
upgradeDialog.value.memory = convertMemoryToMB(upgradeDialog.value.memoryValue, upgradeDialog.value.memoryUnit);
17851852
});
17861853

1854+
watch([() => vmForm.value.swapValue, () => vmForm.value.swapUnit], () => {
1855+
vmForm.value.swap_size = convertSwapToBytes(vmForm.value.swapValue, vmForm.value.swapUnit);
1856+
});
1857+
1858+
watch([() => upgradeDialog.value.swapValue, () => upgradeDialog.value.swapUnit], () => {
1859+
upgradeDialog.value.swap_size = convertSwapToBytes(upgradeDialog.value.swapValue, upgradeDialog.value.swapUnit);
1860+
});
1861+
17871862
const createVm = async () => {
17881863
try {
17891864
// Convert memory based on selected unit
@@ -1801,6 +1876,12 @@ <h3>Derive VM</h3>
18011876
user_config: vmForm.value.user_config,
18021877
gpus: configGpu(vmForm.value),
18031878
};
1879+
const swapBytes = Math.max(0, Math.round(form.swap_size || 0));
1880+
if (swapBytes > 0) {
1881+
form.swap_size = swapBytes;
1882+
} else {
1883+
delete form.swap_size;
1884+
}
18041885
const _response = await rpcCall('CreateVm', form);
18051886
loadVMList();
18061887
showCreateDialog.value = false;
@@ -1976,6 +2057,10 @@ <h3>Derive VM</h3>
19762057
const selectedGpuSlots = currentGpuConfig.gpus?.map(gpu => gpu.slot) || [];
19772058
const appCompose = JSON.parse(updatedVM.configuration?.compose_file || "{}");
19782059

2060+
const swapBytes = Number(appCompose.swap_size || 0);
2061+
const swapMb = bytesToMB(swapBytes);
2062+
const swapDisplay = autoMemoryDisplay(swapMb);
2063+
19792064
upgradeDialog.value = {
19802065
show: true,
19812066
vm: updatedVM,
@@ -1988,6 +2073,9 @@ <h3>Derive VM</h3>
19882073
vcpu: updatedVM.configuration?.vcpu || 1,
19892074
memory: updatedVM.configuration?.memory || 1024,
19902075
...autoMemoryDisplay(updatedVM.configuration?.memory),
2076+
swap_size: swapBytes,
2077+
swapValue: Number(swapDisplay.memoryValue),
2078+
swapUnit: swapDisplay.memoryUnit,
19912079
disk_size: updatedVM.configuration?.disk_size || 10,
19922080
image: updatedVM.configuration?.image || '',
19932081
ports: updatedVM.configuration?.ports?.map(port => ({ ...port })) || [],
@@ -2022,6 +2110,13 @@ <h3>Derive VM</h3>
20222110
}
20232111
}
20242112
app_compose.pre_launch_script = upgradeDialog.value.preLaunchScript?.trim();
2113+
2114+
const swapBytes = Math.max(0, Math.round(upgradeDialog.value.swap_size || 0));
2115+
if (swapBytes > 0) {
2116+
app_compose.swap_size = swapBytes;
2117+
} else {
2118+
delete app_compose.swap_size;
2119+
}
20252120
return JSON.stringify(app_compose);
20262121
}
20272122

@@ -2414,6 +2509,7 @@ <h3>Derive VM</h3>
24142509
composeHashPreview,
24152510
upgradeComposeHashPreview,
24162511
formatMemory,
2512+
bytesToMB,
24172513
// Pagination and search variables
24182514
searchQuery,
24192515
currentPage,
@@ -2435,4 +2531,4 @@ <h3>Derive VM</h3>
24352531
</script>
24362532
</body>
24372533

2438-
</html>
2534+
</html>

vmm/src/vmm-cli.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,12 @@ def create_app_compose(self, args) -> None:
498498
if args.prelaunch_script:
499499
app_compose["pre_launch_script"] = open(
500500
args.prelaunch_script, 'rb').read().decode('utf-8')
501+
if args.swap is not None:
502+
swap_bytes = max(0, int(round(args.swap)) * 1024 * 1024)
503+
if swap_bytes > 0:
504+
app_compose["swap_size"] = swap_bytes
505+
else:
506+
app_compose.pop("swap_size", None)
501507

502508
compose_file = json.dumps(
503509
app_compose, indent=4, ensure_ascii=False).encode('utf-8')
@@ -537,6 +543,10 @@ def create_vm(self, args) -> None:
537543
"pin_numa": args.pin_numa,
538544
"stopped": args.stopped,
539545
}
546+
if args.swap is not None:
547+
swap_bytes = max(0, int(round(args.swap)) * 1024 * 1024)
548+
if swap_bytes > 0:
549+
params["swap_size"] = swap_bytes
540550

541551
if args.ppcie:
542552
params["gpus"] = {
@@ -908,6 +918,9 @@ def main():
908918
'--no-instance-id', action='store_true', help='Disable instance ID')
909919
compose_parser.add_argument(
910920
'--secure-time', action='store_true', help='Enable secure time')
921+
compose_parser.add_argument(
922+
'--swap', type=parse_memory_size, default=None,
923+
help='Swap size (e.g. 4G). Set to 0 to disable')
911924
compose_parser.add_argument(
912925
'--output', required=True, help='Path to output app-compose.json file')
913926

@@ -923,6 +936,9 @@ def main():
923936
'--memory', type=parse_memory_size, default=1024, help='Memory size (e.g. 1G, 100M)')
924937
deploy_parser.add_argument(
925938
'--disk', type=parse_disk_size, default=20, help='Disk size (e.g. 1G, 100M)')
939+
deploy_parser.add_argument(
940+
'--swap', type=parse_memory_size, default=None,
941+
help='Swap size (e.g. 4G). Set to 0 to disable')
926942
deploy_parser.add_argument(
927943
'--env-file', help='File with environment variables to encrypt', default=None)
928944
deploy_parser.add_argument(

0 commit comments

Comments
 (0)