4

I'm trying to make an application that reads system information (on MacOS) and I've been able to read sysctl STRINGS like so:

 func cpu() -> String {
    var size = 0
    sysctlbyname("machdep.cpu.brand_string", nil, &size, nil, 0)
    var machine = [CChar](repeating: 0,  count: Int(size))
    sysctlbyname("machdep.cpu.brand_string", &machine, &size, nil, 0)
    return String(cString: machine)

}

but when I try to read integers like hw.cpufrequency like so:

func cpuFreq() -> String {
    var size = 0
    sysctlbyname("hw.cpufrequency", nil, &size, nil, 0)
    var machine = [CChar](repeating: 0,  count: Int(size))
    sysctlbyname("hw.cpufrequency", &machine, &size, nil, 0)
    return String(cString: machine)

}

It returns absolutely nothing, any clues?

user265889
  • 667
  • 1
  • 10
  • 24

2 Answers2

4

Your code is assuming that the return value will be a string, but it's not; it's actually an integer. If you look at the man page for sysctl(3) [type 'man 3 sysctl' in the Terminal to see it], you'll see that "hw.cpufrequency" returns an int64_t in C, which translates to an Int64 in Swift. So you want to read the value into an Int64, not a string. You can do that like this:

func cpuFreq() throws -> Int64 {
    var frequency: Int64 = 0
    var size = MemoryLayout<Int64>.size

    if sysctlbyname("hw.cpufrequency", &frequency, &size, nil, 0) != 0 {
        throw POSIXError.Code(rawValue: errno).map { POSIXError($0) } ?? CocoaError(.fileReadUnknown)
    }

    return frequency
}
Charles Srstka
  • 16,665
  • 3
  • 34
  • 60
0

In your case you are trying to retrive an integer value using code made t retrive string values, so it's not going to work.

Basically sysctl gives you 2 types of values (except for the value tables), which are integers and strings.

Here is some code i made for my library SwiftCPUDetect to retrive both:

//Strings
func getString(_ name: String) -> String?{
    
    var size: size_t = 0
    //gets the string size
    var res = sysctlbyname(name, nil, &size, nil, 0)
    
    if res != 0 {
        //Returns nil if the specified name entry has not been found
        return nil
    }
    //Allocates the appropriate vector (with extra termination just t be sure)
    var ret = [CChar].init(repeating: 0, count: size + 1)
    //retrives value
    res = sysctlbyname(name, &ret, &size, nil, 0)
    
    return res == 0 ? String(cString: ret) : nil
}

//Integers
func getInteger<T: FixedWidthInteger>(_ name: String) -> T?{
    var ret = T()
    
    var size = MemoryLayout.size(ofValue: ret) //gets the size of the provided integer value
    
    let res = sysctlbyname(name, &ret, &size, nil, 0) //fetches the value
    
    return res == 0 ? ret : nil //returns the retrieved integer if the value name entry exists otherwise returns nil
}

And here are some example usages (working only on macOS):

///Gets the number of cores of the current CPU
func cores_count() -> UInt?{
    return getInteger("machdep.cpu.core_count")
}
        
///Gets the brand name for the current CPU
func brand_string() -> String?{
    return getString("machdep.cpu.brand_string")
}

Also sysctl can do boolean values using integers set to either 0 or 1, so you can use this simple function to retrive boolean values too:

//Boolean
func getBool(_ name: String) -> Bool?{
    guard let res: Int32 = getInteger(name) else{
        return nil
    }
    
    return res == 1
}

I hope this can be useful to you, and feel free to use the library i mentioned in your projects, it contains a lot of useful stuff like this.