diff --git a/Sources/tart/Commands/Clone.swift b/Sources/tart/Commands/Clone.swift index e7aa948..73a8493 100644 --- a/Sources/tart/Commands/Clone.swift +++ b/Sources/tart/Commands/Clone.swift @@ -73,7 +73,7 @@ struct Clone: AsyncParsableCommand { // APFS is doing copy-on-write so the above cloning operation (just copying files on disk) // is not actually claiming new space until the VM is started and it writes something to disk. // So once we clone the VM let's try to claim a little bit of space for the VM to run. - try Prune.reclaimIfNeeded(UInt64(sourceVM.sizeBytes()), sourceVM) + try Prune.reclaimIfNeeded(UInt64(sourceVM.allocatedSizeBytes()), sourceVM) }, onCancel: { try? FileManager.default.removeItem(at: tmpVMDir.baseURL) }) diff --git a/Sources/tart/Commands/Get.swift b/Sources/tart/Commands/Get.swift index 599eeb8..80fd9a6 100644 --- a/Sources/tart/Commands/Get.swift +++ b/Sources/tart/Commands/Get.swift @@ -5,6 +5,7 @@ fileprivate struct VMInfo: Encodable { let CPU: Int let Memory: UInt64 let Disk: Int + let Size: String let Display: String let Running: Bool let State: String @@ -22,10 +23,9 @@ struct Get: AsyncParsableCommand { func run() async throws { let vmDir = try VMStorageLocal().open(name) let vmConfig = try VMConfig(fromURL: vmDir.configURL) - let diskSizeInGb = try vmDir.sizeGB() let memorySizeInMb = vmConfig.memorySize / 1024 / 1024 - let info = VMInfo(CPU: vmConfig.cpuCount, Memory: memorySizeInMb, Disk: diskSizeInGb, + let info = VMInfo(CPU: vmConfig.cpuCount, Memory: memorySizeInMb, Disk: try vmDir.sizeGB(), Size: String(format: "%.3f", Float(try vmDir.allocatedSizeBytes()) / 1000 / 1000 / 1000), Display: vmConfig.display.description, Running: try vmDir.running(), State: try vmDir.state()) print(format.renderSingle(info)) } diff --git a/Sources/tart/Commands/List.swift b/Sources/tart/Commands/List.swift index 7cccbc5..8a1af18 100644 --- a/Sources/tart/Commands/List.swift +++ b/Sources/tart/Commands/List.swift @@ -5,6 +5,7 @@ import SwiftUI fileprivate struct VMInfo: Encodable { let Source: String let Name: String + let Disk: Int let Size: Int let Running: Bool let State: String @@ -37,13 +38,13 @@ struct List: AsyncParsableCommand { if source == nil || source == "local" { infos += sortedInfos(try VMStorageLocal().list().map { (name, vmDir) in - try VMInfo(Source: "local", Name: name, Size: vmDir.sizeGB(), Running: vmDir.running(), State: vmDir.state()) + try VMInfo(Source: "local", Name: name, Disk: vmDir.sizeGB(), Size: vmDir.allocatedSizeGB(), Running: vmDir.running(), State: vmDir.state()) }) } if source == nil || source == "oci" { infos += sortedInfos(try VMStorageOCI().list().map { (name, vmDir, _) in - try VMInfo(Source: "oci", Name: name, Size: vmDir.sizeGB(), Running: vmDir.running(), State: vmDir.state()) + try VMInfo(Source: "oci", Name: name, Disk: vmDir.sizeGB(), Size: vmDir.allocatedSizeGB(), Running: vmDir.running(), State: vmDir.state()) }) } diff --git a/Sources/tart/Commands/Prune.swift b/Sources/tart/Commands/Prune.swift index abb555e..777676c 100644 --- a/Sources/tart/Commands/Prune.swift +++ b/Sources/tart/Commands/Prune.swift @@ -89,7 +89,7 @@ struct Prune: AsyncParsableCommand { var prunablesToDelete: [Prunable] = [] for prunable in prunables { - let prunableSizeBytes = UInt64(try prunable.sizeBytes()) + let prunableSizeBytes = UInt64(try prunable.allocatedSizeBytes()) if prunableSizeBytes <= spaceBudgetBytes { // Don't mark for deletion as @@ -158,7 +158,7 @@ struct Prune: AsyncParsableCommand { .sorted { try $0.accessDate() < $1.accessDate() } // Does it even make sense to start? - let cacheUsedBytes = try prunables.map { try $0.sizeBytes() }.reduce(0, +) + let cacheUsedBytes = try prunables.map { try $0.allocatedSizeBytes() }.reduce(0, +) if cacheUsedBytes < reclaimBytes { return } @@ -177,9 +177,9 @@ struct Prune: AsyncParsableCommand { continue } - try SentrySDK.span?.setData(value: prunable.sizeBytes(), key: prunable.url.path) + try SentrySDK.span?.setData(value: prunable.allocatedSizeBytes(), key: prunable.url.path) - cacheReclaimedBytes += try prunable.sizeBytes() + cacheReclaimedBytes += try prunable.allocatedSizeBytes() try prunable.delete() } diff --git a/Sources/tart/Prunable.swift b/Sources/tart/Prunable.swift index e30a3a1..fe837cb 100644 --- a/Sources/tart/Prunable.swift +++ b/Sources/tart/Prunable.swift @@ -8,5 +8,8 @@ protocol Prunable { var url: URL { get } func delete() throws func accessDate() throws -> Date + // size on disk as seen in Finder including empty blocks func sizeBytes() throws -> Int + // actual size on disk without empty blocks + func allocatedSizeBytes() throws -> Int } diff --git a/Sources/tart/URL+Prunable.swift b/Sources/tart/URL+Prunable.swift index 8b87e2c..57656e2 100644 --- a/Sources/tart/URL+Prunable.swift +++ b/Sources/tart/URL+Prunable.swift @@ -9,7 +9,11 @@ extension URL: Prunable { try FileManager.default.removeItem(at: self) } - func sizeBytes() throws -> Int { + func allocatedSizeBytes() throws -> Int { try resourceValues(forKeys: [.totalFileAllocatedSizeKey]).totalFileAllocatedSize! } + + func sizeBytes() throws -> Int { + try resourceValues(forKeys: [.totalFileSizeKey]).totalFileSize! + } } diff --git a/Sources/tart/VMDirectory.swift b/Sources/tart/VMDirectory.swift index 694d8b4..14473de 100644 --- a/Sources/tart/VMDirectory.swift +++ b/Sources/tart/VMDirectory.swift @@ -165,6 +165,14 @@ struct VMDirectory: Prunable { try baseURL.accessDate() } + func allocatedSizeBytes() throws -> Int { + try configURL.allocatedSizeBytes() + diskURL.allocatedSizeBytes() + nvramURL.allocatedSizeBytes() + } + + func allocatedSizeGB() throws -> Int { + try allocatedSizeBytes() / 1000 / 1000 / 1000 + } + func sizeBytes() throws -> Int { try configURL.sizeBytes() + diskURL.sizeBytes() + nvramURL.sizeBytes() }