Compare commits

...
3 Commits
Author SHA1 Message Date
Fedor Korotkov 5c7743b7cd 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
```
2024-02-27 20:13:38 +04:00
Fedor Korotkov a40e104c03 Update Platinum Tier (#741)
5x jump in price from Gold to Platinum is a bit too high. Most of known large deployments target 200-300 hosts. Let's accommodate such users by lowering Platinum tier.
2024-02-23 15:27:30 +00:00
Nikolay Edigaryev e2d6c13ed0 DHCP MAC-address resolver: handle duplicate leases (#740) 2024-02-23 16:49:06 +04:00
11 changed files with 56 additions and 14 deletions
+1 -1
View File
@@ -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)
})
+2 -2
View File
@@ -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))
}
+3 -2
View File
@@ -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())
})
}
+4 -4
View File
@@ -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()
}
+4 -1
View File
@@ -45,7 +45,10 @@ class Leases {
(lease.mac, lease)
})
self.leases = Dictionary(uniqueKeysWithValues: leases)
self.leases = Dictionary(leases) { (left, right) in
// When duplicate lease is found, prefer a newer lease over the older one
(left.expiresAt > right.expiresAt) ? left : right
}
}
/// Parse leases from the host cache similarly to the PLCache_read() function found in Apple's Open Source releases.
+3
View File
@@ -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
}
+5 -1
View File
@@ -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!
}
}
+8
View File
@@ -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()
}
+23
View File
@@ -35,4 +35,27 @@ final class LeasesTests: XCTestCase {
XCTAssertEqual(IPv4Address("1.2.3.4"), leases.ResolveMACAddress(macAddress: macAddress))
}
func testDuplicateYetNotExpiredLeases() throws {
let macAddress = MACAddress(fromString: "11:22:33:44:55:66")!
let leases = try Leases("""
{
name=debian
ip_address=192.168.64.1
hw_address=1,\(macAddress)
identifier=1,\(macAddress)
lease=\(Int((Date() + 10.minutes).timeIntervalSince1970).hex)
}
{
name=debian
ip_address=192.168.64.2
hw_address=1,\(macAddress)
identifier=1,\(macAddress)
lease=\(Int((Date() + 5.minutes).timeIntervalSince1970).hex)
}
""")
XCTAssertEqual(IPv4Address("192.168.64.1"), leases.ResolveMACAddress(macAddress: macAddress))
}
}
@@ -62,7 +62,7 @@ and before reaching the 100 CPU cores limit is royalty-free and does not have th
When an organization surpasses the 100 CPU cores limit, they will be required to obtain a [Gold Tier License](/licensing#license-tiers),
which costs \$1000 per month. Upon reaching a limit of 500 CPU cores, a [Platinum Tier License](/licensing#license-tiers)
(\$5000 per month) will be required, and for organizations that exceed 5000 CPU cores, a custom [Diamond Tier License](/licensing#license-tiers)
(\$3000 per month) will be required, and for organizations that exceed 3000 CPU cores, a custom [Diamond Tier License](/licensing#license-tiers)
(\$1 per core per month) will be necessary. **All paid license tiers will include priority feature development and SLAs on support with urgent issues.**
## Have we considered alternatives?
+2 -2
View File
@@ -40,9 +40,9 @@ Gold Tier license has a 500 CPU core limit for Tart and 20 Orchard Workers limit
### Platinum Tier
If an organization wishes to exceed the limits of the Gold Tier license, a purchase of the [Platinum Tier License](#get-the-license) is required, which costs \$5000 per month.
If an organization wishes to exceed the limits of the Gold Tier license, a purchase of the [Platinum Tier License](#get-the-license) is required, which costs \$3000 per month.
Platinum Tier license has a 5,000 CPU core limit for Tart and 200 Orchard Workers limit for Orchard.
Platinum Tier license has a 3,000 CPU core limit for Tart and 200 Orchard Workers limit for Orchard.
### Diamond Tier