Swift pointer problems with MACH_TASK_BASIC_INFO

9k views Asked by At

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'
8

There are 8 answers

0
Nate Cook On BEST ANSWER

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")
}
8
Jerry On

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.

0
Amr On

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
2
Airspeed Velocity On

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"))
    }
}
0
iUrii On

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)
*/
0
Prashant Tukadiya On

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)
1
AudioBubble On

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)")
}
0
zackpep On

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"))
    }
}