From 5c7743b7cd8ca3c5747349672227d0a63fc0de70 Mon Sep 17 00:00:00 2001 From: Fedor Korotkov Date: Tue, 27 Feb 2024 11:13:38 -0500 Subject: [PATCH] Show both size and actual size of files (#742) Right now we show only actual size of files on disk which excludes empty blocks of the recently introduced sparced format in #671. This makes impossible to get info about disk size that we just set via `tart set`. Here is an example of `tart list` output before the change: ``` Source Name Size State local sonoma-base 22 stopped local sonoma-vanilla 18 stopped local sonoma-xcode 67 stopped local ubuntu 1 stopped oci ghcr.io/cirruslabs/macos-sonoma-base:latest 22 stopped oci ghcr.io/cirruslabs/macos-sonoma-base@sha256:16c1593bbaf787b20b3c0bc094c5b6baf71c937d22c2e4596da85ac55c92e6cc 22 stopped oci ghcr.io/cirruslabs/macos-sonoma-vanilla:14.3 17 stopped oci ghcr.io/cirruslabs/macos-sonoma-vanilla@sha256:23c4e853d48d00a4333346d66a32b2b5aad900cc0dc10e7ecb9dbe67b6f587f4 17 stopped oci ghcr.io/cirruslabs/macos-sonoma-xcode:latest 67 stopped oci ghcr.io/cirruslabs/macos-sonoma-xcode@sha256:d0cb8d01424a68b89e0f16f5371bf2152b2c115bd886341a6ba8da42121d1f41 67 stopped oci ghcr.io/cirruslabs/ubuntu:22.04 1 stopped oci ghcr.io/cirruslabs/ubuntu@sha256:037763feb7a15d6077edeb7a097738c34313637d16036764b4c196d28d8b429c 1 stopped ``` And here is the output after the change: ``` Source Name Disk Size State local sonoma-base 50 22 stopped local sonoma-vanilla 50 18 stopped local sonoma-xcode 90 67 stopped local ubuntu 20 1 stopped oci ghcr.io/cirruslabs/macos-sonoma-base:latest 50 22 stopped oci ghcr.io/cirruslabs/macos-sonoma-base@sha256:16c1593bbaf787b20b3c0bc094c5b6baf71c937d22c2e4596da85ac55c92e6cc 50 22 stopped oci ghcr.io/cirruslabs/macos-sonoma-vanilla:14.3 50 17 stopped oci ghcr.io/cirruslabs/macos-sonoma-vanilla@sha256:23c4e853d48d00a4333346d66a32b2b5aad900cc0dc10e7ecb9dbe67b6f587f4 50 17 stopped oci ghcr.io/cirruslabs/macos-sonoma-xcode:latest 90 67 stopped oci ghcr.io/cirruslabs/macos-sonoma-xcode@sha256:d0cb8d01424a68b89e0f16f5371bf2152b2c115bd886341a6ba8da42121d1f41 90 67 stopped oci ghcr.io/cirruslabs/ubuntu:22.04 20 1 stopped oci ghcr.io/cirruslabs/ubuntu@sha256:037763feb7a15d6077edeb7a097738c34313637d16036764b4c196d28d8b429c 20 1 stopped ``` Additionally, `tart get` will print actual size with a 3 decimal point precision which will help to track growth in disk images for our templates. `tart get` before: ``` CPU Memory Disk Display State 4 8192 67 1024x768 stopped ``` `tart get` after: ``` CPU Memory Disk Size Display State 4 8192 90 67.333 1024x768 stopped ``` --- Sources/tart/Commands/Clone.swift | 2 +- Sources/tart/Commands/Get.swift | 4 ++-- Sources/tart/Commands/List.swift | 5 +++-- Sources/tart/Commands/Prune.swift | 8 ++++---- Sources/tart/Prunable.swift | 3 +++ Sources/tart/URL+Prunable.swift | 6 +++++- Sources/tart/VMDirectory.swift | 8 ++++++++ 7 files changed, 26 insertions(+), 10 deletions(-) diff --git a/Sources/tart/Commands/Clone.swift b/Sources/tart/Commands/Clone.swift index e7aa948b..73a84934 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 599eeb80..80fd9a67 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 7cccbc54..8a1af180 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 abb555ec..777676ca 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 e30a3a13..fe837cbf 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 8b87e2c6..57656e2f 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 694d8b48..14473ded 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() }