Swift pointer problems with MACH_TASK_BASIC_INFO
Asked Answered
L

8

17

I am trying to convert an ObjC stackoverflow answer to Swift and failing. It looks like I am passing a UnsafeMutablePointer<mach_msg_type_number_t> when I should be passing an inout mach_msg_type_number_t and I can't seem to work out my problem. From what I understand of the Swift pointer documentation (not much) these should be interchangeable..?

Further info below.

Here's the Objective C:

struct task_basic_info info;
mach_msg_type_number_t size = sizeof(info);
kern_return_t kerr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &size);

and here's as far as I got in Swift (many lines for easier type checking)

let name: task_name_t = mach_task_self_
let flavor: task_flavor_t = task_flavor_t(MACH_TASK_BASIC_INFO)
var info: mach_task_basic_info
var size: mach_msg_type_number_t = UnsignedFixed(sizeof(mach_task_basic_info_t))
let kerr = task_info(name, flavor, info as task_info_t, &size)

The task_info signature is:

func task_info(target_task: task_name_t, flavor: task_flavor_t, task_info_out: task_info_t, task_info_outCnt: UnsafeMutablePointer<mach_msg_type_number_t>) -> kern_return_t

and the error on the last line is:

Cannot convert the expression's type '(@!lvalue task_name_t, task_flavor_t, task_info_t, inout mach_msg_type_number_t)' to type 'kern_return_t'
Leclair answered 18/12, 2014 at 22:26 Comment(0)
C
16

When interacting with C functions, you can't rely on the compiler's error messages - break it down parameter by parameter, command-clicking until you know what you're working with. To start with, the types you're running into are:

  • task_name_t: UInt32
  • task_flavor_t: UInt32
  • task_info_t: UnsafeMutablePointer<Int32>
  • UnsafeMutablePointer<mach_msg_type_number_t>: UnsafeMutablePointer<UInt32>
  • kern_return_t - Int32

There's one tricky Swift bit along with a bug in your code standing in your way here. First, the task_info_out parameter needs to be a UnsafeMutablePointer<UInt32>, but needs to actually point to an instance of mach_task_basic_info. We can get around this by creating a UnsafeMutablePointer<mach_task_basic_info> and wrapping it in another UnsafeMutablePointer at call time - the compiler will use type inference to know we want that wrapping pointer to be sub-typed as UInt32.

Second, you're calling sizeof(mach_task_basic_info_t) (the pointer to mach_task_basic_info) when you should be calling sizeinfo(mach_task_basic_info), so your byte count ends up too low to hold the data structure.

On further research, this got a little more complicated. The original code for this was incorrect, in that size should be initialized to the constant MACH_TASK_BASIC_INFO_COUNT. Unfortunately, that's a macro, not a simple constant:

#define MACH_TASK_BASIC_INFO_COUNT (sizeof(mach_task_basic_info_data_t) / sizeof(natural_t)) 

Swift doesn't import those, so we'll need to redefine it ourselves. Here's working code for all this:

// constant
let MACH_TASK_BASIC_INFO_COUNT = (sizeof(mach_task_basic_info_data_t) / sizeof(natural_t))

// prepare parameters
let name   = mach_task_self_
let flavor = task_flavor_t(MACH_TASK_BASIC_INFO)
var size   = mach_msg_type_number_t(MACH_TASK_BASIC_INFO_COUNT)

// allocate pointer to mach_task_basic_info
var infoPointer = UnsafeMutablePointer<mach_task_basic_info>.alloc(1)

// call task_info - note extra UnsafeMutablePointer(...) call
let kerr = task_info(name, flavor, UnsafeMutablePointer(infoPointer), &size)

// get mach_task_basic_info struct out of pointer
let info = infoPointer.move()

// deallocate pointer
infoPointer.dealloc(1)

// check return value for success / failure
if kerr == KERN_SUCCESS {
    println("Memory in use (in bytes): \(info.resident_size)")
} else {
    let errorString = String(CString: mach_error_string(kerr), encoding: NSASCIIStringEncoding)
    println(errorString ?? "Error: couldn't parse error string")
}
Cortes answered 19/12, 2014 at 4:33 Comment(0)
A
41

Took me a bit to update Airspeed Velocity's answer to the latest swift syntax (Swift 3, beta 6), but here is what I got:

func report_memory() {
    var info = mach_task_basic_info()
    let MACH_TASK_BASIC_INFO_COUNT = MemoryLayout<mach_task_basic_info>.stride/MemoryLayout<natural_t>.stride
    var count = mach_msg_type_number_t(MACH_TASK_BASIC_INFO_COUNT)

    let kerr: kern_return_t = withUnsafeMutablePointer(to: &info) {
        $0.withMemoryRebound(to: integer_t.self, capacity: MACH_TASK_BASIC_INFO_COUNT) {
            task_info(mach_task_self_,
                      task_flavor_t(MACH_TASK_BASIC_INFO),
                      $0,
                      &count)
        }
    }

    if kerr == KERN_SUCCESS {
        print("Memory in use (in bytes): \(info.resident_size)")
    }
    else {
        print("Error with task_info(): " +
            (String(cString: mach_error_string(kerr), encoding: String.Encoding.ascii) ?? "unknown error"))
    }
}

Hope that's helpful.

Aesthetic answered 19/8, 2016 at 22:46 Comment(8)
Is info.resident_size memory used by this App, or total memory usage in the system? It is quite small only 26M so I guess it is the memory used by current App.Cuneo
Yes, it's the memory used by this processAesthetic
Would you help me understand how this value compares with the memory values shown by the debugger? I'm currently getting a resident_size of 106mb, but xcode is telling me I'm using 34mb with other processes using 1gb and 1gb free.Kurbash
There's a WWDC video from a few years ago (probably on instruments) that talks about memory allocated to the app vs. to system processing on behalf of the app. Sorry, my memory is fuzzy on the topic. But I believe Xcode doesn't show that memory as used by the app, but this does, or something like that.Aesthetic
always getting: "(os/kern) invalid argument"Minuscule
@Hogdotmac, Where are you getting this error? Does your code look the same as above? I just tried with the latest version of Swift and this is still working.Aesthetic
After comparing the storage display in "Documents & Data" to the number produced by your solution. They do not get anywhere close. "Documents & Data" in settings shows 297MB, while your solution shows 100MB every timeGnomon
I think you're mixing up memory used and storage used. Storage describes the amount written to disk, memory shows the amount of space loaded in to memory while the app is running.Aesthetic
L
17

For a quick copy and paste solution in Swift 5, use

func reportMemory() {
    var taskInfo = task_vm_info_data_t()
    var count = mach_msg_type_number_t(MemoryLayout<task_vm_info>.size) / 4
    let result: kern_return_t = withUnsafeMutablePointer(to: &taskInfo) {
        $0.withMemoryRebound(to: integer_t.self, capacity: 1) {
            task_info(mach_task_self_, task_flavor_t(TASK_VM_INFO), $0, &count)
        }
    }
    let usedMb = Float(taskInfo.phys_footprint) / 1048576.0
    let totalMb = Float(ProcessInfo.processInfo.physicalMemory) / 1048576.0
    result != KERN_SUCCESS ? print("Memory used: ? of \(totalMb)") : print("Memory used: \(usedMb) of \(totalMb)")
}
Lavelle answered 8/11, 2020 at 12:44 Comment(1)
note that this will just track the usage of your app, not all of them.Lanoralanose
C
16

When interacting with C functions, you can't rely on the compiler's error messages - break it down parameter by parameter, command-clicking until you know what you're working with. To start with, the types you're running into are:

  • task_name_t: UInt32
  • task_flavor_t: UInt32
  • task_info_t: UnsafeMutablePointer<Int32>
  • UnsafeMutablePointer<mach_msg_type_number_t>: UnsafeMutablePointer<UInt32>
  • kern_return_t - Int32

There's one tricky Swift bit along with a bug in your code standing in your way here. First, the task_info_out parameter needs to be a UnsafeMutablePointer<UInt32>, but needs to actually point to an instance of mach_task_basic_info. We can get around this by creating a UnsafeMutablePointer<mach_task_basic_info> and wrapping it in another UnsafeMutablePointer at call time - the compiler will use type inference to know we want that wrapping pointer to be sub-typed as UInt32.

Second, you're calling sizeof(mach_task_basic_info_t) (the pointer to mach_task_basic_info) when you should be calling sizeinfo(mach_task_basic_info), so your byte count ends up too low to hold the data structure.

On further research, this got a little more complicated. The original code for this was incorrect, in that size should be initialized to the constant MACH_TASK_BASIC_INFO_COUNT. Unfortunately, that's a macro, not a simple constant:

#define MACH_TASK_BASIC_INFO_COUNT (sizeof(mach_task_basic_info_data_t) / sizeof(natural_t)) 

Swift doesn't import those, so we'll need to redefine it ourselves. Here's working code for all this:

// constant
let MACH_TASK_BASIC_INFO_COUNT = (sizeof(mach_task_basic_info_data_t) / sizeof(natural_t))

// prepare parameters
let name   = mach_task_self_
let flavor = task_flavor_t(MACH_TASK_BASIC_INFO)
var size   = mach_msg_type_number_t(MACH_TASK_BASIC_INFO_COUNT)

// allocate pointer to mach_task_basic_info
var infoPointer = UnsafeMutablePointer<mach_task_basic_info>.alloc(1)

// call task_info - note extra UnsafeMutablePointer(...) call
let kerr = task_info(name, flavor, UnsafeMutablePointer(infoPointer), &size)

// get mach_task_basic_info struct out of pointer
let info = infoPointer.move()

// deallocate pointer
infoPointer.dealloc(1)

// check return value for success / failure
if kerr == KERN_SUCCESS {
    println("Memory in use (in bytes): \(info.resident_size)")
} else {
    let errorString = String(CString: mach_error_string(kerr), encoding: NSASCIIStringEncoding)
    println(errorString ?? "Error: couldn't parse error string")
}
Cortes answered 19/12, 2014 at 4:33 Comment(0)
S
10

Nate’s answer is excellent but there’s a tweak you can make to simplify it.

First, rather than allocating/deallocating the task_basic_info pointer, you can create the struct on the stack, then use withUnsafeMutablePointer to get a pointer directly to it which you can pass in.

func report_memory() {
    var info = mach_task_basic_info()
    var count = mach_msg_type_number_t(sizeofValue(info))/4

    let kerr: kern_return_t = withUnsafeMutablePointer(&info) {

        task_info(mach_task_self_,
            task_flavor_t(MACH_TASK_BASIC_INFO),
            task_info_t($0),
            &count)

    }

    if kerr == KERN_SUCCESS {
        println("Memory in use (in bytes): \(info.resident_size)")
    }
    else {
        println("Error with task_info(): " +
            (String.fromCString(mach_error_string(kerr)) ?? "unknown error"))
    }
}
Schnapps answered 22/4, 2015 at 12:8 Comment(2)
what's with the /4 ?Minuscule
Each Int32 has 4 bytes, that is how we tell count of fields required (not the size or stride, and instead of manual calculation one should use MACH_TASK_BASIC_INFO_COUNT).Compressibility
G
7

Swift 5 + Combine, Continuous memory Monitoring

Show exact memory in MB like XCODE

import Foundation
import Combine

 enum MemoryMonitorState {
    case started
    case paused
}


class MemoryUsageCustom {
    
    private var displayLink: CADisplayLink!

    var state = MemoryMonitorState.paused
    
    let subject = PassthroughSubject<String, Never>()

    
    private static var sharedInstance: MemoryUsageCustom!
    
    public class func shared() -> MemoryUsageCustom {
        if self.sharedInstance == nil {
            self.sharedInstance = MemoryUsageCustom()
        }
        return self.sharedInstance
    }
    
    private init() {
        self.configureDisplayLink()

    }
    
    func startMemoryMonitor() {
        
        if self.state == .started {
            return
        }
        
        self.state = .started
        self.start()
    }
    
    func stopMemoryMonitor() {
        self.state = .paused
        self.pause()
    }
    

    //--------------------------------------------------------------------------------
    
    //MARK:- Display Link
    
    //--------------------------------------------------------------------------------

 
    func configureDisplayLink() {
        self.displayLink = CADisplayLink(target: self, selector: #selector(displayLinkAction(displayLink:)))
        self.displayLink.isPaused = true
        self.displayLink?.add(to: .current, forMode: .common)
    }
    
    private func start() {
        self.displayLink?.isPaused = false
    }
    
    /// Pauses performance monitoring.
    private func pause() {
        self.displayLink?.isPaused = true
    }
    
    @objc func displayLinkAction(displayLink: CADisplayLink) {
        let memoryUsage = self.memoryUsage()
        
        let bytesInMegabyte = 1024.0 * 1024.0
        let usedMemory = Double(memoryUsage.used) / bytesInMegabyte
        let totalMemory = Double(memoryUsage.total) / bytesInMegabyte
        let memory = String(format: "%.1f of %.0f MB used", usedMemory, totalMemory)

     //   self.memoryString = memory
        subject.send(memory)
    }
    
    func memoryUsage() -> (used: UInt64, total: UInt64) {
        var taskInfo = task_vm_info_data_t()
        var count = mach_msg_type_number_t(MemoryLayout<task_vm_info>.size) / 4
        let result: kern_return_t = withUnsafeMutablePointer(to: &taskInfo) {
            $0.withMemoryRebound(to: integer_t.self, capacity: 1) {
                task_info(mach_task_self_, task_flavor_t(TASK_VM_INFO), $0, &count)
            }
        }
        
        var used: UInt64 = 0
        if result == KERN_SUCCESS {
            used = UInt64(taskInfo.phys_footprint)
        }
        
        let total = ProcessInfo.processInfo.physicalMemory
        return (used, total)
    }

}

How To use


   //Start Monitoring 
    MemoryUsageCustom.shared().startMemoryMonitor()

var storage = Set<AnyCancellable>()

    MemoryUsageCustom.shared().subject.sink {[weak self] (string) in
        print(string)

    }.store(in: &storage)
Grouper answered 2/11, 2020 at 6:36 Comment(0)
H
5

Airspeed Velocity's answer in Swift 3...

func GetMemory()
{
    var info = mach_task_basic_info()
    var count = mach_msg_type_number_t(MemoryLayout.size(ofValue: info))/4

    let kerr: kern_return_t = withUnsafeMutablePointer(to: &info)
    {

        task_info(mach_task_self_,
                  task_flavor_t(MACH_TASK_BASIC_INFO),
                  $0.withMemoryRebound(to: Int32.self, capacity: 1) { zeroPtr in
                    task_info_t(zeroPtr)
                  },
                  &count)

    }

    if kerr == KERN_SUCCESS {
        print("Memory in use (in bytes): \(info.resident_size)")
    }
    else {
        print("Error with task_info(): " +
            (String.init(validatingUTF8: mach_error_string(kerr)) ?? "unknown error"))
    }
}
Hanover answered 3/4, 2017 at 19:24 Comment(0)
S
0

For Linux:

import Foundation

@available(macOS 10.13, *)
public func shell(_ args: String...) throws -> String? {
    let task = Process()
    task.launchPath = "/usr/bin/env"
    task.arguments = args
    
    let pipe = Pipe()
    task.standardOutput = pipe
    task.standardError = pipe
    
    try task.run()
    let data = pipe.fileHandleForReading.readDataToEndOfFile()
    if let output = String(data: data, encoding: String.Encoding.utf8) {
        if output.count > 0 {
            //remove newline character.
            let lastIndex = output.index(before: output.endIndex)
            return String(output[output.startIndex ..< lastIndex])
        }
        task.waitUntilExit()
        return output
    } else {
        return nil
    }
}

@available(macOS 10.13, *)
public func shellWithPipes(_ args: String...) throws -> String? {
    var task: Process!
    var prevPipe: Pipe? = nil
    guard args.count > 0 else {
        return nil
    }
    for i in 0..<args.count {
        task = Process()
        task.launchPath = "/usr/bin/env"
        let taskArgs = args[i].components(separatedBy: " ")
        var refinedArgs = [String]()
        var refinedArg = ""
        for arg in taskArgs {
            if !refinedArg.isEmpty {
                refinedArg += " " + arg
                if arg.suffix(1) == "'" {
                    refinedArgs.append(refinedArg.replacingOccurrences(of: "\'", with: ""))
                    refinedArg = ""
                }
            } else {
                if arg.prefix(1) == "'" {
                    refinedArg = arg
                } else {
                    refinedArgs.append(arg)
                }
            }
        }
        task.arguments = refinedArgs
        
        let pipe = Pipe()
        if let prevPipe = prevPipe {
            task.standardInput = prevPipe
        }
        task.standardOutput = pipe
        task.standardError = pipe
        try task.run()
        prevPipe = pipe
    }
    if let data = prevPipe?.fileHandleForReading.readDataToEndOfFile(),
       let output = String(data: data, encoding: String.Encoding.utf8) {
        if output.count > 0 {
            //remove newline character.
            let lastIndex = output.index(before: output.endIndex)
            return String(output[output.startIndex ..< lastIndex])
        }
        task.waitUntilExit()
        return output
    }
    return nil
}

#if os(Linux)
public func reportMemory() {
    do {
        if let usage = try shellWithPipes("free -m", "grep Mem", "awk '{print $3 \"MB of \" $2 \"MB\"}'") {
            NSLog("Memory used: \(usage)")
        }
    } catch {
        NSLog("reportMemory error: \(error)")
    }
}

public func availableMemory() -> Int {
    do {
        if let avaiable = try shellWithPipes("free -m", "grep Mem", "awk '{print $7}'") {
            return Int(avaiable) ?? -1
        }
    } catch {
        NSLog("availableMemory error: \(error)")
    }
    return -1
}

public func freeMemory() -> Int {
    do {
        if let result = try shellWithPipes("free -m", "grep Mem", "awk '{print $4}'") {
            return Int(result) ?? -1
        }
    } catch {
        NSLog("freeMemory error: \(error)")
    }
    return -1
}
#endif
Sian answered 7/7, 2022 at 2:52 Comment(0)
C
0

Swift 5

There is a general approach to work with task_info that covers all machine-independent task information structures in convenient way:

struct TaskInfo {
  
  private static func taskInfo<T>(_ info: T, _ flavor: Int32) -> T {
    var info = info
    var count = mach_msg_type_number_t(MemoryLayout<T>.size / MemoryLayout<natural_t>.size)
    _ = withUnsafeMutablePointer(to: &info) {
      $0.withMemoryRebound(to: integer_t.self, capacity: 1) {
        task_info(mach_task_self_, task_flavor_t(flavor), $0, &count)
      }
    }
    return info
  }
  
  static var basic: task_basic_info { taskInfo(task_basic_info(), TASK_BASIC_INFO) }
  static var events: task_events_info { taskInfo(task_events_info(), TASK_EVENTS_INFO) }
  static var thread_times: task_thread_times_info { taskInfo(task_thread_times_info(), TASK_THREAD_TIMES_INFO) }
  static var absolutetime: task_absolutetime_info { taskInfo(task_absolutetime_info(), TASK_ABSOLUTETIME_INFO) }
  static var kernelmemory: task_kernelmemory_info { taskInfo(task_kernelmemory_info(), TASK_KERNELMEMORY_INFO) }
  static var affinity_tag: task_affinity_tag_info { taskInfo(task_affinity_tag_info(), TASK_AFFINITY_TAG_INFO) }
  static var dyld: task_dyld_info { taskInfo(task_dyld_info(), TASK_DYLD_INFO) }
  static var extmod: task_extmod_info { taskInfo(task_extmod_info(), TASK_EXTMOD_INFO) }
  static var power: task_power_info_v2 { taskInfo(task_power_info_v2(), TASK_POWER_INFO_V2) }
  static var vm: task_vm_info { taskInfo(task_vm_info(), TASK_VM_INFO) }
  static var trace_memory: task_trace_memory_info { taskInfo(task_trace_memory_info(), TASK_TRACE_MEMORY_INFO) }
  static var wait_state: task_wait_state_info { taskInfo(task_wait_state_info(), TASK_WAIT_STATE_INFO) }
  static var flags: task_flags_info { taskInfo(task_flags_info(), TASK_FLAGS_INFO) }
}

How to use:

let basic = TaskInfo.basic
print("Memory usage: \(basic.resident_size / (1024 * 1024)) MB")

let vm = TaskInfo.vm
print("VM memory usage: \(vm.internal / (1024 * 1024)) MB")

let power = TaskInfo.power
print("Energy usage: \(power.task_energy) nJ (nano Joules)")

/*
Prints:

Memory usage: 34 MB
VM memory usage: 9 MB
Energy usage: 204697898 nJ (nano Joules)
*/
Congener answered 19/12, 2023 at 9:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.