Use ioreg and Perl to Hardware Sensor Information


Square Dot Square Dot Square Dot Little red trilobyte thingy Square Dot Square Dot Square Dot

Background

This is a way to get raw sensor information using a Perl script and the command line program ioreg. The example is for OS X Tiger and recent PowerBooks, but if you're good with Perl, you should be able to adapt it to different hardware. This is what the output looks like for a 1.67Ghz PowerBook from the fall of 2005, running 10.4.3:

Temperature Location: CPU/INTREPID BOTTOMSIDE
      High Threshold:  67.0 C
 Current Temperature:  47.0 C

Temperature Location: CPU BOTTOMSIDE
      High Threshold:  67.0 C
 Current Temperature:  54.3 C

Temperature Location: PWR SUPPLY BOTTOMSIDE
      High Threshold:  67.0 C
 Current Temperature:  52.8 C

Temperature Location: TRACK PAD
      High Threshold:  49.0 C
 Current Temperature:  38.0 C

Temperature Location: GPU
      High Threshold: 100.0 C
 Current Temperature:  95.0 C

Temperature Location: BATT-TEMP
      High Threshold:  50.0 C
 Current Temperature:  34.0 C

        Fan Location: REAR LEFT EXHAUST
       Current Value:  0000 rpm

        Fan Location: REAR RIGHT EXHAUST
       Current Value:  0000 rpm

If you have a different computer, it will have different sensors, and you'll have to customize the script to match the sensors in your own computer.

The Script

The script is pretty straightforward. It just calls ioreg, looks for certain kinds of information, and then pipes it to grep in order to restrict the output, then formats and prints it out. First, take a look at the whole script, then follow the links in the script for more detailed explanations of some of the more intricate parts.

#!/usr/bin/perl

# Get and print temperature sensor readings and fan speeds
# As configured, will work with a PowerBook 

use strict;

my @locations;
my @currentValues;
my @thresholds;
my @fanlocs;
my @fanvals;

# See note
# old values for 10.3.9 and 1.33 Ghz PowerBook
# my $temp = eval {`ioreg -c IOHWSensor -w 0 | grep "high-threshold" -B5 -A11` };

#new values for the 1.67 Hi Res PowerBook:
my $temp = eval {`ioreg -c IOHWSensor -w 0 | grep "high-threshold" -B15 -A2` }; 

my @lines = split('\n', $temp);

foreach my $line (@lines)
{
	#just discard lines we don't want:
	next if $line =~ m/^--/;
	next if $line =~ m/{$/;
	next if $line =~ m/}$/;
	
	#clean up the junk at the head of each line:
	$line =~ s/^(\ |\|)+//g;
	
	chomp($line);
	
	my ($key, $value) = split(' = ', $line);
	
	$key = StripQuotes($key);
	$value = StripQuotes($value);
	
	push(@locations, $value) if ($key eq 'location');
	push(@thresholds, $value) if ($key eq 'high-threshold');
	push(@currentValues, $value) if ($key eq 'current-value');
	
}

my $numlocs = @locations;

#print out the results:
for (my $i = 0; $i < $numlocs; $i++)
{
	# See Note
	print qq(\nTemperature Location: $locations[$i]\n);
	$thresholds[$i] = $thresholds[$i] / 65536.0;
	printf("      High Threshold: %5.1f C\n", $thresholds[$i]);
	$currentValues[$i] = $currentValues[$i] / 65536.0;
	printf(" Current Temperature: %5.1f C\n", $currentValues[$i]);
}

# See Note
# now do the same for the fan speed values:
$temp = eval { `ioreg -c IOHWSensor | grep -B3 -A11 '"type" = "fanspeed"'`};
@lines = split("\n", $temp);

foreach my $line (@lines)
{
	next if $line =~ m/^--/;
	next if $line =~ m/{$/;
	next if $line =~ m/}$/;
	
	$line =~ s/^(\ |\|)+//g;
	chomp($line);
	
	my ($key, $value) = split(' = ', $line);
	
	$key = StripQuotes($key);
	$value = StripQuotes($value);

	push(@fanlocs, $value) if ($key eq 'location');
	push(@fanvals, $value) if ($key eq 'current-value');	
	
}

$numlocs = @fanlocs;

for (my $i = 0; $i < $numlocs; $i++)
{
	print qq(\n        Fan Location: $fanlocs[$i]\n);
	$fanvals[$i] = $fanvals[$i] / 65536.0;
	printf("       Current Value:  %04i rpm\n", $fanvals[$i]);
}

print qq(\n);

#========= input a string with quotes at the head and tail, get back one without:
sub StripQuotes
{
	my $temp = shift;
	
	$temp =~ s/^"//;
	$temp =~ s/"$//;
	
	return $temp;
}

Setting up ioreg and grep

The first step is to get the values from ioreg. This is by far the most difficult part, as it can vary quite a bit from computer to computer. First, I just run ioreg and pipe it into a file:

ioreg -c IOHWSensor -w 0 > ioreg.txt

With the file, I'm able to put it in a text editor and start looking for the obvious keywords I'll want to search for. The format of this file is kind of hard to decipher at first, but it's displaying the hardware sensors in a kind of a hierarchy. The vertical lines and spaces at the left are there to illustrate this hierarchical structure. The important values for this script, however, are in groups enclosed by braces: "{" and "}". If you open the ioreg.txt file in a text editor that doesn't wrap long lines, you'll see this structure.

The blocks you're looking for are pretty easy to spot they're a set of values with a key on the left hand side and a value on the right:

    | |           |   | +-o IOHWSensor  <class IOHWSensor, registered, matched, active, busy 0, retain count 7>
    | |           |   |     {
    | |           |   |       "location" = "CPU/INTREPID BOTTOMSIDE"
    | |           |   |       "polling-period-ns" = 18446744073709551615
    | |           |   |       "IOMatchCategory" = "IODefaultMatchCategory"
    | |           |   |       "CFBundleIdentifier" = "com.apple.driver.AppleHWSensor"
    | |           |   |       "IOPropertyMatch" = {"device_type"="temp-sensor"}
    | |           |   |       "sensor-id" = 0
    | |           |   |       "version" = 1
    | |           |   |       "type" = "temperature"
    | |           |   |       "low-threshold" = 0
    | |           |   |       "IOProbeScore" = 0
    | |           |   |       "IOClass" = "IOHWSensor"
    | |           |   |       "IOProviderClass" = "IOService"
    | |           |   |       "Power Management private data" = "{ this object = 022bef0,...}"
    | |           |   |       "polling-period" = 18446744073709551615
    | |           |   |       "Power Management protected data" = "{ theNumberOfPowerStates = 2,...}"
    | |           |   |       "high-threshold" = 4390912
    | |           |   |       "current-value" = 3080192
    | |           |   |       "zone" = <00000000>
    | |           |   |     }

I've truncated the two lines starting with "Power Management" for clarity, in reality, they're much longer, but I didn't find anything useful there.

The next step is to find a pattern that grep can use to get just the information you need. At first glance, I thought I'd try "temperature" as my keyword. So I tried this in the Terminal window:

ioreg -c IOHWSensor -w 0 | grep temperature

But when I did that, I noticed that I only got four lines of output, even though looking through the ioreg.txt file showed six sensors. A closer look at the file shows that two of the sensors have the word "temp" instead of "temperature" as the value for the "type" key. But if you try "temp" in the grep pattern, you get a bunch of extra lines you probably don't want. So the next step is to try something else. Each of the sensors has a property for "high-threshold". And sure enough, this:

ioreg -c IOHWSensor -w 0 | grep "high-threshold"

gives six lines, so that's a good sign. The next step is to add the lines before and after the match. That's what the -A and -B switches are for, to adjust the number of lines after and before your match. Eventually, you'll get it so you get everything from "location" to "zone", with a line starting with "--" in between to separate matches. So, for the new PowerBook, the final bit for the eval statement is:

ioreg -c IOHWSensor -w 0 | grep "high-threshold" -B15 -A2

Finding the Fan Speed

This is really pretty much the same as finding the temperature, except for one difference: all the combinations of "fan" and "speed" that I tried gave more results than I wanted from my grep pattern. But if I used both the key and its value just like they appear, I was able to limit my results the way I wanted. Then I just started adding lines before and after until I got the ones I wanted:

ioreg -c IOHWSensor | grep -B3 -A11 '"type" = "fanspeed"'

Extracting the Values

There are two things to watch for on extracting the values. The first is that sometimes the value isn't where you think it's going to be. For example, for the fan speeds on an iMac G5 running 10.3.9, ioreg has "target-value" and "current-value" properties. No matter how hard I run my iMac, the "current-value" never budges, even though the fans might be really ramped up. By trial and error, it would appear that the fan speed there is indicated by "target-value". So the script has to be adjusted accordingly. The iMac also doesn't show thresholds for temperatures, though it shows minimum and maximum speeds for its fans.

Also, the sensor values themselves are frequently encoded values. For example the temperatures on the iMac G5 are in degrees Centigrade, but multiplied by 10. So if ioreg says:

"current-value" = 615

the temperature is really 615/10 or 61.5° C. It's not hard to guess what the real value might be. For the PowerBook, the high threshold is reported by ioreg as 4390912, but that's pretty illogical. So just start dividing it by different numbers to get one that seems logical. It's often a good bet to start with either powers of 10 or powers of 2. In this case, it's pretty unlikely that dividing by 100,000 will yield a good value. The closest to a realistic value is 43.9° C, which is hardly very hot at all. A good guess is 2 to the 16th power, or 65536. Another one might be 2 to the 8th power, which would be 256. In this case, 65536 yields a more logical value of 67° C.

Charlie Minow
February 9, 2020 - 5:09 PM MST

Dot Dot Dot Rose Thing Dot Dot Dot
Cool doo wah