diff --git a/Modules/RAM/readers.swift b/Modules/RAM/readers.swift index 3772c2b5ee5..d21eaeb70e2 100644 --- a/Modules/RAM/readers.swift +++ b/Modules/RAM/readers.swift @@ -12,6 +12,15 @@ import Cocoa import Kit +internal struct RAMMemoryBreakdown { + let app: Double + let wired: Double + let compressed: Double + let cache: Double + let used: Double + let free: Double +} + internal class UsageReader: Reader { public var totalSize: Double = 0 @@ -46,18 +55,15 @@ internal class UsageReader: Reader { if result == KERN_SUCCESS { let active = Double(stats.active_count) * Double(vm_page_size) - let speculative = Double(stats.speculative_count) * Double(vm_page_size) let inactive = Double(stats.inactive_count) * Double(vm_page_size) - let wired = Double(stats.wire_count) * Double(vm_page_size) - let compressed = Double(stats.compressor_page_count) * Double(vm_page_size) - let purgeable = Double(stats.purgeable_count) * Double(vm_page_size) - let external = Double(stats.external_page_count) * Double(vm_page_size) + let breakdown = Self.memoryBreakdown( + totalSize: self.totalSize, + stats: stats, + pageSize: Double(vm_page_size) + ) let swapins = Int64(stats.swapins) let swapouts = Int64(stats.swapouts) - let used = active + inactive + speculative + wired + compressed - purgeable - external - let free = self.totalSize - used - var intSize: size_t = MemoryLayout.size var pressureLevel: Int = 0 sysctlbyname("kern.memorystatus_vm_pressure_level", &pressureLevel, &intSize, nil, 0) @@ -75,16 +81,16 @@ internal class UsageReader: Reader { self.callback(RAM_Usage( total: self.totalSize, - used: used, - free: free, + used: breakdown.used, + free: breakdown.free, active: active, inactive: inactive, - wired: wired, - compressed: compressed, + wired: breakdown.wired, + compressed: breakdown.compressed, - app: used - wired - compressed, - cache: purgeable + external, + app: breakdown.app, + cache: breakdown.cache, swap: Swap( total: Double(swap.xsu_total), @@ -101,6 +107,27 @@ internal class UsageReader: Reader { error("host_statistics64(): \(String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error")", log: self.log) } + + static func memoryBreakdown(totalSize: Double, stats: vm_statistics64, pageSize: Double) -> RAMMemoryBreakdown { + let wired = Double(stats.wire_count) * pageSize + let compressed = Double(stats.compressor_page_count) * pageSize + let purgeable = Double(stats.purgeable_count) * pageSize + let external = Double(stats.external_page_count) * pageSize + let appPages = max(Int64(stats.internal_page_count) - Int64(stats.purgeable_count), 0) + let app = Double(appPages) * pageSize + let cache = purgeable + external + let used = min(max(app + wired + compressed, 0), totalSize) + let free = max(totalSize - used, 0) + + return RAMMemoryBreakdown( + app: app, + wired: wired, + compressed: compressed, + cache: cache, + used: used, + free: free + ) + } } public class ProcessReader: Reader<[TopProcess]> { diff --git a/Tests/RAM.swift b/Tests/RAM.swift index 2a9b1b83942..44d55cb6e1a 100644 --- a/Tests/RAM.swift +++ b/Tests/RAM.swift @@ -10,7 +10,8 @@ // import XCTest -import RAM +import Darwin +@testable import RAM class RAM: XCTestCase { func testProcessReader_parseProcess() throws { @@ -78,4 +79,27 @@ class RAM: XCTestCase { XCTAssertEqual(process.name, "Safari") XCTAssertEqual(process.usage, 658 * Double(1000 * 1000)) } + + func testMemoryBreakdownUsesAppWiredAndCompressedMemory() throws { + var stats = vm_statistics64() + stats.internal_page_count = 100 + stats.purgeable_count = 10 + stats.wire_count = 20 + stats.compressor_page_count = 5 + stats.external_page_count = 30 + stats.active_count = 40 + stats.inactive_count = 50 + + let pageSize = Double(4_096) + let totalSize = Double(200) * pageSize + let breakdown = UsageReader.memoryBreakdown(totalSize: totalSize, stats: stats, pageSize: pageSize) + + XCTAssertEqual(breakdown.app, Double(90) * pageSize) + XCTAssertEqual(breakdown.wired, Double(20) * pageSize) + XCTAssertEqual(breakdown.compressed, Double(5) * pageSize) + XCTAssertEqual(breakdown.cache, Double(40) * pageSize) + XCTAssertEqual(breakdown.used, Double(115) * pageSize) + XCTAssertEqual(breakdown.free, Double(85) * pageSize) + } + }